Skip to content

Commit

Permalink
RUM-7937 Use deterministic random ids
Browse files Browse the repository at this point in the history
  • Loading branch information
maxep committed Jan 8, 2025
1 parent 4519c78 commit f763e63
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 7 deletions.
12 changes: 12 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,8 @@
D297324B2A5C108700827599 /* MessageEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29732472A5C108700827599 /* MessageEmitter.swift */; };
D29732512A5C109A00827599 /* MessageEmitterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D297324F2A5C109A00827599 /* MessageEmitterTests.swift */; };
D29732532A5C109A00827599 /* WebViewTrackingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29732502A5C109A00827599 /* WebViewTrackingTests.swift */; };
D29A470D2D2ED49F0092BC79 /* Xoshiro.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29A470C2D2ED49F0092BC79 /* Xoshiro.swift */; };
D29A470F2D2ED6410092BC79 /* Int64+SessionReplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = D29A470E2D2ED6410092BC79 /* Int64+SessionReplay.swift */; };
D29A9F3C29DD84AB005C54A4 /* DatadogRUM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D29A9F3429DD84AA005C54A4 /* DatadogRUM.framework */; };
D29A9F4B29DD8525005C54A4 /* DatadogInternal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D23039A5298D513C001A1FA3 /* DatadogInternal.framework */; };
D29A9F5029DD85BA005C54A4 /* RUMContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C3E63824BF19B4008053F2 /* RUMContext.swift */; };
Expand Down Expand Up @@ -1328,6 +1330,7 @@
D2A1EE3F2885D7EC00D28DFB /* LaunchTimePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE3D2885D7EC00D28DFB /* LaunchTimePublisherTests.swift */; };
D2A1EE442886B8B400D28DFB /* UserInfoPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE432886B8B400D28DFB /* UserInfoPublisherTests.swift */; };
D2A1EE452886B8B400D28DFB /* UserInfoPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE432886B8B400D28DFB /* UserInfoPublisherTests.swift */; };
D2A1FAB02D2ECDF3007E9B40 /* XoshiroTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1FAAF2D2ECDF3007E9B40 /* XoshiroTests.swift */; };
D2A783D429A5309F003B03BB /* SwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BBA2423979B00786299 /* SwiftExtensions.swift */; };
D2A783D529A530A0003B03BB /* SwiftExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61133BBA2423979B00786299 /* SwiftExtensions.swift */; };
D2A783D929A530EF003B03BB /* SwiftExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */; };
Expand Down Expand Up @@ -3043,6 +3046,8 @@
D297324F2A5C109A00827599 /* MessageEmitterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageEmitterTests.swift; sourceTree = "<group>"; };
D29732502A5C109A00827599 /* WebViewTrackingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebViewTrackingTests.swift; sourceTree = "<group>"; };
D29889C72734136200A4D1A9 /* RUMViewsHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RUMViewsHandlerTests.swift; sourceTree = "<group>"; };
D29A470C2D2ED49F0092BC79 /* Xoshiro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xoshiro.swift; sourceTree = "<group>"; };
D29A470E2D2ED6410092BC79 /* Int64+SessionReplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int64+SessionReplay.swift"; sourceTree = "<group>"; };
D29A9F3429DD84AA005C54A4 /* DatadogRUM.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogRUM.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D29A9F3B29DD84AB005C54A4 /* DatadogRUMTests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DatadogRUMTests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
D29A9F9429DDB1DB005C54A4 /* UIKitExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitExtensions.swift; sourceTree = "<group>"; };
Expand All @@ -3060,6 +3065,7 @@
D2A1EE3A287EECA800D28DFB /* CarrierInfoPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarrierInfoPublisherTests.swift; sourceTree = "<group>"; };
D2A1EE3D2885D7EC00D28DFB /* LaunchTimePublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTimePublisherTests.swift; sourceTree = "<group>"; };
D2A1EE432886B8B400D28DFB /* UserInfoPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfoPublisherTests.swift; sourceTree = "<group>"; };
D2A1FAAF2D2ECDF3007E9B40 /* XoshiroTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XoshiroTests.swift; sourceTree = "<group>"; };
D2A38DDA29C37E1B007C6900 /* TracingURLSessionHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingURLSessionHandlerTests.swift; sourceTree = "<group>"; };
D2A434AD2A8E426C0028E329 /* DDSessionReplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDSessionReplayTests.swift; sourceTree = "<group>"; };
D2A7840129A534F9003B03BB /* DatadogLogsTests tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "DatadogLogsTests tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -3717,6 +3723,8 @@
61054E172A6EE10A00AAA894 /* SystemColors.swift */,
61054E182A6EE10A00AAA894 /* CGRect+SessionReplay.swift */,
D274FD1B2CBFEF6D005270B5 /* CGSize+SessionReplay.swift */,
D29A470E2D2ED6410092BC79 /* Int64+SessionReplay.swift */,
D29A470C2D2ED49F0092BC79 /* Xoshiro.swift */,
);
name = Utilities;
path = Recorder/Utilities;
Expand Down Expand Up @@ -3915,6 +3923,7 @@
61054F432A6EE1B900AAA894 /* CFType+SafetyTests.swift */,
61054F442A6EE1B900AAA894 /* QueueTests.swift */,
61054F452A6EE1B900AAA894 /* SwiftExtensionsTests.swift */,
D2A1FAAF2D2ECDF3007E9B40 /* XoshiroTests.swift */,
61054F462A6EE1B900AAA894 /* Schedulers */,
);
path = Utilities;
Expand Down Expand Up @@ -8528,6 +8537,7 @@
61054E972A6EE10A00AAA894 /* Diff.swift in Sources */,
61054E6E2A6EE10A00AAA894 /* RecordingCoordinator.swift in Sources */,
61054E9F2A6EE10B00AAA894 /* Errors.swift in Sources */,
D29A470D2D2ED49F0092BC79 /* Xoshiro.swift in Sources */,
61054E642A6EE10A00AAA894 /* SessionReplay.swift in Sources */,
61054E952A6EE10A00AAA894 /* SnapshotProcessor.swift in Sources */,
61054E722A6EE10A00AAA894 /* TouchIdentifierGenerator.swift in Sources */,
Expand All @@ -8554,6 +8564,7 @@
61054E752A6EE10A00AAA894 /* ViewTreeSnapshot.swift in Sources */,
61054EA02A6EE10B00AAA894 /* Colors.swift in Sources */,
61054E7F2A6EE10A00AAA894 /* UISliderRecorder.swift in Sources */,
D29A470F2D2ED6410092BC79 /* Int64+SessionReplay.swift in Sources */,
61054E842A6EE10A00AAA894 /* UITabBarRecorder.swift in Sources */,
61054E682A6EE10A00AAA894 /* PrivacyLevel.swift in Sources */,
D22442C52CA301DA002E71E4 /* UIColor+SessionReplay.swift in Sources */,
Expand Down Expand Up @@ -8620,6 +8631,7 @@
61054FA02A6EE1BA00AAA894 /* SRCompressionTests.swift in Sources */,
A74A72852B10CC6700771FEB /* ResourceRequestBuilderTests.swift in Sources */,
61054FB62A6EE1BA00AAA894 /* UnsupportedViewRecorderTests.swift in Sources */,
D2A1FAB02D2ECDF3007E9B40 /* XoshiroTests.swift in Sources */,
61054F9F2A6EE1BA00AAA894 /* RecordsWriterTests.swift in Sources */,
61054FB82A6EE1BA00AAA894 /* UIDatePickerRecorderTests.swift in Sources */,
962D72C52CF7806300F86EF0 /* GraphicsImageReflectionTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

#if os(iOS)

import Foundation

@_spi(Internal)
public extension Int64 {
static func positiveRandom<T>(using generator: inout T) -> Int64 where T: RandomNumberGenerator {
.random(in: 0..<Int64.max, using: &generator)
}
}

#endif
42 changes: 42 additions & 0 deletions DatadogSessionReplay/Sources/Recorder/Utilities/Xoshiro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

import Foundation

/// http://xoshiro.di.unimi.it
/// by David Blackman and Sebastiano Vigna
internal struct XoshiroRandomNumberGenerator: RandomNumberGenerator {
typealias StateType = (UInt64, UInt64, UInt64, UInt64)

private var state: StateType = (0, 0, 0, 0)

internal init(seed: StateType) {
self.state = seed
}

internal init<T>(seed: T) where T: FixedWidthInteger {
self.state = (
UInt64(seed),
UInt64(seed),
UInt64(seed),
UInt64(seed)
)
}

internal mutating func next() -> UInt64 {
// Derived from public domain implementation of xoshiro256**:
let x = state.1 &* 5
let result = ((x &<< 7) | (x &>> 57)) &* 9
let t = state.1 &<< 17
state.2 ^= state.0
state.3 ^= state.1
state.1 ^= state.2
state.0 ^= state.3
state.2 ^= t
state.3 = (state.3 &<< 45) | (state.3 &>> 19)
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,14 @@ internal struct SwiftUIWireframesBuilder: NodeWireframesBuilder {
private func contentWireframe(item: DisplayList.Item, content: DisplayList.Content, context: Context) -> SRWireframe? {
let viewInfo = renderer.viewCache.map[.init(id: .init(identity: item.identity))]

var generator = XoshiroRandomNumberGenerator(seed: content.seed.value)
let id: Int64 = .positiveRandom(using: &generator)

switch content.value {
case let .shape(_, paint, _):
return paint.paint.map { paint in
context.builder.createShapeWireframe(
id: Int64(content.seed.value),
id: id,
frame: context.convert(frame: item.frame),
clip: context.clip,
backgroundColor: CGColor(
Expand All @@ -113,7 +116,7 @@ internal struct SwiftUIWireframesBuilder: NodeWireframesBuilder {
let foregroundColor = storage.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? UIColor
let font = storage.attribute(.font, at: 0, effectiveRange: nil) as? UIFont
return context.builder.createTextWireframe(
id: Int64(content.seed.value),
id: id,
frame: context.convert(frame: item.frame),
clip: context.clip,
text: textObfuscator.mask(text: storage.string),
Expand All @@ -124,7 +127,7 @@ internal struct SwiftUIWireframesBuilder: NodeWireframesBuilder {
)
case .color:
return context.builder.createShapeWireframe(
id: Int64(content.seed.value),
id: id,
frame: context.convert(frame: item.frame),
clip: context.clip,
borderColor: viewInfo?.borderColor,
Expand All @@ -148,22 +151,22 @@ internal struct SwiftUIWireframesBuilder: NodeWireframesBuilder {
tintColor: nil
)
return context.builder.createImageWireframe(
id: Int64(content.seed.value),
id: id,
resource: imageResource,
frame: context.convert(frame: item.frame),
clip: context.clip
)
} else {
return context.builder.createPlaceholderWireframe(
id: Int64(content.seed.value),
id: id,
frame: context.convert(frame: item.frame),
clip: context.clip,
label: imagePrivacyLevel == .maskNonBundledOnly ? "Content Image" : "Image"
)
}
case .unknown:
return context.builder.createPlaceholderWireframe(
id: Int64(content.seed.value),
id: id,
frame: context.convert(frame: item.frame),
clip: context.clip,
label: "Unsupported image type"
Expand All @@ -174,7 +177,7 @@ internal struct SwiftUIWireframesBuilder: NodeWireframesBuilder {
return nil // Should be recorder by UIKit recorder
case .unknown:
return context.builder.createPlaceholderWireframe(
id: Int64(content.seed.value),
id: id,
frame: context.convert(frame: item.frame),
clip: context.clip,
label: "Unsupported SwiftUI component"
Expand Down
35 changes: 35 additions & 0 deletions DatadogSessionReplay/Tests/Utilities/XoshiroTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

import XCTest

@_spi(Internal)
@testable import DatadogSessionReplay
@testable import TestUtilities

class XoshiroTests: XCTestCase {
func testRandomnValues() {
let seed: XoshiroRandomNumberGenerator.StateType = (.mockRandom(), .mockRandom(), .mockRandom(), .mockRandom())
var g1 = XoshiroRandomNumberGenerator(seed: seed)

let a = g1.next()
let b = g1.next()
let c = g1.next()
let d = g1.next()

XCTAssert(a != b && a != c && a != d && b != c && b != d && c != d, "Technically, we *could* get a collision...")
}

func testDeterministicValues() {
let seed: XoshiroRandomNumberGenerator.StateType = (.mockRandom(), .mockRandom(), .mockRandom(), .mockRandom())
var g1 = XoshiroRandomNumberGenerator(seed: seed)
var g2 = XoshiroRandomNumberGenerator(seed: seed)

for _ in 0..<1_000 {
XCTAssert(g1.next() == g2.next())
}
}
}

0 comments on commit f763e63

Please sign in to comment.