Skip to content

Commit

Permalink
Use String for JSON coding of Profile (10x speedup) (#144)
Browse files Browse the repository at this point in the history
  • Loading branch information
CyonAlexRDX authored May 23, 2024
1 parent c124c5a commit b88e1aa
Show file tree
Hide file tree
Showing 13 changed files with 305 additions and 162 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sargon"
version = "0.7.18"
version = "0.7.20"
edition = "2021"
build = "build.rs"

Expand Down Expand Up @@ -80,7 +80,8 @@ radix-rust = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "397a
] }
radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "397a1bc0bb67429f1c82fbe0a62b3ac2f89a6fad" }
radix-common = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "397a1bc0bb67429f1c82fbe0a62b3ac2f89a6fad", features = [
"serde", "secp256k1_sign_and_validate"
"serde",
"secp256k1_sign_and_validate",
] }
radix-common-derive = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "397a1bc0bb67429f1c82fbe0a62b3ac2f89a6fad" }
radix-engine-interface = { git = "https://github.com/radixdlt/radixdlt-scrypto", rev = "397a1bc0bb67429f1c82fbe0a62b3ac2f89a6fad" }
Expand Down
9 changes: 1 addition & 8 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ var swiftSettings: [SwiftSetting] = [
.enableExperimentalFeature("StrictConcurrency")
]

var strictSwiftSettings: [SwiftSetting] = swiftSettings

let sargonBinaryTargetName = "SargonCoreRS"
let binaryTarget: Target
Expand All @@ -20,12 +19,6 @@ if useLocalFramework {
// import SargonCore unless you specify this as a relative path!
path: "./target/swift/libsargon-rs.xcframework"
)

// MUST NOT be part of release, since results in compilation error:
// The package product 'Sargon' cannot be used as a dependency of this target because it uses unsafe build flags.
strictSwiftSettings.append(
.unsafeFlags(["-warnings-as-errors"])
)
} else {
let releaseTag = "0.1.0"
let releaseChecksum = "befef7d56108305ff6ff69d67483471395c3e603e299b3b15f5a826328de272b"
Expand Down Expand Up @@ -75,7 +68,7 @@ let package = Package(
.product(name: "CustomDump", package: "swift-custom-dump"),
],
path: "apple/Tests",
swiftSettings: strictSwiftSettings
swiftSettings: swiftSettings
),
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,52 @@ import SargonUniFFI

extension Profile {

public static func analyzeFile(contents: some DataProtocol) -> ProfileFileContents {
profileAnalyzeContentsOfFile(bytes: Data(contents))
/// Just a convenience for `analyzeContents(of: String(data: data, encoding: .utf8)!)`
public static func analyzeContents(data: some DataProtocol) -> ProfileFileContents {
let contents = String(data: Data(data), encoding: .utf8)!
return analyzeContents(of: contents)
}

public init(jsonData bytes: some DataProtocol) throws {
self = try newProfileFromJsonBytes(jsonBytes: Data(bytes))
public static func analyzeContents(of contents: String) -> ProfileFileContents {
profileAnalyzeContentsOfFile(contents: contents)
}

public func toJSONString(prettyPrinted: Bool = false) -> String {
profileToJsonString(profile: self, prettyPrinted: prettyPrinted)
}

@available(*, deprecated, message: "Use `toJSONString(prettyPrinted:) instead")
public func profileSnapshot() -> Data {
Data(toJSONString(prettyPrinted: false).utf8)
}

@available(*, deprecated, message: "Use `toJSONString(prettyPrinted:) instead")
public func jsonData() -> Data {
profileSnapshot()
}

@available(*, deprecated, message: "Use `init(jsonString:) instead")
public init(jsonData: some DataProtocol) throws {
let jsonString = String(data: Data(jsonData), encoding: .utf8)!
try self.init(jsonString: jsonString)
}

public init(jsonString: String) throws {
self = try newProfileFromJsonString(jsonStr: jsonString)
}

@available(*, deprecated, message: "Use `init(encryptedProfileJSONString:decryptionPassword)` instead")
public init(encrypted bytes: some DataProtocol, decryptionPassword: String) throws {
let jsonString = String(data: Data(bytes), encoding: .utf8)!
try self.init(encryptedProfileJSONString: jsonString, decryptionPassword: decryptionPassword)
}

public init(
encryptedProfileJSONString jsonString: String,
decryptionPassword: String
) throws {
self = try newProfileFromEncryptionBytes(
json: Data(bytes),
jsonString: jsonString,
decryptionPassword: decryptionPassword
)
}
Expand All @@ -26,24 +61,40 @@ extension Profile {
public func toDebugString() -> String {
profileToDebugString(profile: self)
}

public func profileSnapshot() -> Data {
profileToJsonBytes(profile: self)
}

public func jsonData() -> Data {
profileSnapshot()

/// Returns an Encrypted Profile as JSON Data
@available(*, deprecated, message: "Use `encryptedJsonString:password` instead")
public func encrypt(password: String) -> Data {
let jsonString = encryptedJsonString(password: password)
return Data(jsonString.utf8)
}

public func encrypt(password: String) -> Data {
/// Returns an Encrypted Profile as JSON String
public func encryptedJsonString(password: String) -> String {
profileEncryptWithPassword(profile: self, encryptionPassword: password)
}

/// This delegates to `checkIfProfileJsonStringContainsLegacyP2PLinks`
@available(*, deprecated, message: "Use `checkIfProfileJsonStringContainsLegacyP2PLinks` instead")
public static func checkIfProfileJsonContainsLegacyP2PLinks(contents: some DataProtocol) -> Bool {
checkIfProfileJsonContainsLegacyP2pLinks(json: Data(contents))
let jsonData = Data(contents)
let jsonString = String(data: jsonData, encoding: .utf8)!
return checkIfProfileJsonStringContainsLegacyP2PLinks(jsonString: jsonString)
}

public static func checkIfProfileJsonStringContainsLegacyP2PLinks(jsonString: String) -> Bool {
checkIfProfileJsonContainsLegacyP2pLinks(jsonStr: jsonString)
}

/// This delegates to `checkIfEncryptedProfileJsonStringContainsLegacyP2PLinks:jsonString:password`
@available(*, deprecated, message: "Use `checkIfEncryptedProfileJsonStringContainsLegacyP2PLinks:jsonString:password` instead")
public static func checkIfEncryptedProfileJsonContainsLegacyP2PLinks(contents: some DataProtocol, password: String) -> Bool {
checkIfEncryptedProfileJsonContainsLegacyP2pLinks(json: Data(contents), password: password)
let jsonData = Data(contents)
let jsonString = String(data: jsonData, encoding: .utf8)!
return checkIfEncryptedProfileJsonStringContainsLegacyP2PLinks(jsonString: jsonString, password: password)
}

public static func checkIfEncryptedProfileJsonStringContainsLegacyP2PLinks(jsonString: String, password: String) -> Bool {
checkIfEncryptedProfileJsonContainsLegacyP2pLinks(jsonStr: jsonString, password: password)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import CustomDump
import Foundation
import Sargon
@testable import Sargon
import SargonUniFFI
import XCTest
import SwiftyJSON
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import CustomDump
import Foundation
import Sargon
@testable import Sargon
import SargonUniFFI
import XCTest
import SwiftyJSON
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import CustomDump
import Foundation
import Sargon
@testable import Sargon
import SargonUniFFI
import XCTest
import SwiftyJSON
Expand Down
75 changes: 57 additions & 18 deletions apple/Tests/TestCases/Profile/ProfileTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import CustomDump
import Foundation
import Sargon
@testable import Sargon
import SargonUniFFI
import XCTest

Expand Down Expand Up @@ -36,6 +36,19 @@ final class ProfileTests: Test<Profile> {
)
XCTAssertNoDifference(decrypted, sut)
}

func test_encryption_roundtrip_string() throws {
let password = "ultra secret"
let sut = SUT.sample
let encryptedString = sut.encryptedJsonString(
password: password
)
let decrypted = try Profile(
encryptedProfileJSONString: encryptedString,
decryptionPassword: password
)
XCTAssertNoDifference(decrypted, sut)
}

func test_init_with_header_and_dfs() {
let header = Header.sampleOther
Expand All @@ -48,17 +61,17 @@ final class ProfileTests: Test<Profile> {
}

func test_analyze_file_not_profile() {
XCTAssertEqual(SUT.analyzeFile(contents: Data()), .notProfile)
XCTAssertEqual(SUT.analyzeContents(data: Data()), .notProfile)
}

func test_analyze_file_profile() {
func doTest(_ sut: SUT, _ json: Data) {
func doTest(_ sut: SUT, _ jsonString: String) {
XCTAssertEqual(
SUT.analyzeFile(contents: json),
SUT.analyzeContents(of: jsonString),
.plaintextProfile(sut)
)
}
var vectors = Array(zip(SUT.sampleValues, SUT.sampleValues.map { $0.jsonData() }))
var vectors = Array(zip(SUT.sampleValues, SUT.sampleValues.map { $0.toJSONString() }))
vectors.append(vector)

vectors.forEach(doTest)
Expand All @@ -72,9 +85,9 @@ final class ProfileTests: Test<Profile> {
let passwords = ["Mellon", "open sesame", "REINDEER FLOTILLA", "swordfish"]
func doTest(_ index: Int, _ sut: SUT) {
let password = passwords[index % passwords.count]
let encrypted = sut.encrypt(password: password)
let encrypted = sut.encryptedJsonString(password: password)
XCTAssertEqual(
SUT.analyzeFile(contents: encrypted),
SUT.analyzeContents(of: encrypted),
.encryptedProfile
)
}
Expand All @@ -83,38 +96,56 @@ final class ProfileTests: Test<Profile> {
}

func test_encrypted_profile_contents() throws {
let encrypted = SUT.sample.encrypt(password: "open sesame")
let jsonString = try XCTUnwrap(String(data: encrypted, encoding: .utf8))
let jsonString = SUT.sample.encryptedJsonString(password: "open sesame")
XCTAssertTrue(jsonString.contains("encryptionScheme"))
XCTAssertTrue(jsonString.contains("keyDerivationScheme"))
XCTAssertTrue(jsonString.contains("encryptedSnapshot"))
XCTAssertTrue(jsonString.contains("version"))
}

func test_json_roundtrip() throws {
func doTest(_ sut: SUT, _ json: Data) throws {
let encoded = sut.profileSnapshot()
func doTest(_ sut: SUT, _ json: String) throws {
let encoded = sut.toJSONString(prettyPrinted: false)
XCTAssertEqual(encoded, json)
let decoded = try SUT(jsonData: json)
let decoded = try SUT(jsonString: json)
XCTAssertEqual(decoded, sut)
let decodedFromData = try SUT(jsonData: sut.jsonData())
XCTAssertEqual(decodedFromData, sut)
}
var vectors = Array(zip(SUT.sampleValues, SUT.sampleValues.map { $0.jsonData() }))
var vectors = Array(zip(SUT.sampleValues, SUT.sampleValues.map { $0.toJSONString(prettyPrinted: false) }))
vectors.append(vector)
try vectors.forEach(doTest)
}

lazy var vector: (model: Profile, json: Data) = {
try! jsonFixture(

// Macbook Pro M2: 0.06 (10x speedup vs BagOfBytes)
func test_performance_json_encoding_string() throws {
let (sut, _) = vector
measure {
let _ = sut.toJSONString()
}
}

// Macbook Pro M2: 0.1 (2.5x speedup vs BagOfBytes)
func test_performance_json_decoding_string() throws {
let (_, jsonString) = vector
measure {
let _ = try! SUT(jsonString: jsonString)
}
}

lazy var vector: (model: Profile, jsonString: String) = {
try! jsonString(
as: SUT.self,
file: "huge_profile_1000_accounts",
decode: { try Profile(jsonData: $0) }
decode: { try Profile(jsonString: $0) }
)
}()

func test_check_if_profile_json_contains_legacy_p2p_links_when_p2p_links_are_not_present() {
eachSample { sut in
XCTAssertFalse(
SUT.checkIfProfileJsonContainsLegacyP2PLinks(contents: sut.jsonData())
SUT.checkIfProfileJsonStringContainsLegacyP2PLinks(jsonString: sut.toJSONString())
)
}
}
Expand All @@ -128,7 +159,7 @@ final class ProfileTests: Test<Profile> {

func test_check_if_encrypted_profile_json_contains_legacy_p2p_links_when_empty_json() {
XCTAssertFalse(
SUT.checkIfEncryptedProfileJsonContainsLegacyP2PLinks(contents: Data(), password: "babylon")
SUT.checkIfEncryptedProfileJsonStringContainsLegacyP2PLinks(jsonString: "", password: "babylon")
)
}

Expand All @@ -138,4 +169,12 @@ final class ProfileTests: Test<Profile> {
SUT.checkIfEncryptedProfileJsonContainsLegacyP2PLinks(contents: json, password: "babylon")
)
}

func test_check_if_encrypted_profile_json_string_contains_legacy_p2p_links_when_p2p_links_are_present() throws {
let json = try openFile(subPath: "vector", "profile_encrypted_by_password_of_babylon", extension: "json")
let jsonString = try XCTUnwrap(String(data: json, encoding: .utf8))
XCTAssert(
SUT.checkIfEncryptedProfileJsonStringContainsLegacyP2PLinks(jsonString: jsonString, password: "babylon")
)
}
}
11 changes: 11 additions & 0 deletions apple/Tests/Utils/Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ class TestCase: XCTestCase {
return (model, json)
}

func jsonString<T>(
as: T.Type = T.self,
file fileName: String,
decode: (String) throws -> T
) throws -> (model: T, jsonString: String) {
let jsonData = try openFile(subPath: "vector", fileName, extension: "json")
let jsonString = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
let model: T = try decode(jsonString)
return (model, jsonString)
}

}

class Test<SUT_: SargonModel>: TestCase {
Expand Down
Loading

0 comments on commit b88e1aa

Please sign in to comment.