KMVerificationRSA.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. //
  2. // KMVerificationRSA.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by Niehaoyu on 2024/8/9.
  6. //
  7. //import Cocoa
  8. //import CryptoKit
  9. //import Security
  10. //import CommonCrypto
  11. //import Foundation
  12. //
  13. //@objc class KMVerificationRSA: NSObject {
  14. //
  15. // @objc func active(uniquesn: String, cdkey: String, model: String, os: String, language: String, appversion: String) -> String? {
  16. //
  17. // let dictionary: [String: Any] = ["subscription": ["app_code":"com.imyfone.pdf",
  18. // "cdkey":cdkey],
  19. // "device": ["unique_sn":uniquesn,
  20. // "os":os,
  21. // "platform":"DMG",
  22. // "time_zone":"UTC",
  23. // "language":language,
  24. // "app_version":appversion]]
  25. //
  26. // if let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options:[]),
  27. // let jsonString = String(data: jsonData, encoding: .utf8) {
  28. // print(jsonString)
  29. // if let publicKey = loadPublicKey() {
  30. // let encryptedData = encrypt(plainText: jsonString, publicKey: publicKey)
  31. // print("Encrypted data: \(encryptedData?.base64EncodedString() ?? "nil")")
  32. // let encryptedString = encryptedData?.base64EncodedString();
  33. // return encryptedString
  34. //
  35. // //Test
  36. //// let dataToEncrypt = jsonString.data(using: .utf8)!
  37. ////
  38. //// if let encryptedDataChunks = encryptDataInChunks(data: dataToEncrypt, publicKey: publicKey) {
  39. //// print("Encrypted data chunks:")
  40. //// for chunk in encryptedDataChunks {
  41. //// print(chunk.base64EncodedString()) // 输出每个加密块
  42. //// }
  43. //// print("111")
  44. //// }
  45. ////
  46. ////
  47. //// let message = jsonString
  48. //// if let messageData = message.data(using: .utf8) {
  49. //// if let publicKey = loadPublicKey() {
  50. //// if let encryptedChunks = encrypt(data: messageData, publicKey: publicKey) {
  51. //// let encryptedString = encryptedChunks.map { $0.base64EncodedString() }
  52. //// print("Encrypted data: \(encryptedString)")
  53. //// return encryptedString.first
  54. //// }
  55. //// }
  56. //// }
  57. // }
  58. // }
  59. // return nil
  60. // }
  61. //
  62. // @objc func verify(uniquesn: String, model: String, os: String, language: String, appversion: String) -> String? {
  63. // let dictionary: [String: Any] = ["subscription": ["app_code":"com.imyfone.pdf"],
  64. // "device": ["unique_sn":uniquesn,
  65. // "os":os,
  66. // "platform":"DMG",
  67. // "time_zone":"UTC",
  68. // "language":language,
  69. // "app_version":appversion]]
  70. //
  71. // if let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options:[]),
  72. // let jsonString = String(data: jsonData, encoding: .utf8) {
  73. // print(jsonString)
  74. // if let publicKey = loadPublicKey() {
  75. // let encryptedData = encrypt(plainText: jsonString, publicKey: publicKey)
  76. // print("Encrypted data: \(encryptedData?.base64EncodedString() ?? "nil")")
  77. // let encryptedString = encryptedData?.base64EncodedString();
  78. // return encryptedString
  79. // }
  80. //
  81. // //Test
  82. // let message = jsonString
  83. // if let messageData = message.data(using: .utf8) {
  84. // if let publicKey = loadPublicKey() {
  85. // if let encryptedChunks = encrypt(data: messageData, publicKey: publicKey) {
  86. // let encryptedString = encryptedChunks.map { $0.base64EncodedString() }
  87. // print("Encrypted data: \(encryptedString)")
  88. // return encryptedString.first
  89. // }
  90. // }
  91. // }
  92. //
  93. // }
  94. //
  95. //
  96. // return nil
  97. // }
  98. //
  99. // func encrypt(plainText: String, publicKey: SecKey) -> Data? {
  100. // guard let data = plainText.data(using: .utf8) else {
  101. // return nil
  102. // }
  103. //
  104. // var error: Unmanaged<CFError>?
  105. // let encryptedData = SecKeyCreateEncryptedData(publicKey, .rsaEncryptionPKCS1, data as CFData, &error)
  106. //
  107. // if let error = error?.takeRetainedValue() {
  108. // print("Error encrypting data: \(error)")
  109. // return nil
  110. // }
  111. //
  112. // return encryptedData as Data?
  113. // }
  114. //
  115. // func encrypt(data: Data, publicKey: SecKey) -> [Data]? {
  116. // let maxChunkSize = 214 // 对于 RSA-2048,214 是最大加密长度
  117. // var chunks = [Data]()
  118. // var offset = 0
  119. //
  120. // while offset < data.count {
  121. // let chunkSize = min(maxChunkSize, data.count - offset)
  122. // let chunk = data.subdata(in: offset..<offset + chunkSize)
  123. // if let encryptedData = SecKeyCreateEncryptedData(publicKey, .rsaEncryptionPKCS1, chunk as CFData, nil) {
  124. // chunks.append(encryptedData as Data)
  125. // } else {
  126. // return nil // 加密失败
  127. // }
  128. // offset += chunkSize
  129. // }
  130. //
  131. // return chunks
  132. // }
  133. //
  134. //
  135. // func loadPublicKey() -> SecKey? {
  136. //
  137. //// let publicKeyString = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqYKtu5pbT3bhbOfZ7XFhw1IEiqGvx/z3YwvcrLvG6I+EBbim/YuDfSTqpcTZSjbMeLz8nCzkAsMakoimzI6XpNQOZN35cDCFkjn0vicpnfla2JPMxREwddblAz7u/EMdx71ElcY+UYfSu1QM0Lepc2QPWw9oaD/cNktH6xE6eogLEH0k8ZYP8YIzTW02og7mNtLVO1ssKQYUCIQ5LkKA7zypQul5upajE51rq49vdCoA98y2zBRTMXGM7tpa2rbXQ9fDMn5heCLVCXCHNXDwBMxNhURm7fEfxZPwq7DUmH8EWKXCKKhu+GP0c/eom50FzMxfN2wpQSgNfyNQ7bBgwIDAQAB"
  138. //
  139. // let publicKeyString = Self.RsaPublic
  140. //
  141. // guard let data = Data(base64Encoded: publicKeyString) else {
  142. // return nil
  143. // }
  144. //
  145. // let options: [String: Any] = [
  146. // kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
  147. // kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
  148. // kSecAttrKeySizeInBits as String: 4096
  149. // ]
  150. //
  151. // var error: Unmanaged<CFError>?
  152. // let publicKey = SecKeyCreateWithData(data as CFData, options as CFDictionary, &error)
  153. //
  154. // if let error = error?.takeRetainedValue() {
  155. // print("Error loading public key: \(error)")
  156. // return nil
  157. // }
  158. //
  159. // return publicKey
  160. // }
  161. //
  162. // func base64Encode(string: String) -> String? {
  163. // // 将字符串转换为 Data
  164. // guard let data = string.data(using: .utf8) else {
  165. // print("Error converting string to Data.")
  166. // return nil
  167. // }
  168. //
  169. // // 使用 Data 的 base64EncodedString() 方法进行编码
  170. // let base64String = data.base64EncodedString()
  171. // return base64String
  172. // }
  173. //
  174. //
  175. // // RSA 分块加密
  176. // func encryptDataInChunks(data: Data, publicKey: SecKey) -> [Data]? {
  177. // let blockSize = SecKeyGetBlockSize(publicKey) - 11 // PKCS#1 v1.5 填充需要额外字节
  178. // var encryptedChunks: [Data] = []
  179. //
  180. // var offset = 0
  181. // while offset < data.count {
  182. // let size = min(data.count - offset, blockSize) // 计算当前块大小
  183. // let chunk = data.subdata(in: offset..<offset+size)
  184. //
  185. // var error: Unmanaged<CFError>?
  186. // let encryptedData = SecKeyCreateEncryptedData(publicKey, .rsaEncryptionPKCS1, data as CFData, &error)
  187. //
  188. // guard let successData = encryptedData else {
  189. // if let error = error?.takeRetainedValue() {
  190. // print("Encryption failed: \(error)")
  191. // }
  192. // return nil
  193. // }
  194. //
  195. // encryptedChunks.append(successData as Data)
  196. // offset += size
  197. // }
  198. //
  199. // return encryptedChunks
  200. // }
  201. //
  202. // func encrypt22(data: Data, publicKey: SecKey) -> [Data]? {
  203. // let keySizeInBytes = 256 // 对于 RSA-2048,密钥大小为 256 字节
  204. // let paddingSize = 11 // PKCS#1 填充大小
  205. // let maxChunkSize = keySizeInBytes - paddingSize // 最大可加密数据大小
  206. //
  207. // var chunks = [Data]()
  208. // var offset = 0
  209. //
  210. // while offset < data.count {
  211. // let chunkSize = min(maxChunkSize, data.count - offset)
  212. // let chunk = data.subdata(in: offset..<offset + chunkSize)
  213. //
  214. // var error: Unmanaged<CFError>?
  215. // guard let encryptedData = SecKeyCreateEncryptedData(publicKey, .rsaEncryptionPKCS1, chunk as CFData, &error) else {
  216. // print("Encryption error: \(error!.takeRetainedValue() as Error)")
  217. // return nil
  218. // }
  219. // chunks.append(encryptedData as Data)
  220. // offset += chunkSize
  221. // }
  222. //
  223. // return chunks
  224. // }
  225. //
  226. // //https://www.bejson.com/enc/rsa/网站生成的密钥要保留\r\n,去除前缀后缀
  227. // //pem格式公钥 用途加密
  228. //// public static let RsaPublic = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh3U54ETmA3anGcL3D4uAa5vNI0S6rVoM\r\nCe9MhhP3MLRSW1gMaEHJE+vsVLN8ChFytkiI60CInYC91bk2x2FaurIIGEwowGz4lifDNsQFeWEd\r\nxs33U3fGM3+wGXC7sfYJrtmriqqmmM9eTqxvVWARD1EvnSUWseBRquCuSJ3rkQHxm0E9n88SAaM4\r\nopogLrAyz82NlWQSE55Yf0wiNNNh+HdiQOpTBIes5blBcxbai9KVQU5dqlkKNceg7rdDxANFlm49\r\nTn4yNkrS80w75IQL1xLzYfvWSlWn/falJR4jNBfBg7aTyJduGvWytTgphYVTPheJN/fEDRMR0XTV\r\nlnkHZQIDAQAB";
  229. // public static let RsaPublic = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh3U54ETmA3anGcL3D4uAa5vNI0S6rVoMCe9MhhP3MLRSW1gMaEHJE+vsVLN8ChFytkiI60CInYC91bk2x2FaurIIGEwowGz4lifDNsQFeWEdxs33U3fGM3+wGXC7sfYJrtmriqqmmM9eTqxvVWARD1EvnSUWseBRquCuSJ3rkQHxm0E9n88SAaM4opogLrAyz82NlWQSE55Yf0wiNNNh+HdiQOpTBIes5blBcxbai9KVQU5dqlkKNceg7rdDxANFlm49Tn4yNkrS80w75IQL1xLzYfvWSlWn/falJR4jNBfBg7aTyJduGvWytTgphYVTPheJN/fEDRMR0XTVlnkHZQIDAQAB";
  230. // //pem格式私钥 用途解密
  231. //// public static let RsaPrivate = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCHdTngROYDdqcZwvcPi4Brm80j\r\nRLqtWgwJ70yGE/cwtFJbWAxoQckT6+xUs3wKEXK2SIjrQIidgL3VuTbHYVq6sggYTCjAbPiWJ8M2\r\nxAV5YR3GzfdTd8Yzf7AZcLux9gmu2auKqqaYz15OrG9VYBEPUS+dJRax4FGq4K5IneuRAfGbQT2f\r\nzxIBoziimiAusDLPzY2VZBITnlh/TCI002H4d2JA6lMEh6zluUFzFtqL0pVBTl2qWQo1x6Dut0PE\r\nA0WWbj1OfjI2StLzTDvkhAvXEvNh+9ZKVaf99qUlHiM0F8GDtpPIl24a9bK1OCmFhVM+F4k398QN\r\nExHRdNWWeQdlAgMBAAECggEAA7uZQrLjW8kTRcR3pngDq/N5LbWhJ1HJ7yQnCbLXJwJxGo9MmDrn\r\n3aKsupDPpD9i198b1s/Kc/yuPouCFPB73ZU2X+UJYnQeTZT+7i1ssTGZ7naSkDxGu/iS08Cli3vq\r\nPGVHMuUM/j0IrkZWMxYYokFsfHofK/uwhHL4mouviClpCUoSy/OId0epVo81auFZLuAEK3/n9nG0\r\n9zRstRqOIxe4iRFEN45uj4+FZFBR5nDSRqztbVvdPJJip58IyN6ZRPbFJI9RUwzBl/WWvBJ6sXg1\r\n3SJAkvnJoO4q1ECbUL1kFELIEjWhsqM7QdGAaAYfUSYklVq6agwI9LL2Jk00wQKBgQDoPTW5hwqf\r\nlyA56hjZuHSgjOd3Tw8F3FrDanREc1GhZKrzVFmjC8s6o+dmKoRPnzjAJXtLtcJwEItJkkebZYA+\r\nWuY+y4c+oS+fp69UtgneqLn6knJHqZHhVG4wVzSUuLU0vnAeywHQZ7wE41V6coGwp0kb07N8kAWQ\r\nxTI64IafOQKBgQCVUSFVUNqAMToMTCF0qN7bp/AlFdfRMsZb+ANBDCv1pEL9IQpo+umBkTAgexOB\r\nLWuFO7B3+YIbE23HoDyR/IIodsbqemT3N0GqQ4HfSqe2Hlfuspqbx0T3UocSp2+2jEt4aHLrWlS1\r\nMUA24oEg0ybh6aotZF1CjyIOsY1T8Lj9jQKBgQDP/loKgjZyfgA/vjhpbfkN/YQkZ6r1vceQMtxU\r\nnc9jM2yjp0zsaNin+Tl6V+POB7Tk6ezF/tBYW4jT6G23cC1uKy0A2nmTDvs/CwhNXwfx4b1G/Dfb\r\nlLPxUhOpucB+3fp0dYanHXvPcciDvDLHCpy6YOhqQq0Ch15krSfycTpAiQKBgGmrnNdv/PIz5EzH\r\no/WXfQAefYkBSFa4hECZ6FOkdYfF09KoC2H4EECtq02RTGBi8HC9qUl+vmDDAzH/aF44QTS5ulQ+\r\ngi2iYUpJtCN+BeqQ1tIKe/g6scgGE6lT7W9XIiH4aTu+FayVSkiNS60bQa0nXFP7bzSnbwbPCKGa\r\n/pARAoGBAMbpl+vPULiJ4IrGbNQgA1hKNKhSerwBBKRsiM4MlcDVkjlpLd+bQ9POQy2y0r7GZQjW\r\nmt0XF3tb3Fp20U/ETUxKI4BktTX2A9hrmj5R3Uq+40KocQonjmIMEHF9GxnWF1YfzKtUD2DrDVp/\r\n1YMHv7Is7IVCtyIbLXVuMVXvGeab";
  232. // public static let RsaPrivate = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCHdTngROYDdqcZwvcPi4Brm80jRLqtWgwJ70yGE/cwtFJbWAxoQckT6+xUs3wKEXK2SIjrQIidgL3VuTbHYVq6sggYTCjAbPiWJ8M2xAV5YR3GzfdTd8Yzf7AZcLux9gmu2auKqqaYz15OrG9VYBEPUS+dJRax4FGq4K5IneuRAfGbQT2fzxIBoziimiAusDLPzY2VZBITnlh/TCI002H4d2JA6lMEh6zluUFzFtqL0pVBTl2qWQo1x6Dut0PEA0WWbj1OfjI2StLzTDvkhAvXEvNh+9ZKVaf99qUlHiM0F8GDtpPIl24a9bK1OCmFhVM+F4k398QNExHRdNWWeQdlAgMBAAECggEAA7uZQrLjW8kTRcR3pngDq/N5LbWhJ1HJ7yQnCbLXJwJxGo9MmDrn3aKsupDPpD9i198b1s/Kc/yuPouCFPB73ZU2X+UJYnQeTZT+7i1ssTGZ7naSkDxGu/iS08Cli3vqPGVHMuUM/j0IrkZWMxYYokFsfHofK/uwhHL4mouviClpCUoSy/OId0epVo81auFZLuAEK3/n9nG09zRstRqOIxe4iRFEN45uj4+FZFBR5nDSRqztbVvdPJJip58IyN6ZRPbFJI9RUwzBl/WWvBJ6sXg13SJAkvnJoO4q1ECbUL1kFELIEjWhsqM7QdGAaAYfUSYklVq6agwI9LL2Jk00wQKBgQDoPTW5hwqflyA56hjZuHSgjOd3Tw8F3FrDanREc1GhZKrzVFmjC8s6o+dmKoRPnzjAJXtLtcJwEItJkkebZYA+WuY+y4c+oS+fp69UtgneqLn6knJHqZHhVG4wVzSUuLU0vnAeywHQZ7wE41V6coGwp0kb07N8kAWQxTI64IafOQKBgQCVUSFVUNqAMToMTCF0qN7bp/AlFdfRMsZb+ANBDCv1pEL9IQpo+umBkTAgexOBLWuFO7B3+YIbE23HoDyR/IIodsbqemT3N0GqQ4HfSqe2Hlfuspqbx0T3UocSp2+2jEt4aHLrWlS1MUA24oEg0ybh6aotZF1CjyIOsY1T8Lj9jQKBgQDP/loKgjZyfgA/vjhpbfkN/YQkZ6r1vceQMtxUnc9jM2yjp0zsaNin+Tl6V+POB7Tk6ezF/tBYW4jT6G23cC1uKy0A2nmTDvs/CwhNXwfx4b1G/DfblLPxUhOpucB+3fp0dYanHXvPcciDvDLHCpy6YOhqQq0Ch15krSfycTpAiQKBgGmrnNdv/PIz5EzHo/WXfQAefYkBSFa4hECZ6FOkdYfF09KoC2H4EECtq02RTGBi8HC9qUl+vmDDAzH/aF44QTS5ulQ+gi2iYUpJtCN+BeqQ1tIKe/g6scgGE6lT7W9XIiH4aTu+FayVSkiNS60bQa0nXFP7bzSnbwbPCKGa/pARAoGBAMbpl+vPULiJ4IrGbNQgA1hKNKhSerwBBKRsiM4MlcDVkjlpLd+bQ9POQy2y0r7GZQjWmt0XF3tb3Fp20U/ETUxKI4BktTX2A9hrmj5R3Uq+40KocQonjmIMEHF9GxnWF1YfzKtUD2DrDVp/1YMHv7Is7IVCtyIbLXVuMVXvGeab";
  233. //
  234. //
  235. // func getPrivateKeyFromString(key: String) -> SecKey? {
  236. //// guard let data = key.data(using: .utf8) else { return nil }
  237. // guard let data = Data(base64Encoded: key) else {
  238. // return nil
  239. // }
  240. //
  241. // let parameters: [CFString: Any] = [
  242. // kSecAttrKeyType: kSecAttrKeyTypeRSA,
  243. // kSecAttrKeyClass: kSecAttrKeyClassPrivate,
  244. // kSecAttrKeySizeInBits: 2048,
  245. // kSecReturnPersistentRef: true
  246. // ]
  247. //
  248. // var error: Unmanaged<CFError>?
  249. // guard let privkeyData = Self.stripPrivateKeyHeader(data) else {
  250. // return nil
  251. // }
  252. //
  253. // if let privateKey = SecKeyCreateWithData(privkeyData as CFData, parameters as CFDictionary, &error) {
  254. // return privateKey
  255. // } else {
  256. // print("Error creating private key: \(error!.takeRetainedValue())")
  257. // return nil
  258. // }
  259. // }
  260. //
  261. // @objc public func decrypt(source: NSString) -> NSString? {
  262. // guard let data = Data(base64Encoded: source as String), data.isEmpty == false else {
  263. // KMPrint("需要解密的数据为空.")
  264. // return nil
  265. // }
  266. // guard let key = self.getPrivateKeyFromString(key: Self.RsaPrivate) else {
  267. // KMPrint("私钥生成失败.")
  268. // return nil
  269. // }
  270. //
  271. //// let data: Data = (NSData(base64Encoded: (source as String), options: NSData.Base64DecodingOptions.ignoreUnknownCharacters)! as Data)
  272. //// let data = Data(base64Encoded: source as String)
  273. // if let deData = SecKeyCreateDecryptedData(key, .rsaEncryptionPKCS1, data as CFData, nil) {
  274. // return String(data: deData as Data, encoding: .utf8) as NSString?
  275. // }
  276. // return nil
  277. // }
  278. //
  279. // // Verify that the supplied key is in fact a PEM RSA private key key and strip the header
  280. // // On disk, a PEM RSA private key file starts with string "-----BEGIN RSA PRIVATE KEY-----",
  281. // // and ends with string "-----END RSA PRIVATE KEY-----"
  282. // static fileprivate func stripPrivateKeyHeader(_ privkey: Data) -> Data? {
  283. // if ( privkey.count == 0 ) {
  284. // return nil
  285. // }
  286. //
  287. // var keyAsArray = [UInt8](repeating: 0, count: privkey.count / MemoryLayout<UInt8>.size)
  288. // (privkey as NSData).getBytes(&keyAsArray, length: privkey.count)
  289. //
  290. // //magic byte at offset 22, check if it's actually ASN.1
  291. // var idx = 22
  292. // if ( keyAsArray[idx] != 0x04 ) {
  293. // return nil
  294. // }
  295. // idx += 1
  296. //
  297. // //now we need to find out how long the key is, so we can extract the correct hunk
  298. // //of bytes from the buffer.
  299. // var len = Int(keyAsArray[idx])
  300. // idx += 1
  301. // let det = len & 0x80 //check if the high bit set
  302. // if (det == 0) {
  303. // //no? then the length of the key is a number that fits in one byte, (< 128)
  304. // len = len & 0x7f
  305. // } else {
  306. // //otherwise, the length of the key is a number that doesn't fit in one byte (> 127)
  307. // var byteCount = Int(len & 0x7f)
  308. // if (byteCount + idx > privkey.count) {
  309. // return nil
  310. // }
  311. // //so we need to snip off byteCount bytes from the front, and reverse their order
  312. // var accum: UInt = 0
  313. // var idx2 = idx
  314. // idx += byteCount
  315. // while (byteCount > 0) {
  316. // //after each byte, we shove it over, accumulating the value into accum
  317. // accum = (accum << 8) + UInt(keyAsArray[idx2])
  318. // idx2 += 1
  319. // byteCount -= 1
  320. // }
  321. // // now we have read all the bytes of the key length, and converted them to a number,
  322. // // which is the number of bytes in the actual key. we use this below to extract the
  323. // // key bytes and operate on them
  324. // len = Int(accum)
  325. // }
  326. //
  327. // //return privkey.subdata(in: idx..<len)
  328. // //return privkey.subdata(in: NSMakeRange(idx, len))
  329. // return privkey.subdata(in: NSMakeRange(idx, len).toRange()!)
  330. // }
  331. //}