/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#import "LinphoneCoreSettingsStore.h"
#import "Utils.h"
#import "PhoneMainView.h"
#include "linphone/linphone_tunnel.h"
#include "linphone/lpconfig.h"
#include
#include
@implementation LinphoneCoreSettingsStore
- (id)init {
self = [super init];
if (self) {
dict = [[NSMutableDictionary alloc] init];
changedDict = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)setCString:(const char *)value forKey:(NSString *)key {
id obj = @"";
if (value)
obj = [[NSString alloc] initWithCString:value encoding:NSUTF8StringEncoding];
[self setObject:obj forKey:key];
}
- (NSString *)stringForKey:(NSString *)key {
return [self objectForKey:key];
}
- (void)setObject:(id)value forKey:(NSString *)key {
[dict setValue:value forKey:key];
[changedDict setValue:[NSNumber numberWithBool:TRUE] forKey:key];
}
- (id)objectForKey:(NSString *)key {
return [dict valueForKey:key];
}
- (BOOL)valueChangedForKey:(NSString *)key {
return [[changedDict valueForKey:key] boolValue];
}
+ (int)validPort:(int)port {
if (port < 0) {
return 0;
}
if (port > 65535) {
return 65535;
}
return port;
}
+ (BOOL)parsePortRange:(NSString *)text minPort:(int *)minPort maxPort:(int *)maxPort {
/*NSError *error = nil;
*minPort = -1;
*maxPort = -1;
NSRegularExpression *regex =
[NSRegularExpression regularExpressionWithPattern:@"([0-9]+)(([^0-9]+)([0-9]+))?" options:0 error:&error];
if (error != NULL)
return FALSE;
NSArray *matches = [regex matchesInString:text options:0 range:NSMakeRange(0, [text length])];
if ([matches count] == 1) {
NSTextCheckingResult *match = [matches objectAtIndex:0];
bool range = [match rangeAtIndex:2].length > 0;
if (!range) {
NSRange rangeMinPort = [match rangeAtIndex:1];
*minPort = [LinphoneCoreSettingsStore validPort:[[text substringWithRange:rangeMinPort] intValue]];
*maxPort = *minPort;
return TRUE;
} else {
NSRange rangeMinPort = [match rangeAtIndex:1];
*minPort = [LinphoneCoreSettingsStore validPort:[[text substringWithRange:rangeMinPort] intValue]];
NSRange rangeMaxPort = [match rangeAtIndex:4];
*maxPort = [LinphoneCoreSettingsStore validPort:[[text substringWithRange:rangeMaxPort] intValue]];
if (*minPort > *maxPort) {
*minPort = *maxPort;
}
return TRUE;
}
}*/
int err;
err = sscanf(text.UTF8String, "%i - %i", minPort, maxPort);
if (err == 0) {
*minPort = *maxPort = -1;
} else if (err == 1) {
*maxPort = -1;
}
// Minimal port allowed
if (*minPort < 1024) {
*minPort = -1;
}
// Maximal port allowed
if (*maxPort > 65535) {
*maxPort = -1;
}
// minPort must be inferior or equal to maxPort
if (*minPort > *maxPort) {
*maxPort = *minPort;
}
return TRUE;
}
- (void)transformCodecsToKeys:(const MSList *)codecs {
const MSList *elem = codecs;
for (; elem != NULL; elem = elem->next) {
PayloadType *pt = (PayloadType *)elem->data;
NSString *pref = [LinphoneManager getPreferenceForCodec:pt->mime_type withRate:pt->clock_rate];
if (pref) {
bool_t value = linphone_core_payload_type_enabled(LC, pt);
[self setBool:value forKey:pref];
} else {
LOGW(@"Codec %s/%i supported by core is not shown in iOS app config view.", pt->mime_type, pt->clock_rate);
}
}
}
- (void)transformAccountToKeys:(NSString *)username {
const MSList *accountList = linphone_core_get_account_list(LC);
while (username && accountList &&
strcmp(username.UTF8String,
linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(accountList->data)))) != 0) {
accountList = accountList->next;
}
LinphoneAccount *account = NULL;
// default values
{
[self setBool:NO forKey:@"account_pushnotification_preference"];
[self setObject:@"" forKey:@"account_mandatory_username_preference"];
[self setObject:@"" forKey:@"account_mandatory_domain_preference"];
[self setCString:"" forKey:@"account_display_name_preference"];
[self setObject:@"" forKey:@"account_proxy_preference"];
[self setObject:@"udp" forKey:@"account_transport_preference"];
[self setBool:NO forKey:@"account_outbound_proxy_preference"];
[self setBool:NO forKey:@"account_avpf_preference"];
[self setBool:YES forKey:@"account_is_default_preference"];
[self setBool:YES forKey:@"account_is_enabled_preference"];
[self setCString:"" forKey:@"account_userid_preference"];
[self setCString:"" forKey:@"account_mandatory_password_preference"];
[self setCString:"" forKey:@"ha1_preference"];
[self setCString:"" forKey:@"ha1_algo_preference"];
[self setInteger:-1 forKey:@"account_expire_preference"];
[self setInteger:-1 forKey:@"current_proxy_config_preference"];
[self setCString:"" forKey:@"account_prefix_preference"];
[self setBool:NO forKey:@"account_substitute_+_by_00_preference"];
[self setBool:NO forKey:@"account_ice_preference"];
[self setCString:"" forKey:@"account_stun_preference"];
}
if (accountList) {
account = accountList->data;
LinphoneAccountParams const *accountParams = linphone_account_get_params(account);
// root section
{
BOOL pushEnabled = linphone_account_params_get_push_notification_allowed(accountParams);
[self setBool:pushEnabled forKey:@"account_pushnotification_preference"];
const LinphoneAddress *identity_addr = linphone_account_params_get_identity_address(accountParams);
const char *server_addr = linphone_account_params_get_server_addr(accountParams);
LinphoneAddress *proxy_addr = linphone_core_interpret_url(LC, server_addr);
if (identity_addr && proxy_addr) {
int port = linphone_address_get_port(proxy_addr);
[self setCString:linphone_address_get_username(identity_addr)
forKey:@"account_mandatory_username_preference"];
[self setCString:linphone_address_get_display_name(identity_addr)
forKey:@"account_display_name_preference"];
[self setCString:linphone_address_get_domain(identity_addr)
forKey:@"account_mandatory_domain_preference"];
if (strcmp(linphone_address_get_domain(identity_addr), linphone_address_get_domain(proxy_addr)) != 0 ||
port > 0) {
char tmp[256] = {0};
if (port > 0) {
snprintf(tmp, sizeof(tmp) - 1, "%s:%i", linphone_address_get_domain(proxy_addr), port);
} else
snprintf(tmp, sizeof(tmp) - 1, "%s", linphone_address_get_domain(proxy_addr));
[self setCString:tmp forKey:@"account_proxy_preference"];
}
const char *tname = "udp";
switch (linphone_address_get_transport(proxy_addr)) {
case LinphoneTransportTcp:
tname = "tcp";
break;
case LinphoneTransportTls:
tname = "tls";
break;
default:
break;
}
linphone_address_unref(proxy_addr);
[self setCString:tname forKey:@"account_transport_preference"];
}
[self setBool:(linphone_account_params_get_routes_addresses(accountParams) != NULL) forKey:@"account_outbound_proxy_preference"];
[self setBool:linphone_account_is_avpf_enabled(account) forKey:@"account_avpf_preference"];
[self setBool:linphone_account_params_get_register_enabled(accountParams) forKey:@"account_is_enabled_preference"];
[self setBool:(linphone_core_get_default_account(LC) == account)
forKey:@"account_is_default_preference"];
const LinphoneAuthInfo *ai = linphone_core_find_auth_info(
LC, NULL, [self stringForKey:@"account_mandatory_username_preference"].UTF8String,
[self stringForKey:@"account_mandatory_domain_preference"].UTF8String);
if (ai) {
[self setCString:linphone_auth_info_get_userid(ai) forKey:@"account_userid_preference"];
[self setCString:linphone_auth_info_get_passwd(ai) forKey:@"account_mandatory_password_preference"];
// hidden but useful if provisioned
[self setCString:linphone_auth_info_get_ha1(ai) forKey:@"ha1_preference"];
[self setCString:linphone_auth_info_get_algorithm(ai) forKey:@"ha1_algo_preference"];
}
int idx = (int)bctbx_list_index(linphone_core_get_account_list(LC), account);
[self setInteger:idx forKey:@"current_proxy_config_preference"];
int expires = linphone_account_params_get_expires(accountParams);
[self setInteger:expires forKey:@"account_expire_preference"];
LinphoneNatPolicy *policy = linphone_account_params_get_nat_policy(accountParams);
if (policy) {
[self setBool:linphone_nat_policy_ice_enabled(policy) forKey:@"account_ice_preference"];
[self setCString:linphone_nat_policy_get_stun_server(policy) forKey:@"account_stun_preference"];
}
}
// call section
{
const char *dial_prefix = linphone_account_params_get_international_prefix(accountParams);
[self setCString:dial_prefix forKey:@"account_prefix_preference"];
BOOL dial_escape_plus = linphone_account_params_get_dial_escape_plus_enabled(accountParams);
[self setBool:dial_escape_plus forKey:@"account_substitute_+_by_00_preference"];
}
}
}
- (void)transformLinphoneCoreToKeys {
LinphoneManager *lm = LinphoneManager.instance;
// root section
{
const bctbx_list_t *accounts = linphone_core_get_account_list(LC);
size_t count = bctbx_list_size(accounts);
for (size_t i = 1; i <= count; i++, accounts = accounts->next) {
NSString *key = [NSString stringWithFormat:@"menu_account_%lu", i];
LinphoneAccount *account = (LinphoneAccount *)accounts->data;
[self setCString:linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account)))
forKey:key];
}
[self setBool:linphone_core_video_display_enabled(LC) forKey:@"enable_video_preference"];
[self setBool:[LinphoneManager.instance lpConfigBoolForKey:@"auto_answer"]
forKey:@"enable_auto_answer_preference"];
[self setBool:[lm lpConfigBoolForKey:@"account_mandatory_advanced_preference"]
forKey:@"account_mandatory_advanced_preference"];
}
// account section
{ [self transformAccountToKeys:nil]; }
// audio section
{
[self transformCodecsToKeys:linphone_core_get_audio_codecs(LC)];
[self setFloat:linphone_core_get_playback_gain_db(LC) forKey:@"playback_gain_preference"];
[self setFloat:linphone_core_get_mic_gain_db(LC) forKey:@"microphone_gain_preference"];
[self setInteger:[lm lpConfigIntForKey:@"codec_bitrate_limit"
inSection:@"audio"
withDefault:kLinphoneAudioVbrCodecDefaultBitrate]
forKey:@"audio_codec_bitrate_limit_preference"];
[self setInteger:[lm lpConfigIntForKey:@"voiceproc_preference" withDefault:1] forKey:@"voiceproc_preference"];
[self setInteger:[lm lpConfigIntForKey:@"eq_active" inSection:@"sound" withDefault:0] forKey:@"eq_active"];
}
// video section
{
[self transformCodecsToKeys:linphone_core_get_video_codecs(LC)];
const LinphoneVideoPolicy *pol;
pol = linphone_core_get_video_policy(LC);
[self setBool:(pol->automatically_initiate) forKey:@"start_video_preference"];
[self setBool:(pol->automatically_accept) forKey:@"accept_video_preference"];
[self setBool:linphone_core_self_view_enabled(LC) forKey:@"self_video_preference"];
BOOL previewEnabled = [lm lpConfigBoolForKey:@"preview_preference" withDefault:YES];
[self setBool:IPAD && previewEnabled forKey:@"preview_preference"];
const char *preset = linphone_core_get_video_preset(LC);
[self setCString:preset ? preset : "default" forKey:@"video_preset_preference"];
MSVideoSize vsize = linphone_core_get_preferred_video_size(LC);
int index;
if ((vsize.width == MS_VIDEO_SIZE_720P_W) && (vsize.height == MS_VIDEO_SIZE_720P_H)) {
index = 0;
} else if ((vsize.width == MS_VIDEO_SIZE_VGA_W) && (vsize.height == MS_VIDEO_SIZE_VGA_H)) {
index = 1;
} else {
index = 2;
}
[self setInteger:index forKey:@"video_preferred_size_preference"];
[self setInteger:linphone_core_get_preferred_framerate(LC) forKey:@"video_preferred_fps_preference"];
[self setInteger:linphone_core_get_download_bandwidth(LC) forKey:@"download_bandwidth_preference"];
}
// call section
{
[self setBool:linphone_core_get_use_info_for_dtmf(LC) forKey:@"sipinfo_dtmf_preference"];
[self setBool:linphone_core_get_use_rfc2833_for_dtmf(LC) forKey:@"rfc_dtmf_preference"];
[self setInteger:linphone_core_get_inc_timeout(LC) forKey:@"incoming_call_timeout_preference"];
[self setInteger:linphone_core_get_in_call_timeout(LC) forKey:@"in_call_timeout_preference"];
[self setBool:[lm lpConfigBoolForKey:@"repeat_call_notification"]
forKey:@"repeat_call_notification_preference"];
[self setBool:linphone_core_is_media_encryption_mandatory(LC) forKey:@"media_encrption_mandatory_preference"];
[self setBool:[lm lpConfigBoolForKey:@"pref_accept_early_media"]
forKey:@"pref_accept_early_media_preference"];
}
// chat section
{
[self setCString:linphone_core_get_file_transfer_server(LC) forKey:@"file_transfer_server_url_preference"];
int maxSize = linphone_core_get_max_size_for_auto_download_incoming_files(LC);
[self setObject:maxSize==0 ? @"Always" : (maxSize==-1 ? @"Nerver" : @"Customize") forKey:@"auto_download_mode"];
[self setInteger:maxSize forKey:@"auto_download_incoming_files_max_size"];
[self setBool:[VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] forKey:@"vfs_enabled_mode"];
[self setBool:[lm lpConfigBoolForKey:@"auto_write_to_gallery_preference" withDefault:YES] forKey:@"auto_write_to_gallery_mode"];
[self setBool:[lm lpConfigBoolForKey:@"ephemeral_feature" withDefault:NO] forKey:@"ephemeral_feature"];
}
// network section
{
LinphoneNatPolicy *np = linphone_core_get_nat_policy(LC);
[self setBool:[lm lpConfigBoolForKey:@"edge_opt_preference" withDefault:NO] forKey:@"edge_opt_preference"];
[self setBool:[lm lpConfigBoolForKey:@"wifi_only_preference" withDefault:NO] forKey:@"wifi_only_preference"];
[self setCString:linphone_nat_policy_get_stun_server(np) forKey:@"stun_preference"];
[self setBool:linphone_nat_policy_ice_enabled(np) forKey:@"ice_preference"];
[self setBool:linphone_nat_policy_turn_enabled(np) forKey:@"turn_preference"];
[self setCString:linphone_nat_policy_get_stun_server_username(np)
forKey:@"turn_username"];
int random_port_preference = [lm lpConfigIntForKey:@"random_port_preference" withDefault:1];
[self setInteger:random_port_preference forKey:@"random_port_preference"];
int port = [lm lpConfigIntForKey:@"port_preference" withDefault:5060];
[self setInteger:port forKey:@"port_preference"];
{
int minPort, maxPort;
linphone_core_get_audio_port_range(LC, &minPort, &maxPort);
if (minPort != maxPort)
[self setObject:[NSString stringWithFormat:@"%d-%d", minPort, maxPort] forKey:@"audio_port_preference"];
else
[self setObject:[NSString stringWithFormat:@"%d", minPort] forKey:@"audio_port_preference"];
}
{
int minPort, maxPort;
linphone_core_get_video_port_range(LC, &minPort, &maxPort);
if (minPort != maxPort)
[self setObject:[NSString stringWithFormat:@"%d-%d", minPort, maxPort] forKey:@"video_port_preference"];
else
[self setObject:[NSString stringWithFormat:@"%d", minPort] forKey:@"video_port_preference"];
}
[self setBool:linphone_core_ipv6_enabled(LC) forKey:@"use_ipv6"];
LinphoneMediaEncryption menc = linphone_core_get_media_encryption(LC);
const char *val;
switch (menc) {
case LinphoneMediaEncryptionSRTP:
val = "SRTP";
break;
case LinphoneMediaEncryptionZRTP:
val = "ZRTP";
break;
case LinphoneMediaEncryptionDTLS:
val = "DTLS";
break;
case LinphoneMediaEncryptionNone:
val = "None";
break;
}
[self setCString:val forKey:@"media_encryption_preference"];
[self setInteger:linphone_core_get_upload_bandwidth(LC) forKey:@"upload_bandwidth_preference"];
[self setInteger:linphone_core_get_download_bandwidth(LC) forKey:@"download_bandwidth_preference"];
[self setBool:linphone_core_adaptive_rate_control_enabled(LC) forKey:@"adaptive_rate_control_preference"];
}
// tunnel section
if (linphone_core_tunnel_available()) {
LinphoneTunnel *tunnel = linphone_core_get_tunnel(LC);
[self setObject:[lm lpConfigStringForKey:@"tunnel_mode_preference" withDefault:@"off"]
forKey:@"tunnel_mode_preference"];
const MSList *configs = linphone_tunnel_get_servers(tunnel);
if (configs != NULL) {
LinphoneTunnelConfig *ltc = (LinphoneTunnelConfig *)configs->data;
[self setCString:linphone_tunnel_config_get_host(ltc) forKey:@"tunnel_address_preference"];
[self setInteger:linphone_tunnel_config_get_port(ltc) forKey:@"tunnel_port_preference"];
} else {
[self setCString:"" forKey:@"tunnel_address_preference"];
[self setInteger:443 forKey:@"tunnel_port_preference"];
}
}
// advanced section
{
[self setObject:[lm lpConfigStringForKey:@"debugenable_preference"] forKey:@"debugenable_preference"];
[self setBool:ANIMATED forKey:@"animations_preference"];
[self setBool:[lm lpConfigBoolForKey:@"backgroundmode_preference"] forKey:@"backgroundmode_preference"];
[self setBool:[lm lpConfigBoolForKey:@"start_at_boot_preference"] forKey:@"start_at_boot_preference"];
[self setBool:[lm lpConfigBoolForKey:@"autoanswer_notif_preference"] forKey:@"autoanswer_notif_preference"];
[self setBool:[lm lpConfigBoolForKey:@"show_msg_in_notif" withDefault:YES] forKey:@"show_msg_in_notif"];
[self setBool:[lm lpConfigBoolForKey:@"use_rls_presence" withDefault:YES] forKey:@"use_rls_presence"];
[self setBool:[lm lpConfigBoolForKey:@"enable_first_login_view_preference"]
forKey:@"enable_first_login_view_preference"];
LinphoneAddress *parsed = linphone_core_get_primary_contact_parsed(LC);
if (parsed != NULL) {
[self setCString:linphone_address_get_display_name(parsed) forKey:@"primary_displayname_preference"];
[self setCString:linphone_address_get_username(parsed) forKey:@"primary_username_preference"];
linphone_address_unref(parsed);
}
}
changedDict = [[NSMutableDictionary alloc] init];
// Post event
NSDictionary *eventDic = [NSDictionary dictionaryWithObject:self forKey:@"settings"];
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneLogsUpdate object:self userInfo:eventDic];
}
- (void)alertAccountError:(NSString *)error {
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error", nil)
message:error
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
}
- (void)synchronizeAccounts {
LOGI(@"Account changed, synchronizing.");
LinphoneManager *lm = LinphoneManager.instance;
LinphoneAccount *account = NULL;
LinphoneAccountParams *newAccountParams = NULL;
NSString *error = nil;
int port_preference = [self integerForKey:@"port_preference"];
BOOL random_port_preference = [self boolForKey:@"random_port_preference"];
[lm lpConfigSetInt:random_port_preference forKey:@"random_port_preference"];
if (random_port_preference) {
port_preference = -1;
}
LCSipTransports transportValue = {port_preference, port_preference, -1, -1};
// will also update the sip_*_port section of the config
if (linphone_core_set_sip_transports(LC, &transportValue)) {
LOGE(@"cannot set transport");
}
port_preference = linphone_core_get_sip_port(LC);
[self setInteger:port_preference forKey:@"port_preference"]; // Update back preference
BOOL enable_ipv6 = [self boolForKey:@"use_ipv6"];
[lm lpConfigSetBool:enable_ipv6 forKey:@"use_ipv6" inSection:@"sip"];
LOGD(@"%@ IPV6", enable_ipv6 ? @"ENABLING" : @"DISABLING");
linphone_core_enable_ipv6(LC, enable_ipv6);
// configure sip account
// mandatory parameters
NSString *username = [self stringForKey:@"account_mandatory_username_preference"];
NSString *displayName = [self stringForKey:@"account_display_name_preference"];
NSString *userID = [self stringForKey:@"account_userid_preference"];
NSString *domain = [self stringForKey:@"account_mandatory_domain_preference"];
NSString *transport = [self stringForKey:@"account_transport_preference"];
NSString *accountHa1 = [self stringForKey:@"ha1_preference"];
NSString *accountPassword = [self stringForKey:@"account_mandatory_password_preference"];
NSString *accountAlgoPreference = [self stringForKey:@"ha1_algo_preference"];
BOOL isOutboundProxy = [self boolForKey:@"account_outbound_proxy_preference"];
BOOL use_avpf = [self boolForKey:@"account_avpf_preference"];
BOOL is_default = [self boolForKey:@"account_is_default_preference"];
BOOL is_enabled = [self boolForKey:@"account_is_enabled_preference"];
BOOL use_ice = [self boolForKey:@"account_ice_preference"];
NSString *stun_preference = [self stringForKey:@"account_stun_preference"];
if (username && [username length] > 0 && domain && [domain length] > 0) {
int expire = [self integerForKey:@"account_expire_preference"];
BOOL pushnotification = [self boolForKey:@"account_pushnotification_preference"];
NSString *prefix = [self stringForKey:@"account_prefix_preference"];
NSString *proxyAddress = [self stringForKey:@"account_proxy_preference"];
if ((!proxyAddress || [proxyAddress length] < 1) && domain) {
proxyAddress = domain;
}
if (![proxyAddress hasPrefix:@"sip:"] && ![proxyAddress hasPrefix:@"sips:"]) {
proxyAddress = [NSString stringWithFormat:@"sip:%@", proxyAddress];
}
LinphoneAddress *proxy_addr = linphone_core_interpret_url(LC, proxyAddress.UTF8String);
if (proxy_addr) {
LinphoneTransportType type = LinphoneTransportUdp;
if ([transport isEqualToString:@"tcp"])
type = LinphoneTransportTcp;
else if ([transport isEqualToString:@"tls"])
type = LinphoneTransportTls;
linphone_address_set_transport(proxy_addr, type);
}
account = bctbx_list_nth_data(linphone_core_get_account_list(LC),
[self integerForKey:@"current_proxy_config_preference"]);
// if account was deleted, it is not present anymore
if (account == NULL)
goto bad_proxy;
LinphoneAddress *linphoneAddress = linphone_core_interpret_url(LC, "sip:user@domain.com");
linphone_address_set_username(linphoneAddress, username.UTF8String);
if ([LinphoneManager.instance lpConfigBoolForKey:@"use_phone_number" inSection:@"assistant"]) {
char *user = linphone_account_normalize_phone_number(account, username.UTF8String);
if (user) {
linphone_address_set_username(linphoneAddress, user);
ms_free(user);
}
}
linphone_address_set_domain(linphoneAddress, [domain UTF8String]);
linphone_address_set_display_name(linphoneAddress, (displayName.length ? displayName.UTF8String : NULL));
const char *password = [accountPassword UTF8String];
const char *ha1 = [accountHa1 UTF8String];
newAccountParams = linphone_account_params_clone(linphone_account_get_params(account));
if (linphone_account_params_set_identity_address(newAccountParams, linphoneAddress) == -1) {
error = NSLocalizedString(@"Invalid username or domain", nil);
goto bad_proxy;
}
// use proxy as route if outbound_proxy is enabled
if (linphone_account_params_set_server_address(newAccountParams, proxy_addr) == -1) {
error = NSLocalizedString(@"Invalid proxy address", nil);
goto bad_proxy;
}
if (linphone_account_params_set_routes_addresses(newAccountParams, isOutboundProxy ? bctbx_list_new((void*)proxy_addr) : NULL) == -1) {
error = NSLocalizedString(@"Invalid route", nil);
goto bad_proxy;
}
LinphoneNatPolicy *policy = linphone_account_params_get_nat_policy(newAccountParams) ?: linphone_core_create_nat_policy(LC);
linphone_nat_policy_enable_stun(policy, use_ice); // We always use STUN with ICE
linphone_nat_policy_enable_ice(policy, use_ice);
linphone_nat_policy_set_stun_server(policy, stun_preference.UTF8String);
linphone_account_params_set_nat_policy(newAccountParams, policy);
if ([prefix length] > 0) {
linphone_account_params_set_international_prefix(newAccountParams, [prefix UTF8String]);
}
if ([self objectForKey:@"account_substitute_+_by_00_preference"]) {
bool substitute_plus_by_00 = [self boolForKey:@"account_substitute_+_by_00_preference"];
linphone_account_params_set_dial_escape_plus_enabled(newAccountParams, substitute_plus_by_00);
}
// use empty string "" instead of NULL to avoid being overwritten by default proxy config values
linphone_account_params_set_push_notification_allowed(newAccountParams, pushnotification);
linphone_account_params_set_remote_push_notification_allowed(newAccountParams, pushnotification);
linphone_account_params_set_register_enabled(newAccountParams, is_enabled);
linphone_account_params_set_avpf_mode(newAccountParams, use_avpf);
linphone_account_params_set_expires(newAccountParams, expire);
linphone_account_set_params(account, newAccountParams);
linphone_account_params_unref(newAccountParams);
if (is_default) {
linphone_core_set_default_account(LC, account);
} else if (linphone_core_get_default_account(LC) == account) {
linphone_core_set_default_account(LC, NULL);
}
LinphoneAuthInfo *proxyAi = (LinphoneAuthInfo *)linphone_account_find_auth_info(account);
char *realm;
if (proxyAi) {
realm = ms_strdup(linphone_auth_info_get_realm(proxyAi));
} else {
realm = NULL;
}
// modify auth info only after finishing editting the proxy config, so that
// UNREGISTER succeed
if (proxyAi) {
linphone_core_remove_auth_info(LC, proxyAi);
}
if (strcmp(password,"") == 0) {
password = NULL;
}
char *identity = linphone_address_as_string(linphoneAddress);
LinphoneAddress *from = linphone_core_interpret_url(LC, identity);
ms_free(identity);
if (from) {
const char *userid_str = (userID != nil) ? [userID UTF8String] : NULL;
LinphoneAuthInfo *info;
if (password) {
info = linphone_auth_info_new(linphone_address_get_username(from), userid_str, password, NULL,
linphone_account_params_get_realm(newAccountParams),
linphone_account_params_get_domain(newAccountParams));
} else {
info = linphone_auth_info_new_for_algorithm(linphone_address_get_username(from)
, userid_str
, NULL
, ha1
, realm ? realm : linphone_account_params_get_realm(newAccountParams),
linphone_account_params_get_domain(newAccountParams), [accountAlgoPreference UTF8String]);
}
linphone_address_unref(from);
linphone_core_add_auth_info(LC, info);
linphone_auth_info_destroy(info);
ms_free(realm);
}
bad_proxy:
if (linphoneAddress)
linphone_address_destroy(linphoneAddress);
// in case of error, show an alert to the user
if (error != nil) {
if (newAccountParams != NULL) { // If we get here, then we're also sure that Account != NULL
linphone_account_params_unref(newAccountParams);
}
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error", nil)
message:error
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
}
}
// reload address book to prepend proxy config domain to contacts' phone number
[[LinphoneManager.instance fastAddressBook] fetchContactsInBackGroundThread];
}
- (void)synchronizeCodecs:(const MSList *)codecs {
PayloadType *pt;
const MSList *elem;
for (elem = codecs; elem != NULL; elem = elem->next) {
pt = (PayloadType *)elem->data;
NSString *pref = [LinphoneManager getPreferenceForCodec:pt->mime_type withRate:pt->clock_rate];
linphone_core_enable_payload_type(LC, pt, [self boolForKey:pref]);
}
}
- (BOOL)synchronize {
@try {
LinphoneManager *lm = LinphoneManager.instance;
// root section
BOOL account_changed = NO;
for (NSString *key in self->changedDict) {
if ([key hasPrefix:@"account_"] && [self valueChangedForKey:key]) {
account_changed = YES;
break;
}
}
account_changed |= [self valueChangedForKey:@"port_preference"];
account_changed |= [self valueChangedForKey:@"random_port_preference"];
account_changed |= [self valueChangedForKey:@"use_ipv6"];
if (account_changed)
[self synchronizeAccounts];
bool enableVideo = [self boolForKey:@"enable_video_preference"];
linphone_core_enable_video_capture(LC, enableVideo);
linphone_core_enable_video_display(LC, enableVideo);
bool enableAutoAnswer = [self boolForKey:@"enable_auto_answer_preference"];
[LinphoneManager.instance lpConfigSetBool:enableAutoAnswer forKey:@"auto_answer"];
// audio section
[self synchronizeCodecs:linphone_core_get_audio_codecs(LC)];
float playback_gain = [self floatForKey:@"playback_gain_preference"];
linphone_core_set_playback_gain_db(LC, playback_gain);
float mic_gain = [self floatForKey:@"microphone_gain_preference"];
linphone_core_set_mic_gain_db(LC, mic_gain);
[lm lpConfigSetInt:[self integerForKey:@"audio_codec_bitrate_limit_preference"]
forKey:@"codec_bitrate_limit"
inSection:@"audio"];
BOOL voice_processing = [self boolForKey:@"voiceproc_preference"];
[lm lpConfigSetInt:voice_processing forKey:@"voiceproc_preference"];
BOOL equalizer = [self boolForKey:@"eq_active"];
[lm lpConfigSetBool:equalizer forKey:@"eq_active" inSection:@"sound"];
[LinphoneManager.instance configureVbrCodecs];
NSString *au_device = @"AU: Audio Unit Receiver";
if (!voice_processing)
au_device = @"AU: Audio Unit NoVoiceProc";
linphone_core_set_capture_device(LC, [au_device UTF8String]);
linphone_core_set_playback_device(LC, [au_device UTF8String]);
// video section
[self synchronizeCodecs:linphone_core_get_video_codecs(LC)];
LinphoneVideoPolicy policy;
policy.automatically_initiate = [self boolForKey:@"start_video_preference"];
policy.automatically_accept = [self boolForKey:@"accept_video_preference"];
linphone_core_set_video_policy(LC, &policy);
linphone_core_enable_self_view(LC, [self boolForKey:@"self_video_preference"]);
BOOL preview_preference = IPAD && [self boolForKey:@"preview_preference"];
[lm lpConfigSetInt:preview_preference forKey:@"preview_preference"];
NSString *videoPreset = [self stringForKey:@"video_preset_preference"];
linphone_core_set_video_preset(LC, [videoPreset UTF8String]);
MSVideoSize vsize;
switch ([self integerForKey:@"video_preferred_size_preference"]) {
case 0:
MS_VIDEO_SIZE_ASSIGN(vsize, 720P);
break;
case 1:
MS_VIDEO_SIZE_ASSIGN(vsize, VGA);
break;
case 2:
default:
MS_VIDEO_SIZE_ASSIGN(vsize, QVGA);
break;
}
linphone_core_set_preferred_video_size(LC, vsize);
if (![videoPreset isEqualToString:@"custom"]) {
[self setInteger:0 forKey:@"video_preferred_fps_preference"];
[self setInteger:0 forKey:@"download_bandwidth_preference"];
}
linphone_core_set_preferred_framerate(LC, [self integerForKey:@"video_preferred_fps_preference"]);
linphone_core_set_download_bandwidth(LC, [self integerForKey:@"download_bandwidth_preference"]);
linphone_core_set_upload_bandwidth(LC, [self integerForKey:@"download_bandwidth_preference"]);
// call section
linphone_core_set_use_rfc2833_for_dtmf(LC, [self boolForKey:@"rfc_dtmf_preference"]);
linphone_core_set_use_info_for_dtmf(LC, [self boolForKey:@"sipinfo_dtmf_preference"]);
linphone_core_set_inc_timeout(LC, [self integerForKey:@"incoming_call_timeout_preference"]);
linphone_core_set_in_call_timeout(LC, [self integerForKey:@"in_call_timeout_preference"]);
[lm lpConfigSetString:[self stringForKey:@"voice_mail_uri_preference"] forKey:@"voice_mail_uri"];
[lm lpConfigSetBool:[self boolForKey:@"repeat_call_notification_preference"] forKey:@"repeat_call_notification"];
[lm lpConfigSetBool:[self boolForKey:@"pref_accept_early_media_preference"] forKey:@"pref_accept_early_media"];
linphone_core_set_media_encryption_mandatory(LC, [self boolForKey:@"media_encrption_mandatory_preference"]);
linphone_core_set_file_transfer_server(LC, [self stringForKey:@"file_transfer_server_url_preference"].UTF8String);
int maxSize;
NSString *downloadMode = [self stringForKey:@"auto_download_mode"];
if ([downloadMode isEqualToString:@"Never"]) {
maxSize = -1;
} else if ([downloadMode isEqualToString:@"Always"]) {
maxSize = 0;
} else {
maxSize = [[self stringForKey:@"auto_download_incoming_files_max_size"] intValue];
}
linphone_core_set_max_size_for_auto_download_incoming_files(LC, maxSize);
[lm lpConfigSetString:[self stringForKey:@"auto_download_mode"] forKey:@"auto_download_mode"];
BOOL vfsPrefEnabled = [self boolForKey:@"vfs_enabled_mode"] || [VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId];
if (vfsPrefEnabled && ![VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) {
if (TARGET_IPHONE_SIMULATOR) {
LOGW(@"[VFS] Can not active for simulators.");
[VFSUtil setVfsEnabbledWithEnabled:false groupName:kLinphoneMsgNotificationAppGroupId];
[self setBool:FALSE forKey:@"vfs_enabled_mode"];
} else if (![VFSUtil activateVFSForFirstTime:true]) {
[VFSUtil log:@"[VFS] Error unable to activate ! Warning disabling VFS enabled preference." :OS_LOG_TYPE_ERROR];
[VFSUtil setVfsEnabbledWithEnabled:false groupName:kLinphoneMsgNotificationAppGroupId];
[self setBool:FALSE forKey:@"vfs_enabled_mode"];
} else {
[VFSUtil setVfsEnabbledWithEnabled:true groupName:kLinphoneMsgNotificationAppGroupId];
[self setBool:TRUE forKey:@"vfs_enabled_mode"];
}
}
[lm lpConfigSetBool:[self boolForKey:@"auto_write_to_gallery_mode"] forKey:@"auto_write_to_gallery_preference"];
[lm lpConfigSetBool:[self boolForKey:@"ephemeral_feature"] forKey:@"ephemeral_feature"];
// network section
BOOL edgeOpt = [self boolForKey:@"edge_opt_preference"];
[lm lpConfigSetInt:edgeOpt forKey:@"edge_opt_preference"];
BOOL wifiOnly = [self boolForKey:@"wifi_only_preference"];
[lm lpConfigSetInt:wifiOnly forKey:@"wifi_only_preference"];
LinphoneNatPolicy *LNP = linphone_core_get_nat_policy(LC);
BOOL ice_preference = [self boolForKey:@"ice_preference"];
linphone_nat_policy_enable_ice(LNP, ice_preference);
linphone_nat_policy_enable_turn(LNP, [self boolForKey:@"turn_preference"]);
NSString *stun_server = [self stringForKey:@"stun_preference"];
if ([stun_server length] > 0) {
linphone_nat_policy_set_stun_server(LNP, [stun_server UTF8String]);
linphone_nat_policy_enable_stun(LNP, ice_preference); /*we always use STUN with ICE*/
NSString *turn_username = [self stringForKey:@"turn_username"];
NSString *turn_password = [self stringForKey:@"turn_password"];
if ([turn_username length] > 0) {
const LinphoneAuthInfo *turnAuthInfo = nil;
if ([turn_password length] > 0){
turnAuthInfo = linphone_auth_info_new([turn_username UTF8String], NULL, [turn_password UTF8String], NULL, NULL, NULL);
linphone_core_add_auth_info(LC, turnAuthInfo);
} else
turnAuthInfo = linphone_core_find_auth_info(LC, NULL, [turn_username UTF8String], NULL);
linphone_nat_policy_set_stun_server_username(LNP, [turn_username UTF8String]);
}
} else {
linphone_nat_policy_enable_stun(LNP, FALSE);
linphone_nat_policy_set_stun_server(LNP, NULL);
}
linphone_core_set_nat_policy(LC, LNP);
NSString *audio_port_preference = [self stringForKey:@"audio_port_preference"];
int audioMinPort, audioMaxPort;
[LinphoneCoreSettingsStore parsePortRange:audio_port_preference minPort:&audioMinPort maxPort:&audioMaxPort];
linphone_core_set_audio_port_range(LC, audioMinPort, audioMaxPort);
NSString *video_port_preference = [self stringForKey:@"video_port_preference"];
int videoMinPort, videoMaxPort;
[LinphoneCoreSettingsStore parsePortRange:video_port_preference minPort:&videoMinPort maxPort:&videoMaxPort];
linphone_core_set_video_port_range(LC, videoMinPort, videoMaxPort);
NSString *menc = [self stringForKey:@"media_encryption_preference"];
if (menc && [menc compare:@"SRTP"] == NSOrderedSame)
linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionSRTP);
else if (menc && [menc compare:@"ZRTP"] == NSOrderedSame)
linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionZRTP);
else if (menc && [menc compare:@"DTLS"] == NSOrderedSame)
linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionDTLS);
else
linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionNone);
linphone_core_enable_adaptive_rate_control(LC, [self boolForKey:@"adaptive_rate_control_preference"]);
// tunnel section
if (linphone_core_tunnel_available()) {
NSString *lTunnelPrefMode = [self stringForKey:@"tunnel_mode_preference"];
NSString *lTunnelPrefAddress = [self stringForKey:@"tunnel_address_preference"];
int lTunnelPrefPort = [self integerForKey:@"tunnel_port_preference"];
LinphoneTunnel *tunnel = linphone_core_get_tunnel(LC);
LinphoneTunnelMode mode = LinphoneTunnelModeDisable;
int lTunnelPort = 443;
if (lTunnelPrefPort)
lTunnelPort = lTunnelPrefPort;
linphone_tunnel_clean_servers(tunnel);
if (lTunnelPrefAddress && [lTunnelPrefAddress length]) {
LinphoneTunnelConfig *ltc = linphone_tunnel_config_new();
linphone_tunnel_config_set_host(ltc, [lTunnelPrefAddress UTF8String]);
linphone_tunnel_config_set_port(ltc, lTunnelPort);
linphone_tunnel_add_server(tunnel, ltc);
if ([lTunnelPrefMode isEqualToString:@"off"])
mode = LinphoneTunnelModeDisable;
else if ([lTunnelPrefMode isEqualToString:@"on"])
mode = LinphoneTunnelModeEnable;
else if ([lTunnelPrefMode isEqualToString:@"auto"])
mode = LinphoneTunnelModeAuto;
else
LOGE(@"Unexpected tunnel mode [%s]", [lTunnelPrefMode UTF8String]);
}
[lm lpConfigSetString:lTunnelPrefMode forKey:@"tunnel_mode_preference"];
linphone_tunnel_set_mode(tunnel, mode);
}
// advanced section
BOOL animations = [self boolForKey:@"animations_preference"];
[lm lpConfigSetInt:animations forKey:@"animations_preference"];
UIDevice *device = [UIDevice currentDevice];
BOOL backgroundSupported = [device respondsToSelector:@selector(isMultitaskingSupported)] && [device isMultitaskingSupported];
BOOL isbackgroundModeEnabled = backgroundSupported && [self boolForKey:@"backgroundmode_preference"];
[lm lpConfigSetInt:isbackgroundModeEnabled forKey:@"backgroundmode_preference"];
[lm lpConfigSetInt:[self integerForKey:@"start_at_boot_preference"] forKey:@"start_at_boot_preference"];
[lm lpConfigSetInt:[self integerForKey:@"autoanswer_notif_preference"] forKey:@"autoanswer_notif_preference"];
[lm lpConfigSetInt:[self integerForKey:@"show_msg_in_notif"] forKey:@"show_msg_in_notif"];
if ([self integerForKey:@"use_rls_presence"]) {
[self setInteger:0 forKey:@"use_rls_presence"];
NSString *rls_uri = [lm lpConfigStringForKey:@"rls_uri" inSection:@"sip" withDefault:@"sips:rls@sip.linphone.org"];
LinphoneAddress *rls_addr = linphone_address_new(rls_uri.UTF8String);
const char *rls_domain = linphone_address_get_domain(rls_addr);
const MSList *accounts = linphone_core_get_account_list(LC);
if (!accounts) // Enable it if no proxy config for first launch of app
[self setInteger:1 forKey:@"use_rls_presence"];
else {
while (accounts) {
const char *proxy_domain = linphone_account_params_get_domain(linphone_account_get_params(accounts->data));
if (strcmp(rls_domain, proxy_domain) == 0) {
[self setInteger:1 forKey:@"use_rls_presence"];
break;
}
accounts = accounts->next;
}
}
linphone_address_unref(rls_addr);
}
[lm lpConfigSetInt:[self integerForKey:@"use_rls_presence"] forKey:@"use_rls_presence"];
linphone_core_enable_friend_list_subscription(LC, [self integerForKey:@"use_rls_presence"]);
BOOL firstloginview = [self boolForKey:@"enable_first_login_view_preference"];
[lm lpConfigSetInt:firstloginview forKey:@"enable_first_login_view_preference"];
NSString *displayname = [self stringForKey:@"primary_displayname_preference"];
NSString *username = [self stringForKey:@"primary_username_preference"];
LinphoneAddress *parsed = linphone_core_get_primary_contact_parsed(LC);
if (parsed != NULL) {
linphone_address_set_display_name(parsed, [displayname UTF8String]);
linphone_address_set_username(parsed, [username UTF8String]);
char *contact = linphone_address_as_string(parsed);
linphone_core_set_primary_contact(LC, contact);
ms_free(contact);
linphone_address_destroy(parsed);
}
[lm lpConfigSetInt:[self integerForKey:@"account_mandatory_advanced_preference"] forKey:@"account_mandatory_advanced_preference"];
changedDict = [[NSMutableDictionary alloc] init];
// Post event
NSDictionary *eventDic = [NSDictionary dictionaryWithObject:self forKey:@"settings"];
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneSettingsUpdate object:self userInfo:eventDic];
return YES;
} @catch (NSException *exception) {
// may happen when application is terminated, since we are destroying the core
if ([exception.name isEqualToString:@"LinphoneCoreException"]) {
LOGE(@"Core already destroyed, settings not synchronized");
return NO;
}
LOGE(@"Uncaught exception : %@", exception.description);
abort();
}
return NO;
}
- (void)removeAccount {
LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC),
[self integerForKey:@"current_proxy_config_preference"]);
const MSList *lists = linphone_core_get_friends_lists(LC);
while (lists) {
linphone_friend_list_enable_subscriptions(lists->data, FALSE);
linphone_friend_list_update_subscriptions(lists->data);
lists = lists->next;
}
BOOL isDefault = (linphone_core_get_default_account(LC) == account);
const LinphoneAuthInfo *ai = linphone_account_find_auth_info(account);
linphone_core_remove_account(LC, account);
if (ai) {
// Friend list unsubscription above is not instantanous, so give a bit of a time margin before finishing the removal of the auth info
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
linphone_core_remove_auth_info(LC, ai);
});
}
[self setInteger:-1 forKey:@"current_proxy_config_preference"];
if (isDefault) {
// if we removed the default proxy config, set another one instead
if (linphone_core_get_account_list(LC) != NULL) {
linphone_core_set_default_account(LC, (LinphoneAccount *)(linphone_core_get_account_list(LC)->data));
}
}
[self transformLinphoneCoreToKeys];
}
@end