|  | /* | 
|  | *  Copyright 2014 The WebRTC Project Authors. All rights reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  |  | 
|  | #import "ARDWebSocketChannel.h" | 
|  |  | 
|  | #import <WebRTC/RTCLogging.h> | 
|  | #import "SRWebSocket.h" | 
|  |  | 
|  | #import "ARDSignalingMessage.h" | 
|  | #import "ARDUtilities.h" | 
|  |  | 
|  | // TODO(tkchin): move these to a configuration object. | 
|  | static NSString const *kARDWSSMessageErrorKey = @"error"; | 
|  | static NSString const *kARDWSSMessagePayloadKey = @"msg"; | 
|  |  | 
|  | @interface ARDWebSocketChannel () <SRWebSocketDelegate> | 
|  | @end | 
|  |  | 
|  | @implementation ARDWebSocketChannel { | 
|  | NSURL *_url; | 
|  | NSURL *_restURL; | 
|  | SRWebSocket *_socket; | 
|  | } | 
|  |  | 
|  | @synthesize delegate = _delegate; | 
|  | @synthesize state = _state; | 
|  | @synthesize roomId = _roomId; | 
|  | @synthesize clientId = _clientId; | 
|  |  | 
|  | - (instancetype)initWithURL:(NSURL *)url | 
|  | restURL:(NSURL *)restURL | 
|  | delegate:(id<ARDSignalingChannelDelegate>)delegate { | 
|  | if (self = [super init]) { | 
|  | _url = url; | 
|  | _restURL = restURL; | 
|  | _delegate = delegate; | 
|  | _socket = [[SRWebSocket alloc] initWithURL:url]; | 
|  | _socket.delegate = self; | 
|  | RTCLog(@"Opening WebSocket."); | 
|  | [_socket open]; | 
|  | } | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (void)dealloc { | 
|  | [self disconnect]; | 
|  | } | 
|  |  | 
|  | - (void)setState:(ARDSignalingChannelState)state { | 
|  | if (_state == state) { | 
|  | return; | 
|  | } | 
|  | _state = state; | 
|  | [_delegate channel:self didChangeState:_state]; | 
|  | } | 
|  |  | 
|  | - (void)registerForRoomId:(NSString *)roomId | 
|  | clientId:(NSString *)clientId { | 
|  | NSParameterAssert(roomId.length); | 
|  | NSParameterAssert(clientId.length); | 
|  | _roomId = roomId; | 
|  | _clientId = clientId; | 
|  | if (_state == kARDSignalingChannelStateOpen) { | 
|  | [self registerWithCollider]; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)sendMessage:(ARDSignalingMessage *)message { | 
|  | NSParameterAssert(_clientId.length); | 
|  | NSParameterAssert(_roomId.length); | 
|  | NSData *data = [message JSONData]; | 
|  | if (_state == kARDSignalingChannelStateRegistered) { | 
|  | NSString *payload = | 
|  | [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; | 
|  | NSDictionary *message = @{ | 
|  | @"cmd": @"send", | 
|  | @"msg": payload, | 
|  | }; | 
|  | NSData *messageJSONObject = | 
|  | [NSJSONSerialization dataWithJSONObject:message | 
|  | options:NSJSONWritingPrettyPrinted | 
|  | error:nil]; | 
|  | NSString *messageString = | 
|  | [[NSString alloc] initWithData:messageJSONObject | 
|  | encoding:NSUTF8StringEncoding]; | 
|  | RTCLog(@"C->WSS: %@", messageString); | 
|  | [_socket send:messageString]; | 
|  | } else { | 
|  | NSString *dataString = | 
|  | [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; | 
|  | RTCLog(@"C->WSS POST: %@", dataString); | 
|  | NSString *urlString = | 
|  | [NSString stringWithFormat:@"%@/%@/%@", | 
|  | [_restURL absoluteString], _roomId, _clientId]; | 
|  | NSURL *url = [NSURL URLWithString:urlString]; | 
|  | [NSURLConnection sendAsyncPostToURL:url | 
|  | withData:data | 
|  | completionHandler:nil]; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)disconnect { | 
|  | if (_state == kARDSignalingChannelStateClosed || | 
|  | _state == kARDSignalingChannelStateError) { | 
|  | return; | 
|  | } | 
|  | [_socket close]; | 
|  | RTCLog(@"C->WSS DELETE rid:%@ cid:%@", _roomId, _clientId); | 
|  | NSString *urlString = | 
|  | [NSString stringWithFormat:@"%@/%@/%@", | 
|  | [_restURL absoluteString], _roomId, _clientId]; | 
|  | NSURL *url = [NSURL URLWithString:urlString]; | 
|  | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; | 
|  | request.HTTPMethod = @"DELETE"; | 
|  | request.HTTPBody = nil; | 
|  | [NSURLConnection sendAsyncRequest:request completionHandler:nil]; | 
|  | } | 
|  |  | 
|  | #pragma mark - SRWebSocketDelegate | 
|  |  | 
|  | - (void)webSocketDidOpen:(SRWebSocket *)webSocket { | 
|  | RTCLog(@"WebSocket connection opened."); | 
|  | self.state = kARDSignalingChannelStateOpen; | 
|  | if (_roomId.length && _clientId.length) { | 
|  | [self registerWithCollider]; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { | 
|  | NSString *messageString = message; | 
|  | NSData *messageData = [messageString dataUsingEncoding:NSUTF8StringEncoding]; | 
|  | id jsonObject = [NSJSONSerialization JSONObjectWithData:messageData | 
|  | options:0 | 
|  | error:nil]; | 
|  | if (![jsonObject isKindOfClass:[NSDictionary class]]) { | 
|  | RTCLogError(@"Unexpected message: %@", jsonObject); | 
|  | return; | 
|  | } | 
|  | NSDictionary *wssMessage = jsonObject; | 
|  | NSString *errorString = wssMessage[kARDWSSMessageErrorKey]; | 
|  | if (errorString.length) { | 
|  | RTCLogError(@"WSS error: %@", errorString); | 
|  | return; | 
|  | } | 
|  | NSString *payload = wssMessage[kARDWSSMessagePayloadKey]; | 
|  | ARDSignalingMessage *signalingMessage = | 
|  | [ARDSignalingMessage messageFromJSONString:payload]; | 
|  | RTCLog(@"WSS->C: %@", payload); | 
|  | [_delegate channel:self didReceiveMessage:signalingMessage]; | 
|  | } | 
|  |  | 
|  | - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { | 
|  | RTCLogError(@"WebSocket error: %@", error); | 
|  | self.state = kARDSignalingChannelStateError; | 
|  | } | 
|  |  | 
|  | - (void)webSocket:(SRWebSocket *)webSocket | 
|  | didCloseWithCode:(NSInteger)code | 
|  | reason:(NSString *)reason | 
|  | wasClean:(BOOL)wasClean { | 
|  | RTCLog(@"WebSocket closed with code: %ld reason:%@ wasClean:%d", | 
|  | (long)code, reason, wasClean); | 
|  | NSParameterAssert(_state != kARDSignalingChannelStateError); | 
|  | self.state = kARDSignalingChannelStateClosed; | 
|  | } | 
|  |  | 
|  | #pragma mark - Private | 
|  |  | 
|  | - (void)registerWithCollider { | 
|  | if (_state == kARDSignalingChannelStateRegistered) { | 
|  | return; | 
|  | } | 
|  | NSParameterAssert(_roomId.length); | 
|  | NSParameterAssert(_clientId.length); | 
|  | NSDictionary *registerMessage = @{ | 
|  | @"cmd": @"register", | 
|  | @"roomid" : _roomId, | 
|  | @"clientid" : _clientId, | 
|  | }; | 
|  | NSData *message = | 
|  | [NSJSONSerialization dataWithJSONObject:registerMessage | 
|  | options:NSJSONWritingPrettyPrinted | 
|  | error:nil]; | 
|  | NSString *messageString = | 
|  | [[NSString alloc] initWithData:message encoding:NSUTF8StringEncoding]; | 
|  | RTCLog(@"Registering on WSS for rid:%@ cid:%@", _roomId, _clientId); | 
|  | // Registration can fail if server rejects it. For example, if the room is | 
|  | // full. | 
|  | [_socket send:messageString]; | 
|  | self.state = kARDSignalingChannelStateRegistered; | 
|  | } | 
|  |  | 
|  | @end | 
|  |  | 
|  | @interface ARDLoopbackWebSocketChannel () <ARDSignalingChannelDelegate> | 
|  | @end | 
|  |  | 
|  | @implementation ARDLoopbackWebSocketChannel | 
|  |  | 
|  | - (instancetype)initWithURL:(NSURL *)url restURL:(NSURL *)restURL { | 
|  | return [super initWithURL:url restURL:restURL delegate:self]; | 
|  | } | 
|  |  | 
|  | #pragma mark - ARDSignalingChannelDelegate | 
|  |  | 
|  | - (void)channel:(id<ARDSignalingChannel>)channel | 
|  | didReceiveMessage:(ARDSignalingMessage *)message { | 
|  | switch (message.type) { | 
|  | case kARDSignalingMessageTypeOffer: { | 
|  | // Change message to answer, send back to server. | 
|  | ARDSessionDescriptionMessage *sdpMessage = | 
|  | (ARDSessionDescriptionMessage *)message; | 
|  | RTCSessionDescription *description = sdpMessage.sessionDescription; | 
|  | NSString *dsc = description.sdp; | 
|  | dsc = [dsc stringByReplacingOccurrencesOfString:@"offer" | 
|  | withString:@"answer"]; | 
|  | RTCSessionDescription *answerDescription = | 
|  | [[RTCSessionDescription alloc] initWithType:RTCSdpTypeAnswer sdp:dsc]; | 
|  | ARDSignalingMessage *answer = | 
|  | [[ARDSessionDescriptionMessage alloc] | 
|  | initWithDescription:answerDescription]; | 
|  | [self sendMessage:answer]; | 
|  | break; | 
|  | } | 
|  | case kARDSignalingMessageTypeAnswer: | 
|  | // Should not receive answer in loopback scenario. | 
|  | break; | 
|  | case kARDSignalingMessageTypeCandidate: | 
|  | case kARDSignalingMessageTypeCandidateRemoval: | 
|  | // Send back to server. | 
|  | [self sendMessage:message]; | 
|  | break; | 
|  | case kARDSignalingMessageTypeBye: | 
|  | // Nothing to do. | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)channel:(id<ARDSignalingChannel>)channel | 
|  | didChangeState:(ARDSignalingChannelState)state { | 
|  | } | 
|  |  | 
|  | @end | 
|  |  |