Skip to content

Commit 3b01308

Browse files
add OKC support (#547)
1 parent 005b491 commit 3b01308

File tree

9 files changed

+206
-53
lines changed

9 files changed

+206
-53
lines changed

app/build.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#Build Properties
2-
#Mon Aug 04 09:46:16 EDT 2025
3-
version_build=26
2+
#Mon Aug 04 11:58:45 EDT 2025
3+
version_build=27
44
version_major=3
55
version_minor=2
66
version_patch=1

app/jacoco.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def jacocoCoverageVerificationProvider = tasks.register('jacocoTestCoverageVerif
8181
}
8282
limit {
8383
counter = 'LINE'
84-
minimum = 0.98
84+
minimum = 0.99
8585
}
8686
limit {
8787
counter = 'METHOD'

app/src/main/kotlin/com/vrem/wifianalyzer/wifi/model/FastRoaming.kt

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,53 @@ import com.vrem.util.buildMinVersionR
2424
import com.vrem.wifianalyzer.R
2525
import java.nio.ByteBuffer
2626

27-
internal const val RM_ENABLED_CAPABILITIES_IE = 70
28-
internal const val NEIGHBOR_REPORT_IDX = 0
29-
internal const val NEIGHBOR_REPORT_BIT = 1
30-
internal const val EXTENDED_CAPABILITIES_IE = 127
31-
internal const val BSS_TRANSITION_IDX = 2
32-
internal const val BSS_TRANSITION_BIT = 3
33-
internal const val MOBILE_DOMAIN_IE = 54
27+
typealias Available = (ie: Int, bytes: ByteBuffer) -> Boolean
3428

35-
private fun ByteBuffer.contains(
36-
idx: Int,
37-
bit: Int,
38-
): Boolean = this[idx].toInt().and(1 shl bit) == (1 shl bit)
29+
private const val IE_MOBILE_DOMAIN = 54
30+
private const val IE_RM_ENABLED_CAPABILITIES = 70
31+
private const val IE_EXTENDED_CAPABILITIES = 127
32+
private const val IE_VENDOR_SPECIFIC = 221
3933

40-
typealias Available = (id: Int, bytes: ByteBuffer) -> Boolean
41-
42-
private val available802_11k: Available = { id: Int, bytes: ByteBuffer ->
43-
RM_ENABLED_CAPABILITIES_IE == id &&
44-
bytes.contains(NEIGHBOR_REPORT_IDX, NEIGHBOR_REPORT_BIT)
34+
/**
35+
* Checks for 802.11k (Radio Resource Management) support.
36+
* Returns true if the Information Element is RM Enabled Capabilities (IE 70)
37+
* and the Neighbor Report bit (bit 1 of the first byte) is set.
38+
*/
39+
internal val available802_11k: Available = { ie: Int, bytes: ByteBuffer ->
40+
ie == IE_RM_ENABLED_CAPABILITIES &&
41+
bytes.remaining() > 0 &&
42+
(bytes[0].toInt() and (1 shl 1)) != 0
4543
}
46-
private val available802_11r: Available = { id: Int, _: ByteBuffer ->
47-
MOBILE_DOMAIN_IE == id
44+
45+
/**
46+
* Checks for 802.11r (Fast BSS Transition) support.
47+
* Returns true if the Information Element is Mobile Domain IE (IE 54).
48+
*/
49+
internal val available802_11r: Available = { ie: Int, _: ByteBuffer -> ie == IE_MOBILE_DOMAIN }
50+
51+
/**
52+
* Checks for 802.11v (BSS Transition Management) support.
53+
* Returns true if the Information Element is Extended Capabilities (IE 127)
54+
* and the BSS Transition bit (bit 3 of the third byte) is set.
55+
*/
56+
internal val available802_11v: Available = { ie: Int, bytes: ByteBuffer ->
57+
ie == IE_EXTENDED_CAPABILITIES &&
58+
bytes.remaining() > 2 &&
59+
(bytes[2].toInt() and (1 shl 3)) != 0
4860
}
49-
private val available802_11v: Available = { id: Int, bytes: ByteBuffer ->
50-
EXTENDED_CAPABILITIES_IE == id &&
51-
bytes.contains(BSS_TRANSITION_IDX, BSS_TRANSITION_BIT)
61+
62+
/**
63+
* Checks for Opportunistic Key Caching (OKC) support.
64+
* Returns true if the Information Element is Vendor Specific (IE 221)
65+
* and the first 4 bytes match the OKC OUI and type.
66+
*/
67+
internal val availableOKC: Available = { ie: Int, bytes: ByteBuffer ->
68+
ie == IE_VENDOR_SPECIFIC &&
69+
bytes.remaining() >= 4 &&
70+
bytes[0] == 0x00.toByte() &&
71+
bytes[1] == 0x0F.toByte() &&
72+
bytes[2] == 0xAC.toByte() &&
73+
bytes[3] == 0x12.toByte()
5274
}
5375

5476
enum class FastRoaming(
@@ -58,6 +80,7 @@ enum class FastRoaming(
5880
FR_802_11K(R.string.fast_roaming_k, available802_11k),
5981
FR_802_11R(R.string.fast_roaming_r, available802_11r),
6082
FR_802_11V(R.string.fast_roaming_v, available802_11v),
83+
FR_OKC(R.string.fast_roaming_okc, availableOKC),
6184
;
6285

6386
companion object {

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@
276276
<string name="fast_roaming_k" translatable="false">"802.11k"</string>
277277
<string name="fast_roaming_r" translatable="false">"802.11r"</string>
278278
<string name="fast_roaming_v" translatable="false">"802.11v"</string>
279+
<string name="fast_roaming_okc" translatable="false">"OKC"</string>
279280
<!-- fast roaming -->
280281

281282
</resources>

app/src/test/kotlin/com/vrem/wifianalyzer/wifi/accesspoint/AccessPointDetailTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ class AccessPointDetailTest {
333333
fun makeViewDetailedWithFastRoaming() {
334334
// setup
335335
val wiFiDetail = withWiFiDetail()
336-
val expectedFastRoaming = "802.11k 802.11r 802.11v"
336+
val expectedFastRoaming = "802.11k 802.11r 802.11v OKC"
337337
// execute
338338
val actual = fixture.makeViewDetailed(wiFiDetail)
339339
// validate
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.vrem.wifianalyzer.wifi.model
2+
3+
import android.net.wifi.ScanResult
4+
import android.net.wifi.ScanResult.InformationElement
5+
import android.os.Build
6+
import androidx.test.ext.junit.runners.AndroidJUnit4
7+
import org.assertj.core.api.Assertions.assertThat
8+
import org.junit.After
9+
import org.junit.Test
10+
import org.junit.runner.RunWith
11+
import org.mockito.kotlin.doReturn
12+
import org.mockito.kotlin.doThrow
13+
import org.mockito.kotlin.mock
14+
import org.mockito.kotlin.never
15+
import org.mockito.kotlin.times
16+
import org.mockito.kotlin.whenever
17+
import org.mockito.kotlin.verify
18+
import org.mockito.kotlin.verifyNoMoreInteractions
19+
import org.robolectric.annotation.Config
20+
import kotlin.intArrayOf
21+
22+
@RunWith(AndroidJUnit4::class)
23+
@Config(sdk = [Build.VERSION_CODES.VANILLA_ICE_CREAM])
24+
class FastRoamingMockTest {
25+
private val scanResult: ScanResult = mock()
26+
private val informationElement: InformationElement = mock()
27+
28+
@After
29+
fun tearDown() {
30+
verifyNoMoreInteractions(scanResult, informationElement)
31+
}
32+
33+
@Test
34+
fun findShouldReturnEmptyListWhenIdOfInformationElementThrowsException() {
35+
// setup
36+
doThrow(RuntimeException("Test exception")).whenever(informationElement).id
37+
doReturn(listOf(informationElement)).whenever(scanResult).informationElements
38+
// execute
39+
val result = FastRoaming.find(scanResult)
40+
// validate
41+
assertThat(result).isEmpty()
42+
verify(scanResult).informationElements
43+
verify(informationElement, times(FastRoaming.entries.size)).id
44+
verify(informationElement, never()).bytes
45+
}
46+
47+
@Test
48+
fun findShouldReturnEmptyListWhenBytesInformationElementThrowsException() {
49+
// setup
50+
doReturn(0).whenever(informationElement).id
51+
doThrow(RuntimeException("Test exception")).whenever(informationElement).bytes
52+
doReturn(listOf(informationElement)).whenever(scanResult).informationElements
53+
// execute
54+
val result = FastRoaming.find(scanResult)
55+
// validate
56+
assertThat(result).isEmpty()
57+
verify(scanResult).informationElements
58+
verify(informationElement, times(FastRoaming.entries.size)).id
59+
verify(informationElement, times(FastRoaming.entries.size)).bytes
60+
}
61+
62+
}

app/src/test/kotlin/com/vrem/wifianalyzer/wifi/model/FastRoamingParameterizedTest.kt

Lines changed: 81 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import org.robolectric.ParameterizedRobolectricTestRunner
3333
import org.robolectric.annotation.Config
3434
import java.nio.ByteBuffer.wrap
3535

36+
private const val IE_MOBILE_DOMAIN = 54
37+
private const val IE_RM_ENABLED_CAPABILITIES = 70
38+
private const val IE_EXTENDED_CAPABILITIES = 127
39+
private const val IE_VENDOR_SPECIFIC = 221
40+
3641
@RunWith(ParameterizedRobolectricTestRunner::class)
3742
@Config(sdk = [Build.VERSION_CODES.VANILLA_ICE_CREAM])
3843
class FastRoamingParameterizedTest(
@@ -68,8 +73,8 @@ class FastRoamingParameterizedTest(
6873
data class Element(
6974
val id: Int,
7075
val bytes: List<Byte>,
71-
val countId: Int = 3,
72-
val countBytes: Int = 3,
76+
val countId: Int = 4,
77+
val countBytes: Int = 4,
7378
)
7479

7580
companion object {
@@ -79,38 +84,93 @@ class FastRoamingParameterizedTest(
7984
listOf(
8085
arrayOf(
8186
listOf(FastRoaming.FR_802_11K),
82-
listOf(Element(RM_ENABLED_CAPABILITIES_IE, listOf(0x7F, 0, 0, 0, 0))),
87+
listOf(Element(IE_RM_ENABLED_CAPABILITIES, byteArrayOf(0x7F, 0x00, 0x00, 0x00, 0x00).toList())),
8388
),
8489
arrayOf(
8590
listOf(FastRoaming.FR_802_11R),
86-
listOf(Element(MOBILE_DOMAIN_IE, listOf(0))),
91+
listOf(Element(IE_MOBILE_DOMAIN, byteArrayOf(0x00).toList())),
8792
),
8893
arrayOf(
8994
listOf(FastRoaming.FR_802_11V),
90-
listOf(Element(EXTENDED_CAPABILITIES_IE, listOf(0, 0, 0x7F))),
95+
listOf(Element(IE_EXTENDED_CAPABILITIES, byteArrayOf(0x00, 0x00, 0x7F).toList())),
96+
),
97+
arrayOf(
98+
listOf(FastRoaming.FR_OKC),
99+
listOf(Element(IE_VENDOR_SPECIFIC, byteArrayOf(0x00, 0x0F, 0xAC.toByte(), 0x12).toList())),
91100
),
92101
arrayOf(
93102
FastRoaming.entries.toList(),
94103
listOf(
95-
Element(RM_ENABLED_CAPABILITIES_IE, listOf(0x7F, 0, 0, 0, 0), 3, 3),
96-
Element(MOBILE_DOMAIN_IE, listOf(0), 2, 2),
97-
Element(EXTENDED_CAPABILITIES_IE, listOf(0, 0, 0x7F), 1, 1),
104+
Element(IE_RM_ENABLED_CAPABILITIES, byteArrayOf(0x7F, 0x00, 0x00, 0x00, 0x00).toList()),
105+
Element(IE_MOBILE_DOMAIN, byteArrayOf(0x00).toList(), 3, 3),
106+
Element(IE_EXTENDED_CAPABILITIES, byteArrayOf(0x00, 0x00, 0x7F).toList(), 2, 2),
107+
Element(IE_VENDOR_SPECIFIC, byteArrayOf(0x00, 0x0F, 0xAC.toByte(), 0x12).toList(), 1, 1),
98108
),
99109
),
100110
arrayOf(listOf(), listOf()),
101-
arrayOf(listOf(), listOf(Element(0, listOf()))),
102-
arrayOf(listOf(), listOf(Element(RM_ENABLED_CAPABILITIES_IE, listOf(0)))),
103-
arrayOf(listOf(), listOf(Element(RM_ENABLED_CAPABILITIES_IE, listOf(0, 0x7F)))),
104-
arrayOf(listOf(), listOf(Element(RM_ENABLED_CAPABILITIES_IE, listOf(0, 0x7F, 0x7F)))),
105-
arrayOf(listOf(), listOf(Element(RM_ENABLED_CAPABILITIES_IE, listOf(0, 0x7F, 0x7F, 0x7F)))),
106-
arrayOf(listOf(), listOf(Element(RM_ENABLED_CAPABILITIES_IE, listOf(0, 0x7F, 0x7F, 0x7F, 0x7F)))),
107-
arrayOf(listOf(), listOf(Element(RM_ENABLED_CAPABILITIES_IE, listOf(0, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F)))),
108-
arrayOf(listOf(), listOf(Element(EXTENDED_CAPABILITIES_IE, listOf(0)))),
109-
arrayOf(listOf(), listOf(Element(EXTENDED_CAPABILITIES_IE, listOf(0x7F)))),
110-
arrayOf(listOf(), listOf(Element(EXTENDED_CAPABILITIES_IE, listOf(0x7F, 0x7F)))),
111-
arrayOf(listOf(), listOf(Element(EXTENDED_CAPABILITIES_IE, listOf(0x7F, 0x7F, 0)))),
112-
arrayOf(listOf(), listOf(Element(EXTENDED_CAPABILITIES_IE, listOf(0x7F, 0x7F, 0, 0x7F)))),
113-
arrayOf(listOf(), listOf(Element(EXTENDED_CAPABILITIES_IE, listOf(0x7F, 0x7F, 0, 0x7F, 0x7F)))),
111+
arrayOf(listOf(), listOf(Element(0, byteArrayOf().toList()))),
112+
arrayOf(listOf(), listOf(Element(IE_RM_ENABLED_CAPABILITIES, byteArrayOf().toList()))),
113+
arrayOf(listOf(), listOf(Element(IE_RM_ENABLED_CAPABILITIES, byteArrayOf(0x00).toList()))),
114+
arrayOf(listOf(), listOf(Element(IE_RM_ENABLED_CAPABILITIES, byteArrayOf(0x00, 0x7F).toList()))),
115+
arrayOf(listOf(), listOf(Element(IE_RM_ENABLED_CAPABILITIES, byteArrayOf(0x00, 0x7F, 0x7F).toList()))),
116+
arrayOf(
117+
listOf(),
118+
listOf(Element(IE_RM_ENABLED_CAPABILITIES, byteArrayOf(0x00, 0x7F, 0x7F, 0x7F).toList())),
119+
),
120+
arrayOf(
121+
listOf(),
122+
listOf(Element(IE_RM_ENABLED_CAPABILITIES, byteArrayOf(0x00, 0x7F, 0x7F, 0x7F, 0x7F).toList())),
123+
),
124+
arrayOf(
125+
listOf(),
126+
listOf(
127+
Element(IE_RM_ENABLED_CAPABILITIES, byteArrayOf(0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F).toList()),
128+
),
129+
),
130+
arrayOf(listOf(), listOf(Element(IE_EXTENDED_CAPABILITIES, byteArrayOf(0x00).toList()))),
131+
arrayOf(listOf(), listOf(Element(IE_EXTENDED_CAPABILITIES, byteArrayOf(0x7F).toList()))),
132+
arrayOf(listOf(), listOf(Element(IE_EXTENDED_CAPABILITIES, byteArrayOf(0x7F, 0x7F).toList()))),
133+
arrayOf(listOf(), listOf(Element(IE_EXTENDED_CAPABILITIES, byteArrayOf(0x7F, 0x7F, 0x00).toList()))),
134+
arrayOf(
135+
listOf(),
136+
listOf(Element(IE_EXTENDED_CAPABILITIES, byteArrayOf(0x7F, 0x7F, 0x00, 0x7F).toList())),
137+
),
138+
arrayOf(
139+
listOf(),
140+
listOf(Element(IE_EXTENDED_CAPABILITIES, byteArrayOf(0x7F, 0x7F, 0x00, 0x7F, 0x7F).toList())),
141+
),
142+
arrayOf(
143+
listOf(),
144+
listOf(Element(IE_VENDOR_SPECIFIC, byteArrayOf().toList())),
145+
),
146+
arrayOf(
147+
listOf(),
148+
listOf(Element(IE_VENDOR_SPECIFIC, byteArrayOf(0x00).toList())),
149+
),
150+
arrayOf(
151+
listOf(),
152+
listOf(Element(IE_VENDOR_SPECIFIC, byteArrayOf(0x00, 0x0F).toList())),
153+
),
154+
arrayOf(
155+
listOf(),
156+
listOf(Element(IE_VENDOR_SPECIFIC, byteArrayOf(0x00, 0x0F, 0xAC.toByte()).toList())),
157+
),
158+
arrayOf(
159+
listOf(),
160+
listOf(Element(IE_VENDOR_SPECIFIC, byteArrayOf(0x01, 0x0F, 0xAC.toByte(), 0x12).toList())),
161+
),
162+
arrayOf(
163+
listOf(),
164+
listOf(Element(IE_VENDOR_SPECIFIC, byteArrayOf(0x00, 0x01, 0xAC.toByte(), 0x12).toList())),
165+
),
166+
arrayOf(
167+
listOf(),
168+
listOf(Element(IE_VENDOR_SPECIFIC, byteArrayOf(0x00, 0x0F, 0xAD.toByte(), 0x12).toList())),
169+
),
170+
arrayOf(
171+
listOf(),
172+
listOf(Element(IE_VENDOR_SPECIFIC, byteArrayOf(0x00, 0x0F, 0xAC.toByte(), 0x13).toList())),
173+
)
114174
)
115175
}
116176
}

app/src/test/kotlin/com/vrem/wifianalyzer/wifi/model/WiFiSignalExtraTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class WiFiSignalExtraTest {
5252
@Test
5353
fun fastRoamingDisplay() {
5454
// setup
55-
val expected = "802.11k 802.11r 802.11v"
55+
val expected = "802.11k 802.11r 802.11v OKC"
5656
// execute
5757
val actual = fixture.fastRoamingDisplay(mainActivity.applicationContext)
5858
// validate

app/src/test/kotlin/com/vrem/wifianalyzer/wifi/scanner/TransformerTest.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ import android.net.wifi.WifiInfo
2323
import android.os.Build
2424
import androidx.test.ext.junit.runners.AndroidJUnit4
2525
import com.vrem.wifianalyzer.wifi.model.BSSID
26-
import com.vrem.wifianalyzer.wifi.model.EXTENDED_CAPABILITIES_IE
2726
import com.vrem.wifianalyzer.wifi.model.FastRoaming
28-
import com.vrem.wifianalyzer.wifi.model.MOBILE_DOMAIN_IE
29-
import com.vrem.wifianalyzer.wifi.model.RM_ENABLED_CAPABILITIES_IE
3027
import com.vrem.wifianalyzer.wifi.model.SSID
3128
import com.vrem.wifianalyzer.wifi.model.WiFiConnection
3229
import com.vrem.wifianalyzer.wifi.model.WiFiDetail
@@ -46,12 +43,21 @@ import org.mockito.kotlin.whenever
4643
import org.robolectric.annotation.Config
4744
import java.nio.ByteBuffer
4845

46+
private const val IE_MOBILE_DOMAIN = 54
47+
private const val IE_RM_ENABLED_CAPABILITIES = 70
48+
private const val IE_EXTENDED_CAPABILITIES = 127
49+
private const val IE_VENDOR_SPECIFIC = 221
50+
4951
@RunWith(AndroidJUnit4::class)
5052
@Config(sdk = [Build.VERSION_CODES.VANILLA_ICE_CREAM])
5153
class TransformerTest {
52-
private val informationElement1 = withInformationElement(RM_ENABLED_CAPABILITIES_IE, byteArrayOf(0x7F, 0, 0, 0, 0))
53-
private val informationElement2 = withInformationElement(EXTENDED_CAPABILITIES_IE, byteArrayOf(0, 0, 0x7F))
54-
private val informationElement3 = withInformationElement(MOBILE_DOMAIN_IE, byteArrayOf())
54+
private val informationElement1 =
55+
withInformationElement(IE_RM_ENABLED_CAPABILITIES, byteArrayOf(0x7F, 0x00, 0x00, 0x00, 0x00))
56+
private val informationElement2 = withInformationElement(IE_EXTENDED_CAPABILITIES, byteArrayOf(0x00, 0x00, 0x7F))
57+
private val informationElement3 = withInformationElement(IE_MOBILE_DOMAIN, byteArrayOf())
58+
private val informationElement4 =
59+
withInformationElement(IE_VENDOR_SPECIFIC, byteArrayOf(0x00, 0x0F, 0xAC.toByte(), 0x12))
60+
5561
private val scanResult1 =
5662
withScanResult(SSID_1, BSSID_1, WiFiWidth.MHZ_160, WiFiStandard.AX, WiFiSecurityTypeTest.All)
5763
private val scanResult2 =
@@ -65,6 +71,7 @@ class TransformerTest {
6571
informationElement1,
6672
informationElement2,
6773
informationElement3,
74+
informationElement4,
6875
),
6976
)
7077
private val scanResult3 = withScanResult(SSID_3, BSSID_3, WiFiWidth.MHZ_40, WiFiStandard.N, listOf(), listOf())

0 commit comments

Comments
 (0)