@gnolang/gnonative 4.7.1 → 4.7.3
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.
- package/android/libs/gnocore-sources.jar +0 -0
- package/android/libs/gnocore.aar +0 -0
- package/android/src/main/java/land/gno/gnonative/GnonativeModule.kt +3 -1
- package/android/src/main/java/land/gno/gnonative/NativeDBManager.kt +325 -0
- package/ios/Frameworks/GnoCore.xcframework/Info.plist +5 -5
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/GnoCore +0 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Headers/GnoGnonative.objc.h +32 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/A/GnoCore +0 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/A/Headers/GnoGnonative.objc.h +32 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/Current/GnoCore +0 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/Current/Headers/GnoGnonative.objc.h +32 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64_x86_64-simulator/GnoCore.framework/GnoCore +0 -0
- package/ios/Frameworks/GnoCore.xcframework/ios-arm64_x86_64-simulator/GnoCore.framework/Headers/GnoGnonative.objc.h +32 -0
- package/ios/GnonativeModule.swift +1 -0
- package/ios/NativeDBManager.swift +317 -0
- package/package.json +1 -1
|
Binary file
|
package/android/libs/gnocore.aar
CHANGED
|
Binary file
|
|
@@ -17,6 +17,7 @@ class GnonativeModule : Module() {
|
|
|
17
17
|
private var rootDir: File? = null
|
|
18
18
|
private var socketPort = 0
|
|
19
19
|
private var bridgeGnoNative: Bridge? = null
|
|
20
|
+
private var nativeDBManager: NativeDBManager? = null
|
|
20
21
|
|
|
21
22
|
// Each module class must implement the definition function. The definition consists of components
|
|
22
23
|
// that describes the module's functionality and behavior.
|
|
@@ -33,6 +34,7 @@ class GnonativeModule : Module() {
|
|
|
33
34
|
OnCreate {
|
|
34
35
|
context = appContext.reactContext
|
|
35
36
|
rootDir = context!!.filesDir
|
|
37
|
+
nativeDBManager = NativeDBManager(context!!)
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
OnDestroy {
|
|
@@ -53,8 +55,8 @@ class GnonativeModule : Module() {
|
|
|
53
55
|
try {
|
|
54
56
|
val config: BridgeConfig = Gnonative.newBridgeConfig() ?: throw Exception("")
|
|
55
57
|
config.rootDir = rootDir!!.absolutePath
|
|
58
|
+
config.nativeDB = nativeDBManager
|
|
56
59
|
bridgeGnoNative = Gnonative.newBridge(config)
|
|
57
|
-
|
|
58
60
|
promise.resolve(true)
|
|
59
61
|
} catch (err: CodedException) {
|
|
60
62
|
promise.reject(err)
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
package land.gno.gnonative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.SharedPreferences
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.security.keystore.KeyGenParameterSpec
|
|
7
|
+
import android.security.keystore.KeyProperties
|
|
8
|
+
import android.util.Base64
|
|
9
|
+
import gnolang.gno.gnonative.NativeDB
|
|
10
|
+
import java.io.ByteArrayOutputStream
|
|
11
|
+
import java.nio.ByteBuffer
|
|
12
|
+
import java.security.KeyStore
|
|
13
|
+
import javax.crypto.Cipher
|
|
14
|
+
import javax.crypto.KeyGenerator
|
|
15
|
+
import javax.crypto.SecretKey
|
|
16
|
+
import javax.crypto.spec.GCMParameterSpec
|
|
17
|
+
import kotlin.math.min
|
|
18
|
+
import androidx.core.content.edit
|
|
19
|
+
|
|
20
|
+
class NativeDBManager(
|
|
21
|
+
context: Context,
|
|
22
|
+
private val prefsName: String = "gnonative_secure_db",
|
|
23
|
+
private val keyAlias: String = "gnonative_aes_key"
|
|
24
|
+
) : NativeDB {
|
|
25
|
+
|
|
26
|
+
// -------- storage / index --------
|
|
27
|
+
private val prefs: SharedPreferences =
|
|
28
|
+
context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
|
|
29
|
+
private val entryPrefix = "kv:" // entryPrefix + hexKey -> Base64(encrypted blob)
|
|
30
|
+
private val idxKey = "__idx__" // CSV of hex keys in ascending order
|
|
31
|
+
|
|
32
|
+
// -------- crypto --------
|
|
33
|
+
private val ks: KeyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
|
|
34
|
+
|
|
35
|
+
private val lock = Any()
|
|
36
|
+
|
|
37
|
+
init {
|
|
38
|
+
ensureAesKey()
|
|
39
|
+
if (!prefs.contains(idxKey)) prefs.edit { putString(idxKey, "") }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ========== NativeDB implementation ==========
|
|
43
|
+
|
|
44
|
+
override fun delete(p0: ByteArray?) {
|
|
45
|
+
val key = requireKey(p0)
|
|
46
|
+
val hex = hex(key)
|
|
47
|
+
synchronized(lock) {
|
|
48
|
+
val idx = loadIndexAsc().toMutableList()
|
|
49
|
+
val pos = lowerBound(idx, hex)
|
|
50
|
+
if (pos < idx.size && idx[pos] == hex) {
|
|
51
|
+
idx.removeAt(pos)
|
|
52
|
+
saveIndexAsc(idx)
|
|
53
|
+
}
|
|
54
|
+
prefs.edit { remove("$entryPrefix$hex") }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override fun deleteSync(p0: ByteArray?) {
|
|
59
|
+
delete(p0)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override fun get(p0: ByteArray?): ByteArray {
|
|
63
|
+
val key = requireKey(p0)
|
|
64
|
+
val hex = hex(key)
|
|
65
|
+
val b64 = synchronized(lock) { prefs.getString("$entryPrefix$hex", null) }
|
|
66
|
+
?: return ByteArray(0) // gomobile generated non-null return -> use empty on miss
|
|
67
|
+
val blob = Base64.decode(b64, Base64.NO_WRAP)
|
|
68
|
+
return decrypt(blob) ?: throw Exception("Failed to decrypt value for key: $hex")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override fun has(p0: ByteArray?): Boolean {
|
|
72
|
+
val key = requireKey(p0)
|
|
73
|
+
val hex = hex(key)
|
|
74
|
+
return synchronized(lock) { prefs.contains("$entryPrefix$hex") }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
override fun scanChunk(
|
|
78
|
+
p0: ByteArray?, // start
|
|
79
|
+
p1: ByteArray?, // end
|
|
80
|
+
p2: ByteArray?, // seekKey
|
|
81
|
+
p3: Long, // limit
|
|
82
|
+
p4: Boolean // reverse
|
|
83
|
+
): ByteArray {
|
|
84
|
+
val limit = if (p3 < 0) 0 else min(p3, Int.MAX_VALUE.toLong()).toInt()
|
|
85
|
+
return synchronized(lock) {
|
|
86
|
+
val asc = loadIndexAsc() // ascending hex keys
|
|
87
|
+
val startHex = p0?.let { hex(it) }
|
|
88
|
+
val endHex = p1?.let { hex(it) }
|
|
89
|
+
val seekHex = p2?.let { hex(it) }
|
|
90
|
+
|
|
91
|
+
val loBase = startHex?.let { lowerBound(asc, it) } ?: 0
|
|
92
|
+
val hiBase = endHex?.let { lowerBound(asc, it) } ?: asc.size
|
|
93
|
+
var slice: List<String> = if (hiBase <= loBase) emptyList() else asc.subList(loBase, hiBase)
|
|
94
|
+
|
|
95
|
+
// seek positioning & direction
|
|
96
|
+
slice = if (!p4) {
|
|
97
|
+
val from = seekHex?.let { upperBound(slice, it) } ?: 0
|
|
98
|
+
if (from >= slice.size) emptyList() else slice.subList(from, slice.size)
|
|
99
|
+
} else {
|
|
100
|
+
val positioned = if (seekHex != null) {
|
|
101
|
+
val idx = upperBound(slice, seekHex) - 1
|
|
102
|
+
if (idx < 0) emptyList() else slice.subList(0, idx + 1)
|
|
103
|
+
} else slice
|
|
104
|
+
positioned.asReversed()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
val page = if (limit == 0) emptyList() else slice.take(limit)
|
|
108
|
+
val hasMore = page.isNotEmpty() && page.size < slice.size
|
|
109
|
+
val nextSeekHex = if (hasMore) page.last() else null
|
|
110
|
+
|
|
111
|
+
// materialize kv pairs in traversal order
|
|
112
|
+
val pairs = ArrayList<Pair<ByteArray, ByteArray>>(page.size)
|
|
113
|
+
for (h in page) {
|
|
114
|
+
val b64 = prefs.getString("$entryPrefix$h", null) ?: continue
|
|
115
|
+
val blob = Base64.decode(b64, Base64.NO_WRAP)
|
|
116
|
+
val v = decrypt(blob) ?: throw Exception("Failed to decrypt value for key: $h")
|
|
117
|
+
pairs += (unhex(h) to v)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// flags(1) | count(u32 BE) | [kLen k vLen v]* | nextSeekLen(u32 BE) | nextSeek
|
|
121
|
+
encodeChunkBlobBE(pairs, nextSeekHex?.let { unhex(it) }, hasMore)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
override fun set(p0: ByteArray?, p1: ByteArray?) {
|
|
126
|
+
val key = requireKey(p0)
|
|
127
|
+
val value = requireValue(p1)
|
|
128
|
+
val hex = hex(key)
|
|
129
|
+
val enc = encrypt(value)
|
|
130
|
+
val b64 = Base64.encodeToString(enc, Base64.NO_WRAP)
|
|
131
|
+
synchronized(lock) {
|
|
132
|
+
val idx = loadIndexAsc().toMutableList()
|
|
133
|
+
val pos = lowerBound(idx, hex)
|
|
134
|
+
if (pos == idx.size || idx[pos] != hex) {
|
|
135
|
+
idx.add(pos, hex)
|
|
136
|
+
saveIndexAsc(idx)
|
|
137
|
+
}
|
|
138
|
+
prefs.edit { putString("$entryPrefix$hex", b64) }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
override fun setSync(p0: ByteArray?, p1: ByteArray?) {
|
|
143
|
+
set(p0, p1)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ========== helpers ==========
|
|
147
|
+
|
|
148
|
+
private fun requireKey(b: ByteArray?): ByteArray {
|
|
149
|
+
require(!(b == null || b.isEmpty())) { "key must not be null/empty" }
|
|
150
|
+
return b
|
|
151
|
+
}
|
|
152
|
+
private fun requireValue(b: ByteArray?): ByteArray {
|
|
153
|
+
require(b != null) { "value must not be null" }
|
|
154
|
+
return b
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ----- index (csv of hex keys, ascending) -----
|
|
158
|
+
private fun loadIndexAsc(): List<String> {
|
|
159
|
+
val csv = prefs.getString(idxKey, "") ?: ""
|
|
160
|
+
return if (csv.isEmpty()) emptyList() else csv.split(',').filter { it.isNotEmpty() }
|
|
161
|
+
}
|
|
162
|
+
private fun saveIndexAsc(keys: List<String>) {
|
|
163
|
+
prefs.edit { putString(idxKey, if (keys.isEmpty()) "" else keys.joinToString(",")) }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private fun lowerBound(list: List<String>, key: String): Int {
|
|
167
|
+
var lo = 0; var hi = list.size
|
|
168
|
+
while (lo < hi) {
|
|
169
|
+
val mid = (lo + hi) ushr 1
|
|
170
|
+
if (list[mid] < key) lo = mid + 1 else hi = mid
|
|
171
|
+
}
|
|
172
|
+
return lo
|
|
173
|
+
}
|
|
174
|
+
private fun upperBound(list: List<String>, key: String): Int {
|
|
175
|
+
var lo = 0; var hi = list.size
|
|
176
|
+
while (lo < hi) {
|
|
177
|
+
val mid = (lo + hi) ushr 1
|
|
178
|
+
if (list[mid] <= key) lo = mid + 1 else hi = mid
|
|
179
|
+
}
|
|
180
|
+
return lo
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// crypto AES/GCM, StrongBox preferred
|
|
184
|
+
private fun ensureAesKey() {
|
|
185
|
+
if (getAesKey() != null) return
|
|
186
|
+
|
|
187
|
+
val kg = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE)
|
|
188
|
+
val base = KeyGenParameterSpec.Builder(
|
|
189
|
+
keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
|
190
|
+
)
|
|
191
|
+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
|
192
|
+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
|
193
|
+
.setKeySize(256)
|
|
194
|
+
.setRandomizedEncryptionRequired(true)
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
198
|
+
base.setIsStrongBoxBacked(true)
|
|
199
|
+
}
|
|
200
|
+
kg.init(base.build())
|
|
201
|
+
kg.generateKey()
|
|
202
|
+
return
|
|
203
|
+
} catch (_: Throwable) {
|
|
204
|
+
// fall back below without StrongBox
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
kg.init(
|
|
208
|
+
KeyGenParameterSpec.Builder(
|
|
209
|
+
keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
|
210
|
+
)
|
|
211
|
+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
|
212
|
+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
|
213
|
+
.setKeySize(256)
|
|
214
|
+
.setRandomizedEncryptionRequired(true)
|
|
215
|
+
.build()
|
|
216
|
+
)
|
|
217
|
+
kg.generateKey()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private fun getAesKey(): SecretKey? {
|
|
221
|
+
val e = ks.getEntry(keyAlias, null) as? KeyStore.SecretKeyEntry
|
|
222
|
+
return e?.secretKey
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private fun encrypt(plain: ByteArray): ByteArray {
|
|
226
|
+
val key = getAesKey() ?: error("AES key missing")
|
|
227
|
+
|
|
228
|
+
val c = Cipher.getInstance(AES_GCM)
|
|
229
|
+
c.init(Cipher.ENCRYPT_MODE, key)
|
|
230
|
+
|
|
231
|
+
val iv = c.iv
|
|
232
|
+
val ct = c.doFinal(plain)
|
|
233
|
+
|
|
234
|
+
// payload: [version=1][ivLen][iv][ct]
|
|
235
|
+
val out = ByteArray(1 + 1 + iv.size + ct.size)
|
|
236
|
+
var i = 0
|
|
237
|
+
out[i++] = 1
|
|
238
|
+
out[i++] = iv.size.toByte()
|
|
239
|
+
System.arraycopy(iv, 0, out, i, iv.size); i += iv.size
|
|
240
|
+
System.arraycopy(ct, 0, out, i, ct.size)
|
|
241
|
+
return out
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private fun decrypt(blob: ByteArray?): ByteArray? {
|
|
245
|
+
if (blob == null || blob.size < 1 + 1 + 12) return null // iv is usually 12 bytes
|
|
246
|
+
var i = 0
|
|
247
|
+
val ver = blob[i++]
|
|
248
|
+
require(ver.toInt() == 1) { "bad payload version=$ver" }
|
|
249
|
+
val ivLen = blob[i++].toInt() and 0xFF
|
|
250
|
+
require(ivLen in 12..32) { "bad iv length" }
|
|
251
|
+
require(blob.size >= 1 + 1 + ivLen + 1) { "short blob" }
|
|
252
|
+
val iv = ByteArray(ivLen)
|
|
253
|
+
System.arraycopy(blob, i, iv, 0, ivLen); i += ivLen
|
|
254
|
+
val ct = ByteArray(blob.size - i)
|
|
255
|
+
System.arraycopy(blob, i, ct, 0, ct.size)
|
|
256
|
+
|
|
257
|
+
val key = getAesKey() ?: error("AES key missing")
|
|
258
|
+
val c = Cipher.getInstance(AES_GCM)
|
|
259
|
+
c.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(128, iv))
|
|
260
|
+
return c.doFinal(ct)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// chunk framing (match Go format)
|
|
264
|
+
private fun encodeChunkBlobBE(
|
|
265
|
+
entries: List<Pair<ByteArray, ByteArray>>,
|
|
266
|
+
nextSeek: ByteArray?,
|
|
267
|
+
hasMore: Boolean
|
|
268
|
+
): ByteArray {
|
|
269
|
+
val bos = ByteArrayOutputStream()
|
|
270
|
+
|
|
271
|
+
// flags (bit0 = hasMore)
|
|
272
|
+
bos.write(if (hasMore) 0x01 else 0x00)
|
|
273
|
+
|
|
274
|
+
// count (u32 BE)
|
|
275
|
+
bos.write(u32be(entries.size))
|
|
276
|
+
|
|
277
|
+
// entries
|
|
278
|
+
for ((k, v) in entries) {
|
|
279
|
+
bos.write(u32be(k.size)); bos.write(k)
|
|
280
|
+
bos.write(u32be(v.size)); bos.write(v)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// nextSeek
|
|
284
|
+
val ns = nextSeek ?: ByteArray(0)
|
|
285
|
+
bos.write(u32be(ns.size))
|
|
286
|
+
if (ns.isNotEmpty()) bos.write(ns)
|
|
287
|
+
|
|
288
|
+
return bos.toByteArray()
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ----- utils -----
|
|
292
|
+
private fun u32be(n: Int): ByteArray {
|
|
293
|
+
val bb = ByteBuffer.allocate(4)
|
|
294
|
+
bb.putInt(n) // big-endian by default
|
|
295
|
+
return bb.array()
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private fun hex(b: ByteArray): String {
|
|
299
|
+
val out = CharArray(b.size * 2)
|
|
300
|
+
val h = "0123456789abcdef".toCharArray()
|
|
301
|
+
var i = 0
|
|
302
|
+
for (v in b) {
|
|
303
|
+
val x = v.toInt() and 0xFF
|
|
304
|
+
out[i++] = h[x ushr 4]; out[i++] = h[x and 0x0F]
|
|
305
|
+
}
|
|
306
|
+
return String(out)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private fun unhex(s: String): ByteArray {
|
|
310
|
+
require(s.length % 2 == 0) { "odd hex length" }
|
|
311
|
+
val out = ByteArray(s.length / 2)
|
|
312
|
+
var i = 0; var j = 0
|
|
313
|
+
while (i < s.length) {
|
|
314
|
+
val hi = Character.digit(s[i++], 16)
|
|
315
|
+
val lo = Character.digit(s[i++], 16)
|
|
316
|
+
out[j++] = ((hi shl 4) or lo).toByte()
|
|
317
|
+
}
|
|
318
|
+
return out
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
companion object {
|
|
322
|
+
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
|
|
323
|
+
private const val AES_GCM = "AES/GCM/NoPadding"
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>GnoCore.framework/Versions/A/GnoCore</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>GnoCore.framework</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
+
<string>x86_64</string>
|
|
17
18
|
</array>
|
|
18
19
|
<key>SupportedPlatform</key>
|
|
19
20
|
<string>ios</string>
|
|
21
|
+
<key>SupportedPlatformVariant</key>
|
|
22
|
+
<string>simulator</string>
|
|
20
23
|
</dict>
|
|
21
24
|
<dict>
|
|
22
25
|
<key>BinaryPath</key>
|
|
23
26
|
<string>GnoCore.framework/Versions/A/GnoCore</string>
|
|
24
27
|
<key>LibraryIdentifier</key>
|
|
25
|
-
<string>ios-
|
|
28
|
+
<string>ios-arm64</string>
|
|
26
29
|
<key>LibraryPath</key>
|
|
27
30
|
<string>GnoCore.framework</string>
|
|
28
31
|
<key>SupportedArchitectures</key>
|
|
29
32
|
<array>
|
|
30
33
|
<string>arm64</string>
|
|
31
|
-
<string>x86_64</string>
|
|
32
34
|
</array>
|
|
33
35
|
<key>SupportedPlatform</key>
|
|
34
36
|
<string>ios</string>
|
|
35
|
-
<key>SupportedPlatformVariant</key>
|
|
36
|
-
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|
|
Binary file
|
package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Headers/GnoGnonative.objc.h
CHANGED
|
@@ -13,11 +13,23 @@
|
|
|
13
13
|
|
|
14
14
|
@class GnoGnonativeBridge;
|
|
15
15
|
@class GnoGnonativeBridgeConfig;
|
|
16
|
+
@protocol GnoGnonativeNativeDB;
|
|
17
|
+
@class GnoGnonativeNativeDB;
|
|
16
18
|
@protocol GnoGnonativePromiseBlock;
|
|
17
19
|
@class GnoGnonativePromiseBlock;
|
|
18
20
|
@protocol GnoGnonativeServiceClient;
|
|
19
21
|
@class GnoGnonativeServiceClient;
|
|
20
22
|
|
|
23
|
+
@protocol GnoGnonativeNativeDB <NSObject>
|
|
24
|
+
- (BOOL)delete:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
25
|
+
- (BOOL)deleteSync:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
26
|
+
- (NSData* _Nullable)get:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
27
|
+
- (BOOL)has:(NSData* _Nullable)key ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
|
|
28
|
+
- (NSData* _Nullable)scanChunk:(NSData* _Nullable)start end:(NSData* _Nullable)end seekKey:(NSData* _Nullable)seekKey limit:(long)limit reverse:(BOOL)reverse error:(NSError* _Nullable* _Nullable)error;
|
|
29
|
+
- (BOOL)set:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
30
|
+
- (BOOL)setSync:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
31
|
+
@end
|
|
32
|
+
|
|
21
33
|
@protocol GnoGnonativePromiseBlock <NSObject>
|
|
22
34
|
- (void)callReject:(NSError* _Nullable)error;
|
|
23
35
|
- (void)callResolve:(NSString* _Nullable)reply;
|
|
@@ -53,6 +65,7 @@
|
|
|
53
65
|
|
|
54
66
|
- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
|
|
55
67
|
- (nullable instancetype)init;
|
|
68
|
+
@property (nonatomic) id<GnoGnonativeNativeDB> _Nullable nativeDB;
|
|
56
69
|
@property (nonatomic) NSString* _Nonnull rootDir;
|
|
57
70
|
@property (nonatomic) NSString* _Nonnull tmpDir;
|
|
58
71
|
@property (nonatomic) BOOL useTcpListener;
|
|
@@ -66,10 +79,29 @@ FOUNDATION_EXPORT GnoGnonativeBridgeConfig* _Nullable GnoGnonativeNewBridgeConfi
|
|
|
66
79
|
// skipped function NewServiceClient with unsupported parameter or return types
|
|
67
80
|
|
|
68
81
|
|
|
82
|
+
@class GnoGnonativeNativeDB;
|
|
83
|
+
|
|
69
84
|
@class GnoGnonativePromiseBlock;
|
|
70
85
|
|
|
71
86
|
@class GnoGnonativeServiceClient;
|
|
72
87
|
|
|
88
|
+
/**
|
|
89
|
+
* NativeDB is implemented in the native (Kotlin/Swift) layer.
|
|
90
|
+
*/
|
|
91
|
+
@interface GnoGnonativeNativeDB : NSObject <goSeqRefInterface, GnoGnonativeNativeDB> {
|
|
92
|
+
}
|
|
93
|
+
@property(strong, readonly) _Nonnull id _ref;
|
|
94
|
+
|
|
95
|
+
- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
|
|
96
|
+
- (BOOL)delete:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
97
|
+
- (BOOL)deleteSync:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
98
|
+
- (NSData* _Nullable)get:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
99
|
+
- (BOOL)has:(NSData* _Nullable)key ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
|
|
100
|
+
- (NSData* _Nullable)scanChunk:(NSData* _Nullable)start end:(NSData* _Nullable)end seekKey:(NSData* _Nullable)seekKey limit:(long)limit reverse:(BOOL)reverse error:(NSError* _Nullable* _Nullable)error;
|
|
101
|
+
- (BOOL)set:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
102
|
+
- (BOOL)setSync:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
103
|
+
@end
|
|
104
|
+
|
|
73
105
|
@interface GnoGnonativePromiseBlock : NSObject <goSeqRefInterface, GnoGnonativePromiseBlock> {
|
|
74
106
|
}
|
|
75
107
|
@property(strong, readonly) _Nonnull id _ref;
|
|
Binary file
|
|
@@ -13,11 +13,23 @@
|
|
|
13
13
|
|
|
14
14
|
@class GnoGnonativeBridge;
|
|
15
15
|
@class GnoGnonativeBridgeConfig;
|
|
16
|
+
@protocol GnoGnonativeNativeDB;
|
|
17
|
+
@class GnoGnonativeNativeDB;
|
|
16
18
|
@protocol GnoGnonativePromiseBlock;
|
|
17
19
|
@class GnoGnonativePromiseBlock;
|
|
18
20
|
@protocol GnoGnonativeServiceClient;
|
|
19
21
|
@class GnoGnonativeServiceClient;
|
|
20
22
|
|
|
23
|
+
@protocol GnoGnonativeNativeDB <NSObject>
|
|
24
|
+
- (BOOL)delete:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
25
|
+
- (BOOL)deleteSync:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
26
|
+
- (NSData* _Nullable)get:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
27
|
+
- (BOOL)has:(NSData* _Nullable)key ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
|
|
28
|
+
- (NSData* _Nullable)scanChunk:(NSData* _Nullable)start end:(NSData* _Nullable)end seekKey:(NSData* _Nullable)seekKey limit:(long)limit reverse:(BOOL)reverse error:(NSError* _Nullable* _Nullable)error;
|
|
29
|
+
- (BOOL)set:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
30
|
+
- (BOOL)setSync:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
31
|
+
@end
|
|
32
|
+
|
|
21
33
|
@protocol GnoGnonativePromiseBlock <NSObject>
|
|
22
34
|
- (void)callReject:(NSError* _Nullable)error;
|
|
23
35
|
- (void)callResolve:(NSString* _Nullable)reply;
|
|
@@ -53,6 +65,7 @@
|
|
|
53
65
|
|
|
54
66
|
- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
|
|
55
67
|
- (nullable instancetype)init;
|
|
68
|
+
@property (nonatomic) id<GnoGnonativeNativeDB> _Nullable nativeDB;
|
|
56
69
|
@property (nonatomic) NSString* _Nonnull rootDir;
|
|
57
70
|
@property (nonatomic) NSString* _Nonnull tmpDir;
|
|
58
71
|
@property (nonatomic) BOOL useTcpListener;
|
|
@@ -66,10 +79,29 @@ FOUNDATION_EXPORT GnoGnonativeBridgeConfig* _Nullable GnoGnonativeNewBridgeConfi
|
|
|
66
79
|
// skipped function NewServiceClient with unsupported parameter or return types
|
|
67
80
|
|
|
68
81
|
|
|
82
|
+
@class GnoGnonativeNativeDB;
|
|
83
|
+
|
|
69
84
|
@class GnoGnonativePromiseBlock;
|
|
70
85
|
|
|
71
86
|
@class GnoGnonativeServiceClient;
|
|
72
87
|
|
|
88
|
+
/**
|
|
89
|
+
* NativeDB is implemented in the native (Kotlin/Swift) layer.
|
|
90
|
+
*/
|
|
91
|
+
@interface GnoGnonativeNativeDB : NSObject <goSeqRefInterface, GnoGnonativeNativeDB> {
|
|
92
|
+
}
|
|
93
|
+
@property(strong, readonly) _Nonnull id _ref;
|
|
94
|
+
|
|
95
|
+
- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
|
|
96
|
+
- (BOOL)delete:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
97
|
+
- (BOOL)deleteSync:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
98
|
+
- (NSData* _Nullable)get:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
99
|
+
- (BOOL)has:(NSData* _Nullable)key ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
|
|
100
|
+
- (NSData* _Nullable)scanChunk:(NSData* _Nullable)start end:(NSData* _Nullable)end seekKey:(NSData* _Nullable)seekKey limit:(long)limit reverse:(BOOL)reverse error:(NSError* _Nullable* _Nullable)error;
|
|
101
|
+
- (BOOL)set:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
102
|
+
- (BOOL)setSync:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
103
|
+
@end
|
|
104
|
+
|
|
73
105
|
@interface GnoGnonativePromiseBlock : NSObject <goSeqRefInterface, GnoGnonativePromiseBlock> {
|
|
74
106
|
}
|
|
75
107
|
@property(strong, readonly) _Nonnull id _ref;
|
package/ios/Frameworks/GnoCore.xcframework/ios-arm64/GnoCore.framework/Versions/Current/GnoCore
CHANGED
|
Binary file
|
|
@@ -13,11 +13,23 @@
|
|
|
13
13
|
|
|
14
14
|
@class GnoGnonativeBridge;
|
|
15
15
|
@class GnoGnonativeBridgeConfig;
|
|
16
|
+
@protocol GnoGnonativeNativeDB;
|
|
17
|
+
@class GnoGnonativeNativeDB;
|
|
16
18
|
@protocol GnoGnonativePromiseBlock;
|
|
17
19
|
@class GnoGnonativePromiseBlock;
|
|
18
20
|
@protocol GnoGnonativeServiceClient;
|
|
19
21
|
@class GnoGnonativeServiceClient;
|
|
20
22
|
|
|
23
|
+
@protocol GnoGnonativeNativeDB <NSObject>
|
|
24
|
+
- (BOOL)delete:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
25
|
+
- (BOOL)deleteSync:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
26
|
+
- (NSData* _Nullable)get:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
27
|
+
- (BOOL)has:(NSData* _Nullable)key ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
|
|
28
|
+
- (NSData* _Nullable)scanChunk:(NSData* _Nullable)start end:(NSData* _Nullable)end seekKey:(NSData* _Nullable)seekKey limit:(long)limit reverse:(BOOL)reverse error:(NSError* _Nullable* _Nullable)error;
|
|
29
|
+
- (BOOL)set:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
30
|
+
- (BOOL)setSync:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
31
|
+
@end
|
|
32
|
+
|
|
21
33
|
@protocol GnoGnonativePromiseBlock <NSObject>
|
|
22
34
|
- (void)callReject:(NSError* _Nullable)error;
|
|
23
35
|
- (void)callResolve:(NSString* _Nullable)reply;
|
|
@@ -53,6 +65,7 @@
|
|
|
53
65
|
|
|
54
66
|
- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
|
|
55
67
|
- (nullable instancetype)init;
|
|
68
|
+
@property (nonatomic) id<GnoGnonativeNativeDB> _Nullable nativeDB;
|
|
56
69
|
@property (nonatomic) NSString* _Nonnull rootDir;
|
|
57
70
|
@property (nonatomic) NSString* _Nonnull tmpDir;
|
|
58
71
|
@property (nonatomic) BOOL useTcpListener;
|
|
@@ -66,10 +79,29 @@ FOUNDATION_EXPORT GnoGnonativeBridgeConfig* _Nullable GnoGnonativeNewBridgeConfi
|
|
|
66
79
|
// skipped function NewServiceClient with unsupported parameter or return types
|
|
67
80
|
|
|
68
81
|
|
|
82
|
+
@class GnoGnonativeNativeDB;
|
|
83
|
+
|
|
69
84
|
@class GnoGnonativePromiseBlock;
|
|
70
85
|
|
|
71
86
|
@class GnoGnonativeServiceClient;
|
|
72
87
|
|
|
88
|
+
/**
|
|
89
|
+
* NativeDB is implemented in the native (Kotlin/Swift) layer.
|
|
90
|
+
*/
|
|
91
|
+
@interface GnoGnonativeNativeDB : NSObject <goSeqRefInterface, GnoGnonativeNativeDB> {
|
|
92
|
+
}
|
|
93
|
+
@property(strong, readonly) _Nonnull id _ref;
|
|
94
|
+
|
|
95
|
+
- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
|
|
96
|
+
- (BOOL)delete:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
97
|
+
- (BOOL)deleteSync:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
98
|
+
- (NSData* _Nullable)get:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
99
|
+
- (BOOL)has:(NSData* _Nullable)key ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
|
|
100
|
+
- (NSData* _Nullable)scanChunk:(NSData* _Nullable)start end:(NSData* _Nullable)end seekKey:(NSData* _Nullable)seekKey limit:(long)limit reverse:(BOOL)reverse error:(NSError* _Nullable* _Nullable)error;
|
|
101
|
+
- (BOOL)set:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
102
|
+
- (BOOL)setSync:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
103
|
+
@end
|
|
104
|
+
|
|
73
105
|
@interface GnoGnonativePromiseBlock : NSObject <goSeqRefInterface, GnoGnonativePromiseBlock> {
|
|
74
106
|
}
|
|
75
107
|
@property(strong, readonly) _Nonnull id _ref;
|
package/ios/Frameworks/GnoCore.xcframework/ios-arm64_x86_64-simulator/GnoCore.framework/GnoCore
CHANGED
|
Binary file
|
|
@@ -13,11 +13,23 @@
|
|
|
13
13
|
|
|
14
14
|
@class GnoGnonativeBridge;
|
|
15
15
|
@class GnoGnonativeBridgeConfig;
|
|
16
|
+
@protocol GnoGnonativeNativeDB;
|
|
17
|
+
@class GnoGnonativeNativeDB;
|
|
16
18
|
@protocol GnoGnonativePromiseBlock;
|
|
17
19
|
@class GnoGnonativePromiseBlock;
|
|
18
20
|
@protocol GnoGnonativeServiceClient;
|
|
19
21
|
@class GnoGnonativeServiceClient;
|
|
20
22
|
|
|
23
|
+
@protocol GnoGnonativeNativeDB <NSObject>
|
|
24
|
+
- (BOOL)delete:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
25
|
+
- (BOOL)deleteSync:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
26
|
+
- (NSData* _Nullable)get:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
27
|
+
- (BOOL)has:(NSData* _Nullable)key ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
|
|
28
|
+
- (NSData* _Nullable)scanChunk:(NSData* _Nullable)start end:(NSData* _Nullable)end seekKey:(NSData* _Nullable)seekKey limit:(long)limit reverse:(BOOL)reverse error:(NSError* _Nullable* _Nullable)error;
|
|
29
|
+
- (BOOL)set:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
30
|
+
- (BOOL)setSync:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
31
|
+
@end
|
|
32
|
+
|
|
21
33
|
@protocol GnoGnonativePromiseBlock <NSObject>
|
|
22
34
|
- (void)callReject:(NSError* _Nullable)error;
|
|
23
35
|
- (void)callResolve:(NSString* _Nullable)reply;
|
|
@@ -53,6 +65,7 @@
|
|
|
53
65
|
|
|
54
66
|
- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
|
|
55
67
|
- (nullable instancetype)init;
|
|
68
|
+
@property (nonatomic) id<GnoGnonativeNativeDB> _Nullable nativeDB;
|
|
56
69
|
@property (nonatomic) NSString* _Nonnull rootDir;
|
|
57
70
|
@property (nonatomic) NSString* _Nonnull tmpDir;
|
|
58
71
|
@property (nonatomic) BOOL useTcpListener;
|
|
@@ -66,10 +79,29 @@ FOUNDATION_EXPORT GnoGnonativeBridgeConfig* _Nullable GnoGnonativeNewBridgeConfi
|
|
|
66
79
|
// skipped function NewServiceClient with unsupported parameter or return types
|
|
67
80
|
|
|
68
81
|
|
|
82
|
+
@class GnoGnonativeNativeDB;
|
|
83
|
+
|
|
69
84
|
@class GnoGnonativePromiseBlock;
|
|
70
85
|
|
|
71
86
|
@class GnoGnonativeServiceClient;
|
|
72
87
|
|
|
88
|
+
/**
|
|
89
|
+
* NativeDB is implemented in the native (Kotlin/Swift) layer.
|
|
90
|
+
*/
|
|
91
|
+
@interface GnoGnonativeNativeDB : NSObject <goSeqRefInterface, GnoGnonativeNativeDB> {
|
|
92
|
+
}
|
|
93
|
+
@property(strong, readonly) _Nonnull id _ref;
|
|
94
|
+
|
|
95
|
+
- (nonnull instancetype)initWithRef:(_Nonnull id)ref;
|
|
96
|
+
- (BOOL)delete:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
97
|
+
- (BOOL)deleteSync:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
98
|
+
- (NSData* _Nullable)get:(NSData* _Nullable)key error:(NSError* _Nullable* _Nullable)error;
|
|
99
|
+
- (BOOL)has:(NSData* _Nullable)key ret0_:(BOOL* _Nullable)ret0_ error:(NSError* _Nullable* _Nullable)error;
|
|
100
|
+
- (NSData* _Nullable)scanChunk:(NSData* _Nullable)start end:(NSData* _Nullable)end seekKey:(NSData* _Nullable)seekKey limit:(long)limit reverse:(BOOL)reverse error:(NSError* _Nullable* _Nullable)error;
|
|
101
|
+
- (BOOL)set:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
102
|
+
- (BOOL)setSync:(NSData* _Nullable)key value:(NSData* _Nullable)value error:(NSError* _Nullable* _Nullable)error;
|
|
103
|
+
@end
|
|
104
|
+
|
|
73
105
|
@interface GnoGnonativePromiseBlock : NSObject <goSeqRefInterface, GnoGnonativePromiseBlock> {
|
|
74
106
|
}
|
|
75
107
|
@property(strong, readonly) _Nonnull id _ref;
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
//
|
|
2
|
+
// NativeDBManager.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Rémi BARBERO on 25/09/2025.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import Security
|
|
10
|
+
import GnoCore
|
|
11
|
+
|
|
12
|
+
public class NativeDBManager: NSObject, GnoGnonativeNativeDBProtocol {
|
|
13
|
+
public static var shared: NativeDBManager = NativeDBManager()
|
|
14
|
+
|
|
15
|
+
// MARK: - Private Properties
|
|
16
|
+
private let service: String
|
|
17
|
+
private let accessGroup: String?
|
|
18
|
+
|
|
19
|
+
// MARK: - Initialization
|
|
20
|
+
init(service: String = Bundle.main.bundleIdentifier ?? "GnoNativeService", accessGroup: String? = nil) {
|
|
21
|
+
self.service = service
|
|
22
|
+
self.accessGroup = accessGroup
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// MARK: - Public Interface Implementation
|
|
26
|
+
|
|
27
|
+
public func get(_ key: Data?) throws -> Data {
|
|
28
|
+
guard let key = key, !key.isEmpty else {
|
|
29
|
+
throw NativeDBError.invalidArgument(description: "Key must not be nil or empty.")
|
|
30
|
+
}
|
|
31
|
+
let account = keyToAccount(key)
|
|
32
|
+
|
|
33
|
+
var query: [String: Any] = [
|
|
34
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
35
|
+
kSecAttrService as String: service,
|
|
36
|
+
kSecAttrAccount as String: account,
|
|
37
|
+
kSecReturnData as String: true,
|
|
38
|
+
kSecMatchLimit as String: kSecMatchLimitOne
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
if let accessGroup = accessGroup {
|
|
42
|
+
query[kSecAttrAccessGroup as String] = accessGroup
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var result: AnyObject?
|
|
46
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
47
|
+
|
|
48
|
+
switch status {
|
|
49
|
+
case errSecSuccess:
|
|
50
|
+
return (result as? Data) ?? Data()
|
|
51
|
+
case errSecItemNotFound:
|
|
52
|
+
// This is not an error; it's the expected result for a missing key.
|
|
53
|
+
return Data()
|
|
54
|
+
default:
|
|
55
|
+
throw NativeDBError.keychainError(status: status, message: "Failed to get item.")
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public func delete(_ key: Data?) throws {
|
|
60
|
+
try self.deleteSync(key)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public func deleteSync(_ key: Data?) throws {
|
|
64
|
+
guard let key = key, !key.isEmpty else {
|
|
65
|
+
throw NativeDBError.invalidArgument(description: "Key must not be nil or empty.")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let account = keyToAccount(key)
|
|
69
|
+
|
|
70
|
+
var query: [String: Any] = [
|
|
71
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
72
|
+
kSecAttrService as String: service,
|
|
73
|
+
kSecAttrAccount as String: account
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
if let accessGroup = accessGroup {
|
|
77
|
+
query[kSecAttrAccessGroup as String] = accessGroup
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let status = SecItemDelete(query as CFDictionary)
|
|
81
|
+
|
|
82
|
+
// Deleting a non-existent item is not considered an error.
|
|
83
|
+
if status != errSecSuccess && status != errSecItemNotFound {
|
|
84
|
+
throw NativeDBError.keychainError(status: status, message: "Failed to delete item.")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public func has(_ key: Data?, ret0_ returnPointer: UnsafeMutablePointer<ObjCBool>?) throws {
|
|
89
|
+
// 1. Ensure the return pointer provided by the caller is valid.
|
|
90
|
+
guard let returnPointer = returnPointer else {
|
|
91
|
+
throw NativeDBError.invalidArgument(description: "Return pointer must not be nil.")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 2. Validate the input key.
|
|
95
|
+
guard let key = key, !key.isEmpty else {
|
|
96
|
+
throw NativeDBError.invalidArgument(description: "Key must not be nil or empty.")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let account = keyToAccount(key)
|
|
100
|
+
|
|
101
|
+
var query: [String: Any] = [
|
|
102
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
103
|
+
kSecAttrService as String: service,
|
|
104
|
+
kSecAttrAccount as String: account
|
|
105
|
+
]
|
|
106
|
+
if let accessGroup = accessGroup {
|
|
107
|
+
query[kSecAttrAccessGroup as String] = accessGroup
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let status = SecItemCopyMatching(query as CFDictionary, nil)
|
|
111
|
+
|
|
112
|
+
// 3. Handle the result from the Keychain.
|
|
113
|
+
switch status {
|
|
114
|
+
case errSecSuccess:
|
|
115
|
+
// Key exists. Write `true` to the pointer's memory location.
|
|
116
|
+
returnPointer.pointee = ObjCBool(true)
|
|
117
|
+
case errSecItemNotFound:
|
|
118
|
+
// Key does not exist. Write `false` to the pointer's memory location.
|
|
119
|
+
returnPointer.pointee = ObjCBool(false)
|
|
120
|
+
default:
|
|
121
|
+
// A real keychain error occurred. Throw an error.
|
|
122
|
+
throw NativeDBError.keychainError(status: status, message: "Failed to check for item existence.")
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public func set(_ key: Data?, value: Data?) throws {
|
|
127
|
+
try self.setSync(key, value: value)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public func setSync(_ key: Data?, value: Data?) throws {
|
|
131
|
+
guard let key = key, !key.isEmpty else {
|
|
132
|
+
throw NativeDBError.invalidArgument(description: "Key must not be nil or empty.")
|
|
133
|
+
}
|
|
134
|
+
guard let value = value else {
|
|
135
|
+
throw NativeDBError.invalidArgument(description: "Value must not be nil.")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let account = keyToAccount(key)
|
|
139
|
+
|
|
140
|
+
// First, try to update existing item
|
|
141
|
+
var query: [String: Any] = [
|
|
142
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
143
|
+
kSecAttrService as String: service,
|
|
144
|
+
kSecAttrAccount as String: account
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
if let accessGroup = accessGroup {
|
|
148
|
+
query[kSecAttrAccessGroup as String] = accessGroup
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let attributes: [String: Any] = [
|
|
152
|
+
kSecValueData as String: value
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
|
156
|
+
|
|
157
|
+
switch updateStatus {
|
|
158
|
+
case errSecSuccess:
|
|
159
|
+
// Update was successful.
|
|
160
|
+
return
|
|
161
|
+
case errSecItemNotFound:
|
|
162
|
+
// Item doesn't exist, so add it.
|
|
163
|
+
var newItem = query
|
|
164
|
+
newItem[kSecValueData as String] = value
|
|
165
|
+
newItem[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
166
|
+
|
|
167
|
+
let addStatus = SecItemAdd(newItem as CFDictionary, nil)
|
|
168
|
+
if addStatus != errSecSuccess {
|
|
169
|
+
throw NativeDBError.keychainError(status: addStatus, message: "Failed to add new item.")
|
|
170
|
+
}
|
|
171
|
+
default:
|
|
172
|
+
// Another error occurred during update.
|
|
173
|
+
throw NativeDBError.keychainError(status: updateStatus, message: "Failed to update item.")
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public func scanChunk(_ start: Data?, end: Data?, seekKey: Data?, limit: Int, reverse: Bool) throws -> Data {
|
|
178
|
+
// 1) fetch all items for this service
|
|
179
|
+
var query: [String: Any] = [
|
|
180
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
181
|
+
kSecAttrService as String: service,
|
|
182
|
+
kSecMatchLimit as String: kSecMatchLimitAll,
|
|
183
|
+
kSecReturnAttributes as String: true,
|
|
184
|
+
kSecReturnData as String: true,
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
if let accessGroup = accessGroup {
|
|
188
|
+
query[kSecAttrAccessGroup as String] = accessGroup
|
|
189
|
+
}
|
|
190
|
+
var result: CFTypeRef?
|
|
191
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
192
|
+
|
|
193
|
+
var pairs: [(key: Data, val: Data)] = []
|
|
194
|
+
if status == errSecSuccess, let items = result as? [[String: Any]] {
|
|
195
|
+
for item in items {
|
|
196
|
+
guard let account = item[kSecAttrAccount as String] as? String,
|
|
197
|
+
let keyBytes = accountToKey(account),
|
|
198
|
+
let val = item[kSecValueData as String] as? Data else { continue }
|
|
199
|
+
if inRange(keyBytes, start: start, end: end) {
|
|
200
|
+
pairs.append((keyBytes, val))
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 2) sort
|
|
206
|
+
if reverse {
|
|
207
|
+
pairs.sort { a, b in
|
|
208
|
+
// descending: a > b
|
|
209
|
+
return lt(b.key, a.key)
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
pairs.sort { a, b in
|
|
213
|
+
// ascending: a < b
|
|
214
|
+
return lt(a.key, b.key)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 3) apply seekKey (exclusive)
|
|
219
|
+
if let sk = seekKey, !sk.isEmpty {
|
|
220
|
+
if reverse {
|
|
221
|
+
// keep items with key < seekKey
|
|
222
|
+
let idx = pairs.firstIndex(where: { lt($0.key, sk) }) ?? pairs.count
|
|
223
|
+
// pairs are descending, so drop while key >= seekKey
|
|
224
|
+
pairs = Array(pairs[idx...])
|
|
225
|
+
} else {
|
|
226
|
+
// keep items with key > seekKey
|
|
227
|
+
let idx = pairs.lastIndex(where: { lte($0.key, sk) }) ?? -1
|
|
228
|
+
let startIdx = idx + 1
|
|
229
|
+
pairs = (startIdx < pairs.count) ? Array(pairs[startIdx...]) : []
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 4) limit
|
|
234
|
+
let lim = max(0, Int(limit))
|
|
235
|
+
let chunk = (lim > 0 && lim < pairs.count) ? Array(pairs.prefix(lim)) : pairs
|
|
236
|
+
let hasMore = chunk.count < pairs.count
|
|
237
|
+
let nextSeek = chunk.last?.key ?? Data()
|
|
238
|
+
|
|
239
|
+
// 6) Frame the blob
|
|
240
|
+
var blob = Data(capacity: 1 + 4) // will grow as needed
|
|
241
|
+
var flags: UInt8 = 0
|
|
242
|
+
if hasMore { flags |= 0x01 }
|
|
243
|
+
blob.append(&flags, count: 1)
|
|
244
|
+
|
|
245
|
+
var countBE = UInt32(chunk.count).bigEndian
|
|
246
|
+
withUnsafeBytes(of: &countBE) { blob.append($0.bindMemory(to: UInt8.self)) }
|
|
247
|
+
|
|
248
|
+
for (k, v) in chunk {
|
|
249
|
+
var klen = UInt32(k.count).bigEndian
|
|
250
|
+
var vlen = UInt32(v.count).bigEndian
|
|
251
|
+
withUnsafeBytes(of: &klen) { blob.append($0.bindMemory(to: UInt8.self)) }
|
|
252
|
+
blob.append(k)
|
|
253
|
+
withUnsafeBytes(of: &vlen) { blob.append($0.bindMemory(to: UInt8.self)) }
|
|
254
|
+
blob.append(v)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
var nlen = UInt32(nextSeek.count).bigEndian
|
|
258
|
+
withUnsafeBytes(of: &nlen) { blob.append($0.bindMemory(to: UInt8.self)) }
|
|
259
|
+
blob.append(nextSeek)
|
|
260
|
+
|
|
261
|
+
return blob
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// MARK: - Utility Methods
|
|
265
|
+
|
|
266
|
+
private func keyToAccount(_ key: Data) -> String {
|
|
267
|
+
return String(data: key, encoding: .utf8) ?? key.base64EncodedString()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private func accountToKey(_ account: String) -> Data? {
|
|
271
|
+
// Try UTF-8 first
|
|
272
|
+
if let utf8Data = account.data(using: .utf8) {
|
|
273
|
+
// Check if this was originally a base64 string by trying to decode it
|
|
274
|
+
if let base64Data = Data(base64Encoded: account), base64Data != utf8Data {
|
|
275
|
+
// This account was base64 encoded, return the decoded data
|
|
276
|
+
return base64Data
|
|
277
|
+
} else {
|
|
278
|
+
// This was a UTF-8 string, return the UTF-8 data
|
|
279
|
+
return utf8Data
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Fallback: try base64 decoding
|
|
284
|
+
return Data(base64Encoded: account)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// --- byte-wise comparisons on decoded keys ---
|
|
288
|
+
@inline(__always)
|
|
289
|
+
private func lt(_ a: Data, _ b: Data) -> Bool {
|
|
290
|
+
a.lexicographicallyPrecedes(b)
|
|
291
|
+
}
|
|
292
|
+
@inline(__always)
|
|
293
|
+
private func gte(_ a: Data, _ b: Data) -> Bool { !lt(a, b) }
|
|
294
|
+
@inline(__always)
|
|
295
|
+
private func lte(_ a: Data, _ b: Data) -> Bool { !lt(b, a) }
|
|
296
|
+
@inline(__always)
|
|
297
|
+
private func inRange(_ k: Data, start: Data?, end: Data?) -> Bool {
|
|
298
|
+
if let s = start, lt(k, s) { return false } // k >= s
|
|
299
|
+
if let e = end, !lt(k, e) { return false } // k < e
|
|
300
|
+
return true
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
public enum NativeDBError: Error, LocalizedError {
|
|
305
|
+
case invalidArgument(description: String)
|
|
306
|
+
case keychainError(status: OSStatus, message: String)
|
|
307
|
+
|
|
308
|
+
public var errorDescription: String? {
|
|
309
|
+
switch self {
|
|
310
|
+
case .invalidArgument(let description):
|
|
311
|
+
return "Invalid Argument: \(description)"
|
|
312
|
+
case .keychainError(let status, let message):
|
|
313
|
+
let statusMessage = SecCopyErrorMessageString(status, nil) as String? ?? "Unknown error"
|
|
314
|
+
return "Keychain Error: \(message) (status: \(status), reason: \(statusMessage))"
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|