Skip to content

Commit

Permalink
[Rollouts] Rollouts serilization
Browse files Browse the repository at this point in the history
  • Loading branch information
themiswang committed Jan 10, 2024
1 parent 97094c8 commit 427d963
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class CrashlyticsRemoteConfigManager: NSObject {
public static let maxParameterValueLength = 256

var remoteConfig: RemoteConfigInterop
public private(set) var rolloutAssignment: [RolloutAssignment] = []
@objc public private(set) var rolloutAssignment: [RolloutAssignment] = []
weak var persistenceDelegate: CrashlyticsPersistentLog?

@objc public init(remoteConfig: RemoteConfigInterop) {
Expand All @@ -35,6 +35,24 @@ public class CrashlyticsRemoteConfigManager: NSObject {
@objc public func updateRolloutsState(rolloutsState: RolloutsState) {
rolloutAssignment = normalizeRolloutAssignment(assignments: Array(rolloutsState.assignments))
}

@objc public func getRolloutAssignmentsEncodedJson() -> String? {
let contentEncodedRolloutAssignments = rolloutAssignment.map { assignment in
EncodedRolloutAssignment(assignment: assignment)
}

let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.outputFormatting = .sortedKeys
let encodeData = try? encoder.encode(contentEncodedRolloutAssignments)
if let data = encodeData, let returnString = String(data: data, encoding: .utf8) {
return returnString
}

// TODO(themisw): Hook into core logging functions
debugPrint("Failed to serialize rollouts", encodeData ?? "nil")
return nil
}
}

private extension CrashlyticsRemoteConfigManager {
Expand Down
34 changes: 34 additions & 0 deletions Crashlytics/Crashlytics/Rollouts/EncodedRolloutAssignment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import FirebaseRemoteConfigInterop
import Foundation

@objc(FIRCLSEncodedRolloutAssignment)
class EncodedRolloutAssignment: NSObject, Codable {
@objc public private(set) var rolloutId: String
@objc public private(set) var variantId: String
@objc public private(set) var templateVersion: Int64
@objc public private(set) var parameterKey: String
@objc public private(set) var parameterValue: String

public init(assignment: RolloutAssignment) {
rolloutId = FileUtility.stringToHexConverter(for: assignment.rolloutId)
variantId = FileUtility.stringToHexConverter(for: assignment.variantId)
templateVersion = assignment.templateVersion
parameterKey = FileUtility.stringToHexConverter(for: assignment.parameterKey)
parameterValue = FileUtility.stringToHexConverter(for: assignment.parameterValue)
super.init()
}
}
38 changes: 38 additions & 0 deletions Crashlytics/Crashlytics/Rollouts/StringToHexConverter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

// This is a swift rewrite for the logic in FIRCLSFile for the function FIRCLSFileHexEncodeString()
@objc(FIRCLSwiftFileUtility)
public class FileUtility: NSObject {
@objc public static func stringToHexConverter(for string: String) -> String {
let hexMap = "0123456789abcdef"

var processedString = ""
let utf8Array = string.utf8.map { UInt8($0) }
for c in utf8Array {
let index1 = String.Index(
utf16Offset: Int(c >> 4),
in: hexMap
)
let index2 = String.Index(
utf16Offset: Int(c & 0x0F),
in: hexMap
)
processedString = processedString + String(hexMap[index1]) + String(hexMap[index2])
}
return processedString
}
}
30 changes: 30 additions & 0 deletions Crashlytics/UnitTests/FIRCLSFileTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@

#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"

#if SWIFT_PACKAGE
@import FirebaseCrashlyticsSwift;
#else // Swift Package Manager
#import <FirebaseCrashlytics/FirebaseCrashlytics-Swift.h>
#endif // Cocoapod

#import <XCTest/XCTest.h>

@interface FIRCLSFileTests : XCTestCase
Expand Down Expand Up @@ -169,6 +175,30 @@ - (void)hexEncodingStringWithFile:(FIRCLSFile *)file
buffered ? @"" : @"un");
}

// This is the test to compare FIRCLSwiftFileUtility.stringToHexConverter(for:) and FIRCLSFileWriteHexEncodedString return the same hex encoding value
- (void)testHexEncodingStringObjcAndSwiftResultsSame {
NSString *testedValueString = @"是themis的测试数据,输入中文";

FIRCLSFile * unbufferedFile = &_unbufferedFile;
FIRCLSFileWriteHashStart(unbufferedFile);
FIRCLSFileWriteHashEntryHexEncodedString(unbufferedFile, "hex", [testedValueString UTF8String]);
FIRCLSFileWriteHashEnd(unbufferedFile);
NSString *contentsFromObjcHexEncoding = [self contentsOfFileAtPath:self.unbufferedPath];

FIRCLSFile * bufferedFile = &_bufferedFile;
NSString *encodedValue = [FIRCLSwiftFileUtility stringToHexConverterFor:testedValueString];
FIRCLSFileWriteHashStart(bufferedFile);
FIRCLSFileWriteHashKey(bufferedFile, "hex");
FIRCLSFileWriteStringUnquoted(bufferedFile, "\"");
FIRCLSFileWriteStringUnquoted(bufferedFile, [encodedValue UTF8String]);
FIRCLSFileWriteStringUnquoted(bufferedFile, "\"");
FIRCLSFileWriteHashEnd(bufferedFile);
FIRCLSFileFlushWriteBuffer(bufferedFile);
NSString *contentsFromSwiftHexEncoding = [self contentsOfFileAtPath:self.bufferedPath];

XCTAssertTrue([contentsFromObjcHexEncoding isEqualToString:contentsFromSwiftHexEncoding]);
}

#pragma mark -

- (void)testHexEncodingLongString {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ final class CrashlyticsRemoteConfigManagerTests: XCTestCase {
return rollouts
}()

let singleRollout: RolloutsState = {
let assignment1 = RolloutAssignment(
rolloutId: "rollout_1",
variantId: "control",
templateVersion: 1,
parameterKey: "my_feature",
parameterValue: "这是themis的测试数据,输入中文" // check unicode
)
let rollouts = RolloutsState(assignmentList: [assignment1])
return rollouts
}()

let rcInterop = RemoteConfigConfigMock()

func testRemoteConfigManagerProperlyProcessRolloutsState() throws {
Expand All @@ -61,4 +73,15 @@ final class CrashlyticsRemoteConfigManagerTests: XCTestCase {
}
}
}

func testRemoteConfigManagerGenerateEncodedRolloutAssignmentsJson() throws {
let expectedString =
"[{\"parameter_key\":\"6d795f66656174757265\",\"parameter_value\":\"e8bf99e698af7468656d6973e79a84e6b58be8af95e695b0e68daeefbc8ce8be93e585a5e4b8ade69687\",\"rollout_id\":\"726f6c6c6f75745f31\",\"template_version\":1,\"variant_id\":\"636f6e74726f6c\"}]"

let rcManager = CrashlyticsRemoteConfigManager(remoteConfig: rcInterop)
rcManager.updateRolloutsState(rolloutsState: singleRollout)

let string = rcManager.getRolloutAssignmentsEncodedJson()
XCTAssertEqual(string, expectedString)
}
}
7 changes: 4 additions & 3 deletions FirebaseRemoteConfig/Interop/RolloutAssignment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ public class RolloutAssignment: NSObject {
@objc public var parameterKey: String
@objc public var parameterValue: String

public init(rolloutId: String, variantId: String, templateVersion: Int64, parameterKey: String,
parameterValue: String) {
@objc public init(rolloutId: String, variantId: String, templateVersion: Int64,
parameterKey: String,
parameterValue: String) {
self.rolloutId = rolloutId
self.variantId = variantId
self.templateVersion = templateVersion
Expand All @@ -37,7 +38,7 @@ public class RolloutAssignment: NSObject {
public class RolloutsState: NSObject {
@objc public var assignments: Set<RolloutAssignment> = Set()

public init(assignmentList: [RolloutAssignment]) {
@objc public init(assignmentList: [RolloutAssignment]) {
for assignment in assignmentList {
assignments.insert(assignment)
}
Expand Down

0 comments on commit 427d963

Please sign in to comment.