diff --git a/.github/workflows/webrtc-build.yml b/.github/workflows/webrtc-build.yml new file mode 100644 index 0000000000..ff6315f539 --- /dev/null +++ b/.github/workflows/webrtc-build.yml @@ -0,0 +1,69 @@ +name: WebRTC Build +run-name: WebRTC Build + +env: + XCODE_VERSION: 26.6 + WEBRTC_BRANCH: branch-heads/8078 + +on: + workflow_dispatch: + inputs: + branch: + description: "webRTC branch head. Ex: branch-heads/6167" + required: true + default: "branch-heads/8078" + type: string + ios: + description: "Build iOS libraries" + required: true + default: true + type: boolean + macos: + description: "Build macOS libraries" + required: true + default: false + type: boolean + maccatalyst: + description: "Build macOS Catalyst libraries" + required: true + default: false + type: boolean + push: + paths: + - ".github/workflows/webrtc-build.yml" + - "patches/**" + - "scripts/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + Build-Script: + runs-on: macos-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Check out repository code + uses: actions/checkout@v6 + # WebRTC milestone branches ship with frozen Chromium buildtools that + # only support the Xcode/SDK version current at the time of the branch + # cut. Newer Xcode versions introduce SDK framework splits (e.g. + # DarwinFoundation, UIUtilities) that break the build. Pin to the + # version expected by the buildtools (see build/mac_toolchain.py in + # the WebRTC source for each milestone). + - name: Setup Xcode + run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app + + - name: Building WebRTC framework + run: | + sh scripts/clean.sh + sh scripts/build.sh + env: + BRANCH: ${{ inputs.branch || env.WEBRTC_BRANCH }} + IOS: ${{ github.event_name == 'push' || inputs.ios }} + MACOS: ${{ github.event_name == 'workflow_dispatch' && inputs.macos }} + MAC_CATALYST: ${{ github.event_name == 'workflow_dispatch' && inputs.maccatalyst }} + - name: Upload framework + uses: actions/upload-artifact@v6 + with: + name: WebRTC.xcframework + path: out/WebRTC-*.xcframework.zip diff --git a/.github/workflows/webrtc-release.yml b/.github/workflows/webrtc-release.yml index 69f949938e..743e22afe9 100644 --- a/.github/workflows/webrtc-release.yml +++ b/.github/workflows/webrtc-release.yml @@ -1,6 +1,11 @@ name: WebRTC Release run-name: WebRTC Release -on: workflow_dispatch + +env: + XCODE_VERSION: 26.6 + +on: + workflow_dispatch: jobs: Build-Script: runs-on: macos-latest @@ -8,14 +13,28 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Check out repository code - uses: actions/checkout@v3 + uses: actions/checkout@v6 + # WebRTC milestone branches ship with frozen Chromium buildtools that + # only support the Xcode/SDK version current at the time of the branch + # cut. Newer Xcode versions introduce SDK framework splits (e.g. + # DarwinFoundation, UIUtilities) that break the build. Pin to the + # version expected by the buildtools (see build/mac_toolchain.py in + # the WebRTC source for each milestone). + # + # To find which Xcode version is available by which runner: + # https://github.com/actions/runner-images/tree/main/images/macos + - name: Setup Xcode + run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app + - name: Set up Python 3.x - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: "3.x" - - name: Running WebRTC weekly release checks + + - name: Install dependencies + run: pip install requests + + - name: Running WebRTC release checks run: | - sh clean.sh - pip install requests python-telegram-bot + sh scripts/clean.sh python -u scripts/release.py - - run: echo "🍏 This job's status is ${{ job.status }}." diff --git a/.gitignore b/.gitignore index da97b34fbe..20c75c1a8e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ .DS_Store .gclient .gclient_entries +.gcs_entries depot_tools src .cipd -.gclient_previous_sync_commits \ No newline at end of file +.gclient_previous_sync_commits +third_party +.build +out \ No newline at end of file diff --git a/README.md b/README.md index f9c028a2e1..7582066211 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # WebRTC Binaries for iOS and macOS [![Latest version](https://img.shields.io/github/v/release/stasel/webrtc)](https://github.com/stasel/WebRTC/releases) +[![Cocoapods](https://img.shields.io/cocoapods/v/WebRTC-lib)](https://cocoapods.org/pods/WebRTC-lib) [![Release Date](https://img.shields.io/github/release-date/stasel/webrtc)](https://github.com/stasel/WebRTC/releases) +[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/stasel/WebRTC/webrtc-release.yml?branch=latest&label=Release%20job)](https://github.com/stasel/WebRTC/actions/workflows/webrtc-release.yml) [![Total Downloads](https://img.shields.io/github/downloads/stasel/webrtc/total)](https://github.com/stasel/WebRTC/releases) -[![Cocoapods](https://img.shields.io/cocoapods/v/WebRTC-lib)](https://cocoapods.org/pods/WebRTC-lib) -This repository contains unofficial distribution of WebRTC framework binaries for iOS and macOS. +This repository contains a community-driven distribution of WebRTC framework binaries for iOS and macOS. Since version M80, Google has [deprecated](https://groups.google.com/g/discuss-webrtc/c/Ozvbd0p7Q1Y/m/M4WN2cRKCwAJ?pli=1) their mobile binary libraries distributions (Was officially using the [GoogleWebRTC pod](https://cocoapods.org/pods/GoogleWebRTC)). To get the most up to date WebRTC library, you can compile it on your own, or you can use precompiled binaries from here or other sources. @@ -13,13 +14,17 @@ Since version M80, Google has [deprecated](https://groups.google.com/g/discuss-w The binary releases correspond with official Chromium releases and branches as specified in the [Chromium dashboard](https://chromiumdash.appspot.com/branches). ## 💡 Things to know -* All binaries in this repository are compiled from the official WebRTC [source code](https://webrtc.googlesource.com/src/) without any modifications to the source code or to the output binaries. +* All binaries in this repository are compiled from the official WebRTC [source code](https://webrtc.googlesource.com/src/) . +* No modifications are made to the source code or the output binaries. +* The build process is open source using GitHub actions. * Dynamic framework (xcframework format) which contains multiple binaries for macOS and iOS. -* Since [Xcode 14](https://developer.apple.com/documentation/Xcode-Release-Notes/xcode-14-release-notes), bitcode is deprecated. Version M103 and above does not include bitcode. +* Added support for extra encodings: VP9, H264 and AV1. +* H.265 / HEVC: the SDP negotiation and RTP packetization layer is compiled but WebRTC ships no HEVC encoder or decoder for Apple platforms. +* dSYM files are included in case you need them. You can download them from the [releases page](https://github.com/stasel/WebRTC/releases). *(Available from version M152).* ## 📢 Requirements -* iOS 12+ -* macOS 10.11+ +* iOS 15+ +* macOS 12+ * macOS Catalyst 11.0+ ## 📀 Binaries included @@ -40,7 +45,7 @@ Xcode has a built-in support for Swift package manager. You can easily add the p Or, you can add the following dependency to your `Package.swift` file: ```swift dependencies: [ - .package(url: "https://github.com/stasel/WebRTC.git", .upToNextMajor("110.0.0")) + .package(url: "https://github.com/stasel/WebRTC.git", .upToNextMajor("155.0.0")) ] ``` @@ -65,7 +70,6 @@ pod install Read more about Cocoapods: https://cocoapods.org ### Carthage -**Requires Carthage version 0.38 or higher** Add the following dependency to the `Cartfile` in your project: ``` @@ -96,10 +100,10 @@ If you wish to see how to use WebRTC I highly recommend checking out my WebRTC d ## 🛠 Compile your own WebRTC Frameworks If you wish to compile your own WebRTC binary framework, please refer to the following official guide: -https://webrtc.googlesource.com/src/+/refs/heads/master/docs/native-code/ios/index.md +https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/ios/README.md You can also take a look at the [build script](scripts/build.sh) I created for more details. -## 📃 License -* BSD 3-Clause License +## 📃 Licenses +* Build scripts (this repository): `BSD 3-Clause License` * WebRTC License: https://webrtc.org/support/license diff --git a/clean.sh b/clean.sh deleted file mode 100644 index a3e8d0b309..0000000000 --- a/clean.sh +++ /dev/null @@ -1 +0,0 @@ -rm -rf .cipd .gclient* depot_tools src diff --git a/patches/0001-Frame-encryption-in-non-native-API.patch b/patches/0001-Frame-encryption-in-non-native-API.patch index a40316015f..634a548416 100644 --- a/patches/0001-Frame-encryption-in-non-native-API.patch +++ b/patches/0001-Frame-encryption-in-non-native-API.patch @@ -1,68 +1,57 @@ -From dfe7805d74315177850ec46b5fe8e22b6a30bc39 Mon Sep 17 00:00:00 2001 -From: Stanislav Dmitrenko -Date: Tue, 28 Feb 2023 00:25:34 +0300 -Subject: [PATCH] Frame encryption in non-native API - ability to add encryptor - (to RTCRtpSender) and decryptor (to RTCRtpReceiver) from Swift/Objective-C - code - ---- - sdk/BUILD.gn | 10 ++ - .../RTCFrameDecryptor+Private.h | 50 +++++++++ - .../api/peerconnection/RTCFrameDecryptor.h | 45 ++++++++ - .../api/peerconnection/RTCFrameDecryptor.mm | 98 +++++++++++++++++ - .../RTCFrameEncryptor+Private.h | 51 +++++++++ - .../api/peerconnection/RTCFrameEncryptor.h | 45 ++++++++ - .../api/peerconnection/RTCFrameEncryptor.mm | 100 ++++++++++++++++++ - sdk/objc/api/peerconnection/RTCRtpReceiver.h | 2 + - sdk/objc/api/peerconnection/RTCRtpReceiver.mm | 9 ++ - sdk/objc/api/peerconnection/RTCRtpSender.h | 3 + - sdk/objc/api/peerconnection/RTCRtpSender.mm | 8 ++ - 11 files changed, 421 insertions(+) - create mode 100644 sdk/objc/api/peerconnection/RTCFrameDecryptor+Private.h - create mode 100644 sdk/objc/api/peerconnection/RTCFrameDecryptor.h - create mode 100644 sdk/objc/api/peerconnection/RTCFrameDecryptor.mm - create mode 100644 sdk/objc/api/peerconnection/RTCFrameEncryptor+Private.h - create mode 100644 sdk/objc/api/peerconnection/RTCFrameEncryptor.h - create mode 100644 sdk/objc/api/peerconnection/RTCFrameEncryptor.mm - diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn -index 971a323ef5..a9605a4533 100644 +index aeb65e1..5e51ce7 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn -@@ -956,6 +956,12 @@ if (is_ios || is_mac) { +@@ -1000,6 +1000,12 @@ if (is_ios || is_mac) { "objc/api/peerconnection/RTCDtmfSender.mm", "objc/api/peerconnection/RTCFieldTrials.h", "objc/api/peerconnection/RTCFieldTrials.mm", -+ "objc/api/peerconnection/RTCFrameEncryptor+Private.h", -+ "objc/api/peerconnection/RTCFrameEncryptor.h", -+ "objc/api/peerconnection/RTCFrameEncryptor.mm", + "objc/api/peerconnection/RTCFrameDecryptor+Private.h", + "objc/api/peerconnection/RTCFrameDecryptor.h", + "objc/api/peerconnection/RTCFrameDecryptor.mm", ++ "objc/api/peerconnection/RTCFrameEncryptor+Private.h", ++ "objc/api/peerconnection/RTCFrameEncryptor.h", ++ "objc/api/peerconnection/RTCFrameEncryptor.mm", "objc/api/peerconnection/RTCIceCandidate+Private.h", "objc/api/peerconnection/RTCIceCandidate.h", "objc/api/peerconnection/RTCIceCandidate.mm", -@@ -1302,6 +1308,8 @@ if (is_ios || is_mac) { +@@ -1120,6 +1126,7 @@ if (is_ios || is_mac) { + "../api:field_trials_view", + "../api:jsep", + "../api:legacy_stats_types", ++ "../api:make_ref_counted", + "../api:media_stream_interface", + "../api:peer_connection_interface", + "../api:rtc_event_log_output_file", +@@ -1197,6 +1204,7 @@ if (is_ios || is_mac) { + "objc/unittests/RTCDefaultVideoEncoderFactory_xctest.mm", + "objc/unittests/RTCEncodedImage_xctest.mm", + "objc/unittests/RTCFileVideoCapturer_xctest.mm", ++ "objc/unittests/RTCFrameEncryption_xctest.mm", + "objc/unittests/RTCH264ProfileLevelId_xctest.m", + "objc/unittests/RTCIceCandidateTest.mm", + "objc/unittests/RTCIceServerTest.mm", +@@ -1375,6 +1383,8 @@ if (is_ios || is_mac) { "objc/api/peerconnection/RTCDataChannel.h", "objc/api/peerconnection/RTCDataChannelConfiguration.h", "objc/api/peerconnection/RTCFieldTrials.h", -+ "objc/api/peerconnection/RTCFrameEncryptor.h", + "objc/api/peerconnection/RTCFrameDecryptor.h", ++ "objc/api/peerconnection/RTCFrameEncryptor.h", "objc/api/peerconnection/RTCIceCandidate.h", "objc/api/peerconnection/RTCIceCandidateErrorEvent.h", "objc/api/peerconnection/RTCIceServer.h", -@@ -1419,6 +1427,8 @@ if (is_ios || is_mac) { - "objc/api/peerconnection/RTCDataChannelConfiguration.h", +@@ -1495,6 +1505,8 @@ if (is_ios || is_mac) { + "objc/api/peerconnection/RTCDtlsFingerprint.h", "objc/api/peerconnection/RTCDtmfSender.h", "objc/api/peerconnection/RTCFieldTrials.h", -+ "objc/api/peerconnection/RTCFrameEncryptor.h", + "objc/api/peerconnection/RTCFrameDecryptor.h", ++ "objc/api/peerconnection/RTCFrameEncryptor.h", "objc/api/peerconnection/RTCIceCandidate.h", "objc/api/peerconnection/RTCIceCandidateErrorEvent.h", "objc/api/peerconnection/RTCIceServer.h", diff --git a/sdk/objc/api/peerconnection/RTCFrameDecryptor+Private.h b/sdk/objc/api/peerconnection/RTCFrameDecryptor+Private.h new file mode 100644 -index 0000000000..dd59d1722f +index 0000000..522401c --- /dev/null +++ b/sdk/objc/api/peerconnection/RTCFrameDecryptor+Private.h @@ -0,0 +1,50 @@ @@ -79,49 +68,49 @@ index 0000000000..dd59d1722f +#import "RTCFrameDecryptor.h" + +#include "api/crypto/frame_decryptor_interface.h" ++#include "api/scoped_refptr.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace webrtc { + +class RTCFrameDecryptorAdapter : public FrameDecryptorInterface { -+ public: -+ RTCFrameDecryptorAdapter(RTC_OBJC_TYPE(RTCFrameDecryptor) * decryptor, size_t sizeChange); -+ Result Decrypt(cricket::MediaType media_type, -+ const std::vector& csrcs, -+ rtc::ArrayView additional_data, -+ rtc::ArrayView encrypted_frame, -+ rtc::ArrayView frame) override; -+ -+ size_t GetMaxPlaintextByteSize(cricket::MediaType media_type, -+ size_t encrypted_frame_size) override; -+ -+ void AddRef() const override {} -+ rtc::RefCountReleaseStatus Release() const override { -+ return rtc::RefCountReleaseStatus::kDroppedLastRef; -+ } -+ -+ private: -+ __weak RTC_OBJC_TYPE(RTCFrameDecryptor) * decryptor_; -+ size_t sizeChange_; -+ }; ++ public: ++ RTCFrameDecryptorAdapter(RTC_OBJC_TYPE(RTCFrameDecryptor) * decryptor, ++ int size_change); ++ ++ Result Decrypt(MediaType media_type, ++ const std::vector& csrcs, ++ std::span additional_data, ++ std::span encrypted_frame, ++ std::span frame) override; ++ ++ size_t GetMaxPlaintextByteSize(MediaType media_type, ++ size_t encrypted_frame_size) override; ++ ++ private: ++ __weak RTC_OBJC_TYPE(RTCFrameDecryptor) * decryptor_; ++ const int size_change_; ++}; + +} // namespace webrtc + +@interface RTC_OBJC_TYPE (RTCFrameDecryptor) +() + -+@property(nonatomic, readonly) rtc::scoped_refptr nativeFrameDecryptor; ++ @property(nonatomic, readonly) ++ webrtc::scoped_refptr ++ nativeFrameDecryptor; + +@end + +NS_ASSUME_NONNULL_END diff --git a/sdk/objc/api/peerconnection/RTCFrameDecryptor.h b/sdk/objc/api/peerconnection/RTCFrameDecryptor.h new file mode 100644 -index 0000000000..3cb28fba79 +index 0000000..c7fc1d3 --- /dev/null +++ b/sdk/objc/api/peerconnection/RTCFrameDecryptor.h -@@ -0,0 +1,45 @@ +@@ -0,0 +1,52 @@ +/* + * Copyright 2023 The WebRTC project authors. All Rights Reserved. + * @@ -133,7 +122,8 @@ index 0000000000..3cb28fba79 + */ + +#import -+#import "RTCMacros.h" ++ ++#import "sdk/objc/base/RTCMacros.h" + +typedef NS_ENUM(NSInteger, RTCRtpMediaType); + @@ -145,34 +135,40 @@ index 0000000000..3cb28fba79 +@protocol RTC_OBJC_TYPE +(RTCFrameDecryptorDelegate) + -+/** Receives decryption events, should answer with decrypted data per frame */ -+- (nullable NSData *)frameDecryptor: (RTC_OBJC_TYPE (RTCFrameDecryptor) *)decryptor mediaType: (RTCRtpMediaType)mediaType withFrame: (NSData *)encrypted; ++ /** Called on a WebRTC decoder thread. `encrypted` borrows WebRTC's ++ * buffer and is valid only during the call. Returns the decrypted ++ * frame, or nil to drop it. */ ++ - (nullable NSData *)frameDecryptor ++ : (RTC_OBJC_TYPE(RTCFrameDecryptor) *)decryptor mediaType ++ : (RTCRtpMediaType)mediaType withFrame : (NSData *)encrypted; + +@end + -+ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCFrameDecryptor) + -+@property(nonatomic, weak) id delegate; ++ @property(nonatomic, weak) id ++ delegate; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCFrameDecryptor) : NSObject ++ +/** Size change of frame after decryption (in bytes). Can be negative */ -+- (instancetype)initWithSizeChange: (int) sizeChange NS_DESIGNATED_INITIALIZER; ++- (instancetype)initWithSizeChange:(int)sizeChange NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; ++ +@end + +NS_ASSUME_NONNULL_END diff --git a/sdk/objc/api/peerconnection/RTCFrameDecryptor.mm b/sdk/objc/api/peerconnection/RTCFrameDecryptor.mm new file mode 100644 -index 0000000000..a039a29094 +index 0000000..f0aafd5 --- /dev/null +++ b/sdk/objc/api/peerconnection/RTCFrameDecryptor.mm -@@ -0,0 +1,98 @@ +@@ -0,0 +1,111 @@ +/* + * Copyright 2023 The WebRTC project authors. All Rights Reserved. + * @@ -183,65 +179,72 @@ index 0000000000..a039a29094 + * be found in the AUTHORS file in the root of the source tree. + */ + -+#import +#import "RTCFrameDecryptor+Private.h" ++ ++#import "RTCRtpReceiver+Private.h" +#import "base/RTCLogging.h" + ++#include ++ ++#include "api/make_ref_counted.h" +#include "rtc_base/checks.h" -+#include "api/crypto/frame_decryptor_interface.h" -+#include "RTCRtpReceiver+Private.h" + +namespace webrtc { -+RTCFrameDecryptorAdapter::RTCFrameDecryptorAdapter(RTC_OBJC_TYPE(RTCFrameDecryptor) * decryptor, size_t sizeChange) { ++ ++RTCFrameDecryptorAdapter::RTCFrameDecryptorAdapter( ++ RTC_OBJC_TYPE(RTCFrameDecryptor) * decryptor, int size_change) ++ : decryptor_(decryptor), size_change_(size_change) { + RTC_CHECK(decryptor); -+ decryptor_ = decryptor; -+ sizeChange_ = sizeChange; +} + +RTCFrameDecryptorAdapter::Result RTCFrameDecryptorAdapter::Decrypt( -+ cricket::MediaType media_type, -+ const std::vector& csrcs, -+ rtc::ArrayView additional_data, -+ rtc::ArrayView encrypted_frame, -+ rtc::ArrayView frame) { -+ RTC_OBJC_TYPE(RTCFrameDecryptor) *decryptor = decryptor_; -+ NSData *encrypted = [NSData dataWithBytes: encrypted_frame.data() length: encrypted_frame.size()]; -+ NSData *decrypted = [decryptor.delegate frameDecryptor:decryptor mediaType:[RTC_OBJC_TYPE(RTCRtpReceiver) mediaTypeForNativeMediaType: media_type] withFrame:encrypted]; -+ if (decrypted) { -+ const char * decryptedBytes = (const char *)[decrypted bytes]; -+ NSUInteger size = [decrypted length]; -+ for (size_t i = 0; i < size; i++) { -+ frame[i] = decryptedBytes[i]; ++ MediaType media_type, ++ const std::vector &csrcs, ++ std::span additional_data, ++ std::span encrypted_frame, ++ std::span frame) { ++ // Releases per-frame objects here rather than in the calling thread's pool. ++ @autoreleasepool { ++ RTC_OBJC_TYPE(RTCFrameDecryptor) *decryptor = decryptor_; ++ NSData *encrypted = [NSData ++ dataWithBytesNoCopy:const_cast(encrypted_frame.data()) ++ length:encrypted_frame.size() ++ freeWhenDone:NO]; ++ NSData *decrypted = [decryptor.delegate ++ frameDecryptor:decryptor ++ mediaType:[RTC_OBJC_TYPE(RTCRtpReceiver) ++ mediaTypeForNativeMediaType:media_type] ++ withFrame:encrypted]; ++ if (decrypted == nil || decrypted.length > frame.size()) { ++ return Result(Status::kFailedToDecrypt, 0); + } -+ return Result(Status::kOk, size); -+ } else { -+ return Result(Status::kFailedToDecrypt, 0); ++ std::copy_n(static_cast(decrypted.bytes), ++ decrypted.length, ++ frame.data()); ++ return Result(Status::kOk, decrypted.length); + } -+ +} + +size_t RTCFrameDecryptorAdapter::GetMaxPlaintextByteSize( -+ cricket::MediaType media_type, -+ size_t encrypted_frame_size) { -+ return encrypted_frame_size + sizeChange_; ++ MediaType media_type, size_t encrypted_frame_size) { ++ if (size_change_ < 0) { ++ return encrypted_frame_size - ++ std::min(encrypted_frame_size, static_cast(-size_change_)); ++ } ++ return encrypted_frame_size + static_cast(size_change_); +} -+} // namespace webrtc + ++} // namespace webrtc + +@implementation RTC_OBJC_TYPE (RTCFrameDecryptor) { -+ rtc::scoped_refptr _nativeFrameDecryptor; ++ webrtc::scoped_refptr _nativeFrameDecryptor; +} + +@synthesize delegate = _delegate; +@synthesize nativeFrameDecryptor = _nativeFrameDecryptor; + +- (NSString *)description { -+ return [NSString -+ stringWithFormat:@"RTC_OBJC_TYPE(RTCFrameDecryptor)"]; -+} -+ -+- (void)dealloc { -+ _nativeFrameDecryptor = nullptr; ++ return [NSString stringWithFormat:@"RTC_OBJC_TYPE(RTCFrameDecryptor)"]; +} + +- (BOOL)isEqual:(id)object { @@ -254,7 +257,8 @@ index 0000000000..a039a29094 + if (![object isMemberOfClass:[self class]]) { + return NO; + } -+ RTC_OBJC_TYPE(RTCFrameDecryptor) *decryptor = (RTC_OBJC_TYPE(RTCFrameDecryptor) *)object; ++ RTC_OBJC_TYPE(RTCFrameDecryptor) *decryptor = ++ (RTC_OBJC_TYPE(RTCFrameDecryptor) *)object; + return _nativeFrameDecryptor == decryptor.nativeFrameDecryptor; +} + @@ -263,9 +267,14 @@ index 0000000000..a039a29094 +} + +- (instancetype)initWithSizeChange:(int)sizeChange { -+ if (self = [super init]) { -+ RTCLogInfo(@"RTC_OBJC_TYPE(RTCFrameDecryptor)(%p): created decryptor: %@", self, self.description); -+ _nativeFrameDecryptor = new webrtc::RTCFrameDecryptorAdapter(self, sizeChange); ++ self = [super init]; ++ if (self) { ++ RTCLogInfo(@"RTC_OBJC_TYPE(RTCFrameDecryptor)(%p): created decryptor: %@", ++ self, ++ self.description); ++ _nativeFrameDecryptor = ++ webrtc::make_ref_counted(self, ++ sizeChange); + } + return self; +} @@ -273,10 +282,10 @@ index 0000000000..a039a29094 +@end diff --git a/sdk/objc/api/peerconnection/RTCFrameEncryptor+Private.h b/sdk/objc/api/peerconnection/RTCFrameEncryptor+Private.h new file mode 100644 -index 0000000000..821761e5fa +index 0000000..6493b22 --- /dev/null +++ b/sdk/objc/api/peerconnection/RTCFrameEncryptor+Private.h -@@ -0,0 +1,51 @@ +@@ -0,0 +1,53 @@ +/* + * Copyright 2023 The WebRTC project authors. All Rights Reserved. + * @@ -290,50 +299,52 @@ index 0000000000..821761e5fa +#import "RTCFrameEncryptor.h" + +#include "api/crypto/frame_encryptor_interface.h" ++#include "api/scoped_refptr.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace webrtc { + +class RTCFrameEncryptorAdapter : public FrameEncryptorInterface { -+ public: -+ RTCFrameEncryptorAdapter(RTC_OBJC_TYPE(RTCFrameEncryptor) * encryptor, size_t sizeChange); -+ int Encrypt(cricket::MediaType media_type, -+ uint32_t ssrc, -+ rtc::ArrayView additional_data, -+ rtc::ArrayView frame, -+ rtc::ArrayView encrypted_frame, -+ size_t* bytes_written) override; -+ -+ size_t GetMaxCiphertextByteSize(cricket::MediaType media_type, -+ size_t frame_size) override; -+ -+ void AddRef() const override {} -+ rtc::RefCountReleaseStatus Release() const override { -+ return rtc::RefCountReleaseStatus::kDroppedLastRef; -+ } -+ -+ private: -+ __weak RTC_OBJC_TYPE(RTCFrameEncryptor) * encryptor_; -+ size_t sizeChange_; -+ }; ++ public: ++ static constexpr int kEncryptionFailed = 1; ++ ++ RTCFrameEncryptorAdapter(RTC_OBJC_TYPE(RTCFrameEncryptor) * encryptor, ++ int size_change); ++ ++ int Encrypt(MediaType media_type, ++ uint32_t ssrc, ++ std::span additional_data, ++ std::span frame, ++ std::span encrypted_frame, ++ size_t* bytes_written) override; ++ ++ size_t GetMaxCiphertextByteSize(MediaType media_type, ++ size_t frame_size) override; ++ ++ private: ++ __weak RTC_OBJC_TYPE(RTCFrameEncryptor) * encryptor_; ++ const int size_change_; ++}; + +} // namespace webrtc + +@interface RTC_OBJC_TYPE (RTCFrameEncryptor) +() + -+@property(nonatomic, readonly) rtc::scoped_refptr nativeFrameEncryptor; ++ @property(nonatomic, readonly) ++ webrtc::scoped_refptr ++ nativeFrameEncryptor; + +@end + +NS_ASSUME_NONNULL_END diff --git a/sdk/objc/api/peerconnection/RTCFrameEncryptor.h b/sdk/objc/api/peerconnection/RTCFrameEncryptor.h new file mode 100644 -index 0000000000..445649410f +index 0000000..f062dc7 --- /dev/null +++ b/sdk/objc/api/peerconnection/RTCFrameEncryptor.h -@@ -0,0 +1,45 @@ +@@ -0,0 +1,52 @@ +/* + * Copyright 2023 The WebRTC project authors. All Rights Reserved. + * @@ -345,7 +356,8 @@ index 0000000000..445649410f + */ + +#import -+#import "RTCMacros.h" ++ ++#import "sdk/objc/base/RTCMacros.h" + +typedef NS_ENUM(NSInteger, RTCRtpMediaType); + @@ -357,34 +369,40 @@ index 0000000000..445649410f +@protocol RTC_OBJC_TYPE +(RTCFrameEncryptorDelegate) + -+/** Receives encryption events, should answer with encrypted data per frame */ -+- (nullable NSData *)frameEncryptor: (RTC_OBJC_TYPE (RTCFrameEncryptor) *)encryptor mediaType: (RTCRtpMediaType)mediaType withFrame: (NSData *)unencrypted; ++ /** Called on a WebRTC encoder thread. `unencrypted` borrows WebRTC's ++ * buffer and is valid only during the call. Returns the encrypted ++ * frame, or nil to drop it. */ ++ - (nullable NSData *)frameEncryptor ++ : (RTC_OBJC_TYPE(RTCFrameEncryptor) *)encryptor mediaType ++ : (RTCRtpMediaType)mediaType withFrame : (NSData *)unencrypted; + +@end + -+ +RTC_OBJC_EXPORT +@protocol RTC_OBJC_TYPE +(RTCFrameEncryptor) + -+@property(nonatomic, weak) id delegate; ++ @property(nonatomic, weak) id ++ delegate; + +@end + +RTC_OBJC_EXPORT +@interface RTC_OBJC_TYPE (RTCFrameEncryptor) : NSObject ++ +/** Size change of frame after encryption (in bytes). Can be negative */ -+- (instancetype)initWithSizeChange: (int) sizeChange NS_DESIGNATED_INITIALIZER; ++- (instancetype)initWithSizeChange:(int)sizeChange NS_DESIGNATED_INITIALIZER; +- (instancetype)init NS_UNAVAILABLE; ++ +@end + +NS_ASSUME_NONNULL_END diff --git a/sdk/objc/api/peerconnection/RTCFrameEncryptor.mm b/sdk/objc/api/peerconnection/RTCFrameEncryptor.mm new file mode 100644 -index 0000000000..5242859dc1 +index 0000000..1ef3dba --- /dev/null +++ b/sdk/objc/api/peerconnection/RTCFrameEncryptor.mm -@@ -0,0 +1,100 @@ +@@ -0,0 +1,112 @@ +/* + * Copyright 2023 The WebRTC project authors. All Rights Reserved. + * @@ -395,67 +413,73 @@ index 0000000000..5242859dc1 + * be found in the AUTHORS file in the root of the source tree. + */ + -+#import +#import "RTCFrameEncryptor+Private.h" ++ ++#import "RTCRtpReceiver+Private.h" +#import "base/RTCLogging.h" + ++#include ++ ++#include "api/make_ref_counted.h" +#include "rtc_base/checks.h" -+#include "api/crypto/frame_encryptor_interface.h" -+#include "RTCRtpReceiver+Private.h" + +namespace webrtc { -+RTCFrameEncryptorAdapter::RTCFrameEncryptorAdapter(RTC_OBJC_TYPE(RTCFrameEncryptor) * encryptor, size_t sizeChange) { ++ ++RTCFrameEncryptorAdapter::RTCFrameEncryptorAdapter( ++ RTC_OBJC_TYPE(RTCFrameEncryptor) * encryptor, int size_change) ++ : encryptor_(encryptor), size_change_(size_change) { + RTC_CHECK(encryptor); -+ encryptor_ = encryptor; -+ sizeChange_ = sizeChange; -+} -+ -+int RTCFrameEncryptorAdapter::Encrypt( -+ cricket::MediaType media_type, -+ uint32_t ssrc, -+ rtc::ArrayView additional_data, -+ rtc::ArrayView frame, -+ rtc::ArrayView encrypted_frame, -+ size_t* bytes_written) { -+ RTC_OBJC_TYPE(RTCFrameEncryptor) *encryptor = encryptor_; -+ NSData *unencrypted = [NSData dataWithBytes: frame.data() length: frame.size()]; -+ NSData *encrypted = [encryptor.delegate frameEncryptor:encryptor mediaType:[RTC_OBJC_TYPE(RTCRtpReceiver) mediaTypeForNativeMediaType: media_type] withFrame:unencrypted]; -+ if (encrypted) { -+ const char *encryptedBytes = (const char *)[encrypted bytes]; -+ NSUInteger size = [encrypted length]; -+ for (size_t i = 0; i < size; i++) { -+ encrypted_frame[i] = encryptedBytes[i]; ++} ++ ++int RTCFrameEncryptorAdapter::Encrypt(MediaType media_type, ++ uint32_t ssrc, ++ std::span additional_data, ++ std::span frame, ++ std::span encrypted_frame, ++ size_t *bytes_written) { ++ // Releases per-frame objects here rather than in the calling thread's pool. ++ @autoreleasepool { ++ RTC_OBJC_TYPE(RTCFrameEncryptor) *encryptor = encryptor_; ++ NSData *unencrypted = ++ [NSData dataWithBytesNoCopy:const_cast(frame.data()) ++ length:frame.size() ++ freeWhenDone:NO]; ++ NSData *encrypted = [encryptor.delegate ++ frameEncryptor:encryptor ++ mediaType:[RTC_OBJC_TYPE(RTCRtpReceiver) ++ mediaTypeForNativeMediaType:media_type] ++ withFrame:unencrypted]; ++ if (encrypted == nil || encrypted.length > encrypted_frame.size()) { ++ return kEncryptionFailed; + } -+ *bytes_written = size; ++ std::copy_n(static_cast(encrypted.bytes), ++ encrypted.length, ++ encrypted_frame.data()); ++ *bytes_written = encrypted.length; + return 0; -+ } else { -+ return 1; + } -+ +} + -+size_t RTCFrameEncryptorAdapter::GetMaxCiphertextByteSize( -+ cricket::MediaType media_type, -+ size_t frame_size) { -+ return frame_size + sizeChange_; ++size_t RTCFrameEncryptorAdapter::GetMaxCiphertextByteSize(MediaType media_type, ++ size_t frame_size) { ++ if (size_change_ < 0) { ++ return frame_size - ++ std::min(frame_size, static_cast(-size_change_)); ++ } ++ return frame_size + static_cast(size_change_); +} -+} // namespace webrtc + ++} // namespace webrtc + +@implementation RTC_OBJC_TYPE (RTCFrameEncryptor) { -+ rtc::scoped_refptr _nativeFrameEncryptor; ++ webrtc::scoped_refptr _nativeFrameEncryptor; +} + +@synthesize delegate = _delegate; +@synthesize nativeFrameEncryptor = _nativeFrameEncryptor; + +- (NSString *)description { -+ return [NSString -+ stringWithFormat:@"RTC_OBJC_TYPE(RTCFrameEncryptor)"]; -+} -+ -+- (void)dealloc { -+ _nativeFrameEncryptor = nullptr; ++ return [NSString stringWithFormat:@"RTC_OBJC_TYPE(RTCFrameEncryptor)"]; +} + +- (BOOL)isEqual:(id)object { @@ -468,7 +492,8 @@ index 0000000000..5242859dc1 + if (![object isMemberOfClass:[self class]]) { + return NO; + } -+ RTC_OBJC_TYPE(RTCFrameEncryptor) *encryptor = (RTC_OBJC_TYPE(RTCFrameEncryptor) *)object; ++ RTC_OBJC_TYPE(RTCFrameEncryptor) *encryptor = ++ (RTC_OBJC_TYPE(RTCFrameEncryptor) *)object; + return _nativeFrameEncryptor == encryptor.nativeFrameEncryptor; +} + @@ -477,126 +502,416 @@ index 0000000000..5242859dc1 +} + +- (instancetype)initWithSizeChange:(int)sizeChange { -+ if (self = [super init]) { -+ RTCLogInfo(@"RTC_OBJC_TYPE(RTCFrameEncryptor)(%p): created encryptor: %@", self, self.description); -+ _nativeFrameEncryptor = new webrtc::RTCFrameEncryptorAdapter(self, sizeChange); ++ self = [super init]; ++ if (self) { ++ RTCLogInfo(@"RTC_OBJC_TYPE(RTCFrameEncryptor)(%p): created encryptor: %@", ++ self, ++ self.description); ++ _nativeFrameEncryptor = ++ webrtc::make_ref_counted(self, ++ sizeChange); + } + return self; +} + +@end diff --git a/sdk/objc/api/peerconnection/RTCRtpReceiver.h b/sdk/objc/api/peerconnection/RTCRtpReceiver.h -index 1e407fd71b..8adfeae017 100644 +index e2ae27b..0918fba 100644 --- a/sdk/objc/api/peerconnection/RTCRtpReceiver.h +++ b/sdk/objc/api/peerconnection/RTCRtpReceiver.h -@@ -13,6 +13,7 @@ - #import "RTCMacros.h" - #import "RTCMediaStreamTrack.h" - #import "RTCRtpParameters.h" -+#import "RTCFrameDecryptor.h" +@@ -10,6 +10,7 @@ - NS_ASSUME_NONNULL_BEGIN + #import -@@ -74,6 +75,7 @@ RTC_OBJC_EXPORT ++#import "RTCFrameDecryptor.h" + #import "RTCMediaStreamTrack.h" + #import "RTCRtpParameters.h" + #import "sdk/objc/base/RTCMacros.h" +@@ -93,6 +94,8 @@ the current RTCRtpReceiver in the last ten seconds. /** The delegate for this RtpReceiver. */ @property(nonatomic, weak) id delegate; -+- (void)setRtcFrameDecryptor : (RTC_OBJC_TYPE(RTCFrameDecryptor) *)frameDecryptor; ++- (void)setRtcFrameDecryptor:(RTC_OBJC_TYPE(RTCFrameDecryptor) *)frameDecryptor; ++ @end RTC_OBJC_EXPORT diff --git a/sdk/objc/api/peerconnection/RTCRtpReceiver.mm b/sdk/objc/api/peerconnection/RTCRtpReceiver.mm -index 60af86ac1b..6af0563576 100644 +index 19bae4b..50d379d 100644 --- a/sdk/objc/api/peerconnection/RTCRtpReceiver.mm +++ b/sdk/objc/api/peerconnection/RTCRtpReceiver.mm -@@ -15,8 +15,10 @@ - #import "RTCRtpReceiver+Native.h" - #import "base/RTCLogging.h" - #import "helpers/NSString+StdString.h" -+#import "RTCFrameDecryptor+Private.h" - - #include "api/media_stream_interface.h" -+#include "api/crypto/frame_decryptor_interface.h" - - namespace webrtc { - -@@ -39,6 +41,7 @@ @implementation RTC_OBJC_TYPE (RTCRtpReceiver) { - RTC_OBJC_TYPE(RTCPeerConnectionFactory) * _factory; - rtc::scoped_refptr _nativeRtpReceiver; - std::unique_ptr _observer; -+ rtc::scoped_refptr _nativeFrameDecryptor; - } +@@ -10,6 +10,7 @@ - @synthesize delegate = _delegate; -@@ -70,6 +73,7 @@ - (NSString *)description { - - (void)dealloc { - if (_nativeRtpReceiver) { - _nativeRtpReceiver->SetObserver(nullptr); -+ _nativeFrameDecryptor = nullptr; - } - } + #import "RTCRtpReceiver+Private.h" -@@ -91,6 +95,11 @@ - (NSUInteger)hash { ++#import "RTCFrameDecryptor+Private.h" + #import "RTCMediaStreamTrack+Private.h" + #import "RTCRtpParameters+Private.h" + #import "RTCRtpReceiver+Native.h" +@@ -124,6 +125,11 @@ void RtpReceiverDelegateAdapter::OnFirstPacketReceivedAfterReceptiveChange( return (NSUInteger)_nativeRtpReceiver.get(); } -+- (void)setRtcFrameDecryptor:(RTC_OBJC_TYPE(RTCFrameDecryptor) *)frameDecryptor { -+ _nativeFrameDecryptor = frameDecryptor.nativeFrameDecryptor; -+ _nativeRtpReceiver->SetFrameDecryptor(_nativeFrameDecryptor); ++- (void)setRtcFrameDecryptor: ++ (RTC_OBJC_TYPE(RTCFrameDecryptor) *)frameDecryptor { ++ [self setFrameDecryptor:frameDecryptor.nativeFrameDecryptor]; +} + #pragma mark - Native - - (void)setFrameDecryptor:(rtc::scoped_refptr)frameDecryptor { + - (void)setFrameDecryptor: diff --git a/sdk/objc/api/peerconnection/RTCRtpSender.h b/sdk/objc/api/peerconnection/RTCRtpSender.h -index fcdf199869..e365890716 100644 +index 3609b67..fd7a498 100644 --- a/sdk/objc/api/peerconnection/RTCRtpSender.h +++ b/sdk/objc/api/peerconnection/RTCRtpSender.h -@@ -14,6 +14,7 @@ - #import "RTCMacros.h" +@@ -11,6 +11,7 @@ + #import + + #import "RTCDtmfSender.h" ++#import "RTCFrameEncryptor.h" #import "RTCMediaStreamTrack.h" #import "RTCRtpParameters.h" -+#import "RTCFrameEncryptor.h" - - NS_ASSUME_NONNULL_BEGIN + #import "sdk/objc/base/RTCMacros.h" +@@ -43,6 +44,8 @@ RTC_OBJC_EXPORT + @property(nonatomic, readonly, nullable) id + dtmfSender; -@@ -42,6 +43,8 @@ RTC_OBJC_EXPORT - /** The RTCDtmfSender accociated with the RTP sender. */ - @property(nonatomic, readonly, nullable) id dtmfSender; - -+-(void)setRtcFrameEncryptor : (RTC_OBJC_TYPE(RTCFrameEncryptor) *)frameEncryptor; ++- (void)setRtcFrameEncryptor:(RTC_OBJC_TYPE(RTCFrameEncryptor) *)frameEncryptor; + @end RTC_OBJC_EXPORT diff --git a/sdk/objc/api/peerconnection/RTCRtpSender.mm b/sdk/objc/api/peerconnection/RTCRtpSender.mm -index 4fadb30f49..622cb18867 100644 +index f3135c6..b8a2547 100644 --- a/sdk/objc/api/peerconnection/RTCRtpSender.mm +++ b/sdk/objc/api/peerconnection/RTCRtpSender.mm -@@ -16,8 +16,10 @@ - #import "RTCRtpSender+Native.h" - #import "base/RTCLogging.h" - #import "helpers/NSString+StdString.h" -+#import "RTCFrameEncryptor+Private.h" - - #include "api/media_stream_interface.h" -+#include "api/crypto/frame_encryptor_interface.h" +@@ -11,6 +11,7 @@ + #import "RTCRtpSender+Private.h" - @implementation RTC_OBJC_TYPE (RTCRtpSender) { - RTC_OBJC_TYPE(RTCPeerConnectionFactory) * _factory; -@@ -97,6 +99,12 @@ - (NSUInteger)hash { + #import "RTCDtmfSender+Private.h" ++#import "RTCFrameEncryptor+Private.h" + #import "RTCMediaStreamTrack+Private.h" + #import "RTCRtpParameters+Private.h" + #import "RTCRtpSender+Native.h" +@@ -105,6 +106,11 @@ return (NSUInteger)_nativeRtpSender.get(); } -+- (void)setRtcFrameEncryptor:(RTC_OBJC_TYPE(RTCFrameEncryptor) *)frameEncryptor { -+ rtc::scoped_refptr -+ _nativeFrameEncryptor = frameEncryptor.nativeFrameEncryptor; -+ _nativeRtpSender->SetFrameEncryptor(_nativeFrameEncryptor); ++- (void)setRtcFrameEncryptor: ++ (RTC_OBJC_TYPE(RTCFrameEncryptor) *)frameEncryptor { ++ [self setFrameEncryptor:frameEncryptor.nativeFrameEncryptor]; +} + #pragma mark - Native - - (void)setFrameEncryptor:(rtc::scoped_refptr)frameEncryptor { --- -2.37.1 (Apple Git-137.1) - + - (void)setFrameEncryptor: +diff --git a/sdk/objc/unittests/RTCFrameEncryption_xctest.mm b/sdk/objc/unittests/RTCFrameEncryption_xctest.mm +new file mode 100644 +index 0000000..84dc5fc +--- /dev/null ++++ b/sdk/objc/unittests/RTCFrameEncryption_xctest.mm +@@ -0,0 +1,304 @@ ++/* ++ * Copyright 2026 The WebRTC project authors. All Rights Reserved. ++ * ++ * Use of this source code is governed by a BSD-style license ++ * that can be found in the LICENSE file in the root of the source ++ * tree. An additional intellectual property rights grant can be found ++ * in the file PATENTS. All contributing project authors may ++ * be found in the AUTHORS file in the root of the source tree. ++ */ ++ ++#import ++#import ++ ++#include ++#include ++ ++#import "api/peerconnection/RTCConfiguration.h" ++#import "api/peerconnection/RTCFrameDecryptor+Private.h" ++#import "api/peerconnection/RTCFrameEncryptor+Private.h" ++#import "api/peerconnection/RTCMediaConstraints.h" ++#import "api/peerconnection/RTCPeerConnection.h" ++#import "api/peerconnection/RTCPeerConnectionFactory.h" ++#import "api/peerconnection/RTCRtpReceiver+Private.h" ++#import "api/peerconnection/RTCRtpSender+Private.h" ++#import "api/peerconnection/RTCRtpTransceiver.h" ++ ++typedef NSData *_Nullable (^RTCFrameTransform)(NSData *frame); ++ ++@interface RTCFrameTransformDelegate ++ : NSObject ++@property(nonatomic, copy) RTCFrameTransform transform; ++@property(nonatomic, readonly) RTCRtpMediaType lastMediaType; ++@property(nonatomic, readonly) const void *lastFrameBytes; ++ ++- (instancetype)initWithTransform:(RTCFrameTransform)transform; ++ ++@end ++ ++@implementation RTCFrameTransformDelegate ++ ++@synthesize transform = _transform; ++@synthesize lastMediaType = _lastMediaType; ++@synthesize lastFrameBytes = _lastFrameBytes; ++ ++- (instancetype)initWithTransform:(RTCFrameTransform)transform { ++ self = [super init]; ++ if (self) { ++ _transform = transform; ++ _lastMediaType = RTCRtpMediaTypeUnsupported; ++ } ++ return self; ++} ++ ++- (nullable NSData *)frameEncryptor: ++ (RTC_OBJC_TYPE(RTCFrameEncryptor) *)encryptor ++ mediaType:(RTCRtpMediaType)mediaType ++ withFrame:(NSData *)unencrypted { ++ return [self record:mediaType frame:unencrypted]; ++} ++ ++- (nullable NSData *)frameDecryptor: ++ (RTC_OBJC_TYPE(RTCFrameDecryptor) *)decryptor ++ mediaType:(RTCRtpMediaType)mediaType ++ withFrame:(NSData *)encrypted { ++ return [self record:mediaType frame:encrypted]; ++} ++ ++- (nullable NSData *)record:(RTCRtpMediaType)mediaType frame:(NSData *)frame { ++ _lastMediaType = mediaType; ++ _lastFrameBytes = frame.bytes; ++ return _transform(frame); ++} ++ ++@end ++ ++static RTCFrameTransform AppendBytes(NSUInteger count, uint8_t value) { ++ return ^NSData *(NSData *frame) { ++ NSMutableData *result = [frame mutableCopy]; ++ std::vector tail(count, value); ++ [result appendBytes:tail.data() length:tail.size()]; ++ return result; ++ }; ++} ++ ++static RTCFrameTransform DropLastBytes(NSUInteger count) { ++ return ^NSData *(NSData *frame) { ++ return [frame subdataWithRange:NSMakeRange(0, frame.length - count)]; ++ }; ++} ++ ++static constexpr uint8_t kFillByte = 0xAA; ++static constexpr size_t kUnwritten = 12345; ++ ++@interface RTCFrameEncryptionTest : XCTestCase ++@end ++ ++@implementation RTCFrameEncryptionTest ++ ++- (void)testEncryptCopiesDelegateOutputIntoBuffer { ++ RTCFrameTransformDelegate *delegate = [[RTCFrameTransformDelegate alloc] ++ initWithTransform:AppendBytes(4, kFillByte)]; ++ RTC_OBJC_TYPE(RTCFrameEncryptor) *encryptor = ++ [[RTC_OBJC_TYPE(RTCFrameEncryptor) alloc] initWithSizeChange:4]; ++ encryptor.delegate = delegate; ++ webrtc::scoped_refptr native = ++ encryptor.nativeFrameEncryptor; ++ ++ const std::vector frame = {1, 2, 3}; ++ std::vector output( ++ native->GetMaxCiphertextByteSize(webrtc::MediaType::AUDIO, frame.size())); ++ size_t written = kUnwritten; ++ int status = ++ native->Encrypt(webrtc::MediaType::AUDIO, 0, {}, frame, output, &written); ++ ++ XCTAssertEqual(status, 0); ++ XCTAssertEqual(written, 7u); ++ XCTAssertTrue((output == ++ std::vector{ ++ 1, 2, 3, kFillByte, kFillByte, kFillByte, kFillByte})); ++ XCTAssertEqual(delegate.lastMediaType, RTCRtpMediaTypeAudio); ++ XCTAssertTrue(delegate.lastFrameBytes == frame.data(), ++ @"Delegate should receive WebRTC's buffer without a copy"); ++} ++ ++- (void)testEncryptRejectsOutputLargerThanBuffer { ++ RTCFrameTransformDelegate *delegate = [[RTCFrameTransformDelegate alloc] ++ initWithTransform:AppendBytes(8, kFillByte)]; ++ RTC_OBJC_TYPE(RTCFrameEncryptor) *encryptor = ++ [[RTC_OBJC_TYPE(RTCFrameEncryptor) alloc] initWithSizeChange:4]; ++ encryptor.delegate = delegate; ++ webrtc::scoped_refptr native = ++ encryptor.nativeFrameEncryptor; ++ ++ const std::vector frame = {1, 2, 3}; ++ std::vector output( ++ native->GetMaxCiphertextByteSize(webrtc::MediaType::AUDIO, frame.size())); ++ size_t written = kUnwritten; ++ int status = ++ native->Encrypt(webrtc::MediaType::AUDIO, 0, {}, frame, output, &written); ++ ++ XCTAssertEqual(status, webrtc::RTCFrameEncryptorAdapter::kEncryptionFailed); ++ XCTAssertEqual(written, kUnwritten); ++ XCTAssertTrue((output == std::vector(7, 0)), ++ @"Buffer must stay untouched when output does not fit"); ++} ++ ++- (void)testEncryptFailsWithoutDelegate { ++ RTC_OBJC_TYPE(RTCFrameEncryptor) *encryptor = ++ [[RTC_OBJC_TYPE(RTCFrameEncryptor) alloc] initWithSizeChange:4]; ++ webrtc::scoped_refptr native = ++ encryptor.nativeFrameEncryptor; ++ ++ const std::vector frame = {1, 2, 3}; ++ std::vector output(7); ++ size_t written = kUnwritten; ++ int status = ++ native->Encrypt(webrtc::MediaType::VIDEO, 0, {}, frame, output, &written); ++ ++ XCTAssertEqual(status, webrtc::RTCFrameEncryptorAdapter::kEncryptionFailed); ++ XCTAssertEqual(written, kUnwritten); ++} ++ ++- (void)testEncryptFailsAfterEncryptorIsReleased { ++ RTCFrameTransformDelegate *delegate = [[RTCFrameTransformDelegate alloc] ++ initWithTransform:AppendBytes(4, kFillByte)]; ++ webrtc::scoped_refptr native; ++ @autoreleasepool { ++ RTC_OBJC_TYPE(RTCFrameEncryptor) *encryptor = ++ [[RTC_OBJC_TYPE(RTCFrameEncryptor) alloc] initWithSizeChange:4]; ++ encryptor.delegate = delegate; ++ native = encryptor.nativeFrameEncryptor; ++ } ++ ++ const std::vector frame = {1, 2, 3}; ++ std::vector output(7); ++ size_t written = kUnwritten; ++ int status = ++ native->Encrypt(webrtc::MediaType::AUDIO, 0, {}, frame, output, &written); ++ ++ XCTAssertEqual(status, ++ webrtc::RTCFrameEncryptorAdapter::kEncryptionFailed, ++ @"A sender may keep the adapter after the encryptor is gone"); ++ XCTAssertTrue(delegate.lastFrameBytes == nullptr, ++ @"Delegate must not be called once the encryptor is gone"); ++} ++ ++- (void)testDecryptCopiesDelegateOutputIntoBuffer { ++ RTCFrameTransformDelegate *delegate = ++ [[RTCFrameTransformDelegate alloc] initWithTransform:DropLastBytes(2)]; ++ RTC_OBJC_TYPE(RTCFrameDecryptor) *decryptor = ++ [[RTC_OBJC_TYPE(RTCFrameDecryptor) alloc] initWithSizeChange:0]; ++ decryptor.delegate = delegate; ++ webrtc::scoped_refptr native = ++ decryptor.nativeFrameDecryptor; ++ ++ const std::vector encrypted = {1, 2, 3, 4, 5}; ++ std::vector output(native->GetMaxPlaintextByteSize( ++ webrtc::MediaType::VIDEO, encrypted.size())); ++ webrtc::FrameDecryptorInterface::Result result = ++ native->Decrypt(webrtc::MediaType::VIDEO, {}, {}, encrypted, output); ++ ++ XCTAssertTrue(result.IsOk()); ++ XCTAssertEqual(result.bytes_written, 3u); ++ XCTAssertTrue((output == std::vector{1, 2, 3, 0, 0})); ++ XCTAssertEqual(delegate.lastMediaType, RTCRtpMediaTypeVideo); ++ XCTAssertTrue(delegate.lastFrameBytes == encrypted.data(), ++ @"Delegate should receive WebRTC's buffer without a copy"); ++} ++ ++- (void)testDecryptRejectsOutputLargerThanBuffer { ++ RTCFrameTransformDelegate *delegate = [[RTCFrameTransformDelegate alloc] ++ initWithTransform:AppendBytes(1, kFillByte)]; ++ RTC_OBJC_TYPE(RTCFrameDecryptor) *decryptor = ++ [[RTC_OBJC_TYPE(RTCFrameDecryptor) alloc] initWithSizeChange:0]; ++ decryptor.delegate = delegate; ++ webrtc::scoped_refptr native = ++ decryptor.nativeFrameDecryptor; ++ ++ const std::vector encrypted = {1, 2, 3}; ++ std::vector output(native->GetMaxPlaintextByteSize( ++ webrtc::MediaType::AUDIO, encrypted.size())); ++ webrtc::FrameDecryptorInterface::Result result = ++ native->Decrypt(webrtc::MediaType::AUDIO, {}, {}, encrypted, output); ++ ++ XCTAssertEqual(result.status, ++ webrtc::FrameDecryptorInterface::Status::kFailedToDecrypt); ++ XCTAssertEqual(result.bytes_written, 0u); ++ XCTAssertTrue((output == std::vector(3, 0)), ++ @"Buffer must stay untouched when output does not fit"); ++} ++ ++- (void)testDecryptFailsWithoutDelegate { ++ RTC_OBJC_TYPE(RTCFrameDecryptor) *decryptor = ++ [[RTC_OBJC_TYPE(RTCFrameDecryptor) alloc] initWithSizeChange:0]; ++ webrtc::scoped_refptr native = ++ decryptor.nativeFrameDecryptor; ++ ++ const std::vector encrypted = {1, 2, 3}; ++ std::vector output(3); ++ webrtc::FrameDecryptorInterface::Result result = ++ native->Decrypt(webrtc::MediaType::AUDIO, {}, {}, encrypted, output); ++ ++ XCTAssertEqual(result.status, ++ webrtc::FrameDecryptorInterface::Status::kFailedToDecrypt); ++} ++ ++- (void)testMaxSizeAppliesSizeChangeAndClampsAtZero { ++ RTC_OBJC_TYPE(RTCFrameEncryptor) *growing = ++ [[RTC_OBJC_TYPE(RTCFrameEncryptor) alloc] initWithSizeChange:28]; ++ RTC_OBJC_TYPE(RTCFrameDecryptor) *shrinking = ++ [[RTC_OBJC_TYPE(RTCFrameDecryptor) alloc] initWithSizeChange:-28]; ++ ++ XCTAssertEqual(growing.nativeFrameEncryptor->GetMaxCiphertextByteSize( ++ webrtc::MediaType::VIDEO, 100), ++ 128u); ++ XCTAssertEqual(shrinking.nativeFrameDecryptor->GetMaxPlaintextByteSize( ++ webrtc::MediaType::VIDEO, 100), ++ 72u); ++ XCTAssertEqual(shrinking.nativeFrameDecryptor->GetMaxPlaintextByteSize( ++ webrtc::MediaType::VIDEO, 10), ++ 0u, ++ @"A negative size change must not wrap around"); ++} ++ ++- (void)testSetRtcFrameCryptorsAttachToNativeSenderAndReceiver { ++ RTC_OBJC_TYPE(RTCConfiguration) *config = ++ [[RTC_OBJC_TYPE(RTCConfiguration) alloc] init]; ++ config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; ++ RTC_OBJC_TYPE(RTCMediaConstraints) *constraints = ++ [[RTC_OBJC_TYPE(RTCMediaConstraints) alloc] ++ initWithMandatoryConstraints:@{} ++ optionalConstraints:nil]; ++ RTC_OBJC_TYPE(RTCRtpTransceiverInit) *init = ++ [[RTC_OBJC_TYPE(RTCRtpTransceiverInit) alloc] init]; ++ RTC_OBJC_TYPE(RTCFrameEncryptor) *encryptor = ++ [[RTC_OBJC_TYPE(RTCFrameEncryptor) alloc] initWithSizeChange:28]; ++ RTC_OBJC_TYPE(RTCFrameDecryptor) *decryptor = ++ [[RTC_OBJC_TYPE(RTCFrameDecryptor) alloc] initWithSizeChange:0]; ++ ++ @autoreleasepool { ++ RTC_OBJC_TYPE(RTCPeerConnectionFactory) *factory = ++ [[RTC_OBJC_TYPE(RTCPeerConnectionFactory) alloc] init]; ++ RTC_OBJC_TYPE(RTCPeerConnection) *peerConnection = ++ [factory peerConnectionWithConfiguration:config ++ constraints:constraints ++ delegate:nil]; ++ RTC_OBJC_TYPE(RTCRtpTransceiver) *transceiver = ++ [peerConnection addTransceiverOfType:RTCRtpMediaTypeVideo init:init]; ++ XCTAssertNotNil(transceiver); ++ ++ [transceiver.sender setRtcFrameEncryptor:encryptor]; ++ [transceiver.receiver setRtcFrameDecryptor:decryptor]; ++ ++ XCTAssertTrue(transceiver.sender.nativeRtpSender->GetFrameEncryptor() == ++ encryptor.nativeFrameEncryptor); ++ XCTAssertTrue(transceiver.receiver.nativeRtpReceiver->GetFrameDecryptor() == ++ decryptor.nativeFrameDecryptor); ++ ++ [peerConnection close]; ++ } ++} ++ ++@end diff --git a/scripts/build.sh b/scripts/build.sh index 61be7217ee..d7fb6e4c83 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -3,31 +3,30 @@ ## WebRTC library build script ## Created by Stasel ## BSD-3 License -## -## Example usage: MACOS=true IOS=true BUILD_VP9=true PATCH=false REMOVE_OUT=false sh build.sh +## +## Example usage (from the repository root): BRANCH=branch-heads/7727 MACOS=true IOS=true sh scripts/build.sh # Configs DEBUG="${DEBUG:-false}" -BUILD_VP9="${BUILD_VP9:-false}" -BRANCH="${BRANCH:-master}" -COMMIT="${COMMIT:-832ce5eae6}" -PATCH="${PATCH:-true}" -REMOVE_OUT="${REMOVE_OUT:-true}" +BRANCH="${BRANCH:-main}" IOS="${IOS:-false}" MACOS="${MACOS:-false}" MAC_CATALYST="${MAC_CATALYST:-false}" -OUTPUT_DIR="./out" -XCFRAMEWORK_DIR="out/WebRTC.xcframework" -COMMON_GN_ARGS="is_debug=${DEBUG} rtc_libvpx_build_vp9=${BUILD_VP9} is_component_build=false rtc_include_tests=false rtc_enable_objc_symbol_export=true enable_stripping=true enable_dsyms=false use_lld=true" +ROOT_DIR="$(pwd)" +OUTPUT_DIR="${ROOT_DIR}/out" +XCFRAMEWORK_DIR="${OUTPUT_DIR}/WebRTC.xcframework" +COMMON_GN_ARGS="is_debug=${DEBUG} rtc_libvpx_build_vp9=true is_component_build=false rtc_include_tests=false rtc_enable_objc_symbol_export=true enable_stripping=true enable_dsyms=true use_lld=true rtc_ios_use_opengl_rendering=true rtc_system_openh264=true rtc_use_h265=true" PLISTBUDDY_EXEC="/usr/libexec/PlistBuddy" + build_iOS() { local arch=$1 local environment=$2 local gen_dir="${OUTPUT_DIR}/ios-${arch}-${environment}" local gen_args="${COMMON_GN_ARGS} target_cpu=\"${arch}\" target_os=\"ios\" target_environment=\"${environment}\" ios_deployment_target=\"12.0\" ios_enable_code_signing=false" gn gen "${gen_dir}" --args="${gen_args}" + gn args --list ${gen_dir} > ${gen_dir}/gn-args.txt ninja -C "${gen_dir}" framework_objc || exit 1 } @@ -36,16 +35,19 @@ build_macOS() { local gen_dir="${OUTPUT_DIR}/macos-${arch}" local gen_args="${COMMON_GN_ARGS} target_cpu=\"${arch}\" target_os=\"mac\"" gn gen "${gen_dir}" --args="${gen_args}" + gn args --list ${gen_dir} > ${gen_dir}/gn-args.txt ninja -C "${gen_dir}" mac_framework_objc || exit 1 } # Catalyst builds are not working properly yet. # See: https://groups.google.com/g/discuss-webrtc/c/VZXS4V4mSY4 +# Must link Catalyst with Apple's linker instead of lld (use_lld=false) build_catalyst() { local arch=$1 local gen_dir="${OUTPUT_DIR}/catalyst-${arch}" - local gen_args="${COMMON_GN_ARGS} target_cpu=\"${arch}\" target_environment=\"catalyst\" target_os=\"ios\" ios_deployment_target=\"14.0\" ios_enable_code_signing=false" + local gen_args="${COMMON_GN_ARGS} target_cpu=\"${arch}\" target_environment=\"catalyst\" target_os=\"ios\" ios_deployment_target=\"14.0\" ios_enable_code_signing=false use_lld=false" gn gen "${gen_dir}" --args="${gen_args}" + gn args --list ${gen_dir} > ${gen_dir}/gn-args.txt ninja -C "${gen_dir}" framework_objc || exit 1 } @@ -70,6 +72,36 @@ plist_add_architecture() { "$PLISTBUDDY_EXEC" -c "Add :AvailableLibraries:${index}:SupportedArchitectures: string ${arch}" "${INFO_PLIST}" } +fix_privacy_manifest() { + local framework=$1 + local nested="${framework}/Versions/A/Versions" + if [ -f "${nested}/A/Resources/PrivacyInfo.xcprivacy" ]; then + mv "${nested}/A/Resources/PrivacyInfo.xcprivacy" "${framework}/Versions/A/Resources/" || exit 1 + rm -rf "${nested}" + fi +} + +# Stage the dSYM for one XCFramework slice, named after its library identifier. +# Pass a second build directory when the slice's binary is lipo'd from two architectures. +stage_dsym() { + local identifier=$1 + local build_dir=$2 + local extra_build_dir=$3 + local dsym="${OUTPUT_DIR}/WebRTC-${identifier}.dSYM" + local dwarf="Contents/Resources/DWARF/WebRTC" + local relocations="Contents/Resources/Relocations" + + rm -rf "${dsym}" + cp -r "${OUTPUT_DIR}/${build_dir}/WebRTC.dSYM" "${dsym}" || exit 1 + + if [ ! -z "${extra_build_dir}" ]; then + cp -r "${OUTPUT_DIR}/${extra_build_dir}/WebRTC.dSYM/${relocations}/" "${dsym}/${relocations}/" + lipo -create -output "${dsym}/${dwarf}" \ + "${OUTPUT_DIR}/${build_dir}/WebRTC.dSYM/${dwarf}" \ + "${OUTPUT_DIR}/${extra_build_dir}/WebRTC.dSYM/${dwarf}" || exit 1 + fi +} + # Step 1: Download and install depot tools if [ ! -d depot_tools ]; then git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git @@ -80,30 +112,29 @@ else fi export PATH=$(pwd)/depot_tools:$PATH +# Bootstrap depot_tools before running any of its tools. +ensure_bootstrap || exit 1 + # Step 2 - Download and build WebRTC if [ ! -d src ]; then - fetch --nohooks webrtc_ios + fetch --nohooks webrtc_ios || exit 1 fi cd src -git fetch --all -git checkout $BRANCH -#git checkout --detach $COMMIT +git fetch --all || exit 1 +git checkout "$BRANCH" || exit 1 cd .. -gclient sync --with_branch_heads --with_tags -cd src +gclient sync --with_branch_heads --with_tags || exit 1 -# Step 2.5 - Apply patches -# sed -i '' 's/"-mllvm",$/# "-mllvm",/g' ./build/config/compiler/BUILD.gn -# sed -i '' 's/"-instcombine-lower-dbg-declare=0",$/# "-instcombine-lower-dbg-declare=0",/g' ./build/config/compiler/BUILD.gn +# Step 2.5 - Temp patch for macOS arm64 builds +# bash "${ROOT_DIR}/scripts/patches/disable_apple_linker.sh" "${ROOT_DIR}/src/build/toolchain/apple/toolchain.gni" || exit 1 -if [ "$PATCH" = true ]; then - git apply ../patches/*.patch -fi +cd src + +# Step 2.6 - Apply SimpleX patches +git apply "${ROOT_DIR}"/patches/*.patch || exit 1 # Step 3 - Compile and build all frameworks -if [ "$REMOVE_OUT" = true ]; then - rm -rf $OUTPUT_DIR -fi +rm -rf $OUTPUT_DIR if [ "$IOS" = true ]; then build_iOS "x64" "simulator" @@ -129,7 +160,7 @@ fi INFO_PLIST="${XCFRAMEWORK_DIR}/Info.plist" rm -rf "${XCFRAMEWORK_DIR}" -mkdir "${XCFRAMEWORK_DIR}" +mkdir -p "${XCFRAMEWORK_DIR}" "$PLISTBUDDY_EXEC" -c "Add :CFBundlePackageType string XFWK" "${INFO_PLIST}" "$PLISTBUDDY_EXEC" -c "Add :XCFrameworkFormatVersion string 1.0" "${INFO_PLIST}" "$PLISTBUDDY_EXEC" -c "Add :AvailableLibraries array" "${INFO_PLIST}" @@ -141,18 +172,20 @@ if [[ "$IOS" = true ]]; then IOS_LIB_IDENTIFIER="ios-arm64" IOS_SIM_LIB_IDENTIFIER="ios-x86_64_arm64-simulator" - mkdir "${XCFRAMEWORK_DIR}/${IOS_LIB_IDENTIFIER}" - mkdir "${XCFRAMEWORK_DIR}/${IOS_SIM_LIB_IDENTIFIER}" + mkdir -p "${XCFRAMEWORK_DIR}/${IOS_LIB_IDENTIFIER}" + mkdir -p "${XCFRAMEWORK_DIR}/${IOS_SIM_LIB_IDENTIFIER}" LIB_IOS_INDEX=0 LIB_IOS_SIMULATOR_INDEX=1 plist_add_library $LIB_IOS_INDEX $IOS_LIB_IDENTIFIER "ios" plist_add_library $LIB_IOS_SIMULATOR_INDEX $IOS_SIM_LIB_IDENTIFIER "ios" "simulator" - cp -r out/ios-arm64-device/WebRTC.framework "${XCFRAMEWORK_DIR}/${IOS_LIB_IDENTIFIER}" - cp -r out/ios-x64-simulator/WebRTC.framework "${XCFRAMEWORK_DIR}/${IOS_SIM_LIB_IDENTIFIER}" + cp -r "${OUTPUT_DIR}/ios-x64-simulator/WebRTC.framework" "${XCFRAMEWORK_DIR}/${IOS_SIM_LIB_IDENTIFIER}" + cp -r "${OUTPUT_DIR}/ios-arm64-device/WebRTC.framework" "${XCFRAMEWORK_DIR}/${IOS_LIB_IDENTIFIER}" + stage_dsym "${IOS_LIB_IDENTIFIER}" "ios-arm64-device" + stage_dsym "${IOS_SIM_LIB_IDENTIFIER}" "ios-x64-simulator" "ios-arm64-simulator" - LIPO_IOS_FLAGS="out/ios-arm64-device/WebRTC.framework/WebRTC" - LIPO_IOS_SIM_FLAGS="out/ios-x64-simulator/WebRTC.framework/WebRTC out/ios-arm64-simulator/WebRTC.framework/WebRTC" + LIPO_IOS_FLAGS="${OUTPUT_DIR}/ios-arm64-device/WebRTC.framework/WebRTC" + LIPO_IOS_SIM_FLAGS="${OUTPUT_DIR}/ios-x64-simulator/WebRTC.framework/WebRTC ${OUTPUT_DIR}/ios-arm64-simulator/WebRTC.framework/WebRTC" plist_add_architecture $LIB_IOS_INDEX "arm64" plist_add_architecture $LIB_IOS_SIMULATOR_INDEX "arm64" @@ -178,8 +211,18 @@ if [ "$MACOS" = true ]; then plist_add_architecture $LIB_COUNT "x86_64" plist_add_architecture $LIB_COUNT "arm64" - cp -RP out/macos-x64/WebRTC.framework "${XCFRAMEWORK_DIR}/${MAC_LIB_IDENTIFIER}" - lipo -create -output "${XCFRAMEWORK_DIR}/${MAC_LIB_IDENTIFIER}/WebRTC.framework/Versions/A/WebRTC" out/macos-x64/WebRTC.framework/WebRTC out/macos-arm64/WebRTC.framework/WebRTC + cp -RP "${OUTPUT_DIR}/macos-x64/WebRTC.framework" "${XCFRAMEWORK_DIR}/${MAC_LIB_IDENTIFIER}" + stage_dsym "${MAC_LIB_IDENTIFIER}" "macos-x64" "macos-arm64" + + # The generated macOS framework bundle contains only the umbrella header: + # since M141 the other public headers are left behind in the intermediate + # gen/ directory and never staged into the bundle, which makes the + # framework unusable (https://github.com/stasel/WebRTC/issues/132). + # Copy them in until this is fixed upstream. + cp "${OUTPUT_DIR}/macos-x64/gen/sdk/WebRTC.framework/Headers/"*.h "${XCFRAMEWORK_DIR}/${MAC_LIB_IDENTIFIER}/WebRTC.framework/Versions/A/Headers/" || exit 1 + fix_privacy_manifest "${XCFRAMEWORK_DIR}/${MAC_LIB_IDENTIFIER}/WebRTC.framework" + + lipo -create -output "${XCFRAMEWORK_DIR}/${MAC_LIB_IDENTIFIER}/WebRTC.framework/Versions/A/WebRTC" "${OUTPUT_DIR}/macos-x64/WebRTC.framework/WebRTC" "${OUTPUT_DIR}/macos-arm64/WebRTC.framework/WebRTC" LIB_COUNT=$((LIB_COUNT+1)) fi @@ -193,8 +236,11 @@ if [ "$MAC_CATALYST" = true ]; then plist_add_architecture $LIB_COUNT "x86_64" plist_add_architecture $LIB_COUNT "arm64" - cp -RP out/catalyst-x64/WebRTC.framework "${XCFRAMEWORK_DIR}/${CATALYST_LIB_IDENTIFIER}" - lipo -create -output "${XCFRAMEWORK_DIR}/${CATALYST_LIB_IDENTIFIER}/WebRTC.framework/Versions/A/WebRTC" out/catalyst-x64/WebRTC.framework/WebRTC out/catalyst-arm64/WebRTC.framework/WebRTC + cp -RP "${OUTPUT_DIR}/catalyst-x64/WebRTC.framework" "${XCFRAMEWORK_DIR}/${CATALYST_LIB_IDENTIFIER}" + stage_dsym "${CATALYST_LIB_IDENTIFIER}" "catalyst-x64" "catalyst-arm64" + + fix_privacy_manifest "${XCFRAMEWORK_DIR}/${CATALYST_LIB_IDENTIFIER}/WebRTC.framework" + lipo -create -output "${XCFRAMEWORK_DIR}/${CATALYST_LIB_IDENTIFIER}/WebRTC.framework/Versions/A/WebRTC" "${OUTPUT_DIR}/catalyst-x64/WebRTC.framework/WebRTC" "${OUTPUT_DIR}/catalyst-arm64/WebRTC.framework/WebRTC" LIB_COUNT=$((LIB_COUNT+1)) fi @@ -202,15 +248,18 @@ fi cp LICENSE ${XCFRAMEWORK_DIR} # Step 7 - archive the framework -cd out +cd "${OUTPUT_DIR}" NOW=$(date -u +"%Y-%m-%dT%H-%M-%S") OUTPUT_NAME=WebRTC-$NOW.xcframework.zip -zip --symlinks -r $OUTPUT_NAME WebRTC.xcframework/ +zip --symlinks -rq $OUTPUT_NAME WebRTC.xcframework/ -# Step 8 calculate SHA256 checksum +# Step 8 - archive the dSYM files +DSYM_OUTPUT_NAME=WebRTC-$NOW-dSYM.zip +zip -rmq $DSYM_OUTPUT_NAME WebRTC-*.dSYM + +# Step 9 - calculate SHA256 checksum CHECKSUM=$(shasum -a 256 $OUTPUT_NAME | awk '{ print $1 }') -COMMIT_HASH=$(git rev-parse HEAD) +COMMIT_HASH=$(git -C ${ROOT_DIR}/src rev-parse HEAD) -echo "{ \"file\": \"${OUTPUT_NAME}\", \"checksum\": \"${CHECKSUM}\", \"commit\": \"${COMMIT_HASH}\", \"branch\": \"${BRANCH}\" }" > metadata.json +echo "{ \"file\": \"${OUTPUT_NAME}\", \"checksum\": \"${CHECKSUM}\", \"commit\": \"${COMMIT_HASH}\", \"branch\": \"${BRANCH}\", \"dsym\": \"${DSYM_OUTPUT_NAME}\" }" > metadata.json cat metadata.json - diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100644 index 0000000000..e236ee5706 --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1 @@ +rm -rf .cipd .gclient* depot_tools src .build out diff --git a/scripts/release.py b/scripts/release.py index fd6c0893fe..f3445fa2f2 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -3,11 +3,9 @@ import requests from datetime import datetime, timedelta from dataclasses import dataclass -import telegram GITHUB_TOKEN=os.environ.get("GITHUB_TOKEN") -TELEGRAM_TOKEN=os.environ.get("TELEGRAM_TOKEN") -TELEGRAM_CHAT_ID=os.environ.get("TELEGRAM_CHAT_ID") +GITHUB_REPO=os.environ.get("GITHUB_REPOSITORY", "stasel/WebRTC") @dataclass class NextReleaseResult: @@ -21,16 +19,39 @@ class BuildMetadata: checksum: str commit: str branch: str + dsym: str + +def getStableMilestone(): + """Find the current stable milestone from the Chromium Dashboard.""" + try: + response = requests.get("https://chromiumdash.appspot.com/fetch_milestones") + response.raise_for_status() + milestones = response.json() + stable = [m for m in milestones if m.get("schedule_phase") == "stable"] + if stable: + return int(max(stable, key=lambda m: m["milestone"])["milestone"]) + print("Warning: no milestone with schedule_phase 'stable' found") + except (requests.RequestException, KeyError, ValueError, TypeError) as e: + print(f"Warning: failed to fetch stable milestone: {e}") + return None def getNextRelease(): # Get current version - releases = requests.get("https://api.github.com/repos/simplex-chat/WebRTC/releases").json() + releases = requests.get(f"https://api.github.com/repos/{GITHUB_REPO}/releases", headers={'Authorization': f"token {GITHUB_TOKEN}"}).json() latestReleaseVersion = int(releases[0]["tag_name"].split(".")[0]) latestReleaseDate = datetime.fromisoformat(releases[0]["published_at"].replace("Z", "")) print(f"Latest release: version {latestReleaseVersion}, date: {latestReleaseDate}") - # Get next version - nextReleaseVersion = latestReleaseVersion + 1 + # Get the current stable milestone + stableMilestone = getStableMilestone() + if not stableMilestone: + print("❌ Could not determine current stable milestone") + os._exit(os.EX_SOFTWARE) + + nextReleaseVersion = max(latestReleaseVersion + 1, stableMilestone) + if nextReleaseVersion > latestReleaseVersion + 1: + print(f"Current stable milestone is M{stableMilestone}, skipping ahead from M{latestReleaseVersion + 1}") + milestones = requests.get(f"https://chromiumdash.appspot.com/fetch_milestone_schedule?mstone={nextReleaseVersion}").json() nextReleaseDate = datetime.fromisoformat(milestones["mstones"][0]["stable_date"]) print(f"Next release: version {nextReleaseVersion}, date: {nextReleaseDate}") @@ -45,7 +66,6 @@ def isReleaseAvailable(release): return datetime.today() >= (release.releaseDate + timedelta(days=1)) def buildWebRTC(branch): - os.environ["BUILD_VP9"] = "true" os.environ["BRANCH"] = branch os.environ["IOS"] = "true" os.environ["MACOS"] = "false" @@ -56,10 +76,10 @@ def buildWebRTC(branch): def getBuildMetadata(outputDir): with open(f"{outputDir}/metadata.json", 'r') as f: jsonData = json.loads(f.read()) - return BuildMetadata(filename = jsonData['file'], checksum = jsonData['checksum'], commit = jsonData['commit'], branch = jsonData['branch']) + return BuildMetadata(filename = jsonData['file'], checksum = jsonData['checksum'], commit = jsonData['commit'], branch = jsonData['branch'], dsym = jsonData['dsym']) def createReleaseDraft(release, buildMetadata): - body = "Release notes: N/A\n" + body = f"Release notes: https://webrtc.googlesource.com/src.git/+log/refs/{buildMetadata.branch}/\n" body += f"WebRTC Branch: [{buildMetadata.branch}](https://chromium.googlesource.com/external/webrtc/+log/{buildMetadata.branch})\n" body += f"WebRTC Commit: `{buildMetadata.commit}`\n" body += f"SHA 256 checksum: `{buildMetadata.checksum}`" @@ -71,7 +91,7 @@ def createReleaseDraft(release, buildMetadata): 'body': body } headers = {'accept': 'application/vnd.github.v3+json', 'Authorization': f'token {GITHUB_TOKEN}'} - return requests.post("https://api.github.com/repos/simplex-chat/WebRTC/releases", json = fields, headers = headers).json() + return requests.post(f"https://api.github.com/repos/{GITHUB_REPO}/releases", json = fields, headers = headers).json() def uploadReleaseAsset(url, assetLocalPath, assetName): url = url.replace(u'{?name,label}','') @@ -91,9 +111,9 @@ def createPullRequest(release, head): 'title': f'Release M{release.version}', 'head': head, 'base': 'latest', - 'body': 'Created by an automated software 🤖' + 'body': f'Updated files for release M{release.version}.' } - response = requests.post("https://api.github.com/repos/simplex-chat/WebRTC/pulls", json = body, headers = headers) + response = requests.post(f"https://api.github.com/repos/{GITHUB_REPO}/pulls", json = body, headers = headers) success = response.status_code == requests.codes.created if not success: print(response) @@ -126,7 +146,7 @@ def createPullRequest(release, head): print("✅ WebRTC build successful\n") # Get metadata build file - it has all the information needed about the build - outputDir="src/out" + outputDir="./out" buildMetadata = getBuildMetadata(outputDir) print(buildMetadata) @@ -135,13 +155,21 @@ def createPullRequest(release, head): githubReleaseDraft = createReleaseDraft(nextRelease ,buildMetadata) # Upload asset to github - print("➡️ Uploading asset to github...") - assetName = f"WebRTC-M{nextRelease.version}.xcframework.zip" - assetPath = os.path.join(outputDir, buildMetadata.filename) + print("➡️ Uploading assets to github...") uploadURL = githubReleaseDraft['upload_url'] - uploadResult = uploadReleaseAsset(uploadURL, assetPath, assetName) - - if not uploadResult: + uploadResultFramework = uploadReleaseAsset( + uploadURL, + os.path.join(outputDir, buildMetadata.filename), + f"WebRTC-M{nextRelease.version}.xcframework.zip" + ) + + uploadResultdSYM = uploadReleaseAsset( + uploadURL, + os.path.join(outputDir, buildMetadata.dsym), + f"WebRTC-M{nextRelease.version}-dSYM.zip" + ) + + if not uploadResultFramework or not uploadResultdSYM: print("❌ Failed uploading asset to github") os._exit(os.EX_SOFTWARE) @@ -154,14 +182,14 @@ def createPullRequest(release, head): # Change code print("➡️ Applying code changes...") - os.system(f"sed -i '' -E 's/[0-9]+.0.0\/WebRTC-M[0-9]+/{nextRelease.version}.0.0\/WebRTC-M{nextRelease.version}/g' Package.swift WebRTC-lib.podspec") + os.system(f"sed -i '' -E 's/[0-9]+\.[0-9]+\.[0-9]+\/WebRTC-M[0-9]+/{nextRelease.version}.0.0\/WebRTC-M{nextRelease.version}/g' Package.swift WebRTC-lib.podspec") os.system(f"sed -i '' -E 's/checksum: \"[0-9a-f]+\"/checksum: \"{buildMetadata.checksum}\"/g' Package.swift WebRTC-lib.podspec ") - os.system(f"sed -i '' -E 's/.upToNextMajor\\(\"[0-9]+.0.0/.upToNextMajor\\(\"{nextRelease.version}.0.0/g' README.md") - os.system(f"sed -i '' -E 's/spec.version = \"[0-9]+.0.0\"/spec.version = \"{nextRelease.version}.0.0\"/g' WebRTC-lib.podspec") + os.system(f"sed -i '' -E 's/.upToNextMajor\\(\"[0-9]+\.[0-9]+\.[0-9]+/.upToNextMajor\\(\"{nextRelease.version}.0.0/g' README.md") + os.system(f"sed -i '' -E 's/spec.version = \"[0-9]+\.[0-9]+\.[0-9]+\"/spec.version = \"{nextRelease.version}.0.0\"/g' WebRTC-lib.podspec") cartageFile = open("WebRTC.json", 'r') cartageJSON = json.loads(cartageFile.read()) - cartageJSON[f'{nextRelease.version}.0.0'] = f'https://github.com/simplex-chat/WebRTC/releases/download/{nextRelease.version}.0.0/WebRTC-M{nextRelease.version}.xcframework.zip' + cartageJSON[f'{nextRelease.version}.0.0'] = f'https://github.com/{GITHUB_REPO}/releases/download/{nextRelease.version}.0.0/WebRTC-M{nextRelease.version}.xcframework.zip' cartageFile.close() cartageJSONWrite = open("WebRTC.json", 'w') cartageJSONWrite.write(json.dumps(cartageJSON, indent=4, sort_keys=True)) @@ -169,8 +197,8 @@ def createPullRequest(release, head): # Commit and push - print("➡️ Commiting and pushing code to remote...") - os.system(f'git add .') + print("➡️ Committing and pushing code to remote...") + os.system(f'git add Package.swift WebRTC-lib.podspec README.md WebRTC.json') os.system(f'git commit -m "Updated files for release M{nextRelease.version}"') os.system(f'git push origin {releaseBranch}') @@ -181,11 +209,4 @@ def createPullRequest(release, head): print("❌ Failed creating pull request in github") os._exit(os.EX_SOFTWARE) - # Notify about the new release via Telegram bot - if TELEGRAM_TOKEN and TELEGRAM_CHAT_ID: - print("➡️ Sending Telegram notification...") - bot = telegram.Bot(token=TELEGRAM_TOKEN) - message = f"New WebRTC Release M{nextRelease.version} is now available.\nCheck the PR here: https://github.com/simplex-chat/WebRTC/pulls" - bot.send_message(chat_id=TELEGRAM_CHAT_ID, text=message) - print(f"✅ Done")