Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Rollouts] Rollouts serialization #12258

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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")
themiswang marked this conversation as resolved.
Show resolved Hide resolved
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
}
}
31 changes: 31 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,31 @@ - (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
Loading