1
1
package net.mullvad.mullvadvpn.talpid.util
2
2
3
3
import android.net.ConnectivityManager
4
+ import android.net.LinkAddress
5
+ import android.net.LinkProperties
4
6
import android.net.Network
7
+ import android.net.NetworkCapabilities
5
8
import app.cash.turbine.test
6
9
import io.mockk.every
7
10
import io.mockk.mockk
11
+ import io.mockk.mockkObject
8
12
import io.mockk.mockkStatic
13
+ import io.mockk.verify
14
+ import java.net.DatagramSocket
15
+ import java.net.Inet4Address
16
+ import java.net.Inet6Address
9
17
import kotlin.test.assertEquals
10
18
import kotlin.time.Duration.Companion.milliseconds
11
19
import kotlin.time.Duration.Companion.seconds
12
20
import kotlinx.coroutines.channels.awaitClose
13
21
import kotlinx.coroutines.delay
14
22
import kotlinx.coroutines.flow.callbackFlow
15
23
import kotlinx.coroutines.test.runTest
24
+ import net.mullvad.talpid.model.Connectivity
25
+ import net.mullvad.talpid.util.IpUtils
16
26
import net.mullvad.talpid.util.NetworkEvent
27
+ import net.mullvad.talpid.util.defaultNetworkEvents
17
28
import net.mullvad.talpid.util.hasInternetConnectivity
18
- import net.mullvad.talpid.util.networkEvents
19
- import net.mullvad.talpid.util.networksWithInternetConnectivity
20
29
import org.junit.jupiter.api.BeforeEach
21
30
import org.junit.jupiter.api.Test
22
31
@@ -26,57 +35,80 @@ class ConnectivityManagerUtilKtTest {
26
35
@BeforeEach
27
36
fun setup () {
28
37
mockkStatic(CONNECTIVITY_MANAGER_UTIL_CLASS )
38
+ mockkObject(IpUtils )
29
39
}
30
40
31
41
/* * User being online, the listener should emit once with `true` */
32
42
@Test
33
43
fun userIsOnline () = runTest {
34
44
val network = mockk<Network >()
35
- every { connectivityManager.networksWithInternetConnectivity() } returns setOf (network)
36
- every { connectivityManager.networkEvents(any()) } returns
45
+ val linkProperties = mockk<LinkProperties >()
46
+ val ipv4Address: Inet4Address = mockk()
47
+ val ipv6Address: Inet6Address = mockk()
48
+ val linkIpv4Address: LinkAddress = mockk()
49
+ val linkIpv6Address: LinkAddress = mockk()
50
+ every { linkIpv4Address.address } returns ipv4Address
51
+ every { linkIpv6Address.address } returns ipv6Address
52
+ every { linkProperties.linkAddresses } returns
53
+ mutableListOf (linkIpv4Address, linkIpv6Address)
54
+ val mockProtect = mockk< (socket: DatagramSocket ) -> Boolean > (relaxed = true )
55
+ every { connectivityManager.defaultNetworkEvents() } returns
37
56
callbackFlow {
38
57
delay(100 .milliseconds) // Simulate connectivity listener being a bit slow
39
58
send(NetworkEvent .Available (network))
59
+ delay(100 .milliseconds) // Simulate connectivity listener being a bit slow
60
+ send(NetworkEvent .LinkPropertiesChanged (network, linkProperties))
40
61
awaitClose {}
41
62
}
42
63
43
- connectivityManager.hasInternetConnectivity().test {
64
+ connectivityManager.hasInternetConnectivity(mockProtect ).test {
44
65
// Since initial state and listener both return `true` within debounce we only see one
45
66
// event
46
- assertEquals(true , awaitItem())
67
+ assertEquals(Connectivity . Status ( true , true ) , awaitItem())
47
68
expectNoEvents()
48
69
}
49
70
}
50
71
51
72
/* * User being offline, the listener should emit once with `false` */
52
73
@Test
53
74
fun userIsOffline () = runTest {
54
- every { connectivityManager.networksWithInternetConnectivity() } returns setOf ( )
55
- every { connectivityManager.networkEvents(any() ) } returns callbackFlow { awaitClose {} }
75
+ val mockProtect = mockk < (socket : DatagramSocket ) -> Boolean > (relaxed = true )
76
+ every { connectivityManager.defaultNetworkEvents( ) } returns callbackFlow { awaitClose {} }
56
77
57
- connectivityManager.hasInternetConnectivity().test {
78
+ connectivityManager.hasInternetConnectivity(mockProtect ).test {
58
79
// Initially offline and no network events, so we should get a single `false` event
59
- assertEquals(false , awaitItem())
80
+ assertEquals(Connectivity . Status ( false , false ) , awaitItem())
60
81
expectNoEvents()
61
82
}
62
83
}
63
84
64
85
/* * User starting offline and then turning on a online after a while */
65
86
@Test
66
87
fun initiallyOfflineThenBecomingOnline () = runTest {
67
- every { connectivityManager.networksWithInternetConnectivity() } returns emptySet()
68
- every { connectivityManager.networkEvents(any()) } returns
88
+ val network = mockk<Network >()
89
+ val linkProperties = mockk<LinkProperties >()
90
+ val ipv4Address: Inet4Address = mockk()
91
+ val ipv6Address: Inet6Address = mockk()
92
+ val linkIpv4Address: LinkAddress = mockk()
93
+ val linkIpv6Address: LinkAddress = mockk()
94
+ every { linkIpv4Address.address } returns ipv4Address
95
+ every { linkIpv6Address.address } returns ipv6Address
96
+ every { linkProperties.linkAddresses } returns
97
+ mutableListOf (linkIpv4Address, linkIpv6Address)
98
+ val mockProtect = mockk< (socket: DatagramSocket ) -> Boolean > (relaxed = true )
99
+ every { connectivityManager.defaultNetworkEvents() } returns
69
100
callbackFlow {
70
101
// Simulate offline for a little while
71
102
delay(5 .seconds)
72
103
// Then become online
73
104
send(NetworkEvent .Available (mockk()))
105
+ send(NetworkEvent .LinkPropertiesChanged (network, linkProperties))
74
106
awaitClose {}
75
107
}
76
108
77
- connectivityManager.hasInternetConnectivity().test {
78
- assertEquals(false , awaitItem())
79
- assertEquals(true , awaitItem())
109
+ connectivityManager.hasInternetConnectivity(protect = mockProtect ).test {
110
+ assertEquals(Connectivity . Status ( false , false ) , awaitItem())
111
+ assertEquals(Connectivity . Status ( true , true ) , awaitItem())
80
112
expectNoEvents()
81
113
}
82
114
}
@@ -85,46 +117,30 @@ class ConnectivityManagerUtilKtTest {
85
117
@Test
86
118
fun initiallyOnlineAndThenTurningBecomingOffline () = runTest {
87
119
val network = mockk<Network >()
88
- every { connectivityManager.networksWithInternetConnectivity() } returns setOf (network)
89
- every { connectivityManager.networkEvents(any()) } returns
120
+ val linkProperties = mockk<LinkProperties >()
121
+ val ipv4Address: Inet4Address = mockk()
122
+ val ipv6Address: Inet6Address = mockk()
123
+ val linkIpv4Address: LinkAddress = mockk()
124
+ val linkIpv6Address: LinkAddress = mockk()
125
+ every { linkIpv4Address.address } returns ipv4Address
126
+ every { linkIpv6Address.address } returns ipv6Address
127
+ every { linkProperties.linkAddresses } returns
128
+ mutableListOf (linkIpv4Address, linkIpv6Address)
129
+ val mockProtect = mockk< (socket: DatagramSocket ) -> Boolean > (relaxed = true )
130
+ every { connectivityManager.defaultNetworkEvents() } returns
90
131
callbackFlow {
91
132
// Starting as online
92
133
send(NetworkEvent .Available (network))
134
+ send(NetworkEvent .LinkPropertiesChanged (network, linkProperties))
93
135
delay(5 .seconds)
94
136
// Then becoming offline
95
137
send(NetworkEvent .Lost (network))
96
138
awaitClose {}
97
139
}
98
140
99
- connectivityManager.hasInternetConnectivity().test {
100
- assertEquals(true , awaitItem())
101
- assertEquals(false , awaitItem())
102
- expectNoEvents()
103
- }
104
- }
105
-
106
- /* *
107
- * User turning on Airplane mode as our connectivity listener starts so we never get any
108
- * onAvailable event from our listener. Initial value will be `true`, followed by no
109
- * `networkEvent` and then turning on network again after 5 seconds
110
- */
111
- @Test
112
- fun incorrectInitialValueThenBecomingOnline () = runTest {
113
- every { connectivityManager.networksWithInternetConnectivity() } returns setOf (mockk())
114
- every { connectivityManager.networkEvents(any()) } returns
115
- callbackFlow {
116
- delay(5 .seconds)
117
- send(NetworkEvent .Available (mockk()))
118
- awaitClose {}
119
- }
120
-
121
- connectivityManager.hasInternetConnectivity().test {
122
- // Initial value is connected
123
- assertEquals(true , awaitItem())
124
- // Debounce time has passed, and we never received any network events, so we are offline
125
- assertEquals(false , awaitItem())
126
- // Network is back online
127
- assertEquals(true , awaitItem())
141
+ connectivityManager.hasInternetConnectivity(mockProtect).test {
142
+ assertEquals(Connectivity .Status (true , true ), awaitItem())
143
+ assertEquals(Connectivity .Status (false , false ), awaitItem())
128
144
expectNoEvents()
129
145
}
130
146
}
@@ -133,26 +149,38 @@ class ConnectivityManagerUtilKtTest {
133
149
@Test
134
150
fun roamingFromCellularToWifi () = runTest {
135
151
val wifiNetwork = mockk<Network >()
152
+ val wifiNetworkLinkProperties = mockk<LinkProperties >()
153
+ every { wifiNetworkLinkProperties.linkAddresses } returns
154
+ listOf (mockk<LinkAddress > { every { address } returns mockk<Inet4Address >() })
136
155
val cellularNetwork = mockk<Network >()
156
+ val cellularNetworkLinkProperties = mockk<LinkProperties >()
157
+ every { cellularNetworkLinkProperties.linkAddresses } returns
158
+ listOf (mockk<LinkAddress > { every { address } returns mockk<Inet4Address >() })
159
+ val mockProtect = mockk< (socket: DatagramSocket ) -> Boolean > (relaxed = true )
137
160
138
- every { connectivityManager.networksWithInternetConnectivity() } returns
139
- setOf (cellularNetwork)
140
- every { connectivityManager.networkEvents(any()) } returns
161
+ every { connectivityManager.defaultNetworkEvents() } returns
141
162
callbackFlow {
142
163
send(NetworkEvent .Available (cellularNetwork))
164
+ send(
165
+ NetworkEvent .LinkPropertiesChanged (
166
+ cellularNetwork,
167
+ cellularNetworkLinkProperties,
168
+ )
169
+ )
143
170
delay(5 .seconds)
144
171
// Turning on WiFi, we'll have duplicate networks until phone decides to turn of
145
172
// cellular
146
173
send(NetworkEvent .Available (wifiNetwork))
174
+ send(NetworkEvent .LinkPropertiesChanged (wifiNetwork, wifiNetworkLinkProperties))
147
175
delay(30 .seconds)
148
176
// Phone turning off cellular network
149
177
send(NetworkEvent .Lost (cellularNetwork))
150
178
awaitClose {}
151
179
}
152
180
153
- connectivityManager.hasInternetConnectivity().test {
181
+ connectivityManager.hasInternetConnectivity(mockProtect ).test {
154
182
// We should always only see us being online
155
- assertEquals(true , awaitItem())
183
+ assertEquals(Connectivity . Status (ipv4 = true , ipv6 = false ) , awaitItem())
156
184
expectNoEvents()
157
185
}
158
186
}
@@ -161,23 +189,36 @@ class ConnectivityManagerUtilKtTest {
161
189
@Test
162
190
fun roamingFromWifiToCellular () = runTest {
163
191
val wifiNetwork = mockk<Network >()
192
+ val wifiNetworkLinkProperties = mockk<LinkProperties >()
193
+ every { wifiNetworkLinkProperties.linkAddresses } returns
194
+ listOf (mockk<LinkAddress > { every { address } returns mockk<Inet4Address >() })
164
195
val cellularNetwork = mockk<Network >()
196
+ val cellularNetworkLinkProperties = mockk<LinkProperties >()
197
+ every { cellularNetworkLinkProperties.linkAddresses } returns
198
+ listOf (mockk<LinkAddress > { every { address } returns mockk<Inet4Address >() })
199
+ val mockProtect = mockk< (socket: DatagramSocket ) -> Boolean > (relaxed = true )
165
200
166
- every { connectivityManager.networksWithInternetConnectivity() } returns setOf (wifiNetwork)
167
- every { connectivityManager.networkEvents(any()) } returns
201
+ every { connectivityManager.defaultNetworkEvents() } returns
168
202
callbackFlow {
169
203
send(NetworkEvent .Available (wifiNetwork))
204
+ send(NetworkEvent .LinkPropertiesChanged (wifiNetwork, wifiNetworkLinkProperties))
170
205
delay(5 .seconds)
171
206
send(NetworkEvent .Lost (wifiNetwork))
172
207
// We will have no network for a little time until cellular chip is on.
173
208
delay(150 .milliseconds)
174
209
send(NetworkEvent .Available (cellularNetwork))
210
+ send(
211
+ NetworkEvent .LinkPropertiesChanged (
212
+ cellularNetwork,
213
+ cellularNetworkLinkProperties,
214
+ )
215
+ )
175
216
awaitClose {}
176
217
}
177
218
178
- connectivityManager.hasInternetConnectivity().test {
219
+ connectivityManager.hasInternetConnectivity(mockProtect ).test {
179
220
// We should always only see us being online, small offline state is caught by debounce
180
- assertEquals(true , awaitItem())
221
+ assertEquals(Connectivity . Status (ipv4 = true , ipv6 = false ) , awaitItem())
181
222
expectNoEvents()
182
223
}
183
224
}
@@ -186,31 +227,105 @@ class ConnectivityManagerUtilKtTest {
186
227
@Test
187
228
fun slowRoamingFromWifiToCellular () = runTest {
188
229
val wifiNetwork = mockk<Network >()
230
+ val wifiNetworkLinkProperties = mockk<LinkProperties >()
231
+ every { wifiNetworkLinkProperties.linkAddresses } returns
232
+ listOf (mockk<LinkAddress > { every { address } returns mockk<Inet6Address >() })
189
233
val cellularNetwork = mockk<Network >()
234
+ val cellularNetworkLinkProperties = mockk<LinkProperties >()
235
+ every { cellularNetworkLinkProperties.linkAddresses } returns
236
+ listOf (mockk<LinkAddress > { every { address } returns mockk<Inet6Address >() })
237
+ val mockProtect = mockk< (socket: DatagramSocket ) -> Boolean > (relaxed = true )
190
238
191
- every { connectivityManager.networksWithInternetConnectivity() } returns setOf (wifiNetwork)
192
- every { connectivityManager.networkEvents(any()) } returns
239
+ every { connectivityManager.defaultNetworkEvents() } returns
193
240
callbackFlow {
194
241
send(NetworkEvent .Available (wifiNetwork))
242
+ send(NetworkEvent .LinkPropertiesChanged (wifiNetwork, wifiNetworkLinkProperties))
195
243
delay(5 .seconds)
196
244
send(NetworkEvent .Lost (wifiNetwork))
197
245
// We will have no network for a little time until cellular chip is on.
198
246
delay(500 .milliseconds)
199
247
send(NetworkEvent .Available (cellularNetwork))
248
+ send(
249
+ NetworkEvent .LinkPropertiesChanged (
250
+ cellularNetwork,
251
+ cellularNetworkLinkProperties,
252
+ )
253
+ )
200
254
awaitClose {}
201
255
}
202
256
203
- connectivityManager.hasInternetConnectivity().test {
257
+ connectivityManager.hasInternetConnectivity(protect = mockProtect ).test {
204
258
// Wifi is online
205
- assertEquals(true , awaitItem())
259
+ assertEquals(Connectivity . Status ( false , true ) , awaitItem())
206
260
// We didn't get any network within debounce time, so we are offline
207
- assertEquals(false , awaitItem())
261
+ assertEquals(Connectivity . Status ( false , false ) , awaitItem())
208
262
// Cellular network is online
209
- assertEquals(true , awaitItem())
263
+ assertEquals(Connectivity .Status (false , true ), awaitItem())
264
+ expectNoEvents()
265
+ }
266
+ }
267
+
268
+ /* * Switching between networks with different configurations. */
269
+ @Test
270
+ fun roamingFromWifiWithIpv6OnlyToWifiWithIpv4Only () = runTest {
271
+ val ipv6Network = mockk<Network >()
272
+ val ipv6NetworkLinkProperties = mockk<LinkProperties >()
273
+ every { ipv6NetworkLinkProperties.linkAddresses } returns
274
+ listOf (mockk<LinkAddress > { every { address } returns mockk<Inet4Address >() })
275
+ val ipv4Network = mockk<Network >()
276
+ val ipv4NetworkkLinkProperties = mockk<LinkProperties >()
277
+ every { ipv4NetworkkLinkProperties.linkAddresses } returns
278
+ listOf (mockk<LinkAddress > { every { address } returns mockk<Inet6Address >() })
279
+ val mockProtect = mockk< (socket: DatagramSocket ) -> Boolean > (relaxed = true )
280
+
281
+ every { connectivityManager.defaultNetworkEvents() } returns
282
+ callbackFlow {
283
+ send(NetworkEvent .Available (ipv6Network))
284
+ send(NetworkEvent .LinkPropertiesChanged (ipv6Network, ipv6NetworkLinkProperties))
285
+ delay(5 .seconds)
286
+ send(NetworkEvent .Lost (ipv6Network))
287
+ delay(100 .milliseconds)
288
+ send(NetworkEvent .Available (ipv4Network))
289
+ send(NetworkEvent .LinkPropertiesChanged (ipv4Network, ipv4NetworkkLinkProperties))
290
+ awaitClose {}
291
+ }
292
+
293
+ connectivityManager.hasInternetConnectivity(protect = mockProtect).test {
294
+ // Ipv4 network is online
295
+ assertEquals(Connectivity .Status (true , false ), awaitItem())
296
+ // Ipv6 network is online
297
+ assertEquals(Connectivity .Status (false , true ), awaitItem())
210
298
expectNoEvents()
211
299
}
212
300
}
213
301
302
+ /* * Vpn network should NOT check link properties but should rather use socket implementation */
303
+ @Test
304
+ fun checkVpnNetworkUsingSocketImplementation () = runTest {
305
+ val vpnNetwork = mockk<Network >()
306
+ val capabilities = mockk<NetworkCapabilities >()
307
+ every { capabilities.hasCapability(NetworkCapabilities .NET_CAPABILITY_NOT_VPN ) } returns
308
+ false
309
+ val mockProtect = mockk< (socket: DatagramSocket ) -> Boolean > ()
310
+ every { IpUtils .hasIPv4(any()) } returns true
311
+ every { IpUtils .hasIPv6(any()) } returns true
312
+
313
+ every { connectivityManager.defaultNetworkEvents() } returns
314
+ callbackFlow {
315
+ send(NetworkEvent .Available (vpnNetwork))
316
+ send(NetworkEvent .CapabilitiesChanged (vpnNetwork, capabilities))
317
+ awaitClose {}
318
+ }
319
+
320
+ connectivityManager.hasInternetConnectivity(protect = mockProtect).test {
321
+ // Network is online
322
+ assertEquals(Connectivity .Status (true , true ), awaitItem())
323
+ }
324
+
325
+ verify(exactly = 1 ) { IpUtils .hasIPv4(any()) }
326
+ verify(exactly = 1 ) { IpUtils .hasIPv6(any()) }
327
+ }
328
+
214
329
companion object {
215
330
private const val CONNECTIVITY_MANAGER_UTIL_CLASS =
216
331
" net.mullvad.talpid.util.ConnectivityManagerUtilKt"
0 commit comments