Commit Graph

258 Commits

Author SHA1 Message Date
Vitalii Ganzha
1a6e0ef42e Update FlipperPlugin C++ code so it can be compiled for Windows
Summary:
I am working in Horizon Worlds and I would like to integrate Flipper with HzW app.
Currently FlipperPlugin C++ code won't compile on Windows since it uses Linux-only headers like `netdb.h` and `sys/fcntl.h`, I posted here and looks like it is not currently supported: https://fb.workplace.com/groups/flippersupport/posts/1704837183330266

The problem seem to be in only in `FlipperConnectionEndpointVerifier.cpp`, and I'm updating it to make it compatible with Windows.

Also apparently there's some issue with `#include` of few files and namespaces, leading to "struct redefinition" errors where `#pragma once` does not help https://fb.workplace.com/groups/474291069286180/posts/25313067014981908/

Solving it with manual #define

Reviewed By: lblasa

Differential Revision: D50337573

fbshipit-source-id: affdf1aee2b9dfe615227827fedf324a5f17d8b0
2023-10-17 09:49:11 -07:00
Lorenzo Blasa
a32f5e7544 More connectivity debug logs
Summary:
Whenever there is an issue, I always find myself adding these logs.

Instead of keep doing that, reuse the existing FLIPPER_DEBUG_LOG definition as a way of enabling/disabling the verbose logs very easily.

Reviewed By: ivanmisuno

Differential Revision: D49227973

fbshipit-source-id: d053df6668b1dda6f6782e8ef7d844c82945f126
2023-09-20 04:05:06 -07:00
Lorenzo Blasa
54f5b1ba03 FlipperClient log format
Summary: Use the same standard used in other places.

Reviewed By: ivanmisuno

Differential Revision: D49228128

fbshipit-source-id: 771b6923f758400200298f3339b83b2c4d51648b
2023-09-19 07:01:41 -07:00
Lorenzo Blasa
b6f7d4c56f Standard format for logs
Summary: ^

Reviewed By: ivanmisuno

Differential Revision: D49228038

fbshipit-source-id: bc2c506fa313158fbd8878156fff74ec2e1bed12
2023-09-19 04:22:04 -07:00
Lorenzo Blasa
3605401e5e FlipperState debug logs improvement
Summary: Make it more explicit. What has succeeded, failed, started.

Reviewed By: ivanmisuno

Differential Revision: D49227799

fbshipit-source-id: fe9e6baaff227cf1db0b3150a3ee8cf194d2b6e6
2023-09-18 06:46:37 -07:00
Pascal Hartig
2a6426ebbe Do not throw when a plugin removal fails
Summary:
Symmetry with D48642974.

Changelog: Don't throw in C++ if a plugin gets added/removed multiple times.

Reviewed By: lblasa

Differential Revision: D48643116

fbshipit-source-id: cc6638061b1dee2a6f7deb1fab1093906decc24a
2023-08-25 02:14:22 -07:00
Pascal Hartig
7822099f50 Do not throw when a plugin is already added
Summary:
Causes a lot of errors that don't seem to be preventable from a plugin author's perspective and also completely benign.

https://www.internalfb.com/logview/flipper_javascript/3b754533c5da4e91fe8c0a3318cf8d5c?trace_tab=latest

Reviewed By: lblasa

Differential Revision: D48642974

fbshipit-source-id: 5ba542afbaa4175e1657d4b229d8bab62fac9862
2023-08-25 02:14:22 -07:00
Lorenzo Blasa
4ac755370d Move socket clean inside operation queue
Summary: Set delegate and close inside the operation's queue as to make it safer i.e. all socket related operations are done inside the queue.

Reviewed By: ivanmisuno

Differential Revision: D47124235

fbshipit-source-id: 48b53db1cd47d017a26186a156046ba68fe358b7
2023-06-29 12:40:09 -07:00
Lorenzo Blasa
e42db220ee Socket connect no longer synchronous and blocking
Summary:
Never really liked this code. Before this change, calls to connect were blocking.

Because of this, we had to make use of promises and a bit of really not that good-looking code.

So, this change makes connect non-blocking meaning that we make full use of our event handler.

These changes contain:
- CSR is not getting generated after each failed attempt.
- Connect is no longer blocking.
- Do not report events via the handler when explicitly disconnecting.

Reviewed By: jknoxville

Differential Revision: D46853228

fbshipit-source-id: 00e6a9c7c039a756175fe14982959e078d92bacb
2023-06-28 12:09:58 -07:00
Lorenzo Blasa
80673f7832 Rework flipper state step for connect
Summary:
Create the step as an attempt.

Remove conditionally marking it as success or failure as connect will not be done 'synchronously' moving forward.

:::notes:::
Documentation is for personal use and will be removed next.

Reviewed By: passy

Differential Revision: D46850048

fbshipit-source-id: d6dce961d5cbd767f428e58850d24a433d50ba14
2023-06-22 04:01:42 -07:00
Lorenzo Blasa
1b5c9e627a Move reset state to a more appropriate location
Summary: Not really part of the connect process in this case, so is getting moved closer to where the CSR is generated.

Reviewed By: passy

Differential Revision: D46849966

fbshipit-source-id: b91e2925552cd7fcab67b3d1d7af006f4f8c1431
2023-06-21 23:16:34 -07:00
Lorenzo Blasa
3f6424930a Add resetState documentation
Summary:
Add documentation to `ConnectionContextStore::resetState`.

Useful to have, I guess.

Reviewed By: passy

Differential Revision: D46849937

fbshipit-source-id: 7dec76e23b566f460f0b22e0fa14b73e16a142f0
2023-06-21 13:33:10 -07:00
Lorenzo Blasa
aa510b3fd0 Remove transient newClient
Summary: This was a temporary variable which is not really needed, so remove.

Reviewed By: passy

Differential Revision: D46849864

fbshipit-source-id: 56fb52f9a80128fb746afcdc4d36225e6d596db2
2023-06-20 13:25:40 -07:00
Lorenzo Blasa
4379317258 Move socket event handler inside FlipperConnectionManagerImpl
Summary:
The event handler is a friend type that was mutating the connection manager state.

Instead, just forward the event handling to it.

Then state mutation is consolidated inside the connection manager.

Reviewed By: passy

Differential Revision: D46849769

fbshipit-source-id: 594ab32c8e891564afa94e1be6b93b1dfeffe26f
2023-06-20 07:55:25 -07:00
Lorenzo Blasa
7cec520729 New 'isConnected' API
Summary:
Expose a new API to be used to check if there's an open connection with Flipper Desktop.

Changelog: new FlipperClient isConnected API

Reviewed By: antonk52

Differential Revision: D46841095

fbshipit-source-id: 82a60f52496fb218cb50c6a28d7ffe7225ae23aa
2023-06-20 00:46:55 -07:00
Murat Seker
6b655d64db Custom log handler setup
Summary: Add custom log handler setup so people can use their own log infra instead of "printf".

Reviewed By: Neil-Clifford-FB

Differential Revision: D46590343

fbshipit-source-id: ded9cf6caa580d477a71a9155cad9db295151c84
2023-06-09 06:26:52 -07:00
Pascal Hartig
4e0bbb62fe Upgrade gradle plugin (#4751)
Summary:
[android] Upgrade gradle project
Makes us compatible with Android Studio Flamingo and unlocks upgrades to Kotlin
(which we need to be in sync with fbsource).

Pull Request resolved: https://github.com/facebook/flipper/pull/4751

Test Plan:
- ./gradlew :android:assembleDebug
- Android Studio Sync
- CI

 ---
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/flipper/pull/4751).
* https://github.com/facebook/flipper/issues/4759
* https://github.com/facebook/flipper/issues/4758
* https://github.com/facebook/flipper/issues/4757
* https://github.com/facebook/flipper/issues/4756
* https://github.com/facebook/flipper/issues/4755
* https://github.com/facebook/flipper/issues/4754
* https://github.com/facebook/flipper/issues/4753
* https://github.com/facebook/flipper/issues/4752
* __->__ https://github.com/facebook/flipper/issues/4751

Reviewed By: aigoncharov

Differential Revision: D46068236

Pulled By: passy

fbshipit-source-id: 707422134b49c0d940b663f6f40937fe6561a038
2023-05-23 08:48:24 -07:00
Nikita Lutsenko
0069155b1e flipper | Replace global static variables in FlipperSocketProvider with local static.
Summary: Replacing global static varaibles with local static. No cost at startup time, and also fixes any potential static initialization order fiasco.

Differential Revision: D45900297

fbshipit-source-id: 80e3ffb6527f7f7b6b08de4a6209f0f8298f7bf5
2023-05-16 14:15:04 -07:00
Lorenzo Blasa
a040fb8d4e Ensure logs don't indefinitely append past capacity
Summary:
^

There could be cases, albeit unlikely, that logs could be appended for the current state indefintely that would ultimate fail due to not having enough memory.

This change puts a cap on that.

Reviewed By: mweststrate

Differential Revision: D42313904

fbshipit-source-id: 7fd96be822c9427720bccb41c6c32a39213c7652
2023-01-10 06:48:53 -08:00
Lorenzo Blasa
c2440b6660 WIN32 support for path concatenation
Summary:
So far, Flipper has concatenated paths assuming is only ever built on unix-like systems.

For RNW, this approach fails.

This change is a very simple way of achieving WIN32 support without using any extra libraries or routines for path building.

Reviewed By: antonk52

Differential Revision: D39053479

fbshipit-source-id: e8b4b71cc1d17ca6aba88d40cad7d96a30ad1267
2022-09-04 12:19:26 -07:00
Lorenzo Blasa
4b8be189ba rnw] ReactNativeFlipperExample
Summary:
This change is the direct results of doing the following:

    npx react-native-windows-init --overwrite

Notes: ignore the format warnings below. In this case, the ordering of includes does matter. Missing license is for auto-generated files, so ignore too.

Reviewed By: aigoncharov

Differential Revision: D36775215

fbshipit-source-id: 1cd00ff2bfc258c8505e97dcdbd9cb4365c4acfb
2022-08-26 08:43:30 -07:00
Lorenzo Blasa
947d00c6c3 Fix send raw messages
Summary:
^

The last change removed the overload of `send` in favour of `sendRaw`

Reviewed By: LukeDefeo

Differential Revision: D38946386

fbshipit-source-id: c4491c4afe4d719602c99878dc7185d436179aef
2022-08-24 11:17:21 -07:00
Lorenzo Blasa
d1c06c9c46 Do not overload send as this causes issues with folly::dynamic
Summary:
folly::dynamic, std::string, implicit constructors and method overloading is not a good combination.

This renames the send method to sendRaw as to avoid issues with existing plugins currently sending string params.

Reviewed By: mweststrate

Differential Revision: D38827539

fbshipit-source-id: 653f62e41ebfbe93d1af25f39c81f6b05bf84cb4
2022-08-18 09:39:42 -07:00
Lorenzo Blasa
c2ed2484d9 Expose a send method with a string params
Summary:
For C++, folly::dynamic is used throughout.

On iOS and Android though, Flipper goes through multiple conversions to get to a folly::dynamic only to ultimately obtain a JSON string from it.

Let's take a look at Android:

There are multiple types like FlipperObject, FlipperArray that wrap around a JSONObject.

When data needs to be sent:
1. The JSONObject is asked for its string representation.
2. The string representation is then parsed by folly to construct the folly::dynamic instance.
3. The step above involves an extra boundary cross through JNI.
4. Ultimately, a socket or ws connection does not understand folly::dynamic so we then get a JSON string representation from it.
5. Data is sent.

As described above, for big enough objects, this represents an issue.

So, the idea of this change, is to allow plugins to send a JSON string instead. This will remove a few serialisation/deserialisation steps from the process.

*Note: this API is not currently used by anything so there's no impact to existing plugins.*

Changelog: expose a send method that accept a string as params

Reviewed By: LukeDefeo

Differential Revision: D38741582

fbshipit-source-id: 78e0acd80fc8c97378ee986cbaf377078996ed60
2022-08-17 09:18:20 -07:00
Marcelo Lopez Ruiz
b7c38556ce Name the Flipper scheduler/threads
Summary:
Plumbs through an optional name for FollyScopedThreadScheduler.
This allows the threads to be named in XCode/lldb.

Differential Revision: D38582449

fbshipit-source-id: 1de50d25c0f91e7003cf81cb22faf4b10a8e23a8
2022-08-10 21:21:39 -07:00
Andrey Goncharov
342bb2bd55 Remove legacy notice for FireAndForgetResponder
Summary:
Quoting lblasa:
History trip:
- It was used at the beginning of times with RSocket.
- RSocket introduced an RPC mechanism so a new responder was introduced which used it, thus making this responder legacy.
- WebSockets is introduced which doesn't have an RPC mechanism by default so this responder became the new non-legacy responder

Reviewed By: lblasa

Differential Revision: D37994288

fbshipit-source-id: 2458aed129058a5bfaafd607e73c52867d9713cf
2022-07-20 06:47:02 -07:00
Lorenzo Blasa
039b647666 Schedule close on the scheduler
Summary:
^

If Flipper stops, we cannot freely access the client outside of the scheduler.

There may be an scheduled operation that depends on it.

Instead, schedule the stop and wait until it finishes.

Reviewed By: cekkaewnumchai

Differential Revision: D37814820

fbshipit-source-id: 44217b5f623a8d92211a3d72a4a204d674b4bb76
2022-07-13 05:09:34 -07:00
Lorenzo Blasa
e44cad5e99 Partially remove dependency on folly async
Summary:
This change isolates the usage of folly async from Flipper. Is now self-contained in Flipper Folly schedulers.

Users of Flipper can decide not to use the types defined in that header and implement their own.

NOTE: changes are minimal, we are just replacing direct calls to folly event base with a scheduler which simply relays this on to folly.

Reviewed By: fabiomassimo

Differential Revision: D36626483

fbshipit-source-id: add0241caf4af0aa5c3b5c2e7efc2e725f5400ab
2022-05-25 15:58:05 -07:00
Billy Ng
3804ccf898 Revert D36052198: Partially remove dependency on folly async
Differential Revision:
D36052198 (ade685c621)

Original commit changeset: 170d64a324a1

Original Phabricator Diff: D36052198 (ade685c621)

fbshipit-source-id: 69d2b18e70a6267667432d6ed9dc1c5bc545b417
2022-05-12 18:47:41 -07:00
Lorenzo Blasa
ade685c621 Partially remove dependency on folly async
Summary:
This change isolates the usage of folly async from Flipper. Is now self-contained in Flipper Folly schedulers.

Users of Flipper can decide not to use the types defined in that header and implement their own.

NOTE: changes are minimal, we are just replacing direct calls to folly event base with a scheduler which simply relays this on to folly.

Reviewed By: fabiomassimo

Differential Revision: D36052198

fbshipit-source-id: 170d64a324a1f1f100224e2622a59cbac3c8b642
2022-05-12 17:56:17 -07:00
Lorenzo Blasa
216c926ca5 Remove dead code in connection context store
Summary:
^
It was used by rsocket.

Reviewed By: fabiomassimo

Differential Revision: D36246038

fbshipit-source-id: 8bdc77d9e1ecf22402436e0102b5522ed36aff27
2022-05-12 09:16:13 -07:00
Lorenzo Blasa
63dde6e5cf Exceptions
Summary:
Flipper doesn really use library specific exceptions throughout, and that's OK.

Introducing SSLException as a replacement for the existing Folly Async Socket SSL exception.

This exception originally thrown by rsocket. Because we had rsocket and websockets using the same code, websockets were creating and throwing this same exception.

With rsocket gone, we can fully replace the usage of that exception. This is also needed as to decouple Flipper from folly async components.

Reviewed By: fabiomassimo

Differential Revision: D36245624

fbshipit-source-id: f5c97c5efe063280ce95be130008dee7f4e5d788
2022-05-12 09:16:13 -07:00
Lorenzo Blasa
afcc695edf Scheduler
Summary: Introduce a 'Scheduler' interface which will allow to decouple from the existing used Folly scheduler.

Reviewed By: fabiomassimo

Differential Revision: D36245587

fbshipit-source-id: 2f28bc1612e37ae53060a134d1c8059231fbc8ad
2022-05-12 07:37:11 -07:00
Lorenzo Blasa
1beae3230b Remove OpenSSL file BIO operations
Summary:
^
Change necessary when OpenSSL is compiled without STDIO for file operations. Basically, don't use the BIO file API's. Instead use in-memory BIO and do file operations manually.

UPDATE:

The changes were good, but have been simplified and fixed. A fixed was needed as to read and write the file in binary mode. This has no effect in POSIX systems but it does on Windows and this change was made for Windows. It meant that the BIO was incorrectly written to disk thus corrupting its content.

Changelog: Remove OpenSSL file BIO operations

Reviewed By: jknoxville

Differential Revision: D36060992

fbshipit-source-id: 21b30582dd0b32c24b8ba001d6993034d92de1da
2022-05-09 05:27:21 -07:00
Lorenzo Blasa
3826f2c8ef Refactor endpoint verification prior to connection
Summary:
^

There's a similar issue/request for Android. So, this change moves the code out from the FlipperKit into Flipper as to be able to reuse it.

Reviewed By: aigoncharov

Differential Revision: D35961745

fbshipit-source-id: aa255db582a7852dc06c2feaba389d1dac3b0f67
2022-05-03 11:24:17 -07:00
Lorenzo Blasa
c72088a137 Execute third-party prepare before configureCMake
Summary:
^

:android:third-party task, on ocassions, gets parallelised with the :configureCMake task resulting in build errors.

:configureCMake depends on dependencies being in-place and patched.

Our current setup was achieving this via setting these tasks as dependencies of the preBuild task.

Unfortunately, this seems not be a bullet-proof solution.

This patch aims to improve this situation by ensuring the tasks are executed before :configureCMake. Whatever happens first.

Changelog: Execute :third-party:prepare before :configureCMake task on Android

Reviewed By: passy

Differential Revision: D36001637

fbshipit-source-id: 6c53b6852e40e354337c0ac940b5bbad4ef83078
2022-04-28 06:36:42 -07:00
Lorenzo Blasa
2bafe32f2a Process certificate signing request in the right event loop
Summary:
To ensure that no deadlocks take place, it is important that there are no re-entrant calls from within the callbacks or event handlers.

For the most part, this was already the case. Event and message handlers run critical sections into a Folly event scheduler.

The only exception was the sendExpectResponse used during the certificate exchange. Once the response was received, the non-secure socket was disconnected.
The solution was to put that operation in the Folly event scheduler as it should've been from the beginning.

changelog: Certificate signing request response to be processed on the right event loop.

Reviewed By: fabiomassimo

Differential Revision: D35548148

fbshipit-source-id: cea2476ad66137f376acda66cdbc27801c0c47e1
2022-04-12 02:30:02 -07:00
Lorenzo Blasa
30becc1ced Remove RSocket
Summary:
^
Changelog: Remove rsocket dependency for Android

Reviewed By: aigoncharov

Differential Revision: D34418565

fbshipit-source-id: d2bfd6cede3c85709e252a8205525aa4595b4791
2022-02-25 02:33:22 -08:00
Lorenzo Blasa
c4f80a826e Remove RSocket
Summary:
^
Changelog: Remove rsocket references from xplat

Reviewed By: aigoncharov

Differential Revision: D34418118

fbshipit-source-id: bd49b9da119e3a2a1ce396d14e0dca73e1b9c692
2022-02-24 23:56:22 -08:00
Lorenzo Blasa
5993a748ba C++ WebSocket
Summary:
^

Changes include:
- C++ WebSocket integration
- Explicit asio namespace usage as namespace asio = websocketpp::lib::asio; this way we don't care if boost::asio or just asio is used in the end.

Reviewed By: javache

Differential Revision: D34388770

fbshipit-source-id: d0b3ee8ac687751ab1b93d483729eb2baccb8687
2022-02-23 04:23:07 -08:00
Lorenzo Blasa
c2f3607d03 remove rsocket fallback for mobile clients
Summary:
^
Note: this is already a working case. The difference is that if we are unable to establish a socket connection, we will not attempt to create one using rsocket.

Changelog: Removes rsocket-fallback for mobile clients

Reviewed By: nikoant

Differential Revision: D33655430

fbshipit-source-id: cb6f752f2d1354ab46d011b1f19c89520e1e7dd3
2022-02-15 04:56:17 -08:00
Lorenzo Blasa
c8c40bca17 openssl from prefab (google) (#3429)
Summary:
Pull Request resolved: https://github.com/facebook/flipper/pull/3429

Instead of downloading/patching/building openssl, use the prefab package prepared by Google.

changelog: Consume openssl from maven (prefab)

Reviewed By: passy

Differential Revision: D34143349

fbshipit-source-id: 0ca92be6628b1b27a59b3e4ad8278cf8fef6d5d8
2022-02-11 02:59:45 -08:00
Lorenzo Blasa
b3cf7e1ad1 C++ WebSocket client for Flipper
Summary:
Introducing a Flipper WebSocket client implemented in C++.
The requirement came from Spark AR (Skylight) as they have a macOS/Linux/Windows clients.

For reviewers:

- This is an implementation of the existing FlipperSocket interface. Effectively, the only type that needs to be reviewed is WebSocketTLSClient.

- BaseClient defined a base class for WebSocketClient and WebSocketTLSClient.

- WebSocketClient is a simplified version of WebSocketTLSClient as there's no TLS configuration.

Reviewed By: mweststrate

Differential Revision: D34081943

fbshipit-source-id: 619a83f5a6783a21069d0f5111d139bb180f9e97
2022-02-10 05:25:56 -08:00
Lorenzo Blasa
37b87b7653 ConnectionContextStore to expose API to retrieve store items path
Summary:
^

This change allow callers to retrieve the path of different store items some of which are used for connection authentication.

Reviewed By: aigoncharov

Differential Revision: D34081942

fbshipit-source-id: c6b8d3590993de6c48a36266a5c16f2caf9f5a93
2022-02-09 06:31:14 -08:00
Lorenzo Blasa
c793549d84 Release client if exists before establishing a connection
Summary:
The problem seems to be exclusive to Android emulators.

Bringing the emulator ON from a warm state brings the app from is previous state before shutting down. At this point there may have been a Flipper connection.

Flipper tries to connect and does so successfully replacing the previously, now dead connection.

The problem is that replacing that connection triggers the reconnect cycle as Flipper thinks the connection is dead and hence the reconnect loop initiates.
Changelog: Release an existing client before attempting a secure connection

Reviewed By: passy

Differential Revision: D34080726

fbshipit-source-id: 8185adb492dd4d9255fcea5874ca2e5b7fee0c84
2022-02-08 12:40:39 -08:00
Lorenzo Blasa
fd3d4d4efb Catch receiver errors
Summary:
This change adds a safety net for receivers that may throw an exception on invocation.

Without this change, the exception is logged but not shown in Flipper.

Reviewed By: nikoant

Differential Revision: D34001224

fbshipit-source-id: ca07d3dd006b277e306ecbc1c033845929a83f4c
2022-02-04 07:05:44 -08:00
Andrey Goncharov
a474a0c2f2 Fallback to RSockets if cert exchange fails
Summary:
Changelog: Fallback to RSockets if cert exchange fails even if the connection was successful.

Currently, we fallback to RSockets only if the connected is rejected.
It turns out that WS server does not reject connection from our RSocket client. Instead, it rejects the messages.
As result, requestSignedCertFromFlipper fails, but it never triggers the change of protocol.
With this diff we start evaluating if we need to change our protocol for every socket error.

Reviewed By: lblasa

Differential Revision: D33890235

fbshipit-source-id: f79b5c6992f01f8a93e0793e180a5cbd4a105619
2022-01-31 08:56:22 -08:00
Lorenzo Blasa
76a9b3d3ae Remove sendLegacyCertificateRequest
Summary:
Legacy certificate request is most likely deadcode by now. Remove it.

Changelog: Remove legacy certificate request

Reviewed By: antonk52

Differential Revision: D33707396

fbshipit-source-id: 47a410204bcd2ed843b716461df105ebc48264a6
2022-01-21 07:29:20 -08:00
Lorenzo Blasa
7579b81b97 Add a bit of defence checks as to minimise exception likelihood when no medium is known
Summary:
^

Changelog: Minimises the probability of throwing an exception if no exchange medium is known

Reviewed By: passy

Differential Revision: D33620655

fbshipit-source-id: e03e7fed0607c376add218ee98dcd2bd0f8880f1
2022-01-17 09:57:47 -08:00
Lorenzo Blasa
f3b0c82f4d Remove custom url_encode in favour of folly uri encode
Summary:
^
There was an issue for some characters in newer android API versions. One option was to fix our implementation, which can be done by casting to char to unsigned int before using std::isalnum.

But, do not duplicate existing functionality, used a solid implementation instead.

Changelog: Fixes an issue whereas the url encoding was incorrect for UTF-8

Reviewed By: mweststrate

Differential Revision: D33405760

fbshipit-source-id: e1c0a4da3dceb27e923b26d0ebfac091febeceb3
2022-01-05 04:06:43 -08:00