@mentra/bluetooth-sdk 3.3.0-dev.418 → 3.3.0-dev.420

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,9 +3,9 @@ package com.mentra.bluetoothsdk
3
3
  /** Generated by release CI. Do not edit in a release checkout. */
4
4
  internal object GeneratedReleaseMetadata {
5
5
  const val FAMILY_BASE_VERSION: String = "3.3.0"
6
- const val RELEASE_IDENTITY: String = "3.3.0-dev.418"
7
- const val RELEASE_SET_ID: String = "mentra-3.3.0-dev.418"
8
- const val SOURCE_COMMIT: String = "aa935586a3c7c6a9a4fb979fd064a07680d17ab1"
9
- const val OTA_MANIFEST_URL: String = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.418.json"
10
- const val OTA_MANIFEST_SHA256: String = "f472ef80d5ff72708f6624ad660d677ca730fa115b01624775056f35266bbe6c"
6
+ const val RELEASE_IDENTITY: String = "3.3.0-dev.420"
7
+ const val RELEASE_SET_ID: String = "mentra-3.3.0-dev.420"
8
+ const val SOURCE_COMMIT: String = "cad421d261e3dc99f743ba902a7e0c4a0f42b095"
9
+ const val OTA_MANIFEST_URL: String = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.420.json"
10
+ const val OTA_MANIFEST_SHA256: String = "0bff87c1bcd2f19eb90079980fc42fe2dbd787ceb0a74bea56b8948f3b4190dc"
11
11
  }
@@ -108,6 +108,9 @@ object BleTraceLogger {
108
108
  }
109
109
 
110
110
  private fun sanitizeValue(key: String?, value: Any?): Any? {
111
+ // Scan security metadata is a boolean, not a credential. Keep the exact
112
+ // typed field while still redacting string/unknown values under this key.
113
+ if (key == "requiresPassword" && value is Boolean) return value
111
114
  if (key != null && sensitiveKeyParts.any { key.contains(it, ignoreCase = true) }) {
112
115
  return "<redacted>"
113
116
  }
@@ -9493,7 +9493,11 @@ class MentraLive : SGCManager() {
9493
9493
  }
9494
9494
  } else {
9495
9495
  // Normal single message transmission
9496
- transportLog("LIVE: Sending data to glasses: " + wireData, bridgeLogging)
9496
+ transportLog(
9497
+ "LIVE: Sending data to glasses: " +
9498
+ loggableOutgoingPayload(wireData, commandTraceInfo.commandType),
9499
+ bridgeLogging
9500
+ )
9497
9501
 
9498
9502
  // Pack the data using the centralized utility with the negotiated endianness
9499
9503
  val packedData =
@@ -9795,6 +9799,18 @@ class MentraLive : SGCManager() {
9795
9799
  }
9796
9800
  }
9797
9801
 
9802
+ /**
9803
+ * Wi-Fi credentials are sent unchanged but never logged; the BLE trace records the command
9804
+ * with the password redacted.
9805
+ */
9806
+ private fun loggableOutgoingPayload(payload: String, commandType: String): String =
9807
+ try {
9808
+ if (JSONObject(payload).has("password")) "<$commandType with credentials omitted>"
9809
+ else payload
9810
+ } catch (_: JSONException) {
9811
+ payload
9812
+ }
9813
+
9798
9814
  private fun summarizeOutgoingMessage(payload: String?): String {
9799
9815
  if (payload == null || payload.isEmpty()) {
9800
9816
  return "type=unknown, requestId=none, appId=none, mode=none, transferMethod=none, bleImgId=none, exposureTimeNs=none, iso=none, mId=none"
@@ -0,0 +1,69 @@
1
+ package com.mentra.bluetoothsdk.debug
2
+
3
+ import java.util.UUID
4
+ import org.json.JSONArray
5
+ import org.json.JSONObject
6
+ import org.junit.Assert.assertEquals
7
+ import org.junit.Assert.assertFalse
8
+ import org.junit.Test
9
+ import org.junit.runner.RunWith
10
+ import org.robolectric.RobolectricTestRunner
11
+ import org.robolectric.annotation.Config
12
+ import org.robolectric.shadows.ShadowLog
13
+
14
+ @RunWith(RobolectricTestRunner::class)
15
+ @Config(sdk = [28])
16
+ class BleTraceLoggerTest {
17
+ @Test
18
+ fun wifiScanPreservesBooleanSecurityWhileRedactingCredentials() {
19
+ val secret = UUID.randomUUID().toString()
20
+ val networks = JSONArray()
21
+ for (secured in listOf(true, false)) {
22
+ networks.put(
23
+ JSONObject()
24
+ .put("ssid", if (secured) "secured-fixture" else "open-fixture")
25
+ .put("requiresPassword", secured)
26
+ .put("signalStrength", -45)
27
+ .put("password", secret)
28
+ .put("token", secret)
29
+ )
30
+ }
31
+ ShadowLog.clear()
32
+ BleTraceLogger.logJson(
33
+ "glasses_to_phone",
34
+ "sdk_ble_event",
35
+ JSONObject().put("type", "wifi_scan_result").put("networks_neo", networks),
36
+ )
37
+ val message = ShadowLog.getLogsForTag("MentraBleTrace").single().msg
38
+ assertFalse(message.contains(secret))
39
+ val actual = JSONObject(message.substringAfter("payload=")).getJSONArray("networks_neo")
40
+ for ((index, secured) in listOf(true, false).withIndex()) {
41
+ val item = actual.getJSONObject(index)
42
+ assertEquals(secured, item.get("requiresPassword"))
43
+ assertEquals(-45, item.getInt("signalStrength"))
44
+ assertEquals("<redacted>", item.getString("password"))
45
+ assertEquals("<redacted>", item.getString("token"))
46
+ }
47
+ }
48
+
49
+ @Test
50
+ fun exceptionDoesNotExposeNonBooleanOrOtherPasswordFields() {
51
+ val secret = UUID.randomUUID().toString()
52
+ ShadowLog.clear()
53
+ BleTraceLogger.logJson(
54
+ "glasses_to_phone",
55
+ "sdk_ble_event",
56
+ JSONObject()
57
+ .put("type", "wifi_scan_result")
58
+ .put("requiresPassword", secret)
59
+ .put("password", true)
60
+ .put("RequiresPassword", false),
61
+ )
62
+ val message = ShadowLog.getLogsForTag("MentraBleTrace").single().msg
63
+ assertFalse(message.contains(secret))
64
+ val actual = JSONObject(message.substringAfter("payload="))
65
+ for (key in listOf("requiresPassword", "password", "RequiresPassword")) {
66
+ assertEquals("<redacted>", actual.getString(key))
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,55 @@
1
+ package com.mentra.bluetoothsdk.sgcs
2
+
3
+ import android.os.Looper
4
+ import androidx.test.core.app.ApplicationProvider
5
+ import com.mentra.bluetoothsdk.Bridge
6
+ import java.nio.charset.StandardCharsets
7
+ import java.time.Duration
8
+ import java.util.UUID
9
+ import org.junit.Assert.assertFalse
10
+ import org.junit.Assert.assertTrue
11
+ import org.junit.Test
12
+ import org.junit.runner.RunWith
13
+ import org.robolectric.RobolectricTestRunner
14
+ import org.robolectric.Shadows.shadowOf
15
+ import org.robolectric.annotation.Config
16
+ import org.robolectric.annotation.LooperMode
17
+ import org.robolectric.shadows.ShadowLog
18
+
19
+ @RunWith(RobolectricTestRunner::class)
20
+ @Config(sdk = [33], shadows = [G1PairingTest.ShadowLc3Cpp::class], instrumentedPackages = ["com.mentra.lc3Lib"])
21
+ @LooperMode(LooperMode.Mode.PAUSED)
22
+ class MentraLiveWifiCredentialLogTest {
23
+ @Test fun `wifi credentials reach the BLE queue without being logged`() {
24
+ Bridge.initialize(ApplicationProvider.getApplicationContext())
25
+ val ssid = "synthetic-${UUID.randomUUID()}"
26
+ val password = UUID.randomUUID().toString()
27
+ val events = mutableListOf<String>()
28
+ val sink = Bridge.addEventSink { type, body -> if (type == "log") events.add(body["message"].toString()) }
29
+ val live = MentraLive()
30
+ try {
31
+ ShadowLog.clear()
32
+ live.sendWifiCredentials(ssid, password)
33
+
34
+ val logs = events + ShadowLog.getLogs().map { it.msg }
35
+ assertFalse("password logged", logs.any { it.contains(password) })
36
+ assertTrue(events.any { it.endsWith("LIVE: Sending data to glasses: <set_wifi_credentials with credentials omitted>") })
37
+ // The glasses still receive the unchanged credentials.
38
+ val wire = queuedWrites(live).joinToString("") { String(it, StandardCharsets.UTF_8) }
39
+ assertTrue(wire.contains("set_wifi_credentials"))
40
+ assertTrue(wire.contains(ssid))
41
+ assertTrue(wire.contains(password))
42
+ } finally {
43
+ Bridge.removeEventSink(sink)
44
+ live.destroy()
45
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofSeconds(2))
46
+ }
47
+ }
48
+
49
+ private fun queuedWrites(live: MentraLive): List<ByteArray> {
50
+ val queue = MentraLive::class.java.getDeclaredField("sendQueue").apply { isAccessible = true }.get(live)
51
+ return (queue as Iterable<*>).map { write ->
52
+ write!!.javaClass.getDeclaredField("data").apply { isAccessible = true }.get(write) as ByteArray
53
+ }
54
+ }
55
+ }
@@ -1,10 +1,10 @@
1
1
  export const BLUETOOTH_SDK_RELEASE_METADATA = Object.freeze({
2
2
  "schemaVersion": 1,
3
3
  "familyBaseVersion": "3.3.0",
4
- "releaseIdentity": "3.3.0-dev.418",
5
- "releaseSetId": "mentra-3.3.0-dev.418",
6
- "sourceCommit": "aa935586a3c7c6a9a4fb979fd064a07680d17ab1",
7
- "otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.418.json",
8
- "otaManifestSha256": "f472ef80d5ff72708f6624ad660d677ca730fa115b01624775056f35266bbe6c"
4
+ "releaseIdentity": "3.3.0-dev.420",
5
+ "releaseSetId": "mentra-3.3.0-dev.420",
6
+ "sourceCommit": "cad421d261e3dc99f743ba902a7e0c4a0f42b095",
7
+ "otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.420.json",
8
+ "otaManifestSha256": "0bff87c1bcd2f19eb90079980fc42fe2dbd787ceb0a74bea56b8948f3b4190dc"
9
9
  });
10
10
  //# sourceMappingURL=releaseMetadata.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,8BAA8B,GAA0C,MAAM,CAAC,MAAM,CAAC;IACjG,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,sBAAsB;IACtC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,iIAAiI;IACnJ,mBAAmB,EAAE,kEAAkE;CACxF,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface BluetoothSdkReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"3.3.0\",\n \"releaseIdentity\": \"3.3.0-dev.418\",\n \"releaseSetId\": \"mentra-3.3.0-dev.418\",\n \"sourceCommit\": \"aa935586a3c7c6a9a4fb979fd064a07680d17ab1\",\n \"otaManifestUrl\": \"https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.418.json\",\n \"otaManifestSha256\": \"f472ef80d5ff72708f6624ad660d677ca730fa115b01624775056f35266bbe6c\"\n})\n"]}
1
+ {"version":3,"file":"releaseMetadata.js","sourceRoot":"","sources":["../../src/generated/releaseMetadata.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,8BAA8B,GAA0C,MAAM,CAAC,MAAM,CAAC;IACjG,eAAe,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO;IAC5B,iBAAiB,EAAE,eAAe;IAClC,cAAc,EAAE,sBAAsB;IACtC,cAAc,EAAE,0CAA0C;IAC1D,gBAAgB,EAAE,iIAAiI;IACnJ,mBAAmB,EAAE,kEAAkE;CACxF,CAAC,CAAA","sourcesContent":["/** Generated by release CI. Do not edit in a release checkout. */\nexport interface BluetoothSdkReleaseMetadata {\n schemaVersion: 1\n familyBaseVersion: string | null\n releaseIdentity: string | null\n releaseSetId: string | null\n sourceCommit: string | null\n otaManifestUrl: string | null\n otaManifestSha256: string | null\n}\n\nexport const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({\n \"schemaVersion\": 1,\n \"familyBaseVersion\": \"3.3.0\",\n \"releaseIdentity\": \"3.3.0-dev.420\",\n \"releaseSetId\": \"mentra-3.3.0-dev.420\",\n \"sourceCommit\": \"cad421d261e3dc99f743ba902a7e0c4a0f42b095\",\n \"otaManifestUrl\": \"https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.420.json\",\n \"otaManifestSha256\": \"0bff87c1bcd2f19eb90079980fc42fe2dbd787ceb0a74bea56b8948f3b4190dc\"\n})\n"]}
@@ -16,7 +16,7 @@ enum BluetoothSdkDefaults {
16
16
  /// silently disable it on every pair the app touches.
17
17
  static let autoPowerOffEnabled = false
18
18
  private static let infoSdkVersionKey = "MentraBluetoothSdkVersion"
19
- private static let swiftPackageSdkVersion = "3.3.0-dev.418"
19
+ private static let swiftPackageSdkVersion = "3.3.0-dev.420"
20
20
  private static let swiftPackageSdkVersionPlaceholder = "__MENTRA" + "_BLUETOOTH_SDK_VERSION__"
21
21
 
22
22
  private static func normalizedSdkVersion(_ value: String?) -> String? {
@@ -1535,7 +1535,7 @@ struct ViewState {
1535
1535
  }
1536
1536
 
1537
1537
  func sendWifiCredentials(_ ssid: String, _ password: String) {
1538
- Bridge.log("MAN: Sending wifi credentials: \(ssid) \(password)")
1538
+ Bridge.log("MAN: Sending wifi credentials: \(ssid)")
1539
1539
  sgc?.sendWifiCredentials(ssid, password)
1540
1540
  }
1541
1541
 
@@ -3,9 +3,9 @@ import Foundation
3
3
  /// Generated by release CI. Do not edit in a release checkout.
4
4
  enum GeneratedReleaseMetadata {
5
5
  static let familyBaseVersion = "3.3.0"
6
- static let releaseIdentity = "3.3.0-dev.418"
7
- static let releaseSetId = "mentra-3.3.0-dev.418"
8
- static let sourceCommit = "aa935586a3c7c6a9a4fb979fd064a07680d17ab1"
9
- static let otaManifestUrl = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.418.json"
10
- static let otaManifestSha256 = "f472ef80d5ff72708f6624ad660d677ca730fa115b01624775056f35266bbe6c"
6
+ static let releaseIdentity = "3.3.0-dev.420"
7
+ static let releaseSetId = "mentra-3.3.0-dev.420"
8
+ static let sourceCommit = "cad421d261e3dc99f743ba902a7e0c4a0f42b095"
9
+ static let otaManifestUrl = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.420.json"
10
+ static let otaManifestSha256 = "0bff87c1bcd2f19eb90079980fc42fe2dbd787ceb0a74bea56b8948f3b4190dc"
11
11
  }
@@ -5223,7 +5223,11 @@ class MentraLive: NSObject, SGCManager {
5223
5223
  bridgeLogging: bridgeLogging
5224
5224
  )
5225
5225
  }
5226
- transportLog("LIVE: Sending data to glasses: \(jsonString)", bridgeLogging: bridgeLogging)
5226
+ // Wi-Fi credentials are sent unchanged but never logged; the BLE trace
5227
+ // above records this command with the password redacted.
5228
+ let loggedPayload = json["password"] == nil
5229
+ ? jsonString : "<\(commandInfo.commandType) with credentials omitted>"
5230
+ transportLog("LIVE: Sending data to glasses: \(loggedPayload)", bridgeLogging: bridgeLogging)
5227
5231
  let packedData =
5228
5232
  packJson(
5229
5233
  jsonString,
@@ -0,0 +1,42 @@
1
+ @testable import MentraBluetoothSDK
2
+ import XCTest
3
+
4
+ /// Captures packed BLE writes instead of sending them, so the real device dispatch,
5
+ /// command serialization and transport logging run without a radio.
6
+ @MainActor
7
+ private final class CapturingMentraLive: MentraLive {
8
+ var writes: [Data] = []
9
+
10
+ override func queueSend(_ data: Data, id _: String, trace _: BleWriteTrace?) {
11
+ writes.append(data)
12
+ }
13
+ }
14
+
15
+ @MainActor
16
+ final class WifiCredentialLoggingTests: XCTestCase {
17
+ func testWifiCredentialsReachTheBleQueueWithoutBeingLogged() {
18
+ let ssid = "synthetic-\(UUID().uuidString)"
19
+ let password = UUID().uuidString
20
+ var logs: [String] = []
21
+ let sink = Bridge.addEventSink { event, body in
22
+ if event == "log", let message = body["message"] as? String {
23
+ logs.append(message)
24
+ }
25
+ }
26
+ defer { Bridge.removeEventSink(sink) }
27
+ let manager = DeviceManager()
28
+ let live = CapturingMentraLive()
29
+ manager.sgc = live
30
+
31
+ manager.sendWifiCredentials(ssid, password)
32
+
33
+ XCTAssertFalse(logs.contains { $0.contains(password) }, "password logged")
34
+ XCTAssertTrue(logs.contains("MAN: Sending wifi credentials: \(ssid)"))
35
+ XCTAssertTrue(logs.contains("LIVE: Sending data to glasses: <set_wifi_credentials with credentials omitted>"))
36
+ // The glasses still receive the unchanged credentials.
37
+ let wire = live.writes.map { String(decoding: $0, as: UTF8.self) }.joined()
38
+ XCTAssertTrue(wire.contains("set_wifi_credentials"))
39
+ XCTAssertTrue(wire.contains(ssid))
40
+ XCTAssertTrue(wire.contains(password))
41
+ }
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/bluetooth-sdk",
3
- "version": "3.3.0-dev.418",
3
+ "version": "3.3.0-dev.420",
4
4
  "description": "SDK for communicating with smart glasses",
5
5
  "main": "build/index.js",
6
6
  "react-native": "src/index.ts",
@@ -114,7 +114,7 @@
114
114
  "registry": "https://registry.npmjs.org/"
115
115
  },
116
116
  "dependencies": {
117
- "@mentra/cloud-protocol": "3.3.0-dev.418"
117
+ "@mentra/cloud-protocol": "3.3.0-dev.420"
118
118
  },
119
119
  "devDependencies": {
120
120
  "@types/node": "^25.9.3",
@@ -12,9 +12,9 @@ export interface BluetoothSdkReleaseMetadata {
12
12
  export const BLUETOOTH_SDK_RELEASE_METADATA: Readonly<BluetoothSdkReleaseMetadata> = Object.freeze({
13
13
  "schemaVersion": 1,
14
14
  "familyBaseVersion": "3.3.0",
15
- "releaseIdentity": "3.3.0-dev.418",
16
- "releaseSetId": "mentra-3.3.0-dev.418",
17
- "sourceCommit": "aa935586a3c7c6a9a4fb979fd064a07680d17ab1",
18
- "otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.418.json",
19
- "otaManifestSha256": "f472ef80d5ff72708f6624ad660d677ca730fa115b01624775056f35266bbe6c"
15
+ "releaseIdentity": "3.3.0-dev.420",
16
+ "releaseSetId": "mentra-3.3.0-dev.420",
17
+ "sourceCommit": "cad421d261e3dc99f743ba902a7e0c4a0f42b095",
18
+ "otaManifestUrl": "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-dev.420.json",
19
+ "otaManifestSha256": "0bff87c1bcd2f19eb90079980fc42fe2dbd787ceb0a74bea56b8948f3b4190dc"
20
20
  })