From ff2733204dd2cc894206716e111dcffabc8898f2 Mon Sep 17 00:00:00 2001 From: "tkchin@webrtc.org" Date: Wed, 30 Apr 2014 18:32:33 +0000 Subject: [PATCH] Implement ObjC DataChannel wrapper R=fischman@webrtc.org BUG=3112 Review URL: https://webrtc-codereview.appspot.com/16369004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@6031 4adac7df-926f-26a2-2b94-8c16560cd09d --- talk/app/webrtc/datachannelinterface.h | 4 +- .../app/webrtc/objc/RTCDataChannel+Internal.h | 55 ++++ talk/app/webrtc/objc/RTCDataChannel.mm | 273 ++++++++++++++++++ talk/app/webrtc/objc/RTCPeerConnection.mm | 10 + .../webrtc/objc/RTCPeerConnectionObserver.mm | 5 +- talk/app/webrtc/objc/public/RTCDataChannel.h | 112 +++++++ .../webrtc/objc/public/RTCPeerConnection.h | 6 + .../objc/public/RTCPeerConnectionDelegate.h | 5 + .../objctests/RTCPeerConnectionSyncObserver.h | 8 +- .../objctests/RTCPeerConnectionSyncObserver.m | 58 +++- .../webrtc/objctests/RTCPeerConnectionTest.mm | 61 +++- .../ios/AppRTCDemo/APPRTCAppDelegate.m | 5 + talk/libjingle.gyp | 15 +- talk/libjingle_tests.gyp | 12 + 14 files changed, 622 insertions(+), 7 deletions(-) create mode 100644 talk/app/webrtc/objc/RTCDataChannel+Internal.h create mode 100644 talk/app/webrtc/objc/RTCDataChannel.mm create mode 100644 talk/app/webrtc/objc/public/RTCDataChannel.h diff --git a/talk/app/webrtc/datachannelinterface.h b/talk/app/webrtc/datachannelinterface.h index 7be8a50f5..57fe200cf 100644 --- a/talk/app/webrtc/datachannelinterface.h +++ b/talk/app/webrtc/datachannelinterface.h @@ -97,7 +97,9 @@ class DataChannelObserver { class DataChannelInterface : public talk_base::RefCountInterface { public: - enum DataState { // Keep in sync with DataChannel.java:State. + // Keep in sync with DataChannel.java:State and + // RTCDataChannel.h:RTCDataChannelState. + enum DataState { kConnecting, kOpen, // The DataChannel is ready to send data. kClosing, diff --git a/talk/app/webrtc/objc/RTCDataChannel+Internal.h b/talk/app/webrtc/objc/RTCDataChannel+Internal.h new file mode 100644 index 000000000..a55089193 --- /dev/null +++ b/talk/app/webrtc/objc/RTCDataChannel+Internal.h @@ -0,0 +1,55 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "RTCDataChannel.h" + +#include "talk/app/webrtc/datachannelinterface.h" +#include "talk/base/scoped_ref_ptr.h" + +@interface RTCDataBuffer (Internal) + +@property(nonatomic, readonly) const webrtc::DataBuffer* dataBuffer; + +- (instancetype)initWithDataBuffer:(const webrtc::DataBuffer&)buffer; + +@end + +@interface RTCDataChannelInit (Internal) + +@property(nonatomic, readonly) const webrtc::DataChannelInit* dataChannelInit; + +@end + +@interface RTCDataChannel (Internal) + +@property(nonatomic, readonly) + talk_base::scoped_refptr dataChannel; + +- (instancetype)initWithDataChannel: + (talk_base::scoped_refptr)dataChannel; + +@end diff --git a/talk/app/webrtc/objc/RTCDataChannel.mm b/talk/app/webrtc/objc/RTCDataChannel.mm new file mode 100644 index 000000000..083794037 --- /dev/null +++ b/talk/app/webrtc/objc/RTCDataChannel.mm @@ -0,0 +1,273 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "RTCDataChannel+Internal.h" + +#include "talk/app/webrtc/datachannelinterface.h" + +namespace webrtc { + +class RTCDataChannelObserver : public DataChannelObserver { + public: + RTCDataChannelObserver(RTCDataChannel* channel) { _channel = channel; } + + virtual void OnStateChange() OVERRIDE { + [_channel.delegate channelDidChangeState:_channel]; + } + + virtual void OnMessage(const DataBuffer& buffer) OVERRIDE { + if (!_channel.delegate) { + return; + } + RTCDataBuffer* dataBuffer = + [[RTCDataBuffer alloc] initWithDataBuffer:buffer]; + [_channel.delegate channel:_channel didReceiveMessageWithBuffer:dataBuffer]; + } + + private: + __weak RTCDataChannel* _channel; +}; +} + +// TODO(tkchin): move to shared location +NSString* NSStringFromStdString(const std::string& stdString) { + // std::string may contain null termination character so we construct + // using length. + return [[NSString alloc] initWithBytes:stdString.data() + length:stdString.length() + encoding:NSUTF8StringEncoding]; +} + +std::string StdStringFromNSString(NSString* nsString) { + NSData* charData = [nsString dataUsingEncoding:NSUTF8StringEncoding]; + return std::string(reinterpret_cast([charData bytes]), + [charData length]); +} + +@implementation RTCDataChannelInit { + webrtc::DataChannelInit _dataChannelInit; +} + +- (BOOL)isOrdered { + return _dataChannelInit.ordered; +} + +- (void)setIsOrdered:(BOOL)isOrdered { + _dataChannelInit.ordered = isOrdered; +} + +- (NSInteger)maxRetransmitTime { + return _dataChannelInit.maxRetransmitTime; +} + +- (void)setMaxRetransmitTime:(NSInteger)maxRetransmitTime { + _dataChannelInit.maxRetransmitTime = maxRetransmitTime; +} + +- (NSInteger)maxRetransmits { + return _dataChannelInit.maxRetransmits; +} + +- (void)setMaxRetransmits:(NSInteger)maxRetransmits { + _dataChannelInit.maxRetransmits = maxRetransmits; +} + +- (NSString*)protocol { + return NSStringFromStdString(_dataChannelInit.protocol); +} + +- (void)setProtocol:(NSString*)protocol { + _dataChannelInit.protocol = StdStringFromNSString(protocol); +} + +- (BOOL)isNegotiated { + return _dataChannelInit.negotiated; +} + +- (void)setIsNegotiated:(BOOL)isNegotiated { + _dataChannelInit.negotiated = isNegotiated; +} + +- (NSInteger)streamId { + return _dataChannelInit.id; +} + +- (void)setStreamId:(NSInteger)streamId { + _dataChannelInit.id = streamId; +} + +@end + +@implementation RTCDataChannelInit (Internal) + +- (const webrtc::DataChannelInit*)dataChannelInit { + return &_dataChannelInit; +} + +@end + +@implementation RTCDataBuffer { + talk_base::scoped_ptr _dataBuffer; +} + +- (instancetype)initWithData:(NSData*)data isBinary:(BOOL)isBinary { + NSAssert(data, @"data cannot be nil"); + if (self = [super init]) { + talk_base::Buffer buffer([data bytes], [data length]); + _dataBuffer.reset(new webrtc::DataBuffer(buffer, isBinary)); + } + return self; +} + +- (NSData*)data { + return [NSData dataWithBytes:_dataBuffer->data.data() + length:_dataBuffer->data.length()]; +} + +- (BOOL)isBinary { + return _dataBuffer->binary; +} + +@end + +@implementation RTCDataBuffer (Internal) + +- (instancetype)initWithDataBuffer:(const webrtc::DataBuffer&)buffer { + if (self = [super init]) { + _dataBuffer.reset(new webrtc::DataBuffer(buffer)); + } + return self; +} + +- (const webrtc::DataBuffer*)dataBuffer { + return _dataBuffer.get(); +} + +@end + +@implementation RTCDataChannel { + talk_base::scoped_refptr _dataChannel; + talk_base::scoped_ptr _observer; + BOOL _isObserverRegistered; +} + +- (NSString*)label { + return NSStringFromStdString(_dataChannel->label()); +} + +- (BOOL)isReliable { + return _dataChannel->reliable(); +} + +- (BOOL)isOrdered { + return _dataChannel->ordered(); +} + +- (NSUInteger)maxRetransmitTimeMs { + return _dataChannel->maxRetransmitTime(); +} + +- (NSUInteger)maxRetransmits { + return _dataChannel->maxRetransmits(); +} + +- (NSString*)protocol { + return NSStringFromStdString(_dataChannel->protocol()); +} + +- (BOOL)isNegotiated { + return _dataChannel->negotiated(); +} + +- (NSInteger)streamId { + return _dataChannel->id(); +} + +- (RTCDataChannelState)state { + switch (_dataChannel->state()) { + case webrtc::DataChannelInterface::DataState::kConnecting: + return kRTCDataChannelStateConnecting; + case webrtc::DataChannelInterface::DataState::kOpen: + return kRTCDataChannelStateOpen; + case webrtc::DataChannelInterface::DataState::kClosing: + return kRTCDataChannelStateClosing; + case webrtc::DataChannelInterface::DataState::kClosed: + return kRTCDataChannelStateClosed; + } +} + +- (NSUInteger)bufferedAmount { + return _dataChannel->buffered_amount(); +} + +- (void)setDelegate:(id)delegate { + if (_delegate == delegate) { + return; + } + if (_isObserverRegistered) { + _dataChannel->UnregisterObserver(); + _isObserverRegistered = NO; + } + _delegate = delegate; + if (_delegate) { + _dataChannel->RegisterObserver(_observer.get()); + _isObserverRegistered = YES; + } +} + +- (void)close { + _dataChannel->Close(); +} + +- (BOOL)sendData:(RTCDataBuffer*)data { + return _dataChannel->Send(*data.dataBuffer); +} + +@end + +@implementation RTCDataChannel (Internal) + +- (instancetype)initWithDataChannel: + (talk_base::scoped_refptr) + dataChannel { + NSAssert(dataChannel != NULL, @"dataChannel cannot be NULL"); + if (self = [super init]) { + _dataChannel = dataChannel; + _observer.reset(new webrtc::RTCDataChannelObserver(self)); + } + return self; +} + +- (talk_base::scoped_refptr)dataChannel { + return _dataChannel; +} + +@end diff --git a/talk/app/webrtc/objc/RTCPeerConnection.mm b/talk/app/webrtc/objc/RTCPeerConnection.mm index 759c6be9e..39e6a007b 100644 --- a/talk/app/webrtc/objc/RTCPeerConnection.mm +++ b/talk/app/webrtc/objc/RTCPeerConnection.mm @@ -31,6 +31,7 @@ #import "RTCPeerConnection+Internal.h" +#import "RTCDataChannel+Internal.h" #import "RTCEnumConverter.h" #import "RTCICECandidate+Internal.h" #import "RTCICEServer+Internal.h" @@ -160,6 +161,15 @@ class RTCStatsObserver : public StatsObserver { return YES; } +- (RTCDataChannel*)createDataChannelWithLabel:(NSString*)label + config:(RTCDataChannelInit*)config { + std::string labelString([label UTF8String]); + talk_base::scoped_refptr dataChannel = + self.peerConnection->CreateDataChannel(labelString, + config.dataChannelInit); + return [[RTCDataChannel alloc] initWithDataChannel:dataChannel]; +} + - (void)createAnswerWithDelegate:(id)delegate constraints:(RTCMediaConstraints*)constraints { talk_base::scoped_refptr diff --git a/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm b/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm index 5fb098f44..5526b6faf 100644 --- a/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm +++ b/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm @@ -31,6 +31,7 @@ #import "RTCPeerConnectionObserver.h" +#import "RTCDataChannel+Internal.h" #import "RTCICECandidate+Internal.h" #import "RTCMediaStream+Internal.h" #import "RTCEnumConverter.h" @@ -74,7 +75,9 @@ void RTCPeerConnectionObserver::OnRemoveStream(MediaStreamInterface* stream) { void RTCPeerConnectionObserver::OnDataChannel( DataChannelInterface* data_channel) { - // TODO(hughv): Implement for future version. + RTCDataChannel* dataChannel = + [[RTCDataChannel alloc] initWithDataChannel:data_channel]; + [_delegate peerConnection:_peerConnection didOpenDataChannel:dataChannel]; } void RTCPeerConnectionObserver::OnRenegotiationNeeded() { diff --git a/talk/app/webrtc/objc/public/RTCDataChannel.h b/talk/app/webrtc/objc/public/RTCDataChannel.h new file mode 100644 index 000000000..762bd662e --- /dev/null +++ b/talk/app/webrtc/objc/public/RTCDataChannel.h @@ -0,0 +1,112 @@ +/* + * libjingle + * Copyright 2014, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import + +// ObjectiveC wrapper for a DataChannelInit object. +@interface RTCDataChannelInit : NSObject + +// Set to YES if ordered delivery is required +@property(nonatomic) BOOL isOrdered; +// Max period in milliseconds in which retransmissions will be sent. After this +// time, no more retransmissions will be sent. -1 if unset. +@property(nonatomic) NSInteger maxRetransmitTimeMs; +// The max number of retransmissions. -1 if unset. +@property(nonatomic) NSInteger maxRetransmits; +// Set to YES if the channel has been externally negotiated and we do not send +// an in-band signalling in the form of an "open" message +@property(nonatomic) BOOL isNegotiated; +// The stream id, or SID, for SCTP data channels. -1 if unset. +@property(nonatomic) NSInteger streamId; +// Set by the application and opaque to the WebRTC implementation. +@property(nonatomic) NSString* protocol; + +@end + +// ObjectiveC wrapper for a DataBuffer object. +@interface RTCDataBuffer : NSObject + +@property(nonatomic, readonly) NSData* data; +@property(nonatomic, readonly) BOOL isBinary; + +- (instancetype)initWithData:(NSData*)data isBinary:(BOOL)isBinary; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// Disallow init and don't add to documentation +- (id)init __attribute__(( + unavailable("init is not a supported initializer for this class."))); +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +@end + +// Keep in sync with webrtc::DataChannelInterface::DataState +typedef enum { + kRTCDataChannelStateConnecting, + kRTCDataChannelStateOpen, + kRTCDataChannelStateClosing, + kRTCDataChannelStateClosed +} RTCDataChannelState; + +@class RTCDataChannel; +// Protocol for receving data channel state and message events. +@protocol RTCDataChannelDelegate + +// Called when the data channel state has changed. +- (void)channelDidChangeState:(RTCDataChannel*)channel; + +// Called when a data buffer was successfully received. +- (void)channel:(RTCDataChannel*)channel + didReceiveMessageWithBuffer:(RTCDataBuffer*)buffer; + +@end + +// ObjectiveC wrapper for a DataChannel object. +// See talk/app/webrtc/datachannelinterface.h +@interface RTCDataChannel : NSObject + +@property(nonatomic, readonly) NSString* label; +@property(nonatomic, readonly) BOOL isReliable; +@property(nonatomic, readonly) BOOL isOrdered; +@property(nonatomic, readonly) NSUInteger maxRetransmitTime; +@property(nonatomic, readonly) NSUInteger maxRetransmits; +@property(nonatomic, readonly) NSString* protocol; +@property(nonatomic, readonly) BOOL isNegotiated; +@property(nonatomic, readonly) NSInteger streamId; +@property(nonatomic, readonly) RTCDataChannelState state; +@property(nonatomic, readonly) NSUInteger bufferedAmount; +@property(nonatomic, weak) id delegate; + +- (void)close; +- (BOOL)sendData:(RTCDataBuffer*)data; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +// Disallow init and don't add to documentation +- (id)init __attribute__(( + unavailable("init is not a supported initializer for this class."))); +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +@end diff --git a/talk/app/webrtc/objc/public/RTCPeerConnection.h b/talk/app/webrtc/objc/public/RTCPeerConnection.h index b5b657c01..4fe89133d 100644 --- a/talk/app/webrtc/objc/public/RTCPeerConnection.h +++ b/talk/app/webrtc/objc/public/RTCPeerConnection.h @@ -27,6 +27,8 @@ #import "RTCPeerConnectionDelegate.h" +@class RTCDataChannel; +@class RTCDataChannelInit; @class RTCICECandidate; @class RTCICEServers; @class RTCMediaConstraints; @@ -68,6 +70,10 @@ // remote peer is notified. - (void)removeStream:(RTCMediaStream *)stream; +// Create a data channel. +- (RTCDataChannel*)createDataChannelWithLabel:(NSString*)label + config:(RTCDataChannelInit*)config; + // Create a new offer. // Success or failure will be reported via RTCSessionDescriptionDelegate. - (void)createOfferWithDelegate:(id)delegate diff --git a/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h b/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h index 9695be8e0..4b177d504 100644 --- a/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h +++ b/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h @@ -29,6 +29,7 @@ #import "RTCTypes.h" +@class RTCDataChannel; @class RTCICECandidate; @class RTCMediaStream; @class RTCPeerConnection; @@ -67,4 +68,8 @@ - (void)peerConnection:(RTCPeerConnection *)peerConnection gotICECandidate:(RTCICECandidate *)candidate; +// New data channel has been opened. +- (void)peerConnection:(RTCPeerConnection*)peerConnection + didOpenDataChannel:(RTCDataChannel*)dataChannel; + @end diff --git a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h index db97816fc..8bbf98251 100644 --- a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h +++ b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h @@ -27,11 +27,14 @@ #import +#import "RTCDataChannel.h" #import "RTCPeerConnectionDelegate.h" // Observer of PeerConnection events, used by RTCPeerConnectionTest to check // expectations. -@interface RTCPeerConnectionSyncObserver : NSObject +@interface RTCPeerConnectionSyncObserver + : NSObject +@property(nonatomic) RTCDataChannel* dataChannel; // TODO(hughv): Add support for RTCVideoRendererDelegate when Video is enabled. // Transfer received ICE candidates to the caller. @@ -46,6 +49,9 @@ - (void)expectICECandidates:(int)count; - (void)expectICEConnectionChange:(RTCICEConnectionState)state; - (void)expectICEGatheringChange:(RTCICEGatheringState)state; +- (void)expectDataChannel:(NSString*)label; +- (void)expectStateChange:(RTCDataChannelState)state; +- (void)expectMessage:(NSData*)message isBinary:(BOOL)isBinary; // Wait until all registered expectations above have been observed. - (void)waitForAllExpectationsToBeSatisfied; diff --git a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m index 65684033c..c3f898a29 100644 --- a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m +++ b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.m @@ -42,6 +42,9 @@ NSMutableArray* _receivedICECandidates; NSMutableArray* _expectedICEConnectionChanges; NSMutableArray* _expectedICEGatheringChanges; + NSMutableArray* _expectedDataChannels; + NSMutableArray* _expectedStateChanges; + NSMutableArray* _expectedMessages; } - (id)init { @@ -54,6 +57,9 @@ _receivedICECandidates = [NSMutableArray array]; _expectedICEConnectionChanges = [NSMutableArray array]; _expectedICEGatheringChanges = [NSMutableArray array]; + _expectedDataChannels = [NSMutableArray array]; + _expectedMessages = [NSMutableArray array]; + _expectedStateChanges = [NSMutableArray array]; } return self; } @@ -78,7 +84,10 @@ [_expectedICEConnectionChanges count] == 0 && [_expectedICEGatheringChanges count] == 0 && [_expectedAddStreamLabels count] == 0 && - [_expectedRemoveStreamLabels count] == 0; + [_expectedRemoveStreamLabels count] == 0 && + [_expectedDataChannels count] == 0 && + [_expectedStateChanges count] == 0 && + [_expectedMessages count] == 0; // TODO(hughv): Test video state here too. } @@ -116,6 +125,20 @@ [_expectedICEGatheringChanges addObject:@((int)state)]; } +- (void)expectDataChannel:(NSString*)label { + [_expectedDataChannels addObject:label]; +} + +- (void)expectStateChange:(RTCDataChannelState)state { + [_expectedStateChanges addObject:@(state)]; +} + +- (void)expectMessage:(NSData*)message isBinary:(BOOL)isBinary { + RTCDataBuffer* buffer = [[RTCDataBuffer alloc] initWithData:message + isBinary:isBinary]; + [_expectedMessages addObject:buffer]; +} + - (void)waitForAllExpectationsToBeSatisfied { // TODO (fischman): Revisit. Keeping in sync with the Java version, but // polling is not optimal. @@ -191,4 +214,37 @@ NSAssert(expectedState == (int)newState, @"Empty expectation array"); } +- (void)peerConnection:(RTCPeerConnection*)peerConnection + didOpenDataChannel:(RTCDataChannel*)dataChannel { + NSString* expectedLabel = + [self popFirstElementAsNSString:_expectedDataChannels]; + NSAssert([expectedLabel isEqual:dataChannel.label], + @"Data channel not expected"); + self.dataChannel = dataChannel; + dataChannel.delegate = self; + NSAssert(kRTCDataChannelStateConnecting == dataChannel.state, + @"Unexpected state"); +} + +#pragma mark - RTCDataChannelDelegate + +- (void)channelDidChangeState:(RTCDataChannel*)channel { + NSAssert([_expectedStateChanges count] > 0, + @"Unexpected state change"); + int expectedState = [self popFirstElementAsInt:_expectedStateChanges]; + NSAssert(expectedState == channel.state, @"Channel state should match"); +} + +- (void)channel:(RTCDataChannel*)channel + didReceiveMessageWithBuffer:(RTCDataBuffer*)buffer { + NSAssert([_expectedMessages count] > 0, + @"Unexpected message received"); + RTCDataBuffer* expectedBuffer = [_expectedMessages objectAtIndex:0]; + NSAssert(expectedBuffer.isBinary == buffer.isBinary, + @"Buffer isBinary should match"); + NSAssert([expectedBuffer.data isEqual:buffer.data], + @"Buffer data should match"); + [_expectedMessages removeObjectAtIndex:0]; +} + @end diff --git a/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm b/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm index b46df7f41..7a178f39f 100644 --- a/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm +++ b/talk/app/webrtc/objctests/RTCPeerConnectionTest.mm @@ -30,6 +30,7 @@ #import "RTCICEServer.h" #import "RTCMediaConstraints.h" #import "RTCMediaStream.h" +#import "RTCPair.h" #import "RTCPeerConnection.h" #import "RTCPeerConnectionFactory.h" #import "RTCPeerConnectionSyncObserver.h" @@ -94,12 +95,20 @@ } - (void)testCompleteSessionWithFactory:(RTCPeerConnectionFactory*)factory { + NSArray* mandatory = @[ + [[RTCPair alloc] initWithKey:@"DtlsSrtpKeyAgreement" value:@"true"], + [[RTCPair alloc] initWithKey:@"internalSctpDataChannels" value:@"true"], + ]; RTCMediaConstraints* constraints = [[RTCMediaConstraints alloc] init]; + RTCMediaConstraints* pcConstraints = + [[RTCMediaConstraints alloc] initWithMandatoryConstraints:mandatory + optionalConstraints:nil]; + RTCPeerConnectionSyncObserver* offeringExpectations = [[RTCPeerConnectionSyncObserver alloc] init]; RTCPeerConnection* pcOffer = [factory peerConnectionWithICEServers:nil - constraints:constraints + constraints:pcConstraints delegate:offeringExpectations]; RTCPeerConnectionSyncObserver* answeringExpectations = @@ -107,7 +116,7 @@ RTCPeerConnection* pcAnswer = [factory peerConnectionWithICEServers:nil - constraints:constraints + constraints:pcConstraints delegate:answeringExpectations]; // TODO(hughv): Create video capturer RTCVideoCapturer* capturer = nil; @@ -123,6 +132,14 @@ streamLabel:@"oLMS" videoTrackID:@"oLMSv0" audioTrackID:@"oLMSa0"]; + + RTCDataChannel* offerDC = + [pcOffer createDataChannelWithLabel:@"offerDC" + config:[[RTCDataChannelInit alloc] init]]; + EXPECT_TRUE([offerDC.label isEqual:@"offerDC"]); + offerDC.delegate = offeringExpectations; + offeringExpectations.dataChannel = offerDC; + RTCSessionDescriptionSyncObserver* sdpObserver = [[RTCSessionDescriptionSyncObserver alloc] init]; [pcOffer createOfferWithDelegate:sdpObserver constraints:constraints]; @@ -181,6 +198,10 @@ [answeringExpectations expectICEConnectionChange:RTCICEConnectionChecking]; [answeringExpectations expectICEConnectionChange:RTCICEConnectionConnected]; + [offeringExpectations expectStateChange:kRTCDataChannelStateOpen]; + [answeringExpectations expectDataChannel:@"offerDC"]; + [answeringExpectations expectStateChange:kRTCDataChannelStateOpen]; + [offeringExpectations expectICEGatheringChange:RTCICEGatheringComplete]; [answeringExpectations expectICEGatheringChange:RTCICEGatheringComplete]; @@ -209,6 +230,42 @@ [offeringExpectations waitForAllExpectationsToBeSatisfied]; [answeringExpectations waitForAllExpectationsToBeSatisfied]; + EXPECT_EQ(pcOffer.signalingState, RTCSignalingStable); + EXPECT_EQ(pcAnswer.signalingState, RTCSignalingStable); + + // Test send and receive UTF-8 text + NSString* text = @"你好"; + NSData* textData = [text dataUsingEncoding:NSUTF8StringEncoding]; + RTCDataBuffer* buffer = + [[RTCDataBuffer alloc] initWithData:textData isBinary:NO]; + [answeringExpectations expectMessage:[textData copy] isBinary:NO]; + EXPECT_TRUE([offeringExpectations.dataChannel sendData:buffer]); + [answeringExpectations waitForAllExpectationsToBeSatisfied]; + + // Test send and receive binary data + const size_t byteLength = 5; + char bytes[byteLength] = {1, 2, 3, 4, 5}; + NSData* byteData = [NSData dataWithBytes:bytes length:byteLength]; + buffer = [[RTCDataBuffer alloc] initWithData:byteData isBinary:YES]; + [answeringExpectations expectMessage:[byteData copy] isBinary:YES]; + EXPECT_TRUE([offeringExpectations.dataChannel sendData:buffer]); + [answeringExpectations waitForAllExpectationsToBeSatisfied]; + + [offeringExpectations expectStateChange:kRTCDataChannelStateClosing]; + [answeringExpectations expectStateChange:kRTCDataChannelStateClosing]; + [offeringExpectations expectStateChange:kRTCDataChannelStateClosed]; + [answeringExpectations expectStateChange:kRTCDataChannelStateClosed]; + + [answeringExpectations.dataChannel close]; + [offeringExpectations.dataChannel close]; + + [offeringExpectations waitForAllExpectationsToBeSatisfied]; + [answeringExpectations waitForAllExpectationsToBeSatisfied]; + // Don't need to listen to further state changes. + // TODO(tkchin): figure out why Closed->Closing without this. + offeringExpectations.dataChannel.delegate = nil; + answeringExpectations.dataChannel.delegate = nil; + // Let the audio feedback run for 2s to allow human testing and to ensure // things stabilize. TODO(fischman): replace seconds with # of video frames, // when we have video flowing. diff --git a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m index 2ed8ff23d..cc33f03da 100644 --- a/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m +++ b/talk/examples/ios/AppRTCDemo/APPRTCAppDelegate.m @@ -150,6 +150,11 @@ }); } +- (void)peerConnection:(RTCPeerConnection*)peerConnection + didOpenDataChannel:(RTCDataChannel*)dataChannel { + NSAssert(NO, @"AppRTC doesn't use DataChannels"); +} + #pragma mark - Private - (void)displayLogMessage:(NSString*)message { diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp index eae8bbd54..96288fe70 100755 --- a/talk/libjingle.gyp +++ b/talk/libjingle.gyp @@ -157,7 +157,8 @@ }, ], }], - ['OS=="ios" or (OS=="mac" and target_arch!="ia32")', { + ['OS=="ios" or (OS=="mac" and target_arch!="ia32" and mac_sdk>="10.7")', { + # The >= 10.7 above is required for ARC. 'targets': [ { 'target_name': 'libjingle_peerconnection_objc', @@ -168,6 +169,8 @@ 'sources': [ 'app/webrtc/objc/RTCAudioTrack+Internal.h', 'app/webrtc/objc/RTCAudioTrack.mm', + 'app/webrtc/objc/RTCDataChannel+Internal.h', + 'app/webrtc/objc/RTCDataChannel.mm', 'app/webrtc/objc/RTCEnumConverter.h', 'app/webrtc/objc/RTCEnumConverter.mm', 'app/webrtc/objc/RTCI420Frame.mm', @@ -205,6 +208,7 @@ 'app/webrtc/objc/RTCVideoTrack.mm', 'app/webrtc/objc/public/RTCAudioSource.h', 'app/webrtc/objc/public/RTCAudioTrack.h', + 'app/webrtc/objc/public/RTCDataChannel.h', 'app/webrtc/objc/public/RTCI420Frame.h', 'app/webrtc/objc/public/RTCICECandidate.h', 'app/webrtc/objc/public/RTCICEServer.h', @@ -249,6 +253,15 @@ # like it is for ios. 'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO', }, + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + # Need to build against 10.7 framework for full ARC support + # on OSX. + 'MACOSX_DEPLOYMENT_TARGET' : '10.7', + }, + }], + ], }, # target libjingle_peerconnection_objc ], }], diff --git a/talk/libjingle_tests.gyp b/talk/libjingle_tests.gyp index e02343d00..fd85451da 100755 --- a/talk/libjingle_tests.gyp +++ b/talk/libjingle_tests.gyp @@ -535,8 +535,20 @@ ], 'xcode_settings': { 'CLANG_ENABLE_OBJC_ARC': 'YES', + # common.gypi enables this for mac but we want this to be disabled + # like it is for ios. + 'CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS': 'NO', 'INFOPLIST_FILE': '<(infoplist_file)', }, + 'conditions': [ + ['OS=="mac"', { + 'xcode_settings': { + # Need to build against 10.7 framework for full ARC support + # on OSX. + 'MACOSX_DEPLOYMENT_TARGET' : '10.7', + }, + }], + ], }, # target libjingle_peerconnection_objc_test ], }],