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

Rework DBP XPC interface #2758

Merged
merged 2 commits into from
May 9, 2024

Conversation

THISISDINOSAUR
Copy link
Contributor

@THISISDINOSAUR THISISDINOSAUR commented May 8, 2024

Task/Issue URL: https://app.asana.com/0/0/1207231867963535/f
Tech Design URL: https://app.asana.com/0/0/1207198317317333/f
CC:

Description:

  1. Moves the existing XPC interface ("DataBrokerProtectionScheduler") from the scheduler into the BackgroundAgentManager
  2. Renames said manager to AgentManager
  3. Renames the XPC interface to DataBrokerProtectionAgentInterface
  4. Completely changes the composition of the interface, separating it out into two different protocols: DataBrokerProtectionAgentAppEvents, DataBrokerProtectionAgentDebugCommands
  5. As those protocols imply, changes the XPC interface methods to be events based, to move decision making from the main app to the agent. Except for the debug commands, which are now clearly separated as for debugging purposes only.
  6. Removes two of the many XPC layers since they were operating solely as a passthrough (i.e. boilerplate for nothing)
  7. Uses protocol composition to further cut down on the repetition in those layers
  8. Changes LoginItemScheduler to rather than be a class, a protocol that inherits the AgentInterface
  9. Removes existing completions from the XPC interface since they were unused
  10. Replaces them with new completions that are solely for reporting the reliability of XPC

It still needs the pixels for #9, including clean up of the old pixels

Steps to test this PR:

  1. Test DBP still works, particularly removing the login item, entirely and starting fresh (since when testing at one point I managed to break that)
  2. Otherwise don't test too much since this PR is not intended to stand alone/be merged into main as is.

Internal references:

Pull Request Review Checklist
Software Engineering Expectations
Technical Design Template
Pull Request Documentation

Copy link
Contributor

github-actions bot commented May 8, 2024

Warnings
⚠️ PR has more than 500 lines of code changing. Consider splitting into smaller PRs if possible.

Generated by 🚫 dangerJS against 968ba2a

@@ -0,0 +1,100 @@
//
// DataBrokerProtectionLoginItemInterface.swift
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite what GitHub thinks, this class isn't really new, just renamed (although it has changed a bit to give it an explicit protocol)

func profileSaved() {
enableLoginItem()
ipcClient.profileSaved { error in
// TODO
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to implement pixels here. It's my next item on my todo list, but could take a while so I don't think the PR should wait for it since it's not merging to main anyway

}

public func runOperationsAndStartSchedulerIfPossible() {
public func agentFinishedLaunching() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also changed this to be more event based, so it's all the manager making the decisions

return
}

scheduler.runQueuedOperations(showWebView: false) { [weak self] _ in
self?.pixelHandler.fire(.backgroundAgentRunOperationsAndStartSchedulerIfPossibleRunQueuedOperationsCallbackStartScheduler)
self?.scheduler.startScheduler()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some more work to do in this completion, but it should all change with the final PR so pointless doing it here

// This is now unusused as we decided the web UI shouldn't issue commands directly
// The background agent itself instead decides to start scans based on events
// This should be removed once we can remove it from the web side
return true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth noting this always returned true anyway unless the scan delegate was nil

}

@objc
public class DataBrokerProtectionAgentErrorCollection: NSObject, NSSecureCoding {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bits not new, but newly in its own file and renamed

})
}

public func getDebugMetadata() async -> DBPBackgroundAgentMetadata? {
await withCheckedContinuation { continuation in
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this might be a bad idea across the XPC boundary, but netP is also doing the same thing

@@ -75,40 +75,18 @@ public final class DBPBackgroundAgentMetadata: NSObject, NSSecureCoding {

/// This protocol describes the server-side IPC interface for controlling the tunnel
///
public protocol IPCServerInterface: AnyObject {
public protocol IPCServerInterface: AnyObject, DataBrokerProtectionAgentDebugCommands {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slightly on the fence about this protocol conformance versus duplicating it again, I don't want to make it more confusing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the conformance is fine 👍🏼

func getDebugMetadata(completion: @escaping (DBPBackgroundAgentMetadata?) -> Void)
func profileSaved(xpcMessageReceivedCompletion: @escaping (Error?) -> Void)
func dataDeleted(xpcMessageReceivedCompletion: @escaping (Error?) -> Void)
func appLaunched(xpcMessageReceivedCompletion: @escaping (Error?) -> Void)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are declared again rather than conforming to the AppEvents protocol to have the xpcMessageReceivedCompletion, and to avoid having it in the AppEvents protocol.


/// Opens a browser window with the specified domain
///
func openBrowser(domain: String)

func startManualScan(showWebView: Bool)
func runQueuedOperations(showWebView: Bool)
func runAllOptOuts(showWebView: Bool)
func getDebugMetadata(completion: @escaping (DBPBackgroundAgentMetadata?) -> Void)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are declared again because this version of getDebugMetadata has to use a completion to go inside xpc.execute, but the higher level interfaces hide this and use a more modern async

func register() {
serverDelegate?.register()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The register method this was calling was empty


extension DataBrokerProtectionAgentManager: DataBrokerProtectionAgentAppEvents {

public func profileSaved() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to unit test that when certain events happen the correct scheduler method is called, but given the next PR is gonna change all that, I don't think it's worth doing until the next PR

@THISISDINOSAUR THISISDINOSAUR requested a review from aataraxiaa May 8, 2024 18:25
Copy link
Contributor

@aataraxiaa aataraxiaa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM @THISISDINOSAUR, nice work! Validated that background agent is started on fresh launch. Left a few comments, main one being that I think we should add tests for the DataBrokerProtectionAgentManager.

func getDebugMetadata() async -> DBPBackgroundAgentMetadata?
}

public protocol DataBrokerProtectionAgentInterface: AnyObject, DataBrokerProtectionAgentAppEvents, DataBrokerProtectionAgentDebugCommands {
Copy link
Contributor

@aataraxiaa aataraxiaa May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my understanding of intention, I find the name DataBrokerProtectionAgentInterface a bit misleading/confusing. On one level we refer to the ‘background agent’ as the ‘agent’. However, this interface is used on both sides of the app -> agent communication. Do we think then maybe something like DataBrokerProtectionClientServerInterface….or DataBrokerProtectionAppAgentInterface…?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's worse since it's strictly one directional (completions or return types not withstanding). It could be DataBrokerProtectionAppToAgentInterface?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more I think about it, I think I do prefer that, since it is specific to the app (what with the appLaunched event and that)

Comment on lines +50 to +52
public static var supportsSecureCoding: Bool {
return true
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor, and not new code maybe, but while we are here this could be:

public static let supportsSecureCoding = true

Just to remove a line or two.

Comment on lines +71 to +77
public protocol DataBrokerProtectionAgentDebugCommands {
func openBrowser(domain: String)
func startManualScan(showWebView: Bool)
func runQueuedOperations(showWebView: Bool)
func runAllOptOuts(showWebView: Bool)
func getDebugMetadata() async -> DBPBackgroundAgentMetadata?
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned on MM, I was also adding something similar based on previous suggestions, and the approach I came up with using enum cases as debug actions, as follows:


enum DataBrokerDebugCommand {
    case startOptOutOperations(showWebView: Bool,
                               operationDependencies: DataBrokerOperationDependencies,
                               completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?)
}

protocol DataBrokerDebugCommandExecutor {
    func execute(_ command: DataBrokerDebugCommand)
}

Any desire to go the enum route? The API it provides is succinct. Just my preference, not a strong opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not possible because it to be able to cross the XPC boundary which means it needs to be representable in Objective-C, i.e. can't have associated values. It's what I originally tried but it didn't work
It might be possible to have the higher level interface be an enum and then have a translation layer in the middle, but I don't think it would be worth the complexity

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, or course. Apologies, you already highlighted this. I was trying for something similar, but only on the agent side, and so the boundary issue slipped my mind. Agree, translation layer etc. not worth the complexity.

@@ -24,7 +24,6 @@ import XPCHelper
/// This protocol describes the server-side IPC interface for controlling the tunnel
///
public protocol IPCClientInterface: AnyObject {
func schedulerStatusChanges(_ status: DataBrokerProtectionSchedulerStatus)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming these empty interfaces will be removed as some point?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still need to give the xpc server something. At the very least our own generic XPCClient requires it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regardless of if we need it or not, I think I prefer explicitly declaring that the interface in the other direction is empty.

@@ -75,40 +75,18 @@ public final class DBPBackgroundAgentMetadata: NSObject, NSSecureCoding {

/// This protocol describes the server-side IPC interface for controlling the tunnel
///
public protocol IPCServerInterface: AnyObject {
public protocol IPCServerInterface: AnyObject, DataBrokerProtectionAgentDebugCommands {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the conformance is fine 👍🏼

@@ -22,24 +22,29 @@ import BrowserServicesKit
import DataBrokerProtection
import PixelKit

public final class DataBrokerProtectionBackgroundManager {
public final class DataBrokerProtectionAgentManager {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should take the time now to make this type testable. It’s currently a singleton with lazy vars, which is not ideal for dependency injection. Does it need to be a singleton? I see it is only referenced once in DuckDuckGoDBPBackgroundAgentAppDelegate. Can we remove the singleton behavior, make the init public, and then inject what is needed to be able to mock dependencies? I think we should be testing that the DataBrokerProtectionAgentAppEvents perform the right actions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow I missed your comment above re: testing DataBrokerProtectionAgentAppEvents, and so if we are gonna test in next PR that’s fine with me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah agreed on testing the app events in that way (#2758 (comment)). I'm indifferent to if we do that particular groundwork in this PR or the next, although think writing the actual tests should wait for the next one since the expected behaviour will change significantly

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing. Indifferent on order of work also. Whatever is most pragmatic to you.

@jotaemepereira jotaemepereira self-requested a review May 9, 2024 11:48
Copy link
Collaborator

@jotaemepereira jotaemepereira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great 👍🏼

@THISISDINOSAUR THISISDINOSAUR merged commit 74e390d into feature/pir-stabilization May 9, 2024
22 of 23 checks passed
@THISISDINOSAUR THISISDINOSAUR deleted the elle/dbp-xpc-changes branch May 9, 2024 20:44
THISISDINOSAUR added a commit that referenced this pull request May 22, 2024
Task/Issue URL:
https://app.asana.com/0/1199230911884351/1207338264132623/f
Tech Design URL:
https://app.asana.com/0/481882893211075/1207174884557414/f
CC:

**Description**:
This PR has the effect of mergeing four different PRs into main:
#2777
#2758
#2757
#2743
This covers significant changes to the XPC interface and how the main
app uses it, the background manager, the scheduler, and the processor.
See individual PRs for details.

There's no code changes here that haven't been reviewed separately as
part of those PRs, so it's up to you how you want to review it code
wise. The more important step at this stage is manual testing.

**Steps to test this PR**:
1. Because this is a fairly significant set of changes, we need to be as
thorough as we can in testing DBP generally works and really try to
break it. Because of this, I'm keen that anyone testing things of their
own ways to break it before trying the steps below.
2. Edit/add profile data, and before scans finish, edit it again. Scans
should start afresh, and you should see an interrupted pixel fire. Check
that you don't get a scans completed notification
3. Edit/add profile data, and before scans finish, close the app, and
reopen it. The same set of manual scans should still continue, and a
blocked pixel should fire.
4. With profile data already existing but initial scans having finished,
close the app and reopen it. Scheduled scans should run.
5. With profile data already existing, and with the app closed, launch
the background agent, scheduled scans should run
6. Edit/add profile data, put the laptop to sleep/lock it/restart it,
check that scans continue as expected.
7. Check that the background agent activity lines up with the UI, I.e.
the progress bar and when we sent the scans completed notification is
accurate

<!--
Tagging instructions
If this PR isn't ready to be merged for whatever reason it should be
marked with the `DO NOT MERGE` label (particularly if it's a draft)
If it's pending Product Review/PFR, please add the `Pending Product
Review` label.

If at any point it isn't actively being worked on/ready for
review/otherwise moving forward (besides the above PR/PFR exception)
strongly consider closing it (or not opening it in the first place). If
you decide not to close it, make sure it's labelled to make it clear the
PRs state and comment with more information.
-->

---
###### Internal references:
[Pull Request Review
Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f)
[Software Engineering
Expectations](https://app.asana.com/0/59792373528535/199064865822552)
[Technical Design
Template](https://app.asana.com/0/59792373528535/184709971311943)
[Pull Request
Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f)

---------

Co-authored-by: Pete Smith <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants