Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/src/event_manager/event_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'events.dart';
export 'call_events.dart';
export 'events.dart';
export 'message_events.dart';
export 'options_events.dart';
export 'refer_events.dart';
export 'register_events.dart';
export 'transport_events.dart';
Expand Down
9 changes: 9 additions & 0 deletions lib/src/event_manager/options_events.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import '../options.dart';
import 'events.dart';

class EventNewOptions extends EventType {
EventNewOptions({this.message, this.originator, this.request});
dynamic request;
String? originator;
Options? message;
}
3 changes: 2 additions & 1 deletion lib/src/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'ua.dart';
import 'uri.dart';
import 'utils.dart' as Utils;

class Message extends EventManager {
class Message extends EventManager with Applicant {
Message(UA ua) {
_ua = ua;
_request = null;
Expand Down Expand Up @@ -184,6 +184,7 @@ class Message extends EventManager {
'Transport Error');
}

@override
void close() {
_closed = true;
_ua!.destroyMessage(this);
Expand Down
233 changes: 233 additions & 0 deletions lib/src/options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import 'package:sip_ua/src/name_addr_header.dart';
import 'constants.dart' as DartSIP_C;
import 'constants.dart';
import 'event_manager/event_manager.dart';
import 'event_manager/internal_events.dart';
import 'exceptions.dart' as Exceptions;
import 'logger.dart';
import 'request_sender.dart';
import 'sip_message.dart';
import 'ua.dart';
import 'uri.dart';
import 'utils.dart' as Utils;

class Options extends EventManager with Applicant {
Options(UA ua) {
_ua = ua;
_request = null;
_closed = false;

_direction = null;
_local_identity = null;
_remote_identity = null;

// Whether an incoming Options has been replied.
_is_replied = false;

// Custom Options empty object for high level use.
_data = <String, dynamic>{};
}

UA? _ua;
dynamic _request;
bool? _closed;
String? _direction;
NameAddrHeader? _local_identity;
NameAddrHeader? _remote_identity;
bool? _is_replied;
Map<String, dynamic>? _data;
String? get direction => _direction;

NameAddrHeader? get local_identity => _local_identity;

NameAddrHeader? get remote_identity => _remote_identity;

Map<String, dynamic>? get data => _data;

void send(String target, String body, [Map<String, dynamic>? options]) {
String originalTarget = target;
options = options ?? <String, dynamic>{};

if (target == null) {
throw Exceptions.TypeError('A target is required for OPTIONS');
}

// Check target validity.
URI? normalized = _ua!.normalizeTarget(target);
if (normalized == null) {
throw Exceptions.TypeError('Invalid target: $originalTarget');
}

// Get call options.
List<dynamic> extraHeaders = Utils.cloneArray(options['extraHeaders']);
EventManager eventHandlers = options['eventHandlers'] ?? EventManager();
String contentType = options['contentType'] ?? 'application/sdp';

// Set event handlers.
addAllEventHandlers(eventHandlers);

extraHeaders.add('Content-Type: $contentType');

_request =
OutgoingRequest(SipMethod.OPTIONS, normalized, _ua, null, extraHeaders);
if (body != null) {
_request.body = body;
}

EventManager handlers = EventManager();
handlers.on(EventOnRequestTimeout(), (EventOnRequestTimeout value) {
_onRequestTimeout();
});
handlers.on(EventOnTransportError(), (EventOnTransportError value) {
_onTransportError();
});
handlers.on(EventOnReceiveResponse(), (EventOnReceiveResponse event) {
_receiveResponse(event.response);
});

RequestSender request_sender = RequestSender(_ua!, _request, handlers);

_newOptions('local', _request);

request_sender.send();
}

void init_incoming(IncomingRequest request) {
_request = request;

_newOptions('remote', request);

// Reply with a 200 OK if the user didn't reply.
if (_is_replied == null || _is_replied == false) {
_is_replied = true;
request.reply(200);
}

close();
}

/*
* Accept the incoming Options
* Only valid for incoming Options
*/
void accept(Map<String, dynamic> options) {
List<dynamic> extraHeaders = Utils.cloneArray(options['extraHeaders']);
String? body = options['body'];

if (_direction != 'incoming') {
throw Exceptions.NotSupportedError(
'"accept" not supported for outgoing Options');
}

if (_is_replied != null) {
throw AssertionError('incoming Options already replied');
}

_is_replied = true;
_request.reply(200, null, extraHeaders, body);
}

/**
* Reject the incoming Options
* Only valid for incoming Optionss
*/
void reject(Map<String, dynamic> options) {
int status_code = options['status_code'] ?? 480;
String? reason_phrase = options['reason_phrase'];
List<dynamic> extraHeaders = Utils.cloneArray(options['extraHeaders']);
String? body = options['body'];

if (_direction != 'incoming') {
throw Exceptions.NotSupportedError(
'"reject" not supported for outgoing Options');
}

if (_is_replied != null) {
throw AssertionError('incoming Options already replied');
}

if (status_code < 300 || status_code >= 700) {
throw Exceptions.TypeError('Invalid status_code: $status_code');
}

_is_replied = true;
_request.reply(status_code, reason_phrase, extraHeaders, body);
}

void _receiveResponse(IncomingResponse? response) {
if (_closed != null) {
return;
}
if (RegExp(r'^1[0-9]{2}$').hasMatch(response!.status_code)) {
// Ignore provisional responses.
} else if (RegExp(r'^2[0-9]{2}$').hasMatch(response.status_code)) {
_succeeded('remote', response);
} else {
String cause = Utils.sipErrorCause(response.status_code);
_failed('remote', response.status_code, cause, response.reason_phrase);
}
}

void _onRequestTimeout() {
if (_closed != null) {
return;
}
_failed(
'system', 408, DartSIP_C.CausesType.REQUEST_TIMEOUT, 'Request Timeout');
}

void _onTransportError() {
if (_closed != null) {
return;
}
_failed('system', 500, DartSIP_C.CausesType.CONNECTION_ERROR,
'Transport Error');
}

@override
void close() {
_closed = true;
_ua!.destroyOptions(this);
}

/**
* Internal Callbacks
*/

void _newOptions(String originator, dynamic request) {
if (originator == 'remote') {
_direction = 'incoming';
_local_identity = request.to;
_remote_identity = request.from;
} else if (originator == 'local') {
_direction = 'outgoing';
_local_identity = request.from;
_remote_identity = request.to;
}

_ua!.newOptions(this, originator, request);
}

void _failed(String originator, int? status_code, String cause,
String? reason_phrase) {
logger.debug('OPTIONS failed');
close();
logger.debug('emit "failed"');
emit(EventCallFailed(
originator: originator,
cause: ErrorCause(
cause: cause,
status_code: status_code,
reason_phrase: reason_phrase)));
}

void _succeeded(String originator, IncomingResponse? response) {
logger.debug('OPTIONS succeeded');

close();

logger.debug('emit "succeeded"');

emit(EventSucceeded(originator: originator, response: response));
}
}
57 changes: 51 additions & 6 deletions lib/src/ua.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'event_manager/internal_events.dart';
import 'exceptions.dart' as Exceptions;
import 'logger.dart';
import 'message.dart';
import 'options.dart';
import 'parser.dart' as Parser;
import 'registrator.dart';
import 'rtc_session.dart';
Expand Down Expand Up @@ -91,8 +92,8 @@ class UA extends EventManager {
_dynConfiguration = DynamicSettings();
_dialogs = <String, Dialog>{};

// User actions outside any session/dialog (MESSAGE).
_applicants = <Message>{};
// User actions outside any session/dialog (MESSAGE/OPTIONS).
_applicants = <Applicant>{};

_sessions = <String?, RTCSession>{};
_transport = null;
Expand Down Expand Up @@ -128,7 +129,7 @@ class UA extends EventManager {
Settings? _configuration;
DynamicSettings? _dynConfiguration;
late Map<String, Dialog> _dialogs;
late Set<Message> _applicants;
late Set<Applicant> _applicants;
Map<String?, RTCSession> _sessions = <String?, RTCSession>{};
Transport? _transport;
Contact? _contact;
Expand Down Expand Up @@ -260,6 +261,24 @@ class UA extends EventManager {
return message;
}

/**
* Send a Options.
*
* -param {String} target
* -param {String} body
* -param {Object} [options]
*
* -throws {TypeError}
*
*/
Options sendOptions(
String target, String body, Map<String, dynamic>? options) {
logger.debug('sendOptions()');
Options message = Options(this);
message.send(target, body, options);
return message;
}

/**
* Terminate ongoing sessions.
*/
Expand Down Expand Up @@ -310,9 +329,9 @@ class UA extends EventManager {
});

// Run _close_ on every applicant.
for (Message message in _applicants) {
for (Applicant applicant in _applicants) {
try {
message.close();
applicant.close();
} catch (error) {}
}

Expand Down Expand Up @@ -441,13 +460,29 @@ class UA extends EventManager {
message: message, originator: originator, request: request));
}

/**
* Options
*/
void newOptions(Options message, String originator, dynamic request) {
_applicants.add(message);
emit(EventNewOptions(
message: message, originator: originator, request: request));
}

/**
* Message destroyed.
*/
void destroyMessage(Message message) {
_applicants.remove(message);
}

/**
* Options destroyed.
*/
void destroyOptions(Options message) {
_applicants.remove(message);
}

/**
* RTCSession
*/
Expand Down Expand Up @@ -548,7 +583,13 @@ class UA extends EventManager {
* They are processed as if they had been received outside the dialog.
*/
if (method == SipMethod.OPTIONS) {
request.reply(200);
if (!hasListeners(EventNewOptions())) {
request.reply(200);
return;
}
Options message = Options(this);
message.init_incoming(request);
return;
} else if (method == SipMethod.MESSAGE) {
if (!hasListeners(EventNewMessage())) {
request.reply(405);
Expand Down Expand Up @@ -883,3 +924,7 @@ class UA extends EventManager {
}
}
}

mixin Applicant {
void close();
}