From 4a6d2dfcf8d8847e1e7fdfc24e15f551a90cbd3d Mon Sep 17 00:00:00 2001 From: taichino Date: Fri, 27 Mar 2026 13:24:56 -0400 Subject: [PATCH 1/2] Fix off-by-one in app group identifier length check for POSIX semaphore prefix --- Source/ios-framework/CommonSource/Store.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Source/ios-framework/CommonSource/Store.swift b/Source/ios-framework/CommonSource/Store.swift index 1d31371..ac7cf23 100644 --- a/Source/ios-framework/CommonSource/Store.swift +++ b/Source/ios-framework/CommonSource/Store.swift @@ -260,8 +260,9 @@ public class Store: CustomDebugStringConvertible { // Semaphore names in macOS are limited to 31 characters. // Internally, we need up to 11 chars to identify the semaphore, - // thus the group ID must be equal or less than 20 (ASCII) charaters. - if let appGroupIdentifier = applicationGroups.first(where: { $0.length <= 20 }) { + // thus the prefix (group ID + "/") must be at most 20 characters, + // meaning the group ID itself must be at most 19 characters. + if let appGroupIdentifier = applicationGroups.first(where: { $0.length <= 19 }) { obx_posix_sem_prefix_set(appGroupIdentifier.appending("/")) // print("found appGroupIdentifier \(appGroupIdentifier)") } else { From 3e65e6f1c2058ab6034c24ad60cb2eb8d3b676d0 Mon Sep 17 00:00:00 2001 From: taichino Date: Mon, 30 Mar 2026 15:50:45 -0400 Subject: [PATCH 2/2] Extract semaphorePrefix(from:) for testability and align with Dart API --- Source/ios-framework/CommonSource/Store.swift | 27 +++++++++++++------ .../CommonTests/StoreTests.swift | 11 ++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Source/ios-framework/CommonSource/Store.swift b/Source/ios-framework/CommonSource/Store.swift index ac7cf23..9cdae6c 100644 --- a/Source/ios-framework/CommonSource/Store.swift +++ b/Source/ios-framework/CommonSource/Store.swift @@ -258,15 +258,11 @@ public class Store: CustomDebugStringConvertible { let entitlements = signingInfo[kSecCodeInfoEntitlementsDict] as? [NSString: NSObject], let applicationGroups = entitlements["com.apple.security.application-groups"] as? [NSString] { - // Semaphore names in macOS are limited to 31 characters. - // Internally, we need up to 11 chars to identify the semaphore, - // thus the prefix (group ID + "/") must be at most 20 characters, - // meaning the group ID itself must be at most 19 characters. - if let appGroupIdentifier = applicationGroups.first(where: { $0.length <= 19 }) { - obx_posix_sem_prefix_set(appGroupIdentifier.appending("/")) - // print("found appGroupIdentifier \(appGroupIdentifier)") + if let prefix = Store.semaphorePrefix(from: applicationGroups.map { $0 as String }) { + obx_posix_sem_prefix_set(prefix) + // print("found appGroupIdentifier \(prefix)") } else { - print("Could not find an application group identifier of 20 characters or fewer.") + print("Could not find an application group identifier that fits within the 20-character prefix limit.") } } else if err3 != noErr { // noErr means app has no entitlements, likely not sandboxed. print("Error reading entitlements: \(err3)") @@ -376,4 +372,19 @@ public class Store: CustomDebugStringConvertible { try block() }) } + + // MARK: - POSIX Semaphore Prefix + + /// Finds the first application group identifier that fits within the POSIX semaphore name budget. + /// + /// Semaphore names in macOS are limited to 31 characters. Internally, ObjectBox needs up to 11 characters + /// to identify the semaphore, so the prefix (group ID + "/") must be at most 20 characters. + /// + /// The trailing "/" is appended if not already present before checking the length, matching the behavior + /// of the Dart API. + static func semaphorePrefix(from applicationGroups: [String]) -> String? { + return applicationGroups.lazy + .map { $0.hasSuffix("/") ? $0 : $0.appending("/") } + .first(where: { $0.count <= 20 }) + } } diff --git a/Source/ios-framework/CommonTests/StoreTests.swift b/Source/ios-framework/CommonTests/StoreTests.swift index 5e6c258..7e6e6cf 100644 --- a/Source/ios-framework/CommonTests/StoreTests.swift +++ b/Source/ios-framework/CommonTests/StoreTests.swift @@ -346,4 +346,15 @@ class StoreTests: XCTestCase { func testClone() throws { try testAttachOrClone(clone: true) } + + // MARK: - Semaphore Prefix + + func testSemaphorePrefix() { + // Appends "/" and checks total length <= 20 + XCTAssertEqual(Store.semaphorePrefix(from: ["ABCDEFGHIJ.12345678"]), "ABCDEFGHIJ.12345678/") // 19 + "/" = 20, OK + XCTAssertNil(Store.semaphorePrefix(from: ["ABCDEFGHIJ.123456789"])) // 20 + "/" = 21, too long + XCTAssertEqual(Store.semaphorePrefix(from: ["ABCDEFGHIJ.1234567/"]), "ABCDEFGHIJ.1234567/") // already has "/", no double slash + XCTAssertEqual(Store.semaphorePrefix(from: ["ABCDEFGHIJ.123456789", "SHORT.id"]), "SHORT.id/") // skips first, picks second + XCTAssertNil(Store.semaphorePrefix(from: [])) // empty + } }