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
       ],
     }],