More work done on cleaning up stack traces
parent
8bca94683f
commit
389af93f0a
585
LICENSE
585
LICENSE
|
@ -1,114 +1,527 @@
|
|||
- Network -
|
||||
- Network - Encrypted, high-performance, and event-driven/reactive network stack for Java 11+
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Network
|
||||
Copyright 2019 - The Apache Software License, Version 2.0
|
||||
Dorkbox LLC
|
||||
Encrypted, high-performance, and event-driven/reactive network stack for Java 6+
|
||||
|
||||
|
||||
- Bennidi Iterator -
|
||||
https://github.com/bennidi/mbassador
|
||||
Copyright 2012 - MIT License
|
||||
Benjamin Diedrichsen
|
||||
Fast iterators from the MBassador project
|
||||
|
||||
|
||||
- BouncyCastle -
|
||||
http://www.bouncycastle.org
|
||||
Copyright 2009 - MIT License
|
||||
The Legion Of The Bouncy Castle
|
||||
|
||||
|
||||
- Dorkbox Utils -
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
Copyright 2019 - The Apache Software License, Version 2.0
|
||||
Copyright 2020
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- KryoNet RMI -
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/EsotericSoftware/kryonet
|
||||
Copyright 2008
|
||||
Nathan Sweet
|
||||
|
||||
- FastThreadLocal -
|
||||
https://github.com/LWJGL/lwjgl3/blob/5819c9123222f6ce51f208e022cb907091dd8023/modules/core/src/main/java/org/lwjgl/system/FastThreadLocal.java
|
||||
Copyright 2014 - BSD 3-Clause License
|
||||
Lightweight Java Game Library Project
|
||||
Riven
|
||||
- LAN HostDiscovery from Apache Commons JCS -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://issues.apache.org/jira/browse/JCS-40
|
||||
Copyright 2014
|
||||
The Apache Software Foundation
|
||||
|
||||
- MathUtils, IntArray, IntMap -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://github.com/libgdx/libgdx
|
||||
Copyright 2013
|
||||
Mario Zechner <badlogicgames@gmail.com>
|
||||
Nathan Sweet <nathan.sweet@gmail.com>
|
||||
|
||||
- Javassist -
|
||||
http://www.csg.is.titech.ac.jp/~chiba/java
|
||||
Copyright 1999 - BSD 3-Clause License
|
||||
Shigeru Chiba
|
||||
Bill Burke
|
||||
Jason T. Greene
|
||||
Licensed under the MPL/LGPL/Apache triple license
|
||||
- Netty (Various network + platform utilities) - An event-driven asynchronous network application framework
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://netty.io
|
||||
Copyright 2014
|
||||
The Netty Project
|
||||
Contributors. See source NOTICE
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 1980
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- Kryo -
|
||||
https://github.com/EsotericSoftware/kryo
|
||||
Copyright 2008 - BSD 3-Clause License
|
||||
Nathan Sweet
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines
|
||||
Copyright 2020
|
||||
JetBrains s.r.o.
|
||||
|
||||
- Aeron - Efficient reliable UDP unicast, UDP multicast, and IPC message transport
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/real-logic/aeron
|
||||
Copyright 1980
|
||||
Real Logic Limited
|
||||
|
||||
- kryo-serializers -
|
||||
https://github.com/magro/kryo-serializers
|
||||
Copyright 2010 - The Apache Software License, Version 2.0
|
||||
Martin Grotzke
|
||||
Rafael Winterhalter
|
||||
- Kryo - Fast and efficient binary object graph serialization framework for Java
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/EsotericSoftware/kryo
|
||||
Copyright 2020
|
||||
Nathan Sweet
|
||||
|
||||
Extra license information
|
||||
- ReflectASM -
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/EsotericSoftware/reflectasm
|
||||
Nathan Sweet
|
||||
|
||||
- KryoNet RMI -
|
||||
https://github.com/EsotericSoftware/kryonet
|
||||
Copyright 2008 - BSD 3-Clause License
|
||||
Nathan Sweet
|
||||
- Objenesis -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://objenesis.org
|
||||
Objenesis Team and all contributors
|
||||
|
||||
- MinLog-SLF4J -
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/EsotericSoftware/minlog
|
||||
Nathan Sweet
|
||||
|
||||
- LAN HostDiscovery from Apache Commons JCS -
|
||||
https://issues.apache.org/jira/browse/JCS-40
|
||||
Copyright 2014 - The Apache Software License, Version 2.0
|
||||
The Apache Software Foundation
|
||||
- Kryo Serializers - Extra kryo serializers
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/magro/kryo-serializers
|
||||
Copyright 2019
|
||||
Martin Grotzke
|
||||
Rafael Winterhalter
|
||||
|
||||
- LZ4 and XXhash - LZ4 compression for Java, based on Yann Collet's work
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/jpountz/lz4-java
|
||||
http://code.google.com/p/lz4/
|
||||
Copyright 2014
|
||||
Yann Collet
|
||||
Adrien Grand
|
||||
|
||||
- LZ4 and XXhash -
|
||||
https://github.com/jpountz/lz4-java
|
||||
Copyright 2011, 2012 - The Apache Software License, Version 2.0
|
||||
Yann Collet
|
||||
Adrien Grand
|
||||
- Conversant Disruptor - Disruptor is the highest performing intra-thread transfer mechanism available in Java.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/conversant/disruptor
|
||||
Copyright 2020
|
||||
Conversant, Inc
|
||||
|
||||
- TypeTools - A simple, zero-dependency library for working with types. Supports Java 1.6+ and Android.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/jhalterman/typetools
|
||||
Copyright 2020
|
||||
Jonathan Halterman and friends
|
||||
|
||||
- MathUtils, IntArray, IntMap -
|
||||
http://github.com/libgdx/libgdx/
|
||||
Copyright 2013 - The Apache Software License, Version 2.0
|
||||
Mario Zechner <badlogicgames@gmail.com>
|
||||
Nathan Sweet <nathan.sweet@gmail.com>
|
||||
- kotlin-logging - Lightweight logging framework for Kotlin
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/MicroUtils/kotlin-logging
|
||||
Copyright 2020
|
||||
Ohad Shai
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2019
|
||||
QOS.ch
|
||||
|
||||
- MinLog-SLF4J -
|
||||
https://git.dorkbox.com/dorkbox/MinLog-SLF4J
|
||||
https://github.com/EsotericSoftware/minlog
|
||||
Copyright 2008 - The Apache Software License, Version 2.0
|
||||
dorkbox, llc
|
||||
Nathan Sweet
|
||||
Dan Brown
|
||||
Drop-in replacement for MinLog to log through SLF4j.
|
||||
- Utilities - Utilities for use within Java projects
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
Copyright 2020
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- MersenneTwisterFast -
|
||||
[BSD 3-Clause License]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
Copyright 2003
|
||||
Sean Luke
|
||||
Michael Lecuyer (portions Copyright 1993
|
||||
|
||||
- ObjectPool -
|
||||
https://git.dorkbox.com/dorkbox/ObjectPool
|
||||
Copyright 2019 - The Apache Software License, Version 2.0
|
||||
dorkbox, llc
|
||||
- FileUtil (code from FilenameUtils.java for normalize + dependencies) -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
http://commons.apache.org/proper/commons-io/
|
||||
Copyright 2013
|
||||
The Apache Software Foundation
|
||||
Kevin A. Burton
|
||||
Scott Sanders
|
||||
Daniel Rall
|
||||
Christoph.Reck
|
||||
Peter Donald
|
||||
Jeff Turner
|
||||
Matthew Hawthorne
|
||||
Martin Cooper
|
||||
Jeremias Maerki
|
||||
Stephen Colebourne
|
||||
|
||||
- FastThreadLocal -
|
||||
[BSD 3-Clause License]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://github.com/LWJGL/lwjgl3/blob/5819c9123222f6ce51f208e022cb907091dd8023/modules/core/src/main/java/org/lwjgl/system/FastThreadLocal.java
|
||||
https://github.com/riven8192/LibStruct/blob/master/src/net/indiespot/struct/runtime/FastThreadLocal.java
|
||||
Copyright 2014
|
||||
Lightweight Java Game Library Project
|
||||
Riven
|
||||
|
||||
- ReflectASM -
|
||||
https://github.com/EsotericSoftware/reflectasm
|
||||
Copyright 2008 - BSD 3-Clause License
|
||||
Nathan Sweet
|
||||
- Base64Fast -
|
||||
[BSD 3-Clause License]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
http://migbase64.sourceforge.net/
|
||||
Copyright 2004
|
||||
Mikael Grev, MiG InfoCom AB. (base64@miginfocom.com)
|
||||
|
||||
- BCrypt -
|
||||
[BSD 2-Clause "Simplified" or "FreeBSD" license]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
http://www.mindrot.org/projects/jBCrypt
|
||||
Copyright 2006
|
||||
Damien Miller (djm@mindrot.org)
|
||||
GWT modified version
|
||||
|
||||
- SLF4J -
|
||||
http://www.slf4j.org
|
||||
Copyright 2008 - MIT License
|
||||
QOS.ch
|
||||
- Bias, BinarySearch -
|
||||
[MIT License]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://github.com/timboudreau/util
|
||||
Copyright 2013
|
||||
Tim Boudreau
|
||||
|
||||
- ConcurrentEntry -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
Copyright 2016
|
||||
bennidi
|
||||
dorkbox
|
||||
|
||||
- TypeTools -
|
||||
https://github.com/jhalterman/typetools/
|
||||
Copyright 2017 - The Apache Software License, Version 2.0
|
||||
Jonathan Halterman
|
||||
Tools for resolving generic types
|
||||
- Byte Utils (UByte, UInteger, ULong, Unsigned, UNumber, UShort) -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://github.com/jOOQ/jOOQ/tree/master/jOOQ/src/main/java/org/jooq/types
|
||||
Copyright 2017
|
||||
Data Geekery GmbH (http://www.datageekery.com)
|
||||
Lukas Eder
|
||||
Ed Schaller
|
||||
Jens Nerche
|
||||
Ivan Sokolov
|
||||
|
||||
- Collection Utilities (Array, ArrayMap, BooleanArray, ByteArray, CharArray, FloatArray, IdentityMap, IntArray, IntFloatMap, IntIntMap, IntMap, IntSet, LongArray, LongMap, ObjectFloatMap, ObjectIntMap, ObjectMap, ObjectSet, OrderedMap, OrderedSet) -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2011
|
||||
LibGDX
|
||||
Mario Zechner (badlogicgames@gmail.com)
|
||||
Nathan Sweet (nathan.sweet@gmail.com)
|
||||
|
||||
- Predicate -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2011
|
||||
LibGDX
|
||||
Mario Zechner (badlogicgames@gmail.com)
|
||||
Nathan Sweet (nathan.sweet@gmail.com)
|
||||
xoppa
|
||||
|
||||
- Select, QuickSelect -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2011
|
||||
LibGDX
|
||||
Mario Zechner (badlogicgames@gmail.com)
|
||||
Nathan Sweet (nathan.sweet@gmail.com)
|
||||
Jon Renner
|
||||
|
||||
- TimSort, ComparableTimSort -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
|
||||
Copyright 2008
|
||||
The Android Open Source Project
|
||||
|
||||
- Modified hex conversion utility methods -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Utilities
|
||||
https://netty.io
|
||||
Copyright 2014
|
||||
The Netty Project
|
||||
|
||||
- JNA - Simplified native library access for Java.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/twall/jna
|
||||
Copyright 2019
|
||||
Timothy Wall
|
||||
|
||||
- JNA-Platform - Mappings for a number of commonly used platform functions
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/twall/jna
|
||||
Copyright 2019
|
||||
Timothy Wall
|
||||
|
||||
- Java Uuid Generator - A set of Java classes for working with UUIDs
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/cowtowncoder/java-uuid-generator
|
||||
Copyright 2020
|
||||
Tatu Saloranta (tatu.saloranta@iki.fi)
|
||||
Contributors. See source release-notes/CREDITS
|
||||
|
||||
- Kryo - Fast and efficient binary object graph serialization framework for Java
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/EsotericSoftware/kryo
|
||||
Copyright 2020
|
||||
Nathan Sweet
|
||||
|
||||
Extra license information
|
||||
- ReflectASM -
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/EsotericSoftware/reflectasm
|
||||
Nathan Sweet
|
||||
|
||||
- Objenesis -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://objenesis.org
|
||||
Objenesis Team and all contributors
|
||||
|
||||
- MinLog-SLF4J -
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/EsotericSoftware/minlog
|
||||
Nathan Sweet
|
||||
|
||||
- Kryo Serializers - Extra kryo serializers
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/magro/kryo-serializers
|
||||
Copyright 2019
|
||||
Martin Grotzke
|
||||
Rafael Winterhalter
|
||||
|
||||
- Netty - An event-driven asynchronous network application framework
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://netty.io
|
||||
Copyright 2020
|
||||
The Netty Project
|
||||
Contributors. See source NOTICE
|
||||
|
||||
- Bouncy Castle Crypto - Lightweight cryptography API and JCE Extension
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.bouncycastle.org
|
||||
Copyright 2020
|
||||
The Legion of the Bouncy Castle Inc
|
||||
|
||||
- Lightweight Java Game Library - Java library that enables cross-platform access to popular native APIs
|
||||
[BSD 3-Clause License]
|
||||
https://github.com/LWJGL/lwjgl3
|
||||
Copyright 2019
|
||||
Lightweight Java Game Library
|
||||
|
||||
- TypeTools - A simple, zero-dependency library for working with types. Supports Java 1.6+ and Android.
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/jhalterman/typetools
|
||||
Copyright 2020
|
||||
Jonathan Halterman and friends
|
||||
|
||||
- Eclipse Platform - Frameworks and common services to support the use of Eclipse and it's tools (SWT)
|
||||
[Eclipse Public License (EPL)]
|
||||
https://projects.eclipse.org/projects/eclipse.platform
|
||||
Copyright 2019
|
||||
The Eclipse Foundation, Inc.
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2019
|
||||
QOS.ch
|
||||
|
||||
- XZ for Java - Complete implementation of XZ data compression in pure Java
|
||||
[Public Domain, per Creative Commons CC0]
|
||||
https://tukaani.org/xz/java.html
|
||||
Copyright 2018
|
||||
Lasse Collin
|
||||
Igor Pavlov
|
||||
|
||||
- Executor - Shell, JVM, and SSH command execution on Linux, MacOS, or Windows for Java 11+
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Executor
|
||||
Copyright 2020
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- ZT Process Executor -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/zeroturnaround/zt-exec
|
||||
Copyright 2014
|
||||
ZeroTurnaround LLC
|
||||
|
||||
- Apache Commons Exec -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://commons.apache.org/proper/commons-exec/
|
||||
Copyright 2014
|
||||
The Apache Software Foundation
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 1980
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines
|
||||
Copyright 2020
|
||||
JetBrains s.r.o.
|
||||
|
||||
- kotlin-logging - Lightweight logging framework for Kotlin
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/MicroUtils/kotlin-logging
|
||||
Copyright 2020
|
||||
Ohad Shai
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2019
|
||||
QOS.ch
|
||||
|
||||
- SSHJ - SSHv2 library for Java
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/hierynomus/sshj
|
||||
Copyright 2020
|
||||
Jeroen van Erp
|
||||
SSHJ Contributors
|
||||
|
||||
Extra license information
|
||||
- Apache MINA -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://mina.apache.org/sshd-project/
|
||||
The Apache Software Foundation
|
||||
|
||||
- Apache Commons-Net -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://commons.apache.org/proper/commons-net/
|
||||
The Apache Software Foundation
|
||||
|
||||
- JZlib -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.jcraft.com/jzlib
|
||||
Atsuhiko Yamanaka
|
||||
JCraft, Inc.
|
||||
|
||||
- Bouncy Castle Crypto -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.bouncycastle.org
|
||||
The Legion of the Bouncy Castle Inc
|
||||
|
||||
- ed25519-java -
|
||||
[Public Domain, per Creative Commons CC0]
|
||||
https://github.com/str4d/ed25519-java
|
||||
https://github.com/str4d
|
||||
|
||||
- NetworkUtils - Utilities for managing network configurations, IP/MAC address conversion, and ping (via OS native commands)
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/NetworkUtils
|
||||
Copyright 2020
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- Netty -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://netty.io/
|
||||
Copyright 2014
|
||||
The Netty Project
|
||||
This product contains a modified portion of Netty Network Utils
|
||||
|
||||
- Apache Harmony -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://archive.apache.org/dist/harmony/
|
||||
Copyright 2010
|
||||
The Apache Software Foundation
|
||||
This product contains a modified portion of 'Apache Harmony', an open source Java SE
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 1980
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- kotlin-logging - Lightweight logging framework for Kotlin
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/MicroUtils/kotlin-logging
|
||||
Copyright 2020
|
||||
Ohad Shai
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2019
|
||||
QOS.ch
|
||||
|
||||
- Executor - Shell, JVM, and SSH command execution on Linux, MacOS, or Windows for Java 11+
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://git.dorkbox.com/dorkbox/Executor
|
||||
Copyright 2020
|
||||
Dorkbox LLC
|
||||
|
||||
Extra license information
|
||||
- ZT Process Executor -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/zeroturnaround/zt-exec
|
||||
Copyright 2014
|
||||
ZeroTurnaround LLC
|
||||
|
||||
- Apache Commons Exec -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://commons.apache.org/proper/commons-exec/
|
||||
Copyright 2014
|
||||
The Apache Software Foundation
|
||||
|
||||
- Kotlin -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/JetBrains/kotlin
|
||||
Copyright 1980
|
||||
JetBrains s.r.o. and Kotlin Programming Language contributors
|
||||
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
|
||||
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
|
||||
|
||||
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/Kotlin/kotlinx.coroutines
|
||||
Copyright 2020
|
||||
JetBrains s.r.o.
|
||||
|
||||
- kotlin-logging - Lightweight logging framework for Kotlin
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/MicroUtils/kotlin-logging
|
||||
Copyright 2020
|
||||
Ohad Shai
|
||||
|
||||
- SLF4J - Simple facade or abstraction for various logging frameworks
|
||||
[MIT License]
|
||||
http://www.slf4j.org
|
||||
Copyright 2019
|
||||
QOS.ch
|
||||
|
||||
- SSHJ - SSHv2 library for Java
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://github.com/hierynomus/sshj
|
||||
Copyright 2020
|
||||
Jeroen van Erp
|
||||
SSHJ Contributors
|
||||
|
||||
Extra license information
|
||||
- Apache MINA -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://mina.apache.org/sshd-project/
|
||||
The Apache Software Foundation
|
||||
|
||||
- Apache Commons-Net -
|
||||
[The Apache Software License, Version 2.0]
|
||||
https://commons.apache.org/proper/commons-net/
|
||||
The Apache Software Foundation
|
||||
|
||||
- JZlib -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.jcraft.com/jzlib
|
||||
Atsuhiko Yamanaka
|
||||
JCraft, Inc.
|
||||
|
||||
- Bouncy Castle Crypto -
|
||||
[The Apache Software License, Version 2.0]
|
||||
http://www.bouncycastle.org
|
||||
The Legion of the Bouncy Castle Inc
|
||||
|
||||
- ed25519-java -
|
||||
[Public Domain, per Creative Commons CC0]
|
||||
https://github.com/str4d/ed25519-java
|
||||
https://github.com/str4d
|
||||
|
|
257
build.gradle.kts
257
build.gradle.kts
|
@ -15,7 +15,6 @@
|
|||
*/
|
||||
|
||||
import dorkbox.gradle.kotlin
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import java.time.Instant
|
||||
|
||||
///////////////////////////////
|
||||
|
@ -27,21 +26,20 @@ import java.time.Instant
|
|||
plugins {
|
||||
java
|
||||
|
||||
id("com.dorkbox.GradleUtils") version "1.8"
|
||||
id("com.dorkbox.CrossCompile") version "1.1"
|
||||
id("com.dorkbox.Licensing") version "1.4.2"
|
||||
id("com.dorkbox.VersionUpdate") version "1.6.1"
|
||||
id("com.dorkbox.GradlePublish") version "1.2"
|
||||
id("com.dorkbox.GradleUtils") version "1.10"
|
||||
id("com.dorkbox.Licensing") version "2.3"
|
||||
id("com.dorkbox.VersionUpdate") version "2.0"
|
||||
id("com.dorkbox.GradlePublish") version "1.6"
|
||||
id("com.dorkbox.GradleModuleInfo") version "1.0"
|
||||
|
||||
kotlin("jvm") version "1.3.72"
|
||||
kotlin("jvm") version "1.4.0"
|
||||
}
|
||||
|
||||
object Extras {
|
||||
// set for the project
|
||||
const val description = "Encrypted, high-performance, and event-driven/reactive network stack for Java 11+"
|
||||
const val group = "com.dorkbox"
|
||||
const val version = "4.1"
|
||||
const val version = "5.0-alpha3"
|
||||
|
||||
// set as project.ext
|
||||
const val name = "Network"
|
||||
|
@ -49,15 +47,8 @@ object Extras {
|
|||
const val vendor = "Dorkbox LLC"
|
||||
const val vendorUrl = "https://dorkbox.com"
|
||||
const val url = "https://git.dorkbox.com/dorkbox/Network"
|
||||
|
||||
val buildDate = Instant.now().toString()
|
||||
|
||||
val JAVA_VERSION = JavaVersion.VERSION_11.toString()
|
||||
const val KOTLIN_API_VERSION = "1.3"
|
||||
const val KOTLIN_LANG_VERSION = "1.3"
|
||||
|
||||
const val bcVersion = "1.60"
|
||||
const val atomicfuVer = "0.14.3"
|
||||
const val coroutineVer = "1.3.7"
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
@ -65,9 +56,22 @@ object Extras {
|
|||
///////////////////////////////
|
||||
GradleUtils.load("$projectDir/../../gradle.properties", Extras)
|
||||
GradleUtils.fixIntellijPaths()
|
||||
GradleUtils.defaultResolutionStrategy()
|
||||
GradleUtils.compileConfiguration(JavaVersion.VERSION_11) { kotlinOptions ->
|
||||
// see: https://kotlinlang.org/docs/reference/using-gradle.html
|
||||
kotlinOptions.apply {
|
||||
// enable the use of inline classes. see https://kotlinlang.org/docs/reference/inline-classes.html
|
||||
freeCompilerArgs += "-Xinline-classes"
|
||||
}
|
||||
}
|
||||
|
||||
// ratelimiter, "other" package
|
||||
// ping, rest of unit tests
|
||||
// getConnectionUpgradeType
|
||||
|
||||
// java 14 is faster with aeron!
|
||||
// NOTE: now using aeron instead of netty
|
||||
|
||||
// todo: remove BC! use conscrypt instead, or native java? (if possible. we are java 11 now, instead of 1.6)
|
||||
// using netty IP filters for connections
|
||||
// /*
|
||||
// * Copyright 2014 The Netty Project
|
||||
|
@ -100,120 +104,35 @@ GradleUtils.fixIntellijPaths()
|
|||
// }
|
||||
|
||||
|
||||
// NOTE: uses network util from netty!
|
||||
licensing {
|
||||
license(License.APACHE_2) {
|
||||
author(Extras.vendor)
|
||||
description(Extras.description)
|
||||
url(Extras.url)
|
||||
note(Extras.description)
|
||||
}
|
||||
|
||||
license("Dorkbox Utils", License.APACHE_2) {
|
||||
author(Extras.vendor)
|
||||
url("https://git.dorkbox.com/dorkbox/Utilities")
|
||||
}
|
||||
|
||||
license("Bennidi Iterator", License.MIT) {
|
||||
copyright(2012)
|
||||
author("Benjamin Diedrichsen")
|
||||
url("https://github.com/bennidi/mbassador")
|
||||
note("Fast iterators from the MBassador project")
|
||||
}
|
||||
|
||||
license("BouncyCastle", License.MIT) {
|
||||
copyright(2009)
|
||||
author("The Legion Of The Bouncy Castle")
|
||||
url("http://www.bouncycastle.org")
|
||||
}
|
||||
|
||||
license("ObjectPool", License.APACHE_2) {
|
||||
author("dorkbox, llc")
|
||||
url("https://git.dorkbox.com/dorkbox/ObjectPool")
|
||||
}
|
||||
|
||||
license("FastThreadLocal", License.BSD_3) {
|
||||
copyright(2014)
|
||||
author("Lightweight Java Game Library Project")
|
||||
author("Riven")
|
||||
url("https://github.com/LWJGL/lwjgl3/blob/5819c9123222f6ce51f208e022cb907091dd8023/modules/core/src/main/java/org/lwjgl/system/FastThreadLocal.java")
|
||||
}
|
||||
|
||||
license("Javassist", License.BSD_3) {
|
||||
copyright(1999)
|
||||
author("Shigeru Chiba")
|
||||
author("Bill Burke")
|
||||
author("Jason T. Greene")
|
||||
url("http://www.csg.is.titech.ac.jp/~chiba/java")
|
||||
note("Licensed under the MPL/LGPL/Apache triple license")
|
||||
}
|
||||
|
||||
license("Kryo", License.BSD_3) {
|
||||
copyright(2008)
|
||||
author("Nathan Sweet")
|
||||
url("https://github.com/EsotericSoftware/kryo")
|
||||
}
|
||||
|
||||
license("kryo-serializers", License.APACHE_2) {
|
||||
copyright(2010)
|
||||
author("Martin Grotzke")
|
||||
author("Rafael Winterhalter")
|
||||
url("https://github.com/magro/kryo-serializers")
|
||||
}
|
||||
|
||||
license("KryoNet RMI", License.BSD_3) {
|
||||
copyright(2008)
|
||||
author("Nathan Sweet")
|
||||
url("https://github.com/EsotericSoftware/kryonet")
|
||||
}
|
||||
|
||||
license("LAN HostDiscovery from Apache Commons JCS", License.APACHE_2) {
|
||||
copyright(2014)
|
||||
author("The Apache Software Foundation")
|
||||
url("https://issues.apache.org/jira/browse/JCS-40")
|
||||
}
|
||||
|
||||
license("LZ4 and XXhash", License.APACHE_2) {
|
||||
copyright(2011)
|
||||
copyright(2012)
|
||||
author("Yann Collet")
|
||||
author("Adrien Grand")
|
||||
url("https://github.com/jpountz/lz4-java")
|
||||
}
|
||||
|
||||
license("MathUtils, IntArray, IntMap", License.APACHE_2) {
|
||||
copyright(2013)
|
||||
author("Mario Zechner <badlogicgames@gmail.com>")
|
||||
author("Nathan Sweet <nathan.sweet@gmail.com>")
|
||||
url("http://github.com/libgdx/libgdx/")
|
||||
}
|
||||
|
||||
license("MinLog-SLF4J", License.APACHE_2) {
|
||||
copyright(2008)
|
||||
author("dorkbox, llc")
|
||||
author("Nathan Sweet")
|
||||
author("Dan Brown")
|
||||
url("https://git.dorkbox.com/dorkbox/MinLog-SLF4J")
|
||||
url("https://github.com/EsotericSoftware/minlog")
|
||||
note("Drop-in replacement for MinLog to log through SLF4j.")
|
||||
}
|
||||
|
||||
license("ReflectASM", License.BSD_3) {
|
||||
copyright(2008)
|
||||
author("Nathan Sweet")
|
||||
url("https://github.com/EsotericSoftware/reflectasm")
|
||||
}
|
||||
|
||||
license("SLF4J", License.MIT) {
|
||||
copyright(2008)
|
||||
author("QOS.ch")
|
||||
url("http://www.slf4j.org")
|
||||
}
|
||||
|
||||
license("TypeTools", License.APACHE_2) {
|
||||
copyright(2017)
|
||||
author("Jonathan Halterman")
|
||||
url("https://github.com/jhalterman/typetools/")
|
||||
note("Tools for resolving generic types")
|
||||
extra("KryoNet RMI", License.BSD_3) {
|
||||
it.copyright(2008)
|
||||
it.author("Nathan Sweet")
|
||||
it.url("https://github.com/EsotericSoftware/kryonet")
|
||||
}
|
||||
extra("LAN HostDiscovery from Apache Commons JCS", License.APACHE_2) {
|
||||
it.copyright(2014)
|
||||
it.author("The Apache Software Foundation")
|
||||
it.url("https://issues.apache.org/jira/browse/JCS-40")
|
||||
}
|
||||
extra("MathUtils, IntArray, IntMap", License.APACHE_2) {
|
||||
it.copyright(2013)
|
||||
it.author("Mario Zechner <badlogicgames@gmail.com>")
|
||||
it.author("Nathan Sweet <nathan.sweet@gmail.com>")
|
||||
it.url("http://github.com/libgdx/libgdx")
|
||||
}
|
||||
extra("Netty (Various network + platform utilities)", License.APACHE_2) {
|
||||
it.copyright(2014)
|
||||
it.description("An event-driven asynchronous network application framework")
|
||||
it.author("The Netty Project")
|
||||
it.author("Contributors. See source NOTICE")
|
||||
it.url("https://netty.io")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,7 +162,7 @@ sourceSets {
|
|||
}
|
||||
|
||||
kotlin {
|
||||
setSrcDirs(listOf("src"))
|
||||
setSrcDirs(listOf("test"))
|
||||
|
||||
// want to include java files for the source. 'setSrcDirs' resets includes...
|
||||
include("**/*.java", "**/*.kt")
|
||||
|
@ -256,43 +175,6 @@ repositories {
|
|||
jcenter()
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
////// Task defaults
|
||||
///////////////////////////////
|
||||
tasks.withType<JavaCompile> {
|
||||
doFirst {
|
||||
println("\tCompiling classes to Java $sourceCompatibility")
|
||||
}
|
||||
|
||||
options.encoding = "UTF-8"
|
||||
|
||||
sourceCompatibility = Extras.JAVA_VERSION
|
||||
targetCompatibility = Extras.JAVA_VERSION
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
doFirst {
|
||||
println("\tCompiling classes to Kotlin, Java ${kotlinOptions.jvmTarget}")
|
||||
}
|
||||
|
||||
sourceCompatibility = Extras.JAVA_VERSION
|
||||
targetCompatibility = Extras.JAVA_VERSION
|
||||
|
||||
// see: https://kotlinlang.org/docs/reference/using-gradle.html
|
||||
kotlinOptions {
|
||||
jvmTarget = Extras.JAVA_VERSION
|
||||
apiVersion = Extras.KOTLIN_API_VERSION
|
||||
languageVersion = Extras.KOTLIN_LANG_VERSION
|
||||
|
||||
// enable the use of inline classes. see https://kotlinlang.org/docs/reference/inline-classes.html
|
||||
freeCompilerArgs += "-Xinline-classes"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<Jar> {
|
||||
duplicatesStrategy = DuplicatesStrategy.FAIL
|
||||
}
|
||||
|
||||
tasks.jar.get().apply {
|
||||
manifest {
|
||||
// https://docs.oracle.com/javase/tutorial/deployment/jar/packageman.html
|
||||
|
@ -311,69 +193,42 @@ tasks.jar.get().apply {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
|
||||
implementation("org.jetbrains.kotlinx:atomicfu:${Extras.atomicfuVer}")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Extras.coroutineVer}")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Extras.coroutineVer}")
|
||||
implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
|
||||
|
||||
|
||||
// https://github.com/real-logic/aeron
|
||||
val aeronVer = "1.28.2"
|
||||
val aeronVer = "1.29.0"
|
||||
implementation("io.aeron:aeron-client:$aeronVer")
|
||||
implementation("io.aeron:aeron-driver:$aeronVer")
|
||||
|
||||
|
||||
implementation("io.netty:netty-buffer:4.1.49.Final")
|
||||
implementation("com.esotericsoftware:kryo:5.0.0-RC6")
|
||||
implementation("com.esotericsoftware:kryo:5.0.0-RC8")
|
||||
implementation("de.javakaffee:kryo-serializers:0.45")
|
||||
|
||||
implementation("net.jpountz.lz4:lz4:1.3.0")
|
||||
|
||||
// this is NOT the same thing as LMAX disruptor.
|
||||
// This is just a really fast queue (where LMAX is a fast queue + other things w/ a difficult DSL)
|
||||
// https://github.com/conversant/disruptor_benchmark
|
||||
// https://www.youtube.com/watch?v=jVMOgQgYzWU
|
||||
implementation("com.conversantmedia:disruptor:1.2.15")
|
||||
implementation("com.conversantmedia:disruptor:1.2.17")
|
||||
|
||||
// todo: remove BC! use conscrypt instead, or native java? (if possible. we are java 11 now, instead of 1.6)
|
||||
// java 14 is faster with aeron!
|
||||
// implementation("org.bouncycastle:bcprov-jdk15on:${Extras.bcVersion}")
|
||||
// implementation("org.bouncycastle:bcpg-jdk15on:${Extras.bcVersion}")
|
||||
// implementation("org.bouncycastle:bcmail-jdk15on:${Extras.bcVersion}")
|
||||
// implementation("org.bouncycastle:bctls-jdk15on:${Extras.bcVersion}")
|
||||
|
||||
implementation("net.jodah:typetools:0.6.2")
|
||||
implementation("de.javakaffee:kryo-serializers:0.45")
|
||||
implementation("org.javassist:javassist:3.27.0-GA")
|
||||
|
||||
implementation("com.dorkbox:ObjectPool:2.12")
|
||||
implementation("com.dorkbox:Utilities:1.5.3")
|
||||
implementation("com.dorkbox:Utilities:1.6")
|
||||
implementation("com.dorkbox:NetworkUtils:1.1")
|
||||
|
||||
|
||||
// https://github.com/MicroUtils/kotlin-logging
|
||||
implementation("io.github.microutils:kotlin-logging:1.7.9") // slick kotlin wrapper for slf4j
|
||||
implementation("io.github.microutils:kotlin-logging:1.8.3") // slick kotlin wrapper for slf4j
|
||||
implementation("org.slf4j:slf4j-api:1.7.30")
|
||||
|
||||
testImplementation("junit:junit:4.13")
|
||||
testImplementation("ch.qos.logback:logback-classic:1.2.3")
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
// fail eagerly on version conflict (includes transitive dependencies)
|
||||
// e.g. multiple different versions of the same dependency (group and name are equal)
|
||||
failOnVersionConflict()
|
||||
|
||||
// if there is a version we specified, USE THAT VERSION (over transitive versions)
|
||||
preferProjectModules()
|
||||
|
||||
// cache dynamic versions for 10 minutes
|
||||
cacheDynamicVersionsFor(10 * 60, "seconds")
|
||||
|
||||
// don't cache changing modules at all
|
||||
cacheChangingModulesFor(0, "seconds")
|
||||
}
|
||||
}
|
||||
|
||||
publishToSonatype {
|
||||
groupId = Extras.group
|
||||
artifactId = Extras.id
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -1,506 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.network.connection.registration.remote;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.registration.MetaChannel;
|
||||
import dorkbox.network.connection.registration.Registration;
|
||||
import dorkbox.network.connection.registration.UpgradeType;
|
||||
import dorkbox.network.pipeline.tcp.KryoDecoderTcpCompression;
|
||||
import dorkbox.network.pipeline.tcp.KryoDecoderTcpCrypto;
|
||||
import dorkbox.network.pipeline.tcp.KryoDecoderTcpNone;
|
||||
import dorkbox.util.crypto.CryptoECC;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.util.AttributeKey;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.FutureListener;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
|
||||
public abstract
|
||||
class RegistrationRemoteHandler<T extends RegistrationWrapper> extends RegistrationHandler<T> {
|
||||
static final AttributeKey<LinkedList> MESSAGES = AttributeKey.valueOf(RegistrationRemoteHandler.class, "messages");
|
||||
|
||||
static final String DELETE_IP = "eleteIP"; // purposefully missing the "D", since that is a system parameter, which starts with "-D"
|
||||
static final ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(CryptoECC.curve25519);
|
||||
|
||||
private static final String UDP_DECODE = "ud1";
|
||||
private static final String UDP_DECODE_NONE = "ud2";
|
||||
private static final String UDP_DECODE_COMPRESS = "ud3";
|
||||
private static final String UDP_DECODE_CRYPTO = "ud4";
|
||||
|
||||
private static final String TCP_DECODE = "td1";
|
||||
private static final String TCP_DECODE_NONE = "td2";
|
||||
private static final String TCP_DECODE_COMPRESS = "td3";
|
||||
private static final String TCP_DECODE_CRYPTO = "td4";
|
||||
|
||||
|
||||
private static final String IDLE_HANDLER = "idle";
|
||||
private static final String IDLE_HANDLER_FULL = "idleFull";
|
||||
|
||||
private static final String UDP_ENCODE = "ue";
|
||||
private static final String UDP_ENCODE_NONE = "ue2";
|
||||
private static final String UDP_ENCODE_COMPRESS = "ue3";
|
||||
private static final String UDP_ENCODE_CRYPTO = "ue4";
|
||||
|
||||
private static final String TCP_ENCODE = "te1";
|
||||
private static final String TCP_ENCODE_NONE = "te2";
|
||||
private static final String TCP_ENCODE_COMPRESS = "te3";
|
||||
private static final String TCP_ENCODE_CRYPTO = "te4";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
RegistrationRemoteHandler(final String name, final T registrationWrapper, final EventLoopGroup workerEventLoop) {
|
||||
super(name, workerEventLoop);
|
||||
}
|
||||
|
||||
/**
|
||||
* STEP 1: Channel is first created
|
||||
*/
|
||||
@Override
|
||||
protected
|
||||
void initChannel(final Channel channel) {
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
|
||||
Class<? extends Channel> channelClass = channel.getClass();
|
||||
// because of the way TCP works, we have to have special readers/writers. For UDP, all data must be in a single packet.
|
||||
|
||||
// boolean isTcpChannel = ConnectionImpl.isTcpChannel(channelClass);
|
||||
// boolean isUdpChannel = !isTcpChannel && ConnectionImpl.isUdpChannel(channelClass);
|
||||
//
|
||||
// if (isTcpChannel) {
|
||||
// ///////////////////////
|
||||
// // DECODE (or upstream)
|
||||
// ///////////////////////
|
||||
// pipeline.addFirst(TCP_DECODE, new KryoDecoderTcp(registrationWrapper.getSerialization())); // cannot be shared because of possible fragmentation.
|
||||
// }
|
||||
// else if (isUdpChannel) {
|
||||
// // can be shared because there cannot be fragmentation for our UDP packets. If there is, we throw an error and continue...
|
||||
// // pipeline.addFirst(UDP_DECODE, this.registrationWrapper.kryoUdpDecoder);
|
||||
// }
|
||||
//
|
||||
// // this makes the proper event get raised in the registrationHandler to kill NEW idle connections. Once "connected" they last a lot longer.
|
||||
// // we ALWAYS have this initial IDLE handler, so we don't have to worry about a SLOW-LORIS ATTACK against the server.
|
||||
// // in Seconds -- not shared, because it is per-connection
|
||||
// pipeline.addFirst(IDLE_HANDLER, new IdleStateHandler(2, 0, 0));
|
||||
//
|
||||
// if (isTcpChannel) {
|
||||
// /////////////////////////
|
||||
// // ENCODE (or downstream)
|
||||
// /////////////////////////
|
||||
// // pipeline.addFirst(TCP_ENCODE, this.registrationWrapper.kryoTcpEncoder); // this is shared
|
||||
// }
|
||||
// else if (isUdpChannel) {
|
||||
// // pipeline.addFirst(UDP_ENCODE, this.registrationWrapper.kryoUdpEncoder);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* STEP 2: Channel is now active. (if debug is enabled...) Debug output, so we can tell what direction the connection is in the log
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void channelActive(ChannelHandlerContext context) throws Exception {
|
||||
// add the channel so we can access it later.
|
||||
// do NOT want to add UDP channels, since they are tracked differently.
|
||||
|
||||
// if (this.logger.isDebugEnabled()) {
|
||||
// Channel channel = context.channel();
|
||||
// Class<? extends Channel> channelClass = channel.getClass();
|
||||
// boolean isUdp = ConnectionImpl.isUdpChannel(channelClass);
|
||||
//
|
||||
// StringBuilder stringBuilder = new StringBuilder(96);
|
||||
//
|
||||
// stringBuilder.append("Connected to remote ");
|
||||
// if (ConnectionImpl.isTcpChannel(channelClass)) {
|
||||
// stringBuilder.append("TCP");
|
||||
// }
|
||||
// else if (isUdp) {
|
||||
// stringBuilder.append("UDP");
|
||||
// }
|
||||
// else if (ConnectionImpl.isLocalChannel(channelClass)) {
|
||||
// stringBuilder.append("LOCAL");
|
||||
// }
|
||||
// else {
|
||||
// stringBuilder.append("UNKNOWN");
|
||||
// }
|
||||
//
|
||||
// stringBuilder.append(" connection [");
|
||||
// EndPoint.Companion.getHostDetails(stringBuilder, channel.localAddress());
|
||||
//
|
||||
// stringBuilder.append(getConnectionDirection());
|
||||
// EndPoint.Companion.getHostDetails(stringBuilder, channel.remoteAddress());
|
||||
// stringBuilder.append("]");
|
||||
//
|
||||
// this.logger.debug(stringBuilder.toString());
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a {@link Channel} has been idle for a while.
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void userEventTriggered(ChannelHandlerContext context, Object event) throws Exception {
|
||||
if (event instanceof IdleStateEvent) {
|
||||
if (((IdleStateEvent) event).state() == IdleState.ALL_IDLE) {
|
||||
// this IS BAD, because we specify an idle handler to prevent slow-loris type attacks on the webserver
|
||||
Channel channel = context.channel();
|
||||
channel.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super.userEventTriggered(context, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception {
|
||||
Channel channel = context.channel();
|
||||
|
||||
this.logger.error("Unexpected exception while trying to send/receive data on remote network channel. ({})" +
|
||||
System.getProperty("line.separator"), channel.remoteAddress(), cause);
|
||||
|
||||
if (channel.isOpen()) {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the direction that traffic is going to this handler (" <== " or " ==> ")
|
||||
*/
|
||||
protected abstract
|
||||
String getConnectionDirection();
|
||||
|
||||
/**
|
||||
* upgrades a channel ONE channel at a time
|
||||
*/
|
||||
final
|
||||
void upgradeDecoders(final int upgradeType, final Channel channel, final MetaChannel metaChannel) {
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
|
||||
try {
|
||||
if (metaChannel.tcpChannel == channel) {
|
||||
// cannot be shared because of possible fragmentation.
|
||||
switch (upgradeType) {
|
||||
case (UpgradeType.NONE) :
|
||||
pipeline.replace(TCP_DECODE, TCP_DECODE_NONE, new KryoDecoderTcpNone(registrationWrapper.getSerialization()));
|
||||
break;
|
||||
|
||||
case (UpgradeType.COMPRESS) :
|
||||
pipeline.replace(TCP_DECODE, TCP_DECODE_COMPRESS, new KryoDecoderTcpCompression(registrationWrapper.getSerialization()));
|
||||
break;
|
||||
|
||||
case (UpgradeType.ENCRYPT) :
|
||||
pipeline.replace(TCP_DECODE, TCP_DECODE_CRYPTO, new KryoDecoderTcpCrypto(registrationWrapper.getSerialization()));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to upgrade TCP connection pipeline for type: " + upgradeType);
|
||||
}
|
||||
}
|
||||
else if (metaChannel.udpChannel == channel) {
|
||||
// shared decoder
|
||||
switch (upgradeType) {
|
||||
case (UpgradeType.NONE) :
|
||||
// pipeline.replace(UDP_DECODE, UDP_DECODE_NONE, this.registrationWrapper.kryoUdpDecoderNone);
|
||||
break;
|
||||
|
||||
case (UpgradeType.COMPRESS) :
|
||||
// pipeline.replace(UDP_DECODE, UDP_DECODE_COMPRESS, this.registrationWrapper.kryoUdpDecoderCompression);
|
||||
break;
|
||||
|
||||
case (UpgradeType.ENCRYPT) :
|
||||
// pipeline.replace(UDP_DECODE, UDP_DECODE_CRYPTO, this.registrationWrapper.kryoUdpDecoderCrypto);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to upgrade UDP connection pipeline for type: " + upgradeType);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during connection pipeline upgrade", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* upgrades a channel ONE channel at a time
|
||||
*/
|
||||
final
|
||||
void upgradeEncoders(final int upgradeType, final Channel channel, final MetaChannel metaChannel) {
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
|
||||
if (metaChannel.tcpChannel == channel) {
|
||||
// shared encoder
|
||||
switch (upgradeType) {
|
||||
case (UpgradeType.NONE) :
|
||||
// pipeline.replace(TCP_ENCODE, TCP_ENCODE_NONE, this.registrationWrapper.kryoTcpEncoderNone);
|
||||
break;
|
||||
|
||||
case (UpgradeType.COMPRESS) :
|
||||
// pipeline.replace(TCP_ENCODE, TCP_ENCODE_COMPRESS, this.registrationWrapper.kryoTcpEncoderCompression);
|
||||
break;
|
||||
|
||||
case (UpgradeType.ENCRYPT) :
|
||||
// pipeline.replace(TCP_ENCODE, TCP_ENCODE_CRYPTO, this.registrationWrapper.kryoTcpEncoderCrypto);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to upgrade TCP connection pipeline for type: " + upgradeType);
|
||||
}
|
||||
}
|
||||
else if (metaChannel.udpChannel == channel) {
|
||||
// shared encoder
|
||||
switch (upgradeType) {
|
||||
case (UpgradeType.NONE) :
|
||||
// pipeline.replace(UDP_ENCODE, UDP_ENCODE_NONE, this.registrationWrapper.kryoUdpEncoderNone);
|
||||
break;
|
||||
|
||||
case (UpgradeType.COMPRESS) :
|
||||
// pipeline.replace(UDP_ENCODE, UDP_ENCODE_COMPRESS, this.registrationWrapper.kryoUdpEncoderCompression);
|
||||
break;
|
||||
|
||||
case (UpgradeType.ENCRYPT) :
|
||||
// pipeline.replace(UDP_ENCODE, UDP_ENCODE_CRYPTO, this.registrationWrapper.kryoUdpEncoderCrypto);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to upgrade UDP connection pipeline for type: " + upgradeType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final
|
||||
void logChannelUpgrade(final int upgradeType, final Channel channel, final MetaChannel metaChannel) {
|
||||
if (this.logger.isInfoEnabled()) {
|
||||
final String channelType;
|
||||
|
||||
if (metaChannel.tcpChannel == channel) {
|
||||
// cannot be shared because of possible fragmentation.
|
||||
switch (upgradeType) {
|
||||
case (UpgradeType.NONE) :
|
||||
channelType = "TCP simple";
|
||||
break;
|
||||
|
||||
case (UpgradeType.COMPRESS) :
|
||||
channelType = "TCP compression";
|
||||
break;
|
||||
|
||||
case (UpgradeType.ENCRYPT) :
|
||||
channelType = "TCP encrypted";
|
||||
break;
|
||||
default:
|
||||
this.logger.error("Unable to upgrade TCP connection pipeline for type: " + upgradeType);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (metaChannel.udpChannel == channel) {
|
||||
// shared decoder
|
||||
switch (upgradeType) {
|
||||
case (UpgradeType.NONE) :
|
||||
channelType = "UDP simple";
|
||||
break;
|
||||
|
||||
case (UpgradeType.COMPRESS) :
|
||||
channelType = "UDP compression";
|
||||
break;
|
||||
|
||||
case (UpgradeType.ENCRYPT) :
|
||||
channelType = "UDP encrypted";
|
||||
break;
|
||||
default:
|
||||
this.logger.error("Unable to upgrade UDP connection pipeline for type: " + upgradeType);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.logger.error("Unable to upgrade unknown connection pipeline for type: " + upgradeType);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder(96);
|
||||
|
||||
stringBuilder.append(channelType);
|
||||
stringBuilder.append(" connection [");
|
||||
|
||||
EndPoint.Companion.getHostDetails(stringBuilder, channel.localAddress());
|
||||
|
||||
stringBuilder.append(getConnectionDirection());
|
||||
EndPoint.Companion.getHostDetails(stringBuilder, channel.remoteAddress());
|
||||
|
||||
stringBuilder.append("]");
|
||||
|
||||
this.logger.info(stringBuilder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
final
|
||||
void cleanupPipeline(final Channel channel, final MetaChannel metaChannel, final Runnable preConnectRunnable, final Runnable postConnectRunnable) {
|
||||
final int idleTimeout = this.registrationWrapper.getIdleTimeout();
|
||||
|
||||
try {
|
||||
// channel should NEVER == null! (we will always have TCP or UDP!)
|
||||
// we also ONLY want to add this to a single cleanup, NOT BOTH, because this must only run once!!
|
||||
final ChannelPromise channelPromise = channel.newPromise();
|
||||
channelPromise.addListener(new FutureListener<Void>() {
|
||||
@Override
|
||||
public
|
||||
void operationComplete(final Future<Void> future) throws Exception {
|
||||
// this runs on the channel's event loop
|
||||
logger.trace("Connection connected");
|
||||
|
||||
if (preConnectRunnable != null) {
|
||||
preConnectRunnable.run();
|
||||
}
|
||||
|
||||
// safe cast, because it's always this way...
|
||||
registrationWrapper.connectionConnected0(metaChannel.connection);
|
||||
|
||||
if (postConnectRunnable != null) {
|
||||
postConnectRunnable.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (metaChannel.tcpChannel != null) {
|
||||
cleanupPipeline0(idleTimeout, metaChannel.tcpChannel, channelPromise);
|
||||
}
|
||||
|
||||
if (metaChannel.udpChannel != null) {
|
||||
cleanupPipeline0(idleTimeout, metaChannel.udpChannel, channelPromise);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error during pipeline replace", e);
|
||||
}
|
||||
}
|
||||
|
||||
private
|
||||
void cleanupPipeline0(final int idleTimeout, final Channel channel, final ChannelPromise channelPromise) {
|
||||
final ChannelPipeline pipeline = channel.pipeline();
|
||||
|
||||
if (idleTimeout > 0) {
|
||||
pipeline.replace(IDLE_HANDLER, IDLE_HANDLER_FULL, new IdleStateHandler(0, 0, idleTimeout, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
else {
|
||||
pipeline.remove(IDLE_HANDLER);
|
||||
}
|
||||
|
||||
// we also DEREGISTER from the HANDSHAKE event-loop and run on the worker event-loop!
|
||||
ChannelFuture future = channel.deregister();
|
||||
future.addListener(new GenericFutureListener<Future<? super Void>>() {
|
||||
@Override
|
||||
public
|
||||
void operationComplete(final Future<? super Void> f) throws Exception {
|
||||
if (f.isSuccess()) {
|
||||
workerEventLoop.register(channel);
|
||||
|
||||
// TCP and UDP register on DIFFERENT event loops. This channel promise ONLY runs on the same worker as the
|
||||
// channel that called `cleanupPipeline`
|
||||
if (channelPromise.channel() == channel) {
|
||||
workerEventLoop.register(channelPromise);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// whoa! Didn't send valid public key info!
|
||||
boolean invalidPublicKey(final Registration message, final String type) {
|
||||
if (message.publicKey == null) {
|
||||
logger.error("Null ECC public key during " + type + " handshake. This shouldn't happen!");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// want to validate the public key used! This is similar to how SSH works, in that once we use a public key, we want to validate
|
||||
// against that ip-address::key pair, so we can better protect against MITM/spoof attacks.
|
||||
boolean invalidRemoteAddress(final MetaChannel metaChannel,
|
||||
final Registration message,
|
||||
final String type,
|
||||
final InetSocketAddress remoteAddress) {
|
||||
|
||||
|
||||
boolean valid = registrationWrapper.validateRemoteAddress(metaChannel, remoteAddress, message.publicKey);
|
||||
if (!valid) {
|
||||
//whoa! abort since something messed up! (log happens inside of validate method)
|
||||
String hostAddress = remoteAddress.getAddress()
|
||||
.getHostAddress();
|
||||
logger.error("Invalid ECC public key for server IP {} during {} handshake. WARNING. The server has changed!", hostAddress, type);
|
||||
logger.error("Fix by adding the argument -D{} {} when starting the client.", DELETE_IP, hostAddress);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* have to have a way for us to store messages in case the remote end calls "onConnect()" and sends messages before we are ready.
|
||||
*/
|
||||
void prepChannelForOutOfOrderMessages(final Channel channel) {
|
||||
// this could POSSIBLY be screwed up when getting created, so we make sure to only create the list ONE time
|
||||
channel.attr(MESSAGES)
|
||||
.setIfAbsent(new LinkedList<Object>());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* have to have a way for us to store messages in case the remote end calls "onConnect()" and sends messages before we are ready.
|
||||
*/
|
||||
void saveOutOfOrderMessage(final Channel channel, final Object message) {
|
||||
// this will ALWAYS have already been created, or IF NULL -- then something really screwed up!
|
||||
LinkedList list = channel.attr(MESSAGES)
|
||||
.get();
|
||||
|
||||
if (list == null) {
|
||||
logger.error("Completely screwed up message order from server!");
|
||||
shutdown(channel, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
//noinspection unchecked
|
||||
list.add(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all of the messages for this channel that were "out of order" (onMessage called before onConnect finished).
|
||||
*/
|
||||
List<Object> getOutOfOrderMessagesAndReset(final Channel channel) {
|
||||
LinkedList messages = channel.attr(MESSAGES)
|
||||
.getAndSet(null);
|
||||
|
||||
return messages;
|
||||
}
|
||||
}
|
|
@ -1,317 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.network.connection.registration.remote;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.bouncycastle.crypto.BasicAgreement;
|
||||
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.bouncycastle.crypto.digests.SHA384Digest;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.RegistrationWrapperClient;
|
||||
import dorkbox.network.connection.registration.MetaChannel;
|
||||
import dorkbox.network.connection.registration.Registration;
|
||||
import dorkbox.util.crypto.CryptoECC;
|
||||
import dorkbox.util.exceptions.SecurityException;
|
||||
import dorkbox.util.serialization.EccPublicKeySerializer;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
|
||||
public
|
||||
class RegistrationRemoteHandlerClient extends RegistrationRemoteHandler<RegistrationWrapperClient> {
|
||||
|
||||
RegistrationRemoteHandlerClient(final String name, final RegistrationWrapperClient registrationWrapper, final EventLoopGroup workerEventLoop) {
|
||||
super(name, registrationWrapper, workerEventLoop);
|
||||
|
||||
// check to see if we need to delete an IP address as commanded from the user prompt
|
||||
String ipAsString = System.getProperty(DELETE_IP);
|
||||
if (ipAsString != null) {
|
||||
System.setProperty(DELETE_IP, "");
|
||||
byte[] address = null;
|
||||
try {
|
||||
String[] split = ipAsString.split("\\.");
|
||||
if (split.length == 4) {
|
||||
address = new byte[4];
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
int asInt = Integer.parseInt(split[i]);
|
||||
if (asInt >= 0 && asInt <= 255) {
|
||||
//noinspection NumericCastThatLosesPrecision
|
||||
address[i] = (byte) Integer.parseInt(split[i]);
|
||||
}
|
||||
else {
|
||||
address = null;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
address = null;
|
||||
}
|
||||
|
||||
if (address != null) {
|
||||
try {
|
||||
registrationWrapper.removeRegisteredServerKey(address);
|
||||
} catch (SecurityException e) {
|
||||
this.logger.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// end command
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the direction that traffic is going to this handler (" <== " or " ==> ")
|
||||
*/
|
||||
@Override
|
||||
protected
|
||||
String getConnectionDirection() {
|
||||
return " ==> ";
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
void readClient(final ChannelHandlerContext context, final Channel channel, final Registration registration, final String type, final MetaChannel metaChannel) {
|
||||
final InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
|
||||
|
||||
// IN: session ID + public key + ecc parameters (which are a nonce. the SERVER defines what these are)
|
||||
// OUT: remote ECDH shared payload
|
||||
if (metaChannel.secretKey == null && registration.publicKey != null) {
|
||||
// whoa! Didn't send valid public key info!
|
||||
if (invalidPublicKey(registration, type)) {
|
||||
shutdown(channel, registration.sessionID);
|
||||
return;
|
||||
}
|
||||
|
||||
// want to validate the public key used! This is similar to how SSH works, in that once we use a public key, we want to validate
|
||||
// against that ip-address::key pair, so we can better protect against MITM/spoof attacks.
|
||||
if (invalidRemoteAddress(metaChannel, registration, type, remoteAddress)) {
|
||||
// whoa! abort since something messed up! (log and recording if key changed happens inside of validate method)
|
||||
shutdown(channel, registration.sessionID);
|
||||
return;
|
||||
}
|
||||
|
||||
// save off remote public key. This is ALWAYS the same, where the ECDH changes every time...
|
||||
metaChannel.publicKey = registration.publicKey;
|
||||
|
||||
// It is OK that we generate a new ECC keypair for ECDHE every time that we connect from the client.
|
||||
// The server rotates keys every XXXX seconds, since this step is expensive (and the server is the 'trusted' endpoint).
|
||||
metaChannel.ecdhKey = CryptoECC.generateKeyPair(eccSpec, registrationWrapper.getSecureRandom());
|
||||
|
||||
Registration outboundRegister = new Registration(metaChannel.sessionId);
|
||||
|
||||
Output output = new Output(1024);
|
||||
EccPublicKeySerializer.write(output, (ECPublicKeyParameters) metaChannel.ecdhKey.getPublic());
|
||||
outboundRegister.payload = output.toBytes();
|
||||
|
||||
channel.writeAndFlush(outboundRegister);
|
||||
return;
|
||||
}
|
||||
|
||||
// IN: remote ECDH shared payload
|
||||
// OUT: hasMore=true if we have more registrations to do, false otherwise
|
||||
if (metaChannel.secretKey == null) {
|
||||
/*
|
||||
* Diffie-Hellman-Merkle key exchange for the AES key
|
||||
* http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
|
||||
*/
|
||||
byte[] ecdhPubKeyBytes = Arrays.copyOfRange(registration.payload, 0, registration.payload.length);
|
||||
ECPublicKeyParameters ecdhPubKey;
|
||||
try {
|
||||
ecdhPubKey = EccPublicKeySerializer.read(new Input(ecdhPubKeyBytes));
|
||||
} catch (KryoException e) {
|
||||
logger.error("Invalid decode of ECDH public key. Aborting.");
|
||||
shutdown(channel, registration.sessionID);
|
||||
return;
|
||||
}
|
||||
|
||||
BasicAgreement agreement = new ECDHCBasicAgreement();
|
||||
agreement.init(metaChannel.ecdhKey.getPrivate());
|
||||
BigInteger shared = agreement.calculateAgreement(ecdhPubKey);
|
||||
|
||||
// now we setup our AES key based on our shared secret! (from ECDH)
|
||||
// the shared secret is different each time a connection is made
|
||||
byte[] keySeed = shared.toByteArray();
|
||||
|
||||
SHA384Digest sha384 = new SHA384Digest();
|
||||
byte[] digest = new byte[sha384.getDigestSize()];
|
||||
sha384.update(keySeed, 0, keySeed.length);
|
||||
sha384.doFinal(digest, 0);
|
||||
|
||||
byte[] key = org.bouncycastle.util.Arrays.copyOfRange(digest, 0, 32); // 256bit keysize (32 bytes)
|
||||
metaChannel.secretKey = new SecretKeySpec(key, "AES");
|
||||
|
||||
Registration outboundRegister = new Registration(metaChannel.sessionId);
|
||||
|
||||
// do we have any more registrations?
|
||||
outboundRegister.hasMore = registrationWrapper.hasMoreRegistrations();
|
||||
if (outboundRegister.hasMore) {
|
||||
metaChannel.totalProtocols.incrementAndGet();
|
||||
}
|
||||
|
||||
channel.writeAndFlush(outboundRegister);
|
||||
|
||||
// wait for ack from the server before registering the next protocol
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// IN: upgrade>0 if we must upgrade this connection
|
||||
// can this pipeline can now be upgraded
|
||||
int upgradeType = registration.upgradeType;
|
||||
if (upgradeType > 0) {
|
||||
// upgrade the connection to an none/compression/encrypted connection
|
||||
upgradeEncoders(upgradeType, channel, metaChannel);
|
||||
upgradeDecoders(upgradeType, channel, metaChannel);
|
||||
|
||||
logChannelUpgrade(upgradeType, channel, metaChannel);
|
||||
}
|
||||
|
||||
// IN: hasMore=true if we have more registrations to do, false otherwise
|
||||
if (registrationWrapper.hasMoreRegistrations()) {
|
||||
logger.trace("Starting another protocol registration");
|
||||
metaChannel.totalProtocols.incrementAndGet();
|
||||
registrationWrapper.startNextProtocolRegistration();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// we only get this when we are 100% done with the registration and upgrade of all connection types.
|
||||
//
|
||||
// THIS is the last channel registered!
|
||||
//
|
||||
|
||||
|
||||
|
||||
// we don't verify anything on the CLIENT. We only verify on the server.
|
||||
// we don't support registering NEW classes after the client starts.
|
||||
|
||||
// this will perform channel WRITE on whatever channel is the last channel registered!
|
||||
if (!registration.upgraded) {
|
||||
// this only get's called once. Ever other time the server talks to the client now, "upgraded" will be true.
|
||||
|
||||
// this can ONLY be created when all protocols are registered!
|
||||
metaChannel.connection = this.registrationWrapper.connection0(metaChannel, remoteAddress);
|
||||
|
||||
if (metaChannel.tcpChannel != null) {
|
||||
// metaChannel.tcpChannel.pipeline().addLast(CONNECTION_HANDLER, metaChannel.connection);
|
||||
}
|
||||
if (metaChannel.udpChannel != null) {
|
||||
// metaChannel.udpChannel.pipeline().addLast(CONNECTION_HANDLER, metaChannel.connection);
|
||||
}
|
||||
|
||||
|
||||
// this tells the server we are now "upgraded" and can continue
|
||||
boolean hasErrors = !registrationWrapper.initClassRegistration(channel, registration);
|
||||
if (hasErrors) {
|
||||
// abort if something messed up!
|
||||
shutdown(channel, registration.sessionID);
|
||||
}
|
||||
|
||||
// the server will ping back when it is ready to connect
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// NOTE: The server will ALWAYS call "onConnect" before we do!
|
||||
// it does this via sending the client the "upgraded" signal JUST before it calls "onConnect"
|
||||
|
||||
|
||||
// It will upgrade the different connections INDIVIDUALLY, and whichever one arrives first will start the process
|
||||
// and the "late" message will be ignored
|
||||
if (!metaChannel.canUpgradePipeline.compareAndSet(true, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove ourselves from handling any more messages, because we are done.
|
||||
|
||||
// since only the FIRST registration gets here, setup other ones as well (since they are no longer needed)
|
||||
if (metaChannel.tcpChannel != null) {
|
||||
// the "other" channel is the TCP channel that we have to cleanup
|
||||
metaChannel.tcpChannel.pipeline().remove(RegistrationRemoteHandlerClientTCP.class);
|
||||
}
|
||||
|
||||
if (metaChannel.udpChannel != null) {
|
||||
// the "other" channel is the UDP channel that we have to cleanup
|
||||
metaChannel.udpChannel.pipeline().remove(RegistrationRemoteHandlerClientUDP.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// remove the ConnectionWrapper (that was used to upgrade the connection) and cleanup the pipeline
|
||||
// always wait until AFTER the server calls "onConnect", then we do this
|
||||
Runnable postConnectRunnable = new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// this method runs after all of the channels have be correctly updated
|
||||
|
||||
// get all of the out of order messages that we missed
|
||||
List<Object> messages = new LinkedList<Object>();
|
||||
|
||||
if (metaChannel.tcpChannel != null) {
|
||||
List<Object> list = getOutOfOrderMessagesAndReset(metaChannel.tcpChannel);
|
||||
if (list != null) {
|
||||
logger.trace("Getting deferred TCP messages: {}", list.size());
|
||||
messages.addAll(list);
|
||||
}
|
||||
}
|
||||
|
||||
if (metaChannel.udpChannel != null) {
|
||||
List<Object> list = getOutOfOrderMessagesAndReset(metaChannel.udpChannel);
|
||||
if (list != null) {
|
||||
logger.trace("Getting deferred UDP messages: {}", list.size());
|
||||
messages.addAll(list);
|
||||
}
|
||||
}
|
||||
|
||||
// now call 'onMessage' in the connection object with our messages!
|
||||
try {
|
||||
ConnectionImpl connection = metaChannel.connection;
|
||||
|
||||
for (Object message : messages) {
|
||||
logger.trace(" deferred onMessage({}, {})", connection.id(), message);
|
||||
try {
|
||||
// connection.channelRead(null, message);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error running deferred messages!", e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error initialising deferred messages!", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cleanupPipeline(channel, metaChannel, null, postConnectRunnable);
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.network.connection.registration.remote;
|
||||
|
||||
import dorkbox.network.connection.RegistrationWrapperClient;
|
||||
import dorkbox.network.connection.registration.MetaChannel;
|
||||
import dorkbox.network.connection.registration.Registration;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
|
||||
public
|
||||
class RegistrationRemoteHandlerClientTCP extends RegistrationRemoteHandlerClient {
|
||||
public
|
||||
RegistrationRemoteHandlerClientTCP(final String name,
|
||||
final RegistrationWrapperClient registrationWrapper,
|
||||
final EventLoopGroup workerEventLoop) {
|
||||
super(name, registrationWrapper, workerEventLoop);
|
||||
}
|
||||
|
||||
/**
|
||||
* STEP 2: Channel is now active. Start the registration process
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void channelActive(final ChannelHandlerContext context) throws Exception {
|
||||
super.channelActive(context);
|
||||
|
||||
logger.trace("Starting a new TCP Connection. Sending request to server");
|
||||
|
||||
Registration registration = new Registration(0);
|
||||
registration.publicKey = this.registrationWrapper.getPublicKey();
|
||||
|
||||
// client start the handshake with a registration packet
|
||||
context.channel().writeAndFlush(registration);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"AutoUnboxing", "AutoBoxing", "Duplicates"})
|
||||
@Override
|
||||
public
|
||||
void channelRead(final ChannelHandlerContext context, final Object message) throws Exception {
|
||||
Channel channel = context.channel();
|
||||
|
||||
if (message instanceof Registration) {
|
||||
Registration registration = (Registration) message;
|
||||
|
||||
MetaChannel metaChannel;
|
||||
int sessionId = registration.sessionID;
|
||||
|
||||
if (sessionId == 0) {
|
||||
logger.error("Invalid TCP channel session ID 0!");
|
||||
shutdown(channel, 0);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
metaChannel = registrationWrapper.getSession(sessionId);
|
||||
|
||||
// TCP channel registration is ALWAYS first, so this is the correct way to do this.
|
||||
if (metaChannel == null) {
|
||||
metaChannel = registrationWrapper.createSession(sessionId);
|
||||
metaChannel.tcpChannel = channel;
|
||||
|
||||
logger.debug("New TCP connection. Saving meta-channel id: {}", metaChannel.sessionId);
|
||||
}
|
||||
|
||||
// have to add a way for us to store messages in case the remote end calls "onConnect()" and sends messages before we are ready.
|
||||
prepChannelForOutOfOrderMessages(channel);
|
||||
}
|
||||
|
||||
logger.trace("TCP read");
|
||||
readClient(context, channel, registration, "TCP client", metaChannel);
|
||||
}
|
||||
else {
|
||||
logger.trace("Out of order TCP message from server!");
|
||||
saveOutOfOrderMessage(channel, message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.network.connection.registration.remote;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import dorkbox.network.connection.RegistrationWrapperClient;
|
||||
import dorkbox.network.connection.registration.MetaChannel;
|
||||
import dorkbox.network.connection.registration.Registration;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
class RegistrationRemoteHandlerClientUDP extends RegistrationRemoteHandlerClient {
|
||||
public
|
||||
RegistrationRemoteHandlerClientUDP(final String name,
|
||||
final RegistrationWrapperClient registrationWrapper,
|
||||
final EventLoopGroup workerEventLoop) {
|
||||
super(name, registrationWrapper, workerEventLoop);
|
||||
}
|
||||
|
||||
/**
|
||||
* STEP 2: Channel is now active. Start the registration process
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void channelActive(final ChannelHandlerContext context) throws Exception {
|
||||
super.channelActive(context);
|
||||
|
||||
Channel channel = context.channel();
|
||||
|
||||
// have to add a way for us to store messages in case the remote end calls "onConnect()" and sends messages before we are ready.
|
||||
// note: UDP channels are also unique (just like TCP channels) because of the SessionManager we added
|
||||
prepChannelForOutOfOrderMessages(channel);
|
||||
|
||||
InetSocketAddress udpRemoteAddress = (InetSocketAddress) channel.remoteAddress();
|
||||
if (udpRemoteAddress != null) {
|
||||
Registration outboundRegister = new Registration(0);
|
||||
outboundRegister.publicKey = this.registrationWrapper.getPublicKey();
|
||||
|
||||
// check to see if we have an already existing TCP connection to the server, so we can reuse the MetaChannel.
|
||||
// UDP will always be registered after TCP
|
||||
MetaChannel firstSession = this.registrationWrapper.getFirstSession();
|
||||
if (firstSession != null) {
|
||||
outboundRegister.sessionID = firstSession.sessionId;
|
||||
outboundRegister.hasMore = registrationWrapper.hasMoreRegistrations();
|
||||
}
|
||||
|
||||
// no size info, since this is UDP, it is not segmented
|
||||
channel.writeAndFlush(outboundRegister);
|
||||
}
|
||||
else {
|
||||
throw new IOException("UDP cannot connect to remote server! No remote address specified!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
void channelRead(final ChannelHandlerContext context, Object message) throws Exception {
|
||||
// REGISTRATION is the ONLY thing NOT encrypted. ALSO, this handler is REMOVED once registration is complete
|
||||
|
||||
Channel channel = context.channel();
|
||||
|
||||
if (message instanceof Registration) {
|
||||
Registration registration = (Registration) message;
|
||||
|
||||
MetaChannel metaChannel;
|
||||
int sessionId = registration.sessionID;
|
||||
|
||||
if (sessionId == 0) {
|
||||
logger.error("Invalid UDP channel session ID 0!");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
metaChannel = registrationWrapper.getSession(sessionId);
|
||||
|
||||
if (metaChannel == null) {
|
||||
metaChannel = registrationWrapper.createSession(sessionId);
|
||||
|
||||
logger.debug("New UDP connection. Saving meta-channel id: {}", metaChannel.sessionId);
|
||||
}
|
||||
else if (metaChannel.udpChannel == null) {
|
||||
logger.debug("Using TCP connection meta-channel for UDP connection");
|
||||
}
|
||||
|
||||
// in the event that we start with a TCP channel first, we still have to set the UDP channel
|
||||
metaChannel.udpChannel = channel;
|
||||
}
|
||||
|
||||
readClient(context, channel, registration, "UDP client", metaChannel);
|
||||
}
|
||||
else {
|
||||
logger.trace("Out of order UDP message from server!");
|
||||
saveOutOfOrderMessage(channel, message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,314 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.network.connection.registration.remote;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.BasicAgreement;
|
||||
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.bouncycastle.crypto.digests.SHA384Digest;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.util.Arrays;
|
||||
|
||||
import com.esotericsoftware.kryo.KryoException;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
import dorkbox.network.connection.EndPoint;
|
||||
import dorkbox.network.connection.RegistrationWrapper.STATE;
|
||||
import dorkbox.network.connection.RegistrationWrapperServer;
|
||||
import dorkbox.network.connection.registration.MetaChannel;
|
||||
import dorkbox.network.connection.registration.Registration;
|
||||
import dorkbox.util.crypto.CryptoECC;
|
||||
import dorkbox.util.serialization.EccPublicKeySerializer;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
|
||||
public
|
||||
class RegistrationRemoteHandlerServer extends RegistrationRemoteHandler<RegistrationWrapperServer> {
|
||||
private static final long ECDH_TIMEOUT = TimeUnit.MINUTES.toNanos(10L); // 10 minutes in nanoseconds
|
||||
|
||||
private static final ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(CryptoECC.curve25519);
|
||||
private final Object ecdhKeyLock = new Object();
|
||||
private AsymmetricCipherKeyPair ecdhKeyPair;
|
||||
private volatile long ecdhTimeout = System.nanoTime();
|
||||
|
||||
|
||||
RegistrationRemoteHandlerServer(final String name, final RegistrationWrapperServer registrationWrapper, final EventLoopGroup workerEventLoop) {
|
||||
super(name, registrationWrapper, workerEventLoop);
|
||||
}
|
||||
|
||||
/**
|
||||
* STEP 1: Channel is first created
|
||||
*/
|
||||
@Override
|
||||
protected
|
||||
void initChannel(final Channel channel) {
|
||||
// check to see if this connection is permitted.
|
||||
final InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
|
||||
if (!registrationWrapper.acceptRemoteConnection(remoteAddress)) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
EndPoint.Companion.getHostDetails(stringBuilder, remoteAddress);
|
||||
|
||||
logger.error("Remote connection [{}] is not permitted! Aborting connection process.", stringBuilder.toString());
|
||||
shutdown(channel, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
super.initChannel(channel);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the direction that traffic is going to this handler (" <== " or " ==> ")
|
||||
*/
|
||||
@Override
|
||||
protected
|
||||
String getConnectionDirection() {
|
||||
return " <== ";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rotates the ECDH key every 10 minutes, as this is a VERY expensive calculation to keep on doing for every connection.
|
||||
*/
|
||||
private
|
||||
AsymmetricCipherKeyPair getEchdKeyOnRotate(final SecureRandom secureRandom) {
|
||||
if (this.ecdhKeyPair == null || System.nanoTime() - this.ecdhTimeout > ECDH_TIMEOUT) {
|
||||
synchronized (this.ecdhKeyLock) {
|
||||
this.ecdhTimeout = System.nanoTime();
|
||||
this.ecdhKeyPair = CryptoECC.generateKeyPair(eccSpec, secureRandom);
|
||||
}
|
||||
}
|
||||
|
||||
return this.ecdhKeyPair;
|
||||
}
|
||||
|
||||
/*
|
||||
* UDP has a VERY limited size, so we have to break up registration steps into the following
|
||||
* 1) session ID == 0 -> exchange session ID and public keys (session ID != 0 now)
|
||||
* 2) session ID != 0 -> establish ECDH shared secret as AES key/iv
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
void readServer(final ChannelHandlerContext context, final Channel channel, final Registration registration, final String type, final MetaChannel metaChannel) {
|
||||
final InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
|
||||
|
||||
// IN: session ID == 0 (start of new connection)
|
||||
// OUT: session ID + public key + ecc parameters (which are a nonce. the SERVER defines what these are)
|
||||
if (registration.sessionID == 0) {
|
||||
// whoa! Didn't send valid public key info!
|
||||
if (invalidPublicKey(registration, type)) {
|
||||
shutdown(channel, registration.sessionID);
|
||||
return;
|
||||
}
|
||||
|
||||
// want to validate the public key used! This is similar to how SSH works, in that once we use a public key, we want to validate
|
||||
// against that ip-address::key pair, so we can better protect against MITM/spoof attacks.
|
||||
if (invalidRemoteAddress(metaChannel, registration, type, remoteAddress)) {
|
||||
// whoa! abort since something messed up! (log and recording if key changed happens inside of validate method)
|
||||
shutdown(channel, registration.sessionID);
|
||||
return;
|
||||
}
|
||||
|
||||
// save off remote public key. This is ALWAYS the same, where the ECDH changes every time...
|
||||
metaChannel.publicKey = registration.publicKey;
|
||||
|
||||
// tell the client to continue it's registration process.
|
||||
Registration outboundRegister = new Registration(metaChannel.sessionId);
|
||||
outboundRegister.publicKey = registrationWrapper.getPublicKey();
|
||||
outboundRegister.eccParameters = CryptoECC.generateSharedParameters(registrationWrapper.getSecureRandom());
|
||||
|
||||
channel.writeAndFlush(outboundRegister);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// IN: remote ECDH shared payload
|
||||
// OUT: server ECDH shared payload
|
||||
if (metaChannel.secretKey == null) {
|
||||
/*
|
||||
* Diffie-Hellman-Merkle key exchange for the AES key
|
||||
* http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
|
||||
*/
|
||||
|
||||
// the ECDH key will ROTATE every 10 minutes, since generating it for EVERY connection is expensive
|
||||
// and since we are combining ECDHE+ECC public/private keys for each connection, other
|
||||
// connections cannot break someone else's connection, since they are still protected by their own private keys.
|
||||
metaChannel.ecdhKey = getEchdKeyOnRotate(registrationWrapper.getSecureRandom());
|
||||
|
||||
byte[] ecdhPubKeyBytes = java.util.Arrays.copyOfRange(registration.payload, 0, registration.payload.length);
|
||||
ECPublicKeyParameters ecdhPubKey;
|
||||
try {
|
||||
ecdhPubKey = EccPublicKeySerializer.read(new Input(ecdhPubKeyBytes));
|
||||
} catch (KryoException e) {
|
||||
logger.error("Invalid decode of ECDH public key. Aborting.");
|
||||
shutdown(channel, registration.sessionID);
|
||||
return;
|
||||
}
|
||||
|
||||
BasicAgreement agreement = new ECDHCBasicAgreement();
|
||||
agreement.init(metaChannel.ecdhKey.getPrivate());
|
||||
BigInteger shared = agreement.calculateAgreement(ecdhPubKey);
|
||||
|
||||
// now we setup our AES key based on our shared secret! (from ECDH)
|
||||
// the shared secret is different each time a connection is made
|
||||
byte[] keySeed = shared.toByteArray();
|
||||
|
||||
SHA384Digest sha384 = new SHA384Digest();
|
||||
byte[] digest = new byte[sha384.getDigestSize()];
|
||||
sha384.update(keySeed, 0, keySeed.length);
|
||||
sha384.doFinal(digest, 0);
|
||||
|
||||
byte[] key = Arrays.copyOfRange(digest, 0, 32); // 256bit keysize (32 bytes)
|
||||
metaChannel.secretKey = new SecretKeySpec(key, "AES");
|
||||
|
||||
Registration outboundRegister = new Registration(metaChannel.sessionId);
|
||||
|
||||
Output output = new Output(1024);
|
||||
EccPublicKeySerializer.write(output, (ECPublicKeyParameters) metaChannel.ecdhKey.getPublic());
|
||||
outboundRegister.payload = output.toBytes();
|
||||
|
||||
channel.writeAndFlush(outboundRegister);
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: if we have more registrations, we will "bounce back" that status so the client knows what to do.
|
||||
// IN: hasMore=true if we have more registrations to do, false otherwise
|
||||
|
||||
// Some cases we want to SKIP encryption (ie, loopback or specific IP/CIDR addresses)
|
||||
// OTHERWISE ALWAYS upgrade the connection at this point.
|
||||
// IN: upgraded=false if we haven't upgraded to encryption yet (this will always be the case right after encryption is setup)
|
||||
|
||||
if (!registration.upgraded) {
|
||||
// this is the last protocol registered
|
||||
if (!registration.hasMore) {
|
||||
// this can ONLY be created when all protocols are registered!
|
||||
// this must happen before we verify class registrations.
|
||||
metaChannel.connection = this.registrationWrapper.connection0(metaChannel, remoteAddress);
|
||||
|
||||
if (metaChannel.tcpChannel != null) {
|
||||
// metaChannel.tcpChannel.pipeline().addLast(CONNECTION_HANDLER, metaChannel.connection);
|
||||
}
|
||||
if (metaChannel.udpChannel != null) {
|
||||
// metaChannel.udpChannel.pipeline().addLast(CONNECTION_HANDLER, metaChannel.connection);
|
||||
}
|
||||
}
|
||||
|
||||
// If we are loopback or the client is a specific IP/CIDR address, then we do things differently. The LOOPBACK address will never encrypt or compress the traffic.
|
||||
byte upgradeType = registrationWrapper.getConnectionUpgradeType(remoteAddress);
|
||||
registration.upgradeType = upgradeType;
|
||||
|
||||
// upgrade the connection to a none/compression/encrypted connection
|
||||
upgradeDecoders(upgradeType, channel, metaChannel);
|
||||
|
||||
// bounce back to the client so it knows we received it
|
||||
channel.write(registration);
|
||||
|
||||
upgradeEncoders(upgradeType, channel, metaChannel);
|
||||
|
||||
logChannelUpgrade(upgradeType, channel, metaChannel);
|
||||
|
||||
channel.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// we only get this when we are 100% done with encrypting/etc the connections
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
// upgraded=true when the client will send their class registration data. VERIFY IT IS CORRECT!
|
||||
STATE state = registrationWrapper.verifyClassRegistration(metaChannel, registration);
|
||||
if (state == STATE.ERROR) {
|
||||
// abort! There was an error
|
||||
shutdown(channel, registration.sessionID);
|
||||
return;
|
||||
}
|
||||
else if (state == STATE.WAIT) {
|
||||
return;
|
||||
}
|
||||
// else, continue.
|
||||
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// we only get this when we are 100% done with validation of class registrations. The last protocol to register gets us here.
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
|
||||
// remove ourselves from handling any more messages, because we are done.
|
||||
ChannelHandler handler = context.handler();
|
||||
channel.pipeline().remove(handler);
|
||||
|
||||
|
||||
// since only the LAST registration gets here, setup other ones as well (since they are no longer needed)
|
||||
if (channel == metaChannel.tcpChannel && metaChannel.udpChannel != null) {
|
||||
// the "other" channel is the UDP channel that we have to cleanup
|
||||
metaChannel.udpChannel.pipeline().remove(RegistrationRemoteHandlerServerUDP.class);
|
||||
}
|
||||
else if (channel == metaChannel.udpChannel && metaChannel.tcpChannel != null) {
|
||||
// the "other" channel is the TCP channel that we have to cleanup
|
||||
metaChannel.tcpChannel.pipeline().remove(RegistrationRemoteHandlerServerTCP.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// remove the ConnectionWrapper (that was used to upgrade the connection) and cleanup the pipeline
|
||||
Runnable preConnectRunnable = new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
// this method BEFORE the "onConnect()" runs and only after all of the channels have be correctly updated
|
||||
|
||||
// this tells the client we are ready to connect (we just bounce back "upgraded" over TCP, preferably).
|
||||
// only the FIRST one to arrive at the client will actually setup the pipeline
|
||||
Registration reg = new Registration(registration.sessionID);
|
||||
reg.upgraded = true;
|
||||
|
||||
// there is a risk of UDP losing the packet, so if we can send via TCP, then we do so.
|
||||
if (metaChannel.tcpChannel != null) {
|
||||
logger.trace("Sending TCP upgraded command");
|
||||
metaChannel.tcpChannel.writeAndFlush(reg);
|
||||
}
|
||||
else if (metaChannel.udpChannel != null) {
|
||||
logger.trace("Sending UDP upgraded command");
|
||||
metaChannel.udpChannel.writeAndFlush(reg);
|
||||
}
|
||||
else {
|
||||
logger.error("This shouldn't happen!");
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cleanupPipeline(channel, metaChannel, preConnectRunnable, null);
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.network.connection.registration.remote;
|
||||
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.RegistrationWrapperServer;
|
||||
import dorkbox.network.connection.registration.MetaChannel;
|
||||
import dorkbox.network.connection.registration.Registration;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
|
||||
public
|
||||
class RegistrationRemoteHandlerServerTCP extends RegistrationRemoteHandlerServer {
|
||||
|
||||
public
|
||||
RegistrationRemoteHandlerServerTCP(final String name,
|
||||
final RegistrationWrapperServer registrationWrapper,
|
||||
final EventLoopGroup workerEventLoop) {
|
||||
super(name, registrationWrapper, workerEventLoop);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* STEP 3-XXXXX: We pass registration messages around until we the registration handshake is complete!
|
||||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
public
|
||||
void channelRead(ChannelHandlerContext context, Object message) throws Exception {
|
||||
Channel channel = context.channel();
|
||||
|
||||
if (message instanceof Registration) {
|
||||
Registration registration = (Registration) message;
|
||||
|
||||
|
||||
MetaChannel metaChannel;
|
||||
int sessionId = registration.sessionID;
|
||||
if (sessionId == 0) {
|
||||
metaChannel = registrationWrapper.createSession();
|
||||
metaChannel.tcpChannel = channel;
|
||||
// TODO: use this: channel.voidPromise();
|
||||
logger.debug("New TCP connection. Saving meta-channel id: {}", metaChannel.sessionId);
|
||||
}
|
||||
else {
|
||||
metaChannel = registrationWrapper.getSession(sessionId);
|
||||
|
||||
if (metaChannel == null) {
|
||||
logger.error("Error getting invalid TCP channel session ID {}! MetaChannel is null!", sessionId);
|
||||
shutdown(channel, sessionId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
readServer(context, channel, registration, "TCP server", metaChannel);
|
||||
}
|
||||
else {
|
||||
logger.error("Error registering TCP with remote client!");
|
||||
|
||||
// this is what happens when the registration happens too quickly...
|
||||
Object connection = context.pipeline().last();
|
||||
if (connection instanceof ConnectionImpl) {
|
||||
// ((ConnectionImpl) connection).channelRead(context, message);
|
||||
}
|
||||
else {
|
||||
shutdown(channel, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
* Copyright 2010 dorkbox, llc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dorkbox.network.connection.registration.remote;
|
||||
|
||||
import dorkbox.network.connection.ConnectionImpl;
|
||||
import dorkbox.network.connection.RegistrationWrapperServer;
|
||||
import dorkbox.network.connection.registration.MetaChannel;
|
||||
import dorkbox.network.connection.registration.Registration;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Sharable
|
||||
public
|
||||
class RegistrationRemoteHandlerServerUDP extends RegistrationRemoteHandlerServer {
|
||||
public
|
||||
RegistrationRemoteHandlerServerUDP(final String name,
|
||||
final RegistrationWrapperServer registrationWrapper,
|
||||
final EventLoopGroup workerEventLoop) {
|
||||
super(name, registrationWrapper, workerEventLoop);
|
||||
}
|
||||
|
||||
/**
|
||||
* STEP 3-XXXXX: We pass registration messages around until we the registration handshake is complete!
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
void channelRead(final ChannelHandlerContext context, Object message) throws Exception {
|
||||
Channel channel = context.channel();
|
||||
|
||||
if (message instanceof Registration) {
|
||||
Registration registration = (Registration) message;
|
||||
|
||||
MetaChannel metaChannel;
|
||||
int sessionId = 0;
|
||||
sessionId = registration.sessionID;
|
||||
if (sessionId == 0) {
|
||||
metaChannel = registrationWrapper.createSession();
|
||||
metaChannel.udpChannel = channel;
|
||||
logger.debug("New UDP connection. Saving meta-channel id: {}", metaChannel.sessionId);
|
||||
}
|
||||
else {
|
||||
metaChannel = registrationWrapper.getSession(sessionId);
|
||||
|
||||
if (metaChannel == null) {
|
||||
logger.error("Error getting invalid UDP channel session ID {}! MetaChannel is null!", sessionId);
|
||||
shutdown(channel, sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
// in the event that we start with a TCP channel first, we still have to set the UDP channel
|
||||
metaChannel.udpChannel = channel;
|
||||
}
|
||||
|
||||
readServer(context, channel, registration, "UDP server", metaChannel);
|
||||
}
|
||||
else if (message instanceof io.netty.channel.socket.DatagramPacket) {
|
||||
logger.error("Error registering UDP with remote client!");
|
||||
shutdown(channel, 0);
|
||||
}
|
||||
else {
|
||||
logger.error("Error registering UDP with remote client! Attempting to queue message: " + message.getClass());
|
||||
|
||||
// this is what happens when the registration happens too quickly...
|
||||
Object connection = context.pipeline().last();
|
||||
if (connection instanceof ConnectionImpl) {
|
||||
// ((ConnectionImpl) connection).channelRead(context, message);
|
||||
}
|
||||
else {
|
||||
shutdown(channel, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -127,7 +127,7 @@ open class Connection(connectionParameters: ConnectionParams<*>) {
|
|||
private val expirationTime = System.currentTimeMillis() +
|
||||
TimeUnit.SECONDS.toMillis(endPoint.config.connectionCleanupTimeoutInSeconds.toLong())
|
||||
|
||||
private val logger = endPoint.logger
|
||||
val logger = endPoint.logger
|
||||
|
||||
|
||||
// private val needsLock = AtomicBoolean(false)
|
||||
|
@ -396,7 +396,7 @@ open class Connection(connectionParameters: ConnectionParams<*>) {
|
|||
suspend fun onDisconnect(function: suspend (Connection) -> Unit) {
|
||||
// make sure we atomically create the listener manager, if necessary
|
||||
listenerManager.getAndUpdate { origManager ->
|
||||
origManager ?: ListenerManager(logger)
|
||||
origManager ?: ListenerManager()
|
||||
}
|
||||
|
||||
listenerManager.value!!.onDisconnect(function)
|
||||
|
@ -408,7 +408,7 @@ open class Connection(connectionParameters: ConnectionParams<*>) {
|
|||
suspend fun <MESSAGE> onMessage(function: suspend (Connection, MESSAGE) -> Unit) {
|
||||
// make sure we atomically create the listener manager, if necessary
|
||||
listenerManager.getAndUpdate { origManager ->
|
||||
origManager ?: ListenerManager(logger)
|
||||
origManager ?: ListenerManager()
|
||||
}
|
||||
|
||||
listenerManager.value!!.onMessage(function)
|
||||
|
|
|
@ -108,7 +108,7 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
|
|||
internal val actionDispatch = CoroutineScope(Dispatchers.Default)
|
||||
// internal val connectionActor = actionDispatch.connectionActor()
|
||||
|
||||
internal val listenerManager = ListenerManager<CONNECTION>(logger)
|
||||
internal val listenerManager = ListenerManager<CONNECTION>()
|
||||
internal val connections = ConnectionManager<CONNECTION>(logger, config)
|
||||
|
||||
internal val mediaDriverContext: MediaDriver.Context
|
||||
|
|
|
@ -24,13 +24,12 @@ import dorkbox.util.collections.IdentityMap
|
|||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import mu.KLogger
|
||||
import net.jodah.typetools.TypeResolver
|
||||
|
||||
/**
|
||||
* Manages all of the different connect/disconnect/etc listeners
|
||||
*/
|
||||
internal class ListenerManager<CONNECTION: Connection>(private val logger: KLogger) {
|
||||
internal class ListenerManager<CONNECTION: Connection> {
|
||||
companion object {
|
||||
/**
|
||||
* Specifies the load-factor for the IdentityMap used to manage keeping track of the number of connections + listeners
|
||||
|
@ -38,39 +37,6 @@ internal class ListenerManager<CONNECTION: Connection>(private val logger: KLogg
|
|||
@Property
|
||||
val LOAD_FACTOR = 0.8f
|
||||
|
||||
|
||||
/**
|
||||
* Remove from the stacktrace until we get to the invoke site (ie: remove kotlin coroutine info + dorkbox network call stack)
|
||||
*
|
||||
* Neither of these are useful in resolving exception handling from a users perspective, and only clutter the stacktrace.
|
||||
*/
|
||||
fun cleanStackTrace(localThrowable: Throwable, invokingClass: Class<*>, remoteException: Exception? = null) {
|
||||
val myClassName = invokingClass.name
|
||||
val stackTrace = localThrowable.stackTrace
|
||||
var newStartIndex = 0
|
||||
for (element in stackTrace) {
|
||||
newStartIndex++
|
||||
|
||||
if (element.className == myClassName && element.methodName == "invoke") {
|
||||
// we do this 1 more time, because we want to remove the proxy invocation off the stack as well.
|
||||
newStartIndex++
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (remoteException == null) {
|
||||
// no remote exception, just cleanup our own callstack
|
||||
localThrowable.stackTrace = stackTrace.copyOfRange(newStartIndex, stackTrace.size)
|
||||
} else {
|
||||
// merge this info into the remote exception, so we can get the correct call stack info
|
||||
val newStack = Array<StackTraceElement>(remoteException.stackTrace.size + stackTrace.size - newStartIndex) { stackTrace[0] }
|
||||
remoteException.stackTrace.copyInto(newStack)
|
||||
stackTrace.copyInto(newStack, remoteException.stackTrace.size, newStartIndex)
|
||||
|
||||
remoteException.stackTrace = newStack
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove from the stacktrace (going in reverse), kotlin coroutine info + dorkbox network call stack.
|
||||
*
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package dorkbox.network.rmi
|
||||
|
||||
import dorkbox.network.connection.Connection
|
||||
import dorkbox.network.connection.ListenerManager
|
||||
import dorkbox.network.rmi.messages.MethodRequest
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
@ -257,13 +256,13 @@ internal class RmiClient(val isGlobal: Boolean,
|
|||
val fancyName = RmiUtils.makeFancyMethodName(method)
|
||||
val exception = TimeoutException("Response timed out: $fancyName")
|
||||
// from top down, clean up the coroutine stack
|
||||
ListenerManager.cleanStackTrace(exception, RmiClient::class.java)
|
||||
RmiUtils.cleanStackTraceForProxy(exception, RmiClient::class.java)
|
||||
continuation.resumeWithException(exception)
|
||||
}
|
||||
is Exception -> {
|
||||
// reconstruct the stack trace, so the calling method knows where the method invocation happened, and can trace the call
|
||||
// this stack will ALWAYS run up to this method (so we remove from the top->down, to get to the call site)
|
||||
ListenerManager.cleanStackTrace(Exception(), RmiClient::class.java, any)
|
||||
RmiUtils.cleanStackTraceForProxy(Exception(), RmiClient::class.java, any)
|
||||
continuation.resumeWithException(any)
|
||||
}
|
||||
else -> {
|
||||
|
@ -280,13 +279,13 @@ internal class RmiClient(val isGlobal: Boolean,
|
|||
val fancyName = RmiUtils.makeFancyMethodName(method)
|
||||
val exception = TimeoutException("Response timed out: $fancyName")
|
||||
// from top down, clean up the coroutine stack
|
||||
ListenerManager.cleanStackTrace(exception, RmiClient::class.java)
|
||||
RmiUtils.cleanStackTraceForProxy(exception, RmiClient::class.java)
|
||||
throw exception
|
||||
}
|
||||
is Exception -> {
|
||||
// reconstruct the stack trace, so the calling method knows where the method invocation happened, and can trace the call
|
||||
// this stack will ALWAYS run up to this method (so we remove from the top->down, to get to the call site)
|
||||
ListenerManager.cleanStackTrace(Exception(), RmiClient::class.java, any)
|
||||
RmiUtils.cleanStackTraceForProxy(Exception(), RmiClient::class.java, any)
|
||||
throw any
|
||||
}
|
||||
else -> {
|
||||
|
|
|
@ -17,7 +17,6 @@ package dorkbox.network.rmi
|
|||
|
||||
import dorkbox.network.connection.Connection
|
||||
import dorkbox.network.connection.EndPoint
|
||||
import dorkbox.network.connection.ListenerManager
|
||||
import dorkbox.network.rmi.messages.ConnectionObjectCreateRequest
|
||||
import dorkbox.network.rmi.messages.ConnectionObjectCreateResponse
|
||||
import dorkbox.network.rmi.messages.GlobalObjectCreateRequest
|
||||
|
@ -379,7 +378,7 @@ internal class RmiManagerGlobal(logger: KLogger,
|
|||
insideResult.initCause(null)
|
||||
}
|
||||
|
||||
ListenerManager.cleanStackTraceReverse(insideResult as Throwable)
|
||||
RmiUtils.cleanStackTraceForImpl(insideResult as Exception, true)
|
||||
val fancyName = RmiUtils.makeFancyMethodName(cachedMethod)
|
||||
logger.error("Error invoking method: $fancyName", insideResult)
|
||||
}
|
||||
|
@ -419,7 +418,7 @@ internal class RmiManagerGlobal(logger: KLogger,
|
|||
result.initCause(null)
|
||||
}
|
||||
|
||||
ListenerManager.cleanStackTraceReverse(result as Throwable)
|
||||
RmiUtils.cleanStackTraceForImpl(result as Exception, false)
|
||||
val fancyName = RmiUtils.makeFancyMethodName(cachedMethod)
|
||||
logger.error("Error invoking method: $fancyName", result)
|
||||
}
|
||||
|
|
|
@ -470,101 +470,87 @@ object RmiUtils {
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* Remove from the stacktrace the "slice" of stack that relates to "dorkbox.network." package
|
||||
*
|
||||
* Then remove from the last occurrence of "dorkbox.network." ALL "kotlinx.coroutines." and "kotlin.coroutines." stacks
|
||||
*
|
||||
* We do this because these stack frames are not useful in resolving exception handling from a users perspective, and only clutter the stacktrace.
|
||||
*/
|
||||
fun cleanStackTraceForProxy(localThrowable: Throwable, invokingClass: Class<*>, remoteException: Exception? = null) {
|
||||
val myClassName = invokingClass.name
|
||||
val stackTrace = localThrowable.stackTrace
|
||||
var newStartIndex = 0
|
||||
for (element in stackTrace) {
|
||||
newStartIndex++
|
||||
|
||||
if (element.className == myClassName && element.methodName == "invoke") {
|
||||
// we do this 1 more time, because we want to remove the proxy invocation off the stack as well.
|
||||
newStartIndex++
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (remoteException == null) {
|
||||
// no remote exception, just cleanup our own callstack
|
||||
localThrowable.stackTrace = stackTrace.copyOfRange(newStartIndex, stackTrace.size)
|
||||
} else {
|
||||
// merge this info into the remote exception, so we can get the correct call stack info
|
||||
val newStack = Array<StackTraceElement>(remoteException.stackTrace.size + stackTrace.size - newStartIndex) { stackTrace[0] }
|
||||
remoteException.stackTrace.copyInto(newStack)
|
||||
stackTrace.copyInto(newStack, remoteException.stackTrace.size, newStartIndex)
|
||||
|
||||
remoteException.stackTrace = newStack
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove from the stacktrace (going in reverse), kotlin coroutine info + dorkbox network call stack.
|
||||
*
|
||||
* Neither of these are useful in resolving exception handling from a users perspective, and only clutter the stacktrace.
|
||||
*/
|
||||
fun cleanStackTraceForImpl(exception: Exception, isSuspendFunction: Boolean) {
|
||||
val packageName = RmiUtils::class.java.packageName
|
||||
|
||||
val stackTrace = exception.stackTrace
|
||||
var newEndIndex = -1 // because we index by size, but access from 0
|
||||
|
||||
// step 1: starting at 0, find the start of our RMI method invocation
|
||||
for (element in stackTrace) {
|
||||
if (element.className.startsWith(packageName)) {
|
||||
break
|
||||
} else {
|
||||
newEndIndex++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// step 2: starting at newEndIndex -> 0, find the start of reflection information (we are java11+ ONLY, so this is easy)
|
||||
for (i in newEndIndex downTo 0) {
|
||||
// this will be either JAVA reflection or ReflectASM reflection
|
||||
val stackModule = stackTrace[i].moduleName
|
||||
if (stackModule == "java.base") {
|
||||
newEndIndex--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
newEndIndex++ // have to add 1 back, because a copy must be by size (and we access from 0)
|
||||
|
||||
if (newEndIndex > 0) {
|
||||
// if we are Java reflection, we are correct.
|
||||
// if we are ReflectASM reflection, there is ONE stack frame extra we have to remove
|
||||
if (stackTrace[newEndIndex].className == CachedAsmMethod::class.java.name) {
|
||||
newEndIndex--
|
||||
}
|
||||
|
||||
// if we are a KOTLIN suspend function, there is ONE stack frame extra we have to remove
|
||||
if (isSuspendFunction) {
|
||||
newEndIndex--
|
||||
}
|
||||
|
||||
//
|
||||
// suspend fun <T : Any> Call<T>.await(): T {
|
||||
// return suspendCancellableCoroutine { continuation ->
|
||||
// continuation.invokeOnCancellation {
|
||||
// cancel()
|
||||
// }
|
||||
// enqueue(object : Callback<T> {
|
||||
// override fun onResponse(call: Call<T>, response: PingResult.Response<T>) {
|
||||
// if (response.isSuccessful) {
|
||||
// val body = response.body()
|
||||
// if (body == null) {
|
||||
// val invocation = call.request().tag(Invocation::class.java)!!
|
||||
// val method = invocation.method()
|
||||
// val e = KotlinNullPointerException("Response from " +
|
||||
// method.declaringClass.name +
|
||||
// '.' +
|
||||
// method.name +
|
||||
// " was null but response body type was declared as non-null")
|
||||
// continuation.resumeWithException(e)
|
||||
// } else {
|
||||
// continuation.resume(body)
|
||||
// }
|
||||
// } else {
|
||||
// continuation.resumeWithException(HttpException(response))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onFailure(call: Call<T>, t: Throwable) {
|
||||
// continuation.resumeWithException(t)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @JvmName("awaitNullable")
|
||||
// suspend fun <T : Any> Call<T?>.await(): T? {
|
||||
// return suspendCancellableCoroutine { continuation ->
|
||||
// continuation.invokeOnCancellation {
|
||||
// cancel()
|
||||
// }
|
||||
// enqueue(object : Callback<T?> {
|
||||
// override fun onResponse(call: Call<T?>, response: PingResult.Response<T?>) {
|
||||
// if (response.isSuccessful) {
|
||||
// continuation.resume(response.body())
|
||||
// } else {
|
||||
// continuation.resumeWithException(HttpException(response))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onFailure(call: Call<T?>, t: Throwable) {
|
||||
// continuation.resumeWithException(t)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// suspend fun <T> Call<T>.awaitResponse(): PingResult.Response<T> {
|
||||
// return suspendCancellableCoroutine { continuation ->
|
||||
// continuation.invokeOnCancellation {
|
||||
// cancel()
|
||||
// }
|
||||
// enqueue(object : Callback<T> {
|
||||
// override fun onResponse(call: Call<T>, response: PingResult.Response<T>) {
|
||||
// continuation.resume(response)
|
||||
// }
|
||||
//
|
||||
// override fun onFailure(call: Call<T>, t: Throwable) {
|
||||
// continuation.resumeWithException(t)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Force the calling coroutine to suspend before throwing [this].
|
||||
// *
|
||||
// * This is needed when a checked exception is synchronously caught in a [java.lang.reflect.Proxy]
|
||||
// * invocation to avoid being wrapped in [java.lang.reflect.UndeclaredThrowableException].
|
||||
// *
|
||||
// * The implementation is derived from:
|
||||
// * https://github.com/Kotlin/kotlinx.coroutines/pull/1667#issuecomment-556106349
|
||||
// */
|
||||
// suspend fun Exception.suspendAndThrow(): Nothing {
|
||||
// kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn<Unit> { continuation: Continuation<Unit> ->
|
||||
// Dispatchers.Default.dispatch(continuation.context, Runnable {
|
||||
// continuation.intercepted().resumeWithException(this@suspendAndThrow)
|
||||
// })
|
||||
//
|
||||
// kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
|
||||
// }
|
||||
// }
|
||||
exception.stackTrace = stackTrace.copyOfRange(0, newEndIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,8 +59,8 @@ class RmiTest : BaseTest() {
|
|||
|
||||
// Default behavior. RMI is transparent, method calls behave like normal
|
||||
// (return values and exceptions are returned, call is synchronous)
|
||||
System.err.println("hashCode: " + test.hashCode())
|
||||
System.err.println("toString: $test")
|
||||
connection.logger.error("hashCode: " + test.hashCode())
|
||||
connection.logger.error("toString: $test")
|
||||
|
||||
test.withSuspend("test", 32)
|
||||
val s1 = test.withSuspendAndReturn("test", 32)
|
||||
|
@ -78,15 +78,14 @@ class RmiTest : BaseTest() {
|
|||
// Test that RMI correctly waits for the remotely invoked method to exit
|
||||
remoteObject.responseTimeout = 5000
|
||||
test.moo("You should see this two seconds before...", 2000)
|
||||
println("...This")
|
||||
connection.logger.error("...This")
|
||||
remoteObject.responseTimeout = 3000
|
||||
|
||||
// Try exception handling
|
||||
try {
|
||||
test.throwException()
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
System.err.println("\tExpected exception (exception log should also be on the object impl side).")
|
||||
e.printStackTrace()
|
||||
connection.logger.error("Expected exception (exception log should also be on the object impl side).", e)
|
||||
caught = true
|
||||
}
|
||||
Assert.assertTrue(caught)
|
||||
|
@ -95,7 +94,7 @@ class RmiTest : BaseTest() {
|
|||
try {
|
||||
test.throwSuspendException()
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
System.err.println("\tExpected exception (exception log should also be on the object impl side).")
|
||||
connection.logger.error("\tExpected exception (exception log should also be on the object impl side).")
|
||||
e.printStackTrace()
|
||||
caught = true
|
||||
}
|
||||
|
@ -106,7 +105,7 @@ class RmiTest : BaseTest() {
|
|||
// Non-blocking call tests
|
||||
// Non-blocking call tests
|
||||
// Non-blocking call tests
|
||||
System.err.println("I'm currently async: ${remoteObject.async}. Now testing ASYNC")
|
||||
connection.logger.error("I'm currently async: ${remoteObject.async}. Now testing ASYNC")
|
||||
|
||||
remoteObject.async = true
|
||||
|
||||
|
@ -123,7 +122,7 @@ class RmiTest : BaseTest() {
|
|||
try {
|
||||
test.throwException()
|
||||
} catch (e: IllegalStateException) {
|
||||
System.err.println("\tExpected exception (exception log should also be on the object impl side).")
|
||||
connection.logger.error("\tExpected exception (exception log should also be on the object impl side).")
|
||||
e.printStackTrace()
|
||||
caught = true
|
||||
}
|
||||
|
@ -134,13 +133,12 @@ class RmiTest : BaseTest() {
|
|||
try {
|
||||
test.throwSuspendException()
|
||||
} catch (e: IllegalStateException) {
|
||||
System.err.println("\tExpected exception (exception log should also be on the object impl side).")
|
||||
connection.logger.error("\tExpected exception (exception log should also be on the object impl side).")
|
||||
e.printStackTrace()
|
||||
caught = true
|
||||
}
|
||||
// exceptions are not caught when async = true!
|
||||
Assert.assertFalse(caught)
|
||||
caught = false
|
||||
|
||||
|
||||
// Call will time out if non-blocking isn't working properly
|
||||
|
@ -150,9 +148,9 @@ class RmiTest : BaseTest() {
|
|||
// should wait for a small time
|
||||
remoteObject.async = false
|
||||
remoteObject.responseTimeout = 6000
|
||||
println("You should see this 2 seconds before")
|
||||
connection.logger.error("You should see this 2 seconds before")
|
||||
val slow = test.slow()
|
||||
println("...This")
|
||||
connection.logger.error("...This")
|
||||
Assert.assertEquals(slow.toDouble(), 123.0, 0.0001)
|
||||
|
||||
|
||||
|
@ -162,7 +160,7 @@ class RmiTest : BaseTest() {
|
|||
m.text = "sometext"
|
||||
connection.send(m)
|
||||
|
||||
println("Finished tests")
|
||||
connection.logger.error("Finished tests")
|
||||
}
|
||||
|
||||
fun register(manager: NetworkSerializationManager) {
|
||||
|
|
|
@ -21,9 +21,9 @@ package dorkboxTest.network.rmi.classes
|
|||
interface TestCow : TestCowBase {
|
||||
fun moo()
|
||||
fun moo(value: String)
|
||||
fun moo(value: String, delay: Long)
|
||||
suspend fun moo(value: String, delay: Long)
|
||||
fun id(): Int
|
||||
fun slow(): Float
|
||||
suspend fun slow(): Float
|
||||
|
||||
suspend fun withSuspend(value: String, v2: Int)
|
||||
suspend fun withSuspendAndReturn(value: String, v2: Int): Int
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package dorkboxTest.network.rmi.classes
|
||||
|
||||
import dorkbox.network.connection.Connection
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class TestCowImpl(val id: Int) : TestCowBaseImpl(), TestCow {
|
||||
|
@ -22,46 +23,62 @@ class TestCowImpl(val id: Int) : TestCowBaseImpl(), TestCow {
|
|||
private var moos = 0
|
||||
|
||||
override fun moo() {
|
||||
throw RuntimeException("Should never be executed!")
|
||||
}
|
||||
|
||||
fun moo(connection: Connection) {
|
||||
moos++
|
||||
println("Moo! $moos")
|
||||
connection.logger.error("Moo! $moos")
|
||||
}
|
||||
|
||||
override fun moo(value: String) {
|
||||
moos += 2
|
||||
println("Moo! $moos: $value")
|
||||
throw RuntimeException("Should never be executed!")
|
||||
}
|
||||
|
||||
override fun moo(value: String, delay: Long) {
|
||||
fun moo(connection: Connection, value: String) {
|
||||
moos += 2
|
||||
connection.logger.error("Moo! $moos: $value")
|
||||
}
|
||||
|
||||
override suspend fun moo(value: String, delay: Long) {
|
||||
throw RuntimeException("Should never be executed!")
|
||||
}
|
||||
|
||||
suspend fun moo(connection: Connection, value: String, delay: Long) {
|
||||
moos += 4
|
||||
println("Moo! $moos: $value ($delay)")
|
||||
try {
|
||||
Thread.sleep(delay)
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
connection.logger.error("Moo! $moos: $value ($delay)")
|
||||
delay(delay)
|
||||
}
|
||||
|
||||
override fun id(): Int {
|
||||
return id
|
||||
}
|
||||
|
||||
override fun slow(): Float {
|
||||
println("Slowdown!!")
|
||||
try {
|
||||
Thread.sleep(2000)
|
||||
} catch (e: InterruptedException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
override suspend fun slow(): Float {
|
||||
throw RuntimeException("Should never be executed!")
|
||||
}
|
||||
|
||||
suspend fun slow(connection: Connection): Float {
|
||||
connection.logger.error("Slowdown!!")
|
||||
delay(2000)
|
||||
return 123.0f
|
||||
}
|
||||
|
||||
override suspend fun withSuspend(value: String, v2: Int) {
|
||||
println("Suspending!")
|
||||
throw RuntimeException("Should never be executed!")
|
||||
}
|
||||
|
||||
suspend fun withSuspend(connection: Connection, value: String, v2: Int) {
|
||||
connection.logger.error("Suspending!")
|
||||
delay(2000)
|
||||
}
|
||||
|
||||
override suspend fun withSuspendAndReturn(value: String, v2: Int): Int {
|
||||
println("Suspending with return!")
|
||||
throw RuntimeException("Should never be executed!")
|
||||
}
|
||||
|
||||
suspend fun withSuspendAndReturn(connection: Connection, value: String, v2: Int): Int {
|
||||
connection.logger.error("Suspending with return!")
|
||||
delay(2000)
|
||||
return v2
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue