Browse Source

Prefer IPv6, default to TLS

tags/v1.0.0
Chris Smith 5 years ago
parent
commit
f1ecbf256f

+ 24
- 20
CHANGELOG View File

5
    client certificate.
5
    client certificate.
6
  * Added NicknameChangeRequired event for the case when a nickname is
6
  * Added NicknameChangeRequired event for the case when a nickname is
7
    not allowed during connection and *MUST* be changed
7
    not allowed during connection and *MUST* be changed
8
+ * BREAKING: Added 'PreferIPv6' behaviour config, defaulting to true.
9
+   With this configuration KtIrc will try to use IPv6 if the server
10
+   publishes AAAA records.
11
+ * BREAKING: Default port is now 6697, TLS is enabled by default
8
 
12
 
9
 v0.11.0
13
 v0.11.0
10
 
14
 
88
 v0.7.0
92
 v0.7.0
89
 
93
 
90
  * Fixed experimental API warnings when using IrcClient
94
  * Fixed experimental API warnings when using IrcClient
91
- * IrcClients are now constructed using a DSL
95
+ * BREAKING: IrcClients are now constructed using a DSL
92
    * Users of the library no longer need to care about the implementing class
96
    * Users of the library no longer need to care about the implementing class
93
    * Facilitates adding more options in the future without breaking existing implementations
97
    * Facilitates adding more options in the future without breaking existing implementations
94
  * SASL improvements
98
  * SASL improvements
144
 
148
 
145
 v0.3.1
149
 v0.3.1
146
 
150
 
147
-  * Added more documentation to public methods/classes
148
-  * Fixed exception when sending multiple lines at once (e.g. when connecting!)
151
+ * Added more documentation to public methods/classes
152
+ * Fixed exception when sending multiple lines at once (e.g. when connecting!)
149
 
153
 
150
 v0.3.0
154
 v0.3.0
151
 
155
 
152
-  * Simplified how messages are constructed.
153
-    Instead of: client.send(joinMessage("#channel"))
154
-    Now use: client.sendJoin("#channel")
155
-  * Added reply utility to easily send replies to message events
156
-  * Server state improvements:
157
-    * Added status field to ServerState
158
-    * ServerConnected event is emitted as soon as the socket is connected
159
-    * ServerReady event is emitted after logging in, negotiating, etc
160
-  * Added extra debugging to show what type of events are being dispatched
161
-  * Added ChannelQuit event, raised for each channel a user is in when they quit
162
-  * (Internal) Event handlers can now return more events to emit
156
+ * Simplified how messages are constructed.
157
+   Instead of: client.send(joinMessage("#channel"))
158
+   Now use: client.sendJoin("#channel")
159
+ * Added reply utility to easily send replies to message events
160
+ * Server state improvements:
161
+   * Added status field to ServerState
162
+   * ServerConnected event is emitted as soon as the socket is connected
163
+   * ServerReady event is emitted after logging in, negotiating, etc
164
+ * Added extra debugging to show what type of events are being dispatched
165
+ * Added ChannelQuit event, raised for each channel a user is in when they quit
166
+ * (Internal) Event handlers can now return more events to emit
163
 
167
 
164
 v0.2.1
168
 v0.2.1
165
 
169
 
166
-  * Added documentation and reduced visibility of some internal methods/classes
167
-  * (Internal) Enabled Travis, Codacy and Coveralls
170
+ * Added documentation and reduced visibility of some internal methods/classes
171
+ * (Internal) Enabled Travis, Codacy and Coveralls
168
 
172
 
169
 v0.2.0
173
 v0.2.0
170
 
174
 
171
-  * Added support for connecting over TLS
172
-  * Simplified how event handlers are registered
173
-  * Improved use of coroutines so users don't have to worry about them
174
-  * (Internal) Upgraded to Gradle 5.1.1
175
+ * Added support for connecting over TLS
176
+ * BREAKING: Simplified how event handlers are registered
177
+ * BREAKING: Improved use of coroutines so users don't have to worry about them
178
+ * (Internal) Upgraded to Gradle 5.1.1

+ 6
- 2
docs/index.adoc View File

237
 wish to connect to:
237
 wish to connect to:
238
 
238
 
239
  * `host` - the hostname or IP address of the server *(required)*
239
  * `host` - the hostname or IP address of the server *(required)*
240
- * `port` - the port to connect on _(default: 6667)_
241
- * `useTls` - whether to use a secure connection or not _(default: false)_
240
+ * `port` - the port to connect on _(default: 6697)_
241
+ * `useTls` - whether to use a secure connection or not _(default: true)_
242
  * `password` - the password to provide to the server _(default: null)_
242
  * `password` - the password to provide to the server _(default: null)_
243
 
243
 
244
 An alternative more compact syntax is available for configuring server details:
244
 An alternative more compact syntax is available for configuring server details:
296
    in a `MessageReceived` event being returned. Servers that support the
296
    in a `MessageReceived` event being returned. Servers that support the
297
    IRCv3 `echo-message` capability will do this automatically; enabling the
297
    IRCv3 `echo-message` capability will do this automatically; enabling the
298
    behaviour will make all servers act the same way _(default: false)_
298
    behaviour will make all servers act the same way _(default: false)_
299
+ * `preferIPv6` - if enabled, KtIrc will prefer to connect over IPv6 if the
300
+   server publishes AAAA DNS records. If disabled, KtIrc will prefer IPv4.
301
+   If the server is available exclusively on IPv4 or IPv6 then this option
302
+   has no effect. _(default: true)_
299
 
303
 
300
 The behaviour block is optional in its entirety.
304
 The behaviour block is optional in its entirety.
301
 
305
 

+ 5
- 3
src/main/kotlin/com/dmdirc/ktirc/Dsl.kt View File

34
  * behaviour {
34
  * behaviour {
35
  *     requestModesOnJoin = true
35
  *     requestModesOnJoin = true
36
  *     alwaysEchoMessages = true
36
  *     alwaysEchoMessages = true
37
+ *     preferIPv6 = false
37
  * }
38
  * }
38
  *
39
  *
39
  * sasl {
40
  * sasl {
141
 class ServerConfig {
142
 class ServerConfig {
142
     /** The hostname (or IP address) of the server to connect to. */
143
     /** The hostname (or IP address) of the server to connect to. */
143
     var host: String = ""
144
     var host: String = ""
144
-    /** The port to connect on. Defaults to 6667. */
145
-    var port: Int = 6667
145
+    /** The port to connect on. Defaults to 6697. */
146
+    var port: Int = 6697
146
     /** Whether or not to use TLS (an encrypted connection). */
147
     /** Whether or not to use TLS (an encrypted connection). */
147
-    var useTls: Boolean = false
148
+    var useTls: Boolean = true
148
     /** The password required to connect to the server, if any. */
149
     /** The password required to connect to the server, if any. */
149
     var password: String? = null
150
     var password: String? = null
150
 }
151
 }
212
 class BehaviourConfig : ClientBehaviour {
213
 class BehaviourConfig : ClientBehaviour {
213
     override var requestModesOnJoin = false
214
     override var requestModesOnJoin = false
214
     override var alwaysEchoMessages = false
215
     override var alwaysEchoMessages = false
216
+    override var preferIPv6 = true
215
 }
217
 }

+ 7
- 0
src/main/kotlin/com/dmdirc/ktirc/IrcClient.kt View File

198
      */
198
      */
199
     val alwaysEchoMessages: Boolean
199
     val alwaysEchoMessages: Boolean
200
 
200
 
201
+    /**
202
+     * If enabled, KtIRC will try to connect to IRC servers over IPv6 if they publish the appropriate DNS entries.
203
+     *
204
+     * Otherwise, KtIrc will prefer IPv4.
205
+     */
206
+    val preferIPv6: Boolean
207
+
201
 }
208
 }
202
 
209
 
203
 /**
210
 /**

+ 32
- 4
src/main/kotlin/com/dmdirc/ktirc/IrcClientImpl.kt View File

10
 import com.dmdirc.ktirc.messages.*
10
 import com.dmdirc.ktirc.messages.*
11
 import com.dmdirc.ktirc.messages.processors.messageProcessors
11
 import com.dmdirc.ktirc.messages.processors.messageProcessors
12
 import com.dmdirc.ktirc.model.*
12
 import com.dmdirc.ktirc.model.*
13
+import com.dmdirc.ktirc.util.RemoveIn
13
 import com.dmdirc.ktirc.util.currentTimeProvider
14
 import com.dmdirc.ktirc.util.currentTimeProvider
14
 import com.dmdirc.ktirc.util.generateLabel
15
 import com.dmdirc.ktirc.util.generateLabel
15
 import com.dmdirc.ktirc.util.logger
16
 import com.dmdirc.ktirc.util.logger
16
 import kotlinx.coroutines.*
17
 import kotlinx.coroutines.*
17
 import kotlinx.coroutines.channels.Channel
18
 import kotlinx.coroutines.channels.Channel
18
 import kotlinx.coroutines.channels.map
19
 import kotlinx.coroutines.channels.map
20
+import java.net.Inet6Address
21
+import java.net.InetAddress
19
 import java.time.Duration
22
 import java.time.Duration
20
 import java.util.concurrent.atomic.AtomicBoolean
23
 import java.util.concurrent.atomic.AtomicBoolean
21
 import java.util.logging.Level
24
 import java.util.logging.Level
23
 /**
26
 /**
24
  * Concrete implementation of an [IrcClient].
27
  * Concrete implementation of an [IrcClient].
25
  */
28
  */
26
-// TODO: How should alternative nicknames work?
27
-// TODO: Should IRC Client take a pool of servers and rotate through, or make the caller do that?
28
 internal class IrcClientImpl(private val config: IrcClientConfig) : ExperimentalIrcClient, CoroutineScope {
29
 internal class IrcClientImpl(private val config: IrcClientConfig) : ExperimentalIrcClient, CoroutineScope {
29
 
30
 
30
     private val log by logger()
31
     private val log by logger()
33
     override val coroutineContext = GlobalScope.newCoroutineContext(Dispatchers.IO)
34
     override val coroutineContext = GlobalScope.newCoroutineContext(Dispatchers.IO)
34
 
35
 
35
     @ExperimentalCoroutinesApi
36
     @ExperimentalCoroutinesApi
36
-    internal var socketFactory: (CoroutineScope, String, Int, Boolean) -> LineBufferedSocket = ::LineBufferedSocketImpl
37
+    internal var socketFactory: (CoroutineScope, String, String, Int, Boolean) -> LineBufferedSocket = ::LineBufferedSocketImpl
38
+    internal var resolver: (String) -> Collection<ResolveResult> = { host ->
39
+        InetAddress.getAllByName(host).map { ResolveResult(it.hostAddress, it is Inet6Address) }
40
+    }
37
 
41
 
38
     internal var asyncTimeout = Duration.ofSeconds(20)
42
     internal var asyncTimeout = Duration.ofSeconds(20)
39
 
43
 
51
 
55
 
52
     private val connecting = AtomicBoolean(false)
56
     private val connecting = AtomicBoolean(false)
53
 
57
 
58
+    @Deprecated("Use structured send instead", ReplaceWith("send(command, arguments)"))
59
+    @RemoveIn("2.0.0")
54
     override fun send(message: String) {
60
     override fun send(message: String) {
55
         socket?.sendChannel?.offer(message.toByteArray()) ?: log.warning { "No send channel for message: $message" }
61
         socket?.sendChannel?.offer(message.toByteArray()) ?: log.warning { "No send channel for message: $message" }
56
     }
62
     }
81
     override fun connect() {
87
     override fun connect() {
82
         check(!connecting.getAndSet(true))
88
         check(!connecting.getAndSet(true))
83
 
89
 
90
+        val ip: String
91
+        try {
92
+            ip = resolve(config.server.host)
93
+        } catch (ex: Exception) {
94
+            log.log(Level.SEVERE, ex) { "Error resolving ${config.server.host}" }
95
+            emitEvent(ServerConnectionError(EventMetadata(currentTimeProvider()), ConnectionError.UnresolvableAddress, ex.localizedMessage))
96
+            reset()
97
+            return
98
+        }
99
+
84
         @Suppress("EXPERIMENTAL_API_USAGE")
100
         @Suppress("EXPERIMENTAL_API_USAGE")
85
-        with(socketFactory(this, config.server.host, config.server.port, config.server.useTls)) {
101
+        with(socketFactory(this, config.server.host, ip, config.server.port, config.server.useTls)) {
86
             socket = this
102
             socket = this
87
 
103
 
88
             emitEvent(ServerConnecting(EventMetadata(currentTimeProvider())))
104
             emitEvent(ServerConnecting(EventMetadata(currentTimeProvider())))
129
         }
145
         }
130
     }
146
     }
131
 
147
 
148
+    private fun resolve(host: String): String {
149
+        val hosts = resolver(host)
150
+        val preferredHosts = hosts.filter { it.isV6 == behaviour.preferIPv6 }
151
+        return if (preferredHosts.isNotEmpty()) {
152
+            preferredHosts.random().ip
153
+        } else {
154
+            hosts.random().ip
155
+        }
156
+    }
157
+
132
     internal fun reset() {
158
     internal fun reset() {
133
         serverState.reset()
159
         serverState.reset()
134
         channelState.clear()
160
         channelState.clear()
138
     }
164
     }
139
 
165
 
140
 }
166
 }
167
+
168
+internal data class ResolveResult(val ip: String, val isV6: Boolean)

+ 2
- 3
src/main/kotlin/com/dmdirc/ktirc/io/LineBufferedSocket.kt View File

31
  */
31
  */
32
 // TODO: Expose advanced TLS options
32
 // TODO: Expose advanced TLS options
33
 @ExperimentalCoroutinesApi
33
 @ExperimentalCoroutinesApi
34
-internal class LineBufferedSocketImpl(coroutineScope: CoroutineScope, private val host: String, private val port: Int, private val tls: Boolean = false) : CoroutineScope, LineBufferedSocket {
34
+internal class LineBufferedSocketImpl(coroutineScope: CoroutineScope, private val host: String, private val ip: String, private val port: Int, private val tls: Boolean = false) : CoroutineScope, LineBufferedSocket {
35
 
35
 
36
     companion object {
36
     companion object {
37
         const val CARRIAGE_RETURN = '\r'.toByte()
37
         const val CARRIAGE_RETURN = '\r'.toByte()
59
                     socket = TlsSocket(this@LineBufferedSocketImpl, socket, this, host)
59
                     socket = TlsSocket(this@LineBufferedSocketImpl, socket, this, host)
60
                 }
60
                 }
61
             }
61
             }
62
-            socket.connect(InetSocketAddress(host, port))
63
-            println("connected!")
62
+            socket.connect(InetSocketAddress(ip, port))
64
             writeChannel = socket.write
63
             writeChannel = socket.write
65
         }
64
         }
66
         launch { writeLines() }
65
         launch { writeLines() }

+ 132
- 5
src/test/kotlin/com/dmdirc/ktirc/IrcClientImplTest.kt View File

17
 import org.junit.jupiter.api.BeforeEach
17
 import org.junit.jupiter.api.BeforeEach
18
 import org.junit.jupiter.api.Test
18
 import org.junit.jupiter.api.Test
19
 import org.junit.jupiter.api.assertThrows
19
 import org.junit.jupiter.api.assertThrows
20
+import java.net.UnknownHostException
20
 import java.nio.channels.UnresolvedAddressException
21
 import java.nio.channels.UnresolvedAddressException
21
 import java.security.cert.CertificateException
22
 import java.security.cert.CertificateException
22
 import java.util.concurrent.atomic.AtomicReference
23
 import java.util.concurrent.atomic.AtomicReference
26
 
27
 
27
     companion object {
28
     companion object {
28
         private const val HOST = "thegibson.com"
29
         private const val HOST = "thegibson.com"
30
+        private const val HOST2 = "irc.thegibson.com"
31
+        private const val IP = "127.0.13.37"
29
         private const val PORT = 12345
32
         private const val PORT = 12345
30
         private const val NICK = "AcidBurn"
33
         private const val NICK = "AcidBurn"
31
         private const val REAL_NAME = "Kate Libby"
34
         private const val REAL_NAME = "Kate Libby"
41
         every { sendChannel } returns sendLineChannel
44
         every { sendChannel } returns sendLineChannel
42
     }
45
     }
43
 
46
 
44
-    private val mockSocketFactory = mockk<(CoroutineScope, String, Int, Boolean) -> LineBufferedSocket> {
45
-        every { this@mockk.invoke(any(), eq(HOST), eq(PORT), any()) } returns mockSocket
47
+    private val mockSocketFactory = mockk<(CoroutineScope, String, String, Int, Boolean) -> LineBufferedSocket> {
48
+        every { this@mockk.invoke(any(), eq(HOST), eq(IP), eq(PORT), any()) } returns mockSocket
49
+        every { this@mockk.invoke(any(), eq(HOST2), any(), eq(PORT), any()) } returns mockSocket
50
+    }
51
+
52
+    private val mockResolver = mockk<(String) -> Collection<ResolveResult>> {
53
+        every { this@mockk.invoke(HOST) } returns listOf(ResolveResult(IP, false))
46
     }
54
     }
47
 
55
 
48
     private val mockEventHandler = mockk<(IrcEvent) -> Unit> {
56
     private val mockEventHandler = mockk<(IrcEvent) -> Unit> {
58
     private val serverConfig = ServerConfig().apply {
66
     private val serverConfig = ServerConfig().apply {
59
         host = HOST
67
         host = HOST
60
         port = PORT
68
         port = PORT
69
+        useTls = false
61
     }
70
     }
62
 
71
 
63
     private val normalConfig = IrcClientConfig(serverConfig, profileConfig, BehaviourConfig(), null)
72
     private val normalConfig = IrcClientConfig(serverConfig, profileConfig, BehaviourConfig(), null)
71
     fun `uses socket factory to create a new socket on connect`() {
80
     fun `uses socket factory to create a new socket on connect`() {
72
         val client = IrcClientImpl(normalConfig)
81
         val client = IrcClientImpl(normalConfig)
73
         client.socketFactory = mockSocketFactory
82
         client.socketFactory = mockSocketFactory
83
+        client.resolver = mockResolver
74
         client.connect()
84
         client.connect()
75
 
85
 
76
-        verify(timeout = 500) { mockSocketFactory(client, HOST, PORT, false) }
86
+        verify(timeout = 500) { mockSocketFactory(client, HOST, IP, PORT, false) }
77
     }
87
     }
78
 
88
 
79
     @Test
89
     @Test
84
             useTls = true
94
             useTls = true
85
         }, profileConfig, BehaviourConfig(), null))
95
         }, profileConfig, BehaviourConfig(), null))
86
         client.socketFactory = mockSocketFactory
96
         client.socketFactory = mockSocketFactory
97
+        client.resolver = mockResolver
98
+        client.connect()
99
+
100
+        verify(timeout = 500) { mockSocketFactory(client, HOST, IP, PORT, true) }
101
+    }
102
+
103
+    @Test
104
+    fun `prefers ipv6 addresses if behaviour is enabled`() {
105
+        val client = IrcClientImpl(IrcClientConfig(ServerConfig().apply {
106
+            host = HOST2
107
+            port = PORT
108
+        }, profileConfig, BehaviourConfig().apply { preferIPv6 = true }, null))
109
+
110
+        every { mockResolver(HOST2) } returns listOf(
111
+                ResolveResult(IP, false),
112
+                ResolveResult("::13:37", true),
113
+                ResolveResult("0.0.0.0", false)
114
+        )
115
+
116
+        client.socketFactory = mockSocketFactory
117
+        client.resolver = mockResolver
118
+        client.connect()
119
+
120
+        verify(timeout = 500) { mockSocketFactory(client, HOST2, "::13:37", PORT, true) }
121
+    }
122
+
123
+    @Test
124
+    fun `falls back to ipv4 if no ipv6 addresses are available`() {
125
+        val client = IrcClientImpl(IrcClientConfig(ServerConfig().apply {
126
+            host = HOST2
127
+            port = PORT
128
+        }, profileConfig, BehaviourConfig().apply { preferIPv6 = true }, null))
129
+
130
+        every { mockResolver(HOST2) } returns listOf(
131
+                ResolveResult("0.0.0.0", false)
132
+        )
133
+
134
+        client.socketFactory = mockSocketFactory
135
+        client.resolver = mockResolver
87
         client.connect()
136
         client.connect()
88
 
137
 
89
-        verify(timeout = 500) { mockSocketFactory(client, HOST, PORT, true) }
138
+        verify(timeout = 500) { mockSocketFactory(client, HOST2, "0.0.0.0", PORT, true) }
139
+    }
140
+
141
+    @Test
142
+    fun `prefers ipv4 addresses if ipv6 behaviour is disabled`() {
143
+        val client = IrcClientImpl(IrcClientConfig(ServerConfig().apply {
144
+            host = HOST2
145
+            port = PORT
146
+        }, profileConfig, BehaviourConfig().apply { preferIPv6 = false }, null))
147
+
148
+        every { mockResolver(HOST2) } returns listOf(
149
+                ResolveResult("::13:37", true),
150
+                ResolveResult("::313:37", true),
151
+                ResolveResult("0.0.0.0", false)
152
+        )
153
+
154
+        client.socketFactory = mockSocketFactory
155
+        client.resolver = mockResolver
156
+        client.connect()
157
+
158
+        verify(timeout = 500) { mockSocketFactory(client, HOST2, "0.0.0.0", PORT, true) }
159
+    }
160
+
161
+    @Test
162
+    fun `falls back to ipv6 if no ipv4 addresses available`() {
163
+        val client = IrcClientImpl(IrcClientConfig(ServerConfig().apply {
164
+            host = HOST2
165
+            port = PORT
166
+        }, profileConfig, BehaviourConfig().apply { preferIPv6 = false }, null))
167
+
168
+        every { mockResolver(HOST2) } returns listOf(
169
+                ResolveResult("::13:37", true)
170
+        )
171
+
172
+        client.socketFactory = mockSocketFactory
173
+        client.resolver = mockResolver
174
+        client.connect()
175
+
176
+        verify(timeout = 500) { mockSocketFactory(client, HOST2, "::13:37", PORT, true) }
177
+    }
178
+
179
+    @Test
180
+    fun `raises error if dns fails`() {
181
+        val client = IrcClientImpl(IrcClientConfig(ServerConfig().apply {
182
+            host = HOST2
183
+        }, profileConfig, BehaviourConfig().apply { preferIPv6 = true }, null))
184
+
185
+        every { mockResolver(HOST2) } throws UnknownHostException("oops")
186
+
187
+        client.socketFactory = mockSocketFactory
188
+        client.resolver = mockResolver
189
+        client.onEvent(mockEventHandler)
190
+        client.connect()
191
+
192
+        verify(timeout = 500) {
193
+            mockEventHandler(match { it is ServerConnectionError && it.error == ConnectionError.UnresolvableAddress })
194
+        }
90
     }
195
     }
91
 
196
 
92
     @Test
197
     @Test
93
     fun `throws if socket already exists`() {
198
     fun `throws if socket already exists`() {
94
         val client = IrcClientImpl(normalConfig)
199
         val client = IrcClientImpl(normalConfig)
95
         client.socketFactory = mockSocketFactory
200
         client.socketFactory = mockSocketFactory
201
+        client.resolver = mockResolver
96
         client.connect()
202
         client.connect()
97
 
203
 
98
         assertThrows<IllegalStateException> {
204
         assertThrows<IllegalStateException> {
112
 
218
 
113
         val client = IrcClientImpl(normalConfig)
219
         val client = IrcClientImpl(normalConfig)
114
         client.socketFactory = mockSocketFactory
220
         client.socketFactory = mockSocketFactory
221
+        client.resolver = mockResolver
115
         client.onEvent(mockEventHandler)
222
         client.onEvent(mockEventHandler)
116
         client.connect()
223
         client.connect()
117
 
224
 
128
     fun `sends basic connection strings`() = runBlocking {
235
     fun `sends basic connection strings`() = runBlocking {
129
         val client = IrcClientImpl(normalConfig)
236
         val client = IrcClientImpl(normalConfig)
130
         client.socketFactory = mockSocketFactory
237
         client.socketFactory = mockSocketFactory
238
+        client.resolver = mockResolver
131
         client.connect()
239
         client.connect()
132
 
240
 
133
         assertEquals("CAP LS 302", String(sendLineChannel.receive()))
241
         assertEquals("CAP LS 302", String(sendLineChannel.receive()))
143
             password = PASSWORD
251
             password = PASSWORD
144
         }, profileConfig, BehaviourConfig(), null))
252
         }, profileConfig, BehaviourConfig(), null))
145
         client.socketFactory = mockSocketFactory
253
         client.socketFactory = mockSocketFactory
254
+        client.resolver = mockResolver
146
         client.connect()
255
         client.connect()
147
 
256
 
148
         assertEquals("CAP LS 302", String(sendLineChannel.receive()))
257
         assertEquals("CAP LS 302", String(sendLineChannel.receive()))
153
     fun `sends events to provided event handler`() {
262
     fun `sends events to provided event handler`() {
154
         val client = IrcClientImpl(normalConfig)
263
         val client = IrcClientImpl(normalConfig)
155
         client.socketFactory = mockSocketFactory
264
         client.socketFactory = mockSocketFactory
265
+        client.resolver = mockResolver
156
         client.onEvent(mockEventHandler)
266
         client.onEvent(mockEventHandler)
157
 
267
 
158
         GlobalScope.launch {
268
         GlobalScope.launch {
204
     fun `sends text to socket`() = runBlocking {
314
     fun `sends text to socket`() = runBlocking {
205
         val client = IrcClientImpl(normalConfig)
315
         val client = IrcClientImpl(normalConfig)
206
         client.socketFactory = mockSocketFactory
316
         client.socketFactory = mockSocketFactory
317
+        client.resolver = mockResolver
207
         client.connect()
318
         client.connect()
208
 
319
 
209
         client.send("testing 123")
320
         client.send("testing 123")
215
     fun `sends structured text to socket`() = runBlocking {
326
     fun `sends structured text to socket`() = runBlocking {
216
         val client = IrcClientImpl(normalConfig)
327
         val client = IrcClientImpl(normalConfig)
217
         client.socketFactory = mockSocketFactory
328
         client.socketFactory = mockSocketFactory
329
+        client.resolver = mockResolver
218
         client.connect()
330
         client.connect()
219
 
331
 
220
         client.send("testing", "123", "456")
332
         client.send("testing", "123", "456")
227
         val config = IrcClientConfig(serverConfig, profileConfig, BehaviourConfig().apply { alwaysEchoMessages = true }, null)
339
         val config = IrcClientConfig(serverConfig, profileConfig, BehaviourConfig().apply { alwaysEchoMessages = true }, null)
228
         val client = IrcClientImpl(config)
340
         val client = IrcClientImpl(config)
229
         client.socketFactory = mockSocketFactory
341
         client.socketFactory = mockSocketFactory
342
+        client.resolver = mockResolver
230
 
343
 
231
         val slot = slot<MessageReceived>()
344
         val slot = slot<MessageReceived>()
232
         val mockkEventHandler = mockk<(IrcEvent) -> Unit>(relaxed = true)
345
         val mockkEventHandler = mockk<(IrcEvent) -> Unit>(relaxed = true)
250
         val config = IrcClientConfig(serverConfig, profileConfig, BehaviourConfig().apply { alwaysEchoMessages = true }, null)
363
         val config = IrcClientConfig(serverConfig, profileConfig, BehaviourConfig().apply { alwaysEchoMessages = true }, null)
251
         val client = IrcClientImpl(config)
364
         val client = IrcClientImpl(config)
252
         client.socketFactory = mockSocketFactory
365
         client.socketFactory = mockSocketFactory
366
+        client.resolver = mockResolver
253
         client.serverState.capabilities.enabledCapabilities[Capability.EchoMessages] = ""
367
         client.serverState.capabilities.enabledCapabilities[Capability.EchoMessages] = ""
254
         client.connect()
368
         client.connect()
255
 
369
 
266
         val config = IrcClientConfig(serverConfig, profileConfig, BehaviourConfig().apply { alwaysEchoMessages = false }, null)
380
         val config = IrcClientConfig(serverConfig, profileConfig, BehaviourConfig().apply { alwaysEchoMessages = false }, null)
267
         val client = IrcClientImpl(config)
381
         val client = IrcClientImpl(config)
268
         client.socketFactory = mockSocketFactory
382
         client.socketFactory = mockSocketFactory
383
+        client.resolver = mockResolver
269
         client.connect()
384
         client.connect()
270
 
385
 
271
         client.onEvent(mockEventHandler)
386
         client.onEvent(mockEventHandler)
273
 
388
 
274
         verify(inverse = true) {
389
         verify(inverse = true) {
275
             mockEventHandler(ofType<MessageReceived>())
390
             mockEventHandler(ofType<MessageReceived>())
276
-        }    }
391
+        }
392
+    }
277
 
393
 
278
     @Test
394
     @Test
279
     fun `sends structured text to socket with tags`() = runBlocking {
395
     fun `sends structured text to socket with tags`() = runBlocking {
280
         val client = IrcClientImpl(normalConfig)
396
         val client = IrcClientImpl(normalConfig)
281
         client.socketFactory = mockSocketFactory
397
         client.socketFactory = mockSocketFactory
398
+        client.resolver = mockResolver
282
         client.connect()
399
         client.connect()
283
 
400
 
284
         client.send(tagMap(MessageTag.AccountName to "acidB"), "testing", "123", "456")
401
         client.send(tagMap(MessageTag.AccountName to "acidB"), "testing", "123", "456")
290
     fun `asynchronously sends text to socket without label if cap is missing`() = runBlocking {
407
     fun `asynchronously sends text to socket without label if cap is missing`() = runBlocking {
291
         val client = IrcClientImpl(normalConfig)
408
         val client = IrcClientImpl(normalConfig)
292
         client.socketFactory = mockSocketFactory
409
         client.socketFactory = mockSocketFactory
410
+        client.resolver = mockResolver
411
+
293
         client.connect()
412
         client.connect()
294
 
413
 
295
         client.sendAsync(tagMap(), "testing", arrayOf("123")) { false }
414
         client.sendAsync(tagMap(), "testing", arrayOf("123")) { false }
302
         generateLabel = { "abc123" }
421
         generateLabel = { "abc123" }
303
         val client = IrcClientImpl(normalConfig)
422
         val client = IrcClientImpl(normalConfig)
304
         client.socketFactory = mockSocketFactory
423
         client.socketFactory = mockSocketFactory
424
+        client.resolver = mockResolver
425
+
305
         client.serverState.capabilities.enabledCapabilities[Capability.LabeledResponse] = ""
426
         client.serverState.capabilities.enabledCapabilities[Capability.LabeledResponse] = ""
306
         client.connect()
427
         client.connect()
307
 
428
 
315
         generateLabel = { "abc123" }
436
         generateLabel = { "abc123" }
316
         val client = IrcClientImpl(normalConfig)
437
         val client = IrcClientImpl(normalConfig)
317
         client.socketFactory = mockSocketFactory
438
         client.socketFactory = mockSocketFactory
439
+        client.resolver = mockResolver
440
+
318
         client.serverState.capabilities.enabledCapabilities[Capability.LabeledResponse] = ""
441
         client.serverState.capabilities.enabledCapabilities[Capability.LabeledResponse] = ""
319
         client.connect()
442
         client.connect()
320
 
443
 
327
     fun `disconnects the socket`() = runBlocking {
450
     fun `disconnects the socket`() = runBlocking {
328
         val client = IrcClientImpl(normalConfig)
451
         val client = IrcClientImpl(normalConfig)
329
         client.socketFactory = mockSocketFactory
452
         client.socketFactory = mockSocketFactory
453
+        client.resolver = mockResolver
330
         client.connect()
454
         client.connect()
331
 
455
 
332
         client.disconnect()
456
         client.disconnect()
341
     fun `sends messages in order`() = runBlocking {
465
     fun `sends messages in order`() = runBlocking {
342
         val client = IrcClientImpl(normalConfig)
466
         val client = IrcClientImpl(normalConfig)
343
         client.socketFactory = mockSocketFactory
467
         client.socketFactory = mockSocketFactory
468
+        client.resolver = mockResolver
344
         client.connect()
469
         client.connect()
345
 
470
 
346
         (0..100).forEach { client.send("TEST", "$it") }
471
         (0..100).forEach { client.send("TEST", "$it") }
399
         every { mockSocket.connect() } throws UnresolvedAddressException()
524
         every { mockSocket.connect() } throws UnresolvedAddressException()
400
         with(IrcClientImpl(normalConfig)) {
525
         with(IrcClientImpl(normalConfig)) {
401
             socketFactory = mockSocketFactory
526
             socketFactory = mockSocketFactory
527
+            resolver = mockResolver
402
             withTimeout(500) {
528
             withTimeout(500) {
403
                 launch {
529
                 launch {
404
                     delay(50)
530
                     delay(50)
415
         every { mockSocket.connect() } throws CertificateException("Boooo")
541
         every { mockSocket.connect() } throws CertificateException("Boooo")
416
         with(IrcClientImpl(normalConfig)) {
542
         with(IrcClientImpl(normalConfig)) {
417
             socketFactory = mockSocketFactory
543
             socketFactory = mockSocketFactory
544
+            resolver = mockResolver
418
             withTimeout(500) {
545
             withTimeout(500) {
419
                 launch {
546
                 launch {
420
                     delay(50)
547
                     delay(50)

+ 10
- 10
src/test/kotlin/com/dmdirc/ktirc/io/LineBufferedSocketImplTest.kt View File

20
     @Test
20
     @Test
21
     fun `KtorLineBufferedSocket can connect to a server`() = runBlocking {
21
     fun `KtorLineBufferedSocket can connect to a server`() = runBlocking {
22
         ServerSocket(12321).use { serverSocket ->
22
         ServerSocket(12321).use { serverSocket ->
23
-            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", 12321)
23
+            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", "localhost", 12321)
24
             val clientSocketAsync = GlobalScope.async { serverSocket.accept() }
24
             val clientSocketAsync = GlobalScope.async { serverSocket.accept() }
25
 
25
 
26
             socket.connect()
26
             socket.connect()
32
     @Test
32
     @Test
33
     fun `KtorLineBufferedSocket can send a byte array to a server`() = runBlocking {
33
     fun `KtorLineBufferedSocket can send a byte array to a server`() = runBlocking {
34
         ServerSocket(12321).use { serverSocket ->
34
         ServerSocket(12321).use { serverSocket ->
35
-            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", 12321)
35
+            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", "localhost", 12321)
36
             val clientBytesAsync = GlobalScope.async {
36
             val clientBytesAsync = GlobalScope.async {
37
                 ByteArray(13).apply {
37
                 ByteArray(13).apply {
38
                     serverSocket.accept().getInputStream().read(this)
38
                     serverSocket.accept().getInputStream().read(this)
51
     @Test
51
     @Test
52
     fun `KtorLineBufferedSocket can send a string to a server over TLS`() = runBlocking {
52
     fun `KtorLineBufferedSocket can send a string to a server over TLS`() = runBlocking {
53
         tlsServerSocket(12321).use { serverSocket ->
53
         tlsServerSocket(12321).use { serverSocket ->
54
-            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", 12321, true)
54
+            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", "localhost", 12321, true)
55
             socket.tlsTrustManager = getTrustingManager()
55
             socket.tlsTrustManager = getTrustingManager()
56
             val clientBytesAsync = GlobalScope.async {
56
             val clientBytesAsync = GlobalScope.async {
57
                 ByteArray(13).apply {
57
                 ByteArray(13).apply {
71
     @Test
71
     @Test
72
     fun `KtorLineBufferedSocket can receive a line of CRLF delimited text`() = runBlocking {
72
     fun `KtorLineBufferedSocket can receive a line of CRLF delimited text`() = runBlocking {
73
         ServerSocket(12321).use { serverSocket ->
73
         ServerSocket(12321).use { serverSocket ->
74
-            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", 12321)
74
+            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", "localhost", 12321)
75
             GlobalScope.launch {
75
             GlobalScope.launch {
76
                 serverSocket.accept().getOutputStream().write("Hi there\r\n".toByteArray())
76
                 serverSocket.accept().getOutputStream().write("Hi there\r\n".toByteArray())
77
             }
77
             }
84
     @Test
84
     @Test
85
     fun `KtorLineBufferedSocket can receive a line of LF delimited text`() = runBlocking {
85
     fun `KtorLineBufferedSocket can receive a line of LF delimited text`() = runBlocking {
86
         ServerSocket(12321).use { serverSocket ->
86
         ServerSocket(12321).use { serverSocket ->
87
-            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", 12321)
87
+            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", "localhost", 12321)
88
             GlobalScope.launch {
88
             GlobalScope.launch {
89
                 serverSocket.accept().getOutputStream().write("Hi there\n".toByteArray())
89
                 serverSocket.accept().getOutputStream().write("Hi there\n".toByteArray())
90
             }
90
             }
97
     @Test
97
     @Test
98
     fun `KtorLineBufferedSocket can receive multiple lines of text in one packet`() = runBlocking {
98
     fun `KtorLineBufferedSocket can receive multiple lines of text in one packet`() = runBlocking {
99
         ServerSocket(12321).use { serverSocket ->
99
         ServerSocket(12321).use { serverSocket ->
100
-            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", 12321)
100
+            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", "localhost", 12321)
101
             GlobalScope.launch {
101
             GlobalScope.launch {
102
                 serverSocket.accept().getOutputStream().write("Hi there\nThis is a test\r".toByteArray())
102
                 serverSocket.accept().getOutputStream().write("Hi there\nThis is a test\r".toByteArray())
103
             }
103
             }
112
     @Test
112
     @Test
113
     fun `KtorLineBufferedSocket can receive multiple long lines of text`() = runBlocking {
113
     fun `KtorLineBufferedSocket can receive multiple long lines of text`() = runBlocking {
114
         ServerSocket(12321).use { serverSocket ->
114
         ServerSocket(12321).use { serverSocket ->
115
-            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", 12321)
115
+            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", "localhost", 12321)
116
             val line1 = "abcdefghijklmnopqrstuvwxyz".repeat(500)
116
             val line1 = "abcdefghijklmnopqrstuvwxyz".repeat(500)
117
             val line2 = "1234567890987654321[];'#,.".repeat(500)
117
             val line2 = "1234567890987654321[];'#,.".repeat(500)
118
             val line3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(500)
118
             val line3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(500)
131
     @Test
131
     @Test
132
     fun `KtorLineBufferedSocket can receive one line of text over multiple packets`() = runBlocking {
132
     fun `KtorLineBufferedSocket can receive one line of text over multiple packets`() = runBlocking {
133
         ServerSocket(12321).use { serverSocket ->
133
         ServerSocket(12321).use { serverSocket ->
134
-            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", 12321)
134
+            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", "localhost", 12321)
135
             GlobalScope.launch {
135
             GlobalScope.launch {
136
                 with(serverSocket.accept().getOutputStream()) {
136
                 with(serverSocket.accept().getOutputStream()) {
137
                     write("Hi".toByteArray())
137
                     write("Hi".toByteArray())
152
     @Test
152
     @Test
153
     fun `KtorLineBufferedSocket returns from readLines when socket is closed`() = runBlocking {
153
     fun `KtorLineBufferedSocket returns from readLines when socket is closed`() = runBlocking {
154
         ServerSocket(12321).use { serverSocket ->
154
         ServerSocket(12321).use { serverSocket ->
155
-            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", 12321)
155
+            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", "localhost", 12321)
156
             GlobalScope.launch {
156
             GlobalScope.launch {
157
                 with(serverSocket.accept()) {
157
                 with(serverSocket.accept()) {
158
                     getOutputStream().write("Hi there\r\n".toByteArray())
158
                     getOutputStream().write("Hi there\r\n".toByteArray())
169
     @Test
169
     @Test
170
     fun `KtorLineBufferedSocket disconnects from server`() = runBlocking {
170
     fun `KtorLineBufferedSocket disconnects from server`() = runBlocking {
171
         ServerSocket(12321).use { serverSocket ->
171
         ServerSocket(12321).use { serverSocket ->
172
-            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", 12321)
172
+            val socket = LineBufferedSocketImpl(GlobalScope, "localhost", "localhost", 12321)
173
             val clientSocketAsync = GlobalScope.async { serverSocket.accept() }
173
             val clientSocketAsync = GlobalScope.async { serverSocket.accept() }
174
 
174
 
175
             socket.connect()
175
             socket.connect()

Loading…
Cancel
Save