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
diff --git a/talk/app/webrtc/datachannelinterface.h b/talk/app/webrtc/datachannelinterface.h
index 7be8a50..57fe200 100644
--- a/talk/app/webrtc/datachannelinterface.h
+++ b/talk/app/webrtc/datachannelinterface.h
@@ -97,7 +97,9 @@
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 0000000..a550891
--- /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<webrtc::DataChannelInterface> dataChannel;
+
+- (instancetype)initWithDataChannel:
+ (talk_base::scoped_refptr<webrtc::DataChannelInterface>)dataChannel;
+
+@end
diff --git a/talk/app/webrtc/objc/RTCDataChannel.mm b/talk/app/webrtc/objc/RTCDataChannel.mm
new file mode 100644
index 0000000..0837940
--- /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<const char*>([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<webrtc::DataBuffer> _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<webrtc::DataChannelInterface> _dataChannel;
+ talk_base::scoped_ptr<webrtc::RTCDataChannelObserver> _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<RTCDataChannelDelegate>)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<webrtc::DataChannelInterface>)
+ 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<webrtc::DataChannelInterface>)dataChannel {
+ return _dataChannel;
+}
+
+@end
diff --git a/talk/app/webrtc/objc/RTCPeerConnection.mm b/talk/app/webrtc/objc/RTCPeerConnection.mm
index 759c6be..39e6a00 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 @@
return YES;
}
+- (RTCDataChannel*)createDataChannelWithLabel:(NSString*)label
+ config:(RTCDataChannelInit*)config {
+ std::string labelString([label UTF8String]);
+ talk_base::scoped_refptr<webrtc::DataChannelInterface> dataChannel =
+ self.peerConnection->CreateDataChannel(labelString,
+ config.dataChannelInit);
+ return [[RTCDataChannel alloc] initWithDataChannel:dataChannel];
+}
+
- (void)createAnswerWithDelegate:(id<RTCSessionDescriptionDelegate>)delegate
constraints:(RTCMediaConstraints*)constraints {
talk_base::scoped_refptr<webrtc::RTCCreateSessionDescriptionObserver>
diff --git a/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm b/talk/app/webrtc/objc/RTCPeerConnectionObserver.mm
index 5fb098f..5526b6f 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::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 0000000..762bd66
--- /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 <Foundation/Foundation.h>
+
+// 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<NSObject>
+
+// 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<RTCDataChannelDelegate> 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 b5b657c..4fe8913 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<RTCSessionDescriptionDelegate>)delegate
diff --git a/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h b/talk/app/webrtc/objc/public/RTCPeerConnectionDelegate.h
index 9695be8..4b177d5 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 db97816..8bbf982 100644
--- a/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h
+++ b/talk/app/webrtc/objctests/RTCPeerConnectionSyncObserver.h
@@ -27,11 +27,14 @@
#import <Foundation/Foundation.h>
+#import "RTCDataChannel.h"
#import "RTCPeerConnectionDelegate.h"
// Observer of PeerConnection events, used by RTCPeerConnectionTest to check
// expectations.
-@interface RTCPeerConnectionSyncObserver : NSObject<RTCPeerConnectionDelegate>
+@interface RTCPeerConnectionSyncObserver
+ : NSObject<RTCPeerConnectionDelegate, RTCDataChannelDelegate>
+@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 6568403..c3f898a 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 b46df7f..7a178f3 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 2ed8ff2..cc33f03 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 eae8bbd..96288fe 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 e02343d..fd85451 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
],
}],