From 0f46facecd558786631c2ad4cf66d27331f16a86 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Fri, 19 Mar 2021 23:09:56 +0100 Subject: Add UI for audio/video calls --- .../ui/conversation_content_view/call_widget.vala | 215 +++++++++++++++++++++ .../content_populator.vala | 3 + .../ui/conversation_content_view/file_widget.vala | 3 - 3 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 main/src/ui/conversation_content_view/call_widget.vala (limited to 'main/src/ui/conversation_content_view') diff --git a/main/src/ui/conversation_content_view/call_widget.vala b/main/src/ui/conversation_content_view/call_widget.vala new file mode 100644 index 00000000..66788e28 --- /dev/null +++ b/main/src/ui/conversation_content_view/call_widget.vala @@ -0,0 +1,215 @@ +using Gee; +using Gdk; +using Gtk; +using Pango; + +using Dino.Entities; + +namespace Dino.Ui { + + public class CallMetaItem : ConversationSummary.ContentMetaItem { + + private StreamInteractor stream_interactor; + + public CallMetaItem(ContentItem content_item, StreamInteractor stream_interactor) { + base(content_item); + this.stream_interactor = stream_interactor; + } + + public override Object? get_widget(Plugins.WidgetType type) { + CallItem call_item = content_item as CallItem; + return new CallWidget(stream_interactor, call_item.call, call_item.conversation) { visible=true }; + } + + public override Gee.List? get_item_actions(Plugins.WidgetType type) { return null; } + } + + [GtkTemplate (ui = "/im/dino/Dino/call_widget.ui")] + public class CallWidget : SizeRequestBox { + + [GtkChild] public Image image; + [GtkChild] public Label title_label; + [GtkChild] public Label subtitle_label; + [GtkChild] public Revealer incoming_call_revealer; + [GtkChild] public Button accept_call_button; + [GtkChild] public Button reject_call_button; + + private StreamInteractor stream_interactor; + private Call call; + private Conversation conversation; + public Call.State call_state { get; set; } // needs to be public for binding + private uint time_update_handler_id = 0; + + construct { + margin_top = 4; + size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH; + } + + public CallWidget(StreamInteractor stream_interactor, Call call, Conversation conversation) { + this.stream_interactor = stream_interactor; + this.call = call; + this.conversation = conversation; + + size_allocate.connect((allocation) => { + if (allocation.height > parent.get_allocated_height()) { + Idle.add(() => { parent.queue_resize(); return false; }); + } + }); + + call.bind_property("state", this, "call-state"); + this.notify["call-state"].connect(update_widget); + + accept_call_button.clicked.connect(() => { + stream_interactor.get_module(Calls.IDENTITY).accept_call(call); + + var call_window = new CallWindow(); + call_window.controller = new CallWindowController(call_window, call, stream_interactor); + call_window.present(); + }); + + reject_call_button.clicked.connect(() => { + stream_interactor.get_module(Calls.IDENTITY).reject_call(call); + }); + + update_widget(); + } + + private void update_widget() { + incoming_call_revealer.reveal_child = false; + incoming_call_revealer.get_style_context().remove_class("incoming"); + + switch (call.state) { + case Call.State.RINGING: + image.set_from_icon_name("dino-phone-ring-symbolic", IconSize.LARGE_TOOLBAR); + if (call.direction == Call.DIRECTION_INCOMING) { + bool video = stream_interactor.get_module(Calls.IDENTITY).should_we_send_video(call); + title_label.label = video ? _("Video call incoming") : _("Call incoming"); + subtitle_label.label = "Ring ring…!"; + incoming_call_revealer.reveal_child = true; + incoming_call_revealer.get_style_context().add_class("incoming"); + } else { + title_label.label = _("Establishing call"); + subtitle_label.label = "Ring ring…?"; + } + break; + case Call.State.ESTABLISHING: + image.set_from_icon_name("dino-phone-ring-symbolic", IconSize.LARGE_TOOLBAR); + if (call.direction == Call.DIRECTION_INCOMING) { + bool video = stream_interactor.get_module(Calls.IDENTITY).should_we_send_video(call); + title_label.label = video ? _("Video call establishing") : _("Call establishing"); + subtitle_label.label = "Connecting…"; + } + break; + case Call.State.IN_PROGRESS: + image.set_from_icon_name("dino-phone-in-talk-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = _("Call in progress…"); + string duration = get_duration_string((new DateTime.now_utc()).difference(call.local_time)); + subtitle_label.label = _("Started %s ago").printf(duration); + + time_update_handler_id = Timeout.add_seconds(get_next_time_change() + 1, () => { + Source.remove(time_update_handler_id); + time_update_handler_id = 0; + update_widget(); + return true; + }); + + break; + case Call.State.OTHER_DEVICE_ACCEPTED: + image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = call.direction == Call.DIRECTION_INCOMING ? _("Incoming call") : _("Outgoing call"); + subtitle_label.label = _("You handled this call on another device"); + + break; + case Call.State.ENDED: + image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = _("Call ended"); + string formated_end = Util.format_time(call.end_time, _("%H∶%M"), _("%l∶%M %p")); + string duration = get_duration_string(call.end_time.difference(call.local_time)); + subtitle_label.label = _("Ended at %s").printf(formated_end) + + " · " + + _("Lasted for %s").printf(duration); + break; + case Call.State.MISSED: + image.set_from_icon_name("dino-phone-missed-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = _("Call missed"); + string who = null; + if (call.direction == Call.DIRECTION_INCOMING) { + who = "You"; + } else { + who = Util.get_participant_display_name(stream_interactor, conversation, call.to); + } + subtitle_label.label = "%s missed this call".printf(who); + break; + case Call.State.DECLINED: + image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = _("Call declined"); + string who = null; + if (call.direction == Call.DIRECTION_INCOMING) { + who = "You"; + } else { + who = Util.get_participant_display_name(stream_interactor, conversation, call.to); + } + subtitle_label.label = "%s declined this call".printf(who); + break; + case Call.State.FAILED: + image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); + title_label.label = _("Call failed"); + subtitle_label.label = "This call failed to establish"; + break; + } + } + + private string get_duration_string(TimeSpan duration) { + DateTime a = new DateTime.now_utc(); + DateTime b = new DateTime.now_utc(); + a.difference(b); + + TimeSpan remainder_duration = duration; + + int hours = (int) Math.floor(remainder_duration / TimeSpan.HOUR); + remainder_duration -= hours * TimeSpan.HOUR; + + int minutes = (int) Math.floor(remainder_duration / TimeSpan.MINUTE); + remainder_duration -= minutes * TimeSpan.MINUTE; + + string ret = ""; + + if (hours > 0) { + ret += n("%i hour", "%i hours", hours).printf(hours); + } + + if (minutes > 0) { + if (ret.length > 0) { + ret += " "; + } + ret += n("%i minute", "%i minutes", minutes).printf(minutes); + } + + if (ret.length > 0) { + return ret; + } + + return _("seconds"); + } + + private int get_next_time_change() { + DateTime now = new DateTime.now_local(); + DateTime item_time = call.local_time; + + if (now.get_second() < item_time.get_second()) { + return item_time.get_second() - now.get_second(); + } else { + return 60 - (now.get_second() - item_time.get_second()); + } + } + + public override void dispose() { + base.dispose(); + + if (time_update_handler_id != 0) { + Source.remove(time_update_handler_id); + time_update_handler_id = 0; + } + } + } +} diff --git a/main/src/ui/conversation_content_view/content_populator.vala b/main/src/ui/conversation_content_view/content_populator.vala index 97f15bf9..d7ce9ce5 100644 --- a/main/src/ui/conversation_content_view/content_populator.vala +++ b/main/src/ui/conversation_content_view/content_populator.vala @@ -68,7 +68,10 @@ public class ContentProvider : ContentItemCollection, Object { return new MessageMetaItem(content_item, stream_interactor); } else if (content_item.type_ == FileItem.TYPE) { return new FileMetaItem(content_item, stream_interactor); + } else if (content_item.type_ == CallItem.TYPE) { + return new CallMetaItem(content_item, stream_interactor); } + critical("Got unknown content item type %s", content_item.type_); return null; } } diff --git a/main/src/ui/conversation_content_view/file_widget.vala b/main/src/ui/conversation_content_view/file_widget.vala index 9b748876..7d77ba11 100644 --- a/main/src/ui/conversation_content_view/file_widget.vala +++ b/main/src/ui/conversation_content_view/file_widget.vala @@ -32,9 +32,6 @@ public class FileWidget : SizeRequestBox { DEFAULT } - private const int MAX_HEIGHT = 300; - private const int MAX_WIDTH = 600; - private StreamInteractor stream_interactor; private FileTransfer file_transfer; public FileTransfer.State file_transfer_state { get; set; } -- cgit v1.2.3-70-g09d2 From 8d1c6c29be7018c74ec3f8ea05f5849eac5b4069 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Thu, 8 Apr 2021 12:07:04 +0200 Subject: Display+store call encryption info --- libdino/src/entity/call.vala | 5 ++ libdino/src/entity/encryption.vala | 4 +- libdino/src/service/calls.vala | 44 ++++++++++++- libdino/src/service/content_item_store.vala | 4 +- libdino/src/service/database.vala | 5 +- main/data/theme.css | 25 ++++++-- main/src/ui/call_window/call_bottom_bar.vala | 53 ++++++++++++++-- .../src/ui/call_window/call_window_controller.vala | 4 ++ .../content_populator.vala | 1 + .../conversation_item_skeleton.vala | 74 +++++++++++++--------- plugins/ice/src/dtls_srtp.vala | 46 ++++++++++---- plugins/ice/src/transport_parameters.vala | 12 ++-- xmpp-vala/src/module/xep/0166_jingle/content.vala | 9 +++ .../xep/0167_jingle_rtp/content_parameters.vala | 3 + .../src/module/xep/0167_jingle_rtp/stream.vala | 2 + .../0176_jingle_ice_udp/jingle_ice_udp_module.vala | 4 +- .../0176_jingle_ice_udp/transport_parameters.vala | 47 +++++++++++--- 17 files changed, 270 insertions(+), 72 deletions(-) (limited to 'main/src/ui/conversation_content_view') diff --git a/libdino/src/entity/call.vala b/libdino/src/entity/call.vala index 7891dae7..577b3ab8 100644 --- a/libdino/src/entity/call.vala +++ b/libdino/src/entity/call.vala @@ -32,6 +32,7 @@ namespace Dino.Entities { public DateTime time { get; set; } public DateTime local_time { get; set; } public DateTime end_time { get; set; } + public Encryption encryption { get; set; default=Encryption.NONE; } public State state { get; set; } @@ -57,6 +58,7 @@ namespace Dino.Entities { time = new DateTime.from_unix_utc(row[db.call.time]); local_time = new DateTime.from_unix_utc(row[db.call.local_time]); end_time = new DateTime.from_unix_utc(row[db.call.end_time]); + encryption = (Encryption) row[db.call.encryption]; state = (State) row[db.call.state]; notify.connect(on_update); @@ -74,6 +76,7 @@ namespace Dino.Entities { .value(db.call.direction, direction) .value(db.call.time, (long) time.to_unix()) .value(db.call.local_time, (long) local_time.to_unix()) + .value(db.call.encryption, encryption) .value(db.call.state, State.ENDED); // No point in persisting states that can't survive a restart if (end_time != null) { builder.value(db.call.end_time, (long) end_time.to_unix()); @@ -116,6 +119,8 @@ namespace Dino.Entities { update_builder.set(db.call.local_time, (long) local_time.to_unix()); break; case "end-time": update_builder.set(db.call.end_time, (long) end_time.to_unix()); break; + case "encryption": + update_builder.set(db.call.encryption, encryption); break; case "state": // No point in persisting states that can't survive a restart if (state == State.RINGING || state == State.ESTABLISHING || state == State.IN_PROGRESS) return; diff --git a/libdino/src/entity/encryption.vala b/libdino/src/entity/encryption.vala index b50556f9..25d55eb1 100644 --- a/libdino/src/entity/encryption.vala +++ b/libdino/src/entity/encryption.vala @@ -3,7 +3,9 @@ namespace Dino.Entities { public enum Encryption { NONE, PGP, - OMEMO + OMEMO, + DTLS_SRTP, + SRTP, } } \ No newline at end of file diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala index 93636c03..b457c764 100644 --- a/libdino/src/service/calls.vala +++ b/libdino/src/service/calls.vala @@ -14,6 +14,7 @@ namespace Dino { public signal void counterpart_ringing(Call call); public signal void counterpart_sends_video_updated(Call call, bool mute); public signal void info_received(Call call, Xep.JingleRtp.CallSessionInfo session_info); + public signal void encryption_updated(Call call, Xep.Jingle.ContentEncryption? encryption); public signal void stream_created(Call call, string media); @@ -22,7 +23,6 @@ namespace Dino { private StreamInteractor stream_interactor; private Database db; - private Xep.JingleRtp.SessionInfoType session_info_type; private HashMap> sid_by_call = new HashMap>(Account.hash_func, Account.equals_func); private HashMap> call_by_sid = new HashMap>(Account.hash_func, Account.equals_func); @@ -38,7 +38,10 @@ namespace Dino { private HashMap audio_content_parameter = new HashMap(Call.hash_func, Call.equals_func); private HashMap video_content_parameter = new HashMap(Call.hash_func, Call.equals_func); + private HashMap audio_content = new HashMap(Call.hash_func, Call.equals_func); private HashMap video_content = new HashMap(Call.hash_func, Call.equals_func); + private HashMap video_encryption = new HashMap(Call.hash_func, Call.equals_func); + private HashMap audio_encryption = new HashMap(Call.hash_func, Call.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { Calls m = new Calls(stream_interactor, db); @@ -290,7 +293,7 @@ namespace Dino { } // Session might have already been accepted via Jingle Message Initiation - bool already_accepted = jmi_sid.contains(account) && + bool already_accepted = jmi_sid.has_key(account) && jmi_sid[account] == session.sid && jmi_call[account].account.equals(account) && jmi_call[account].counterpart.equals_bare(session.peer_full_jid) && jmi_video[account] == counterpart_wants_video; @@ -365,6 +368,7 @@ namespace Dino { if (call.state == Call.State.RINGING || call.state == Call.State.ESTABLISHING) { call.state = Call.State.IN_PROGRESS; } + update_call_encryption(call); } private void on_call_terminated(Call call, bool we_terminated, string? reason_name, string? reason_text) { @@ -429,6 +433,7 @@ namespace Dino { private void connect_content_signals(Call call, Xep.Jingle.Content content, Xep.JingleRtp.Parameters rtp_content_parameter) { if (rtp_content_parameter.media == "audio") { + audio_content[call] = content; audio_content_parameter[call] = rtp_content_parameter; } else if (rtp_content_parameter.media == "video") { video_content[call] = content; @@ -450,6 +455,36 @@ namespace Dino { on_counterpart_mute_update(call, false, "video"); } }); + + content.notify["encryption"].connect((obj, _) => { + if (rtp_content_parameter.media == "audio") { + audio_encryption[call] = ((Xep.Jingle.Content) obj).encryption; + } else if (rtp_content_parameter.media == "video") { + video_encryption[call] = ((Xep.Jingle.Content) obj).encryption; + } + }); + } + + private void update_call_encryption(Call call) { + if (audio_encryption[call] == null) { + call.encryption = Encryption.NONE; + encryption_updated(call, null); + return; + } + + bool consistent_encryption = video_encryption[call] != null && audio_encryption[call].encryption_ns == video_encryption[call].encryption_ns; + + if (video_content[call] == null || consistent_encryption) { + if (audio_encryption[call].encryption_ns == Xep.JingleIceUdp.DTLS_NS_URI) { + call.encryption = Encryption.DTLS_SRTP; + } else if (audio_encryption[call].encryption_name == "SRTP") { + call.encryption = Encryption.SRTP; + } + encryption_updated(call, audio_encryption[call]); + } else { + call.encryption = Encryption.NONE; + encryption_updated(call, null); + } } private void remove_call_from_datastructures(Call call) { @@ -465,7 +500,10 @@ namespace Dino { audio_content_parameter.unset(call); video_content_parameter.unset(call); + audio_content.unset(call); video_content.unset(call); + audio_encryption.unset(call); + video_encryption.unset(call); } private void on_account_added(Account account) { @@ -526,7 +564,7 @@ namespace Dino { } else if (from.equals_bare(call_by_sid[account][sid].counterpart)) { // Message from our peer // We proposed the call if (jmi_sid.has_key(account) && jmi_sid[account] == sid) { - call_resource(account, from, jmi_call[account], jmi_video[account], jmi_sid[account]); + call_resource.begin(account, from, jmi_call[account], jmi_video[account], jmi_sid[account]); jmi_call.unset(account); jmi_sid.unset(account); jmi_video.unset(account); diff --git a/libdino/src/service/content_item_store.vala b/libdino/src/service/content_item_store.vala index cde8dd10..6ab0529c 100644 --- a/libdino/src/service/content_item_store.vala +++ b/libdino/src/service/content_item_store.vala @@ -316,10 +316,12 @@ public class CallItem : ContentItem { public Conversation conversation; public CallItem(Call call, Conversation conversation, int id) { - base(id, TYPE, call.from, call.time, Encryption.NONE, Message.Marked.NONE); + base(id, TYPE, call.from, call.time, call.encryption, Message.Marked.NONE); this.call = call; this.conversation = conversation; + + call.bind_property("encryption", this, "encryption"); } } diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 98e18d16..9703260a 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -7,7 +7,7 @@ using Dino.Entities; namespace Dino { public class Database : Qlite.Database { - private const int VERSION = 20; + private const int VERSION = 21; public class AccountTable : Table { public Column id = new Column.Integer("id") { primary_key = true, auto_increment = true }; @@ -165,11 +165,12 @@ public class Database : Qlite.Database { public Column time = new Column.Long("time") { not_null = true }; public Column local_time = new Column.Long("local_time") { not_null = true }; public Column end_time = new Column.Long("end_time"); + public Column encryption = new Column.Integer("encryption") { min_version=21 }; public Column state = new Column.Integer("state"); internal CallTable(Database db) { base(db, "call"); - init({id, account_id, counterpart_id, counterpart_resource, our_resource, direction, time, local_time, end_time, state}); + init({id, account_id, counterpart_id, counterpart_resource, our_resource, direction, time, local_time, end_time, encryption, state}); } } diff --git a/main/data/theme.css b/main/data/theme.css index 423cbf68..454bd2c1 100644 --- a/main/data/theme.css +++ b/main/data/theme.css @@ -235,17 +235,24 @@ box.dino-input-error label.input-status-highlight-once { outline: 0; border-radius: 1000px; } + .dino-call-window button.white-button { color: #1d1c1d; - background: rgba(255,255,255,0.9); + background: rgba(255,255,255,0.85); border: lightgrey; } +.dino-call-window button.white-button:hover { + background: rgba(255,255,255,1); +} .dino-call-window button.transparent-white-button { color: white; background: rgba(255,255,255,0.15); border: none; } +.dino-call-window button.transparent-white-button:hover { + background: rgba(255,255,255,0.25); +} .dino-call-window button.call-mediadevice-settings-button { border-radius: 1000px; @@ -265,11 +272,21 @@ box.dino-input-error label.input-status-highlight-once { margin: 0; } -.dino-call-window .unencrypted-box { - color: @error_color; - padding: 10px; +.dino-call-window .encryption-box { + color: rgba(255,255,255,0.7); border-radius: 5px; background: rgba(0,0,0,0.5); + padding: 0px; + border: none; + box-shadow: none; +} + +.dino-call-window .encryption-box.unencrypted { + color: @error_color; +} + +.dino-call-window .encryption-box:hover { + background: rgba(20,20,20,0.5); } .dino-call-window .call-header-bar { diff --git a/main/src/ui/call_window/call_bottom_bar.vala b/main/src/ui/call_window/call_bottom_bar.vala index bc800485..c6375ea2 100644 --- a/main/src/ui/call_window/call_bottom_bar.vala +++ b/main/src/ui/call_window/call_bottom_bar.vala @@ -1,5 +1,6 @@ using Dino.Entities; using Gtk; +using Pango; public class Dino.Ui.CallBottomBar : Gtk.Box { @@ -24,6 +25,10 @@ public class Dino.Ui.CallBottomBar : Gtk.Box { private MenuButton video_settings_button = new MenuButton() { halign=Align.END, valign=Align.END }; public VideoSettingsPopover? video_settings_popover; + private EventBox encryption_event_box = new EventBox() { visible=true }; + private MenuButton encryption_button = new MenuButton() { relief=ReliefStyle.NONE, height_request=30, width_request=30, margin_start=20, margin_bottom=25, halign=Align.START, valign=Align.END }; + private Image encryption_image = new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON) { visible=true }; + private Label label = new Label("") { margin=20, halign=Align.CENTER, valign=Align.CENTER, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, hexpand=true, visible=true }; private Stack stack = new Stack() { visible=true }; @@ -31,11 +36,9 @@ public class Dino.Ui.CallBottomBar : Gtk.Box { Object(orientation:Orientation.HORIZONTAL, spacing:0); Overlay default_control = new Overlay() { visible=true }; - Image encryption_image = new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON) { margin_start=20, margin_bottom=25, halign=Align.START, valign=Align.END, visible=true }; - encryption_image.tooltip_text = _("Unencrypted"); - encryption_image.get_style_context().add_class("unencrypted-box"); - - default_control.add_overlay(encryption_image); + encryption_button.add(encryption_image); + encryption_button.get_style_context().add_class("encryption-box"); + default_control.add_overlay(encryption_button); Box main_buttons = new Box(Orientation.HORIZONTAL, 20) { margin_start=40, margin_end=40, margin=20, halign=Align.CENTER, hexpand=true, visible=true }; @@ -87,6 +90,33 @@ public class Dino.Ui.CallBottomBar : Gtk.Box { this.get_style_context().add_class("call-bottom-bar"); } + public void set_encryption(Xmpp.Xep.Jingle.ContentEncryption? encryption) { + encryption_button.visible = true; + + Popover popover = new Popover(encryption_button); + + if (encryption == null) { + encryption_image.set_from_icon_name("changes-allow-symbolic", IconSize.BUTTON); + encryption_button.get_style_context().add_class("unencrypted"); + + popover.add(new Label("This call isn't encrypted.") { margin=10, visible=true } ); + } else { + encryption_image.set_from_icon_name("changes-prevent-symbolic", IconSize.BUTTON); + encryption_button.get_style_context().remove_class("unencrypted"); + + Grid encryption_info_grid = new Grid() { margin=10, row_spacing=3, column_spacing=5, visible=true }; + encryption_info_grid.attach(new Label("This call is end-to-end encrypted.") { use_markup=true, xalign=0, visible=true }, 1, 1, 2, 1); + encryption_info_grid.attach(new Label("Peer key") { xalign=0, visible=true }, 1, 2, 1, 1); + encryption_info_grid.attach(new Label("Your key") { xalign=0, visible=true }, 1, 3, 1, 1); + encryption_info_grid.attach(new Label("" + format_fingerprint(encryption.peer_key) + "") { use_markup=true, max_width_chars=25, ellipsize=EllipsizeMode.MIDDLE, xalign=0, hexpand=true, visible=true }, 2, 2, 1, 1); + encryption_info_grid.attach(new Label("" + format_fingerprint(encryption.our_key) + "") { use_markup=true, max_width_chars=25, ellipsize=EllipsizeMode.MIDDLE, xalign=0, hexpand=true, visible=true }, 2, 3, 1, 1); + + popover.add(encryption_info_grid); + } + + encryption_button.set_popover(popover); + } + public AudioSettingsPopover? show_audio_device_choices(bool show) { audio_settings_button.visible = show; if (audio_settings_popover != null) audio_settings_popover.visible = false; @@ -160,6 +190,17 @@ public class Dino.Ui.CallBottomBar : Gtk.Box { } public bool is_menu_active() { - return video_settings_button.active || audio_settings_button.active; + return video_settings_button.active || audio_settings_button.active || encryption_button.active; + } + + private string format_fingerprint(uint8[] fingerprint) { + var sb = new StringBuilder(); + for (int i = 0; i < fingerprint.length; i++) { + sb.append("%02x".printf(fingerprint[i])); + if (i < fingerprint.length - 1) { + sb.append(":"); + } + } + return sb.str; } } \ No newline at end of file diff --git a/main/src/ui/call_window/call_window_controller.vala b/main/src/ui/call_window/call_window_controller.vala index 09c8f88c..f66a37e1 100644 --- a/main/src/ui/call_window/call_window_controller.vala +++ b/main/src/ui/call_window/call_window_controller.vala @@ -67,6 +67,10 @@ public class Dino.Ui.CallWindowController : Object { call_window.set_status("ringing"); } }); + calls.encryption_updated.connect((call, encryption) => { + if (!this.call.equals(call)) return; + call_window.bottom_bar.set_encryption(encryption); + }); own_video.resolution_changed.connect((width, height) => { if (width == 0 || height == 0) return; diff --git a/main/src/ui/conversation_content_view/content_populator.vala b/main/src/ui/conversation_content_view/content_populator.vala index d7ce9ce5..ef859bde 100644 --- a/main/src/ui/conversation_content_view/content_populator.vala +++ b/main/src/ui/conversation_content_view/content_populator.vala @@ -88,6 +88,7 @@ public abstract class ContentMetaItem : Plugins.MetaConversationItem { this.mark = content_item.mark; content_item.bind_property("mark", this, "mark"); + content_item.bind_property("encryption", this, "encryption"); this.can_merge = true; this.requires_avatar = true; diff --git a/main/src/ui/conversation_content_view/conversation_item_skeleton.vala b/main/src/ui/conversation_content_view/conversation_item_skeleton.vala index c0099bf4..bcb6864e 100644 --- a/main/src/ui/conversation_content_view/conversation_item_skeleton.vala +++ b/main/src/ui/conversation_content_view/conversation_item_skeleton.vala @@ -104,7 +104,7 @@ public class ItemMetaDataHeader : Box { [GtkChild] public Label dot_label; [GtkChild] public Label time_label; public Image received_image = new Image() { opacity=0.4 }; - public Image? unencrypted_image = null; + public Widget? encryption_image = null; public static IconSize ICON_SIZE_HEADER = Gtk.icon_size_register("im.dino.Dino.HEADER_ICON", 17, 12); @@ -124,50 +124,66 @@ public class ItemMetaDataHeader : Box { update_name_label(); name_label.style_updated.connect(update_name_label); + conversation.notify["encryption"].connect(update_unencrypted_icon); + item.notify["encryption"].connect(update_encryption_icon); + update_encryption_icon(); + + this.add(received_image); + + if (item.time != null) { + update_time(); + } + + item.bind_property("mark", this, "item-mark"); + this.notify["item-mark"].connect_after(update_received_mark); + update_received_mark(); + } + + private void update_encryption_icon() { Application app = GLib.Application.get_default() as Application; ContentMetaItem ci = item as ContentMetaItem; - if (ci != null) { + if (item.encryption != Encryption.NONE && ci != null) { + Widget? widget = null; foreach(var e in app.plugin_registry.encryption_list_entries) { if (e.encryption == item.encryption) { - Object? w = e.get_encryption_icon(conversation, ci.content_item); - if (w != null) { - this.add(w as Widget); - } else { - Image image = new Image.from_icon_name("dino-changes-prevent-symbolic", ICON_SIZE_HEADER) { opacity=0.4, visible = true }; - this.add(image); - } + widget = e.get_encryption_icon(conversation, ci.content_item) as Widget; break; } } + if (widget == null) { + widget = new Image.from_icon_name("dino-changes-prevent-symbolic", ICON_SIZE_HEADER) { opacity=0.4, visible = true }; + } + update_encryption_image(widget); } if (item.encryption == Encryption.NONE) { - conversation.notify["encryption"].connect(update_unencrypted_icon); update_unencrypted_icon(); } + } - this.add(received_image); - - if (item.time != null) { - update_time(); + private void update_unencrypted_icon() { + if (item.encryption != Encryption.NONE) return; + + if (conversation.encryption != Encryption.NONE && encryption_image == null) { + Image image = new Image() { opacity=0.4, visible = true }; + image.set_from_icon_name("dino-changes-allowed-symbolic", ICON_SIZE_HEADER); + image.tooltip_text = _("Unencrypted"); + update_encryption_image(image); + Util.force_error_color(image); + } else if (conversation.encryption == Encryption.NONE && encryption_image != null) { + update_encryption_image(null); } - - item.bind_property("mark", this, "item-mark"); - this.notify["item-mark"].connect_after(update_received_mark); - update_received_mark(); } - private void update_unencrypted_icon() { - if (conversation.encryption != Encryption.NONE && unencrypted_image == null) { - unencrypted_image = new Image() { opacity=0.4, visible = true }; - unencrypted_image.set_from_icon_name("dino-changes-allowed-symbolic", ICON_SIZE_HEADER); - unencrypted_image.tooltip_text = _("Unencrypted"); - this.add(unencrypted_image); - this.reorder_child(unencrypted_image, 3); - Util.force_error_color(unencrypted_image); - } else if (conversation.encryption == Encryption.NONE && unencrypted_image != null) { - this.remove(unencrypted_image); - unencrypted_image = null; + private void update_encryption_image(Widget? widget) { + if (encryption_image != null) { + this.remove(encryption_image); + encryption_image = null; + } + if (widget != null) { + this.add(widget); + this.reorder_child(widget, 3); + encryption_image = widget; } } diff --git a/plugins/ice/src/dtls_srtp.vala b/plugins/ice/src/dtls_srtp.vala index f294e66b..e2470cf6 100644 --- a/plugins/ice/src/dtls_srtp.vala +++ b/plugins/ice/src/dtls_srtp.vala @@ -10,7 +10,10 @@ public class DtlsSrtp { private Mutex buffer_mutex = new Mutex(); private Gee.LinkedList buffer_queue = new Gee.LinkedList(); private uint pull_timeout = uint.MAX; - private string peer_fingerprint; + + private DigestAlgorithm? peer_fp_algo = null; + private uint8[] peer_fingerprint = null; + private uint8[] own_fingerprint; private Crypto.Srtp.Session srtp_session = new Crypto.Srtp.Session(); @@ -20,12 +23,13 @@ public class DtlsSrtp { return obj; } - internal string get_own_fingerprint(DigestAlgorithm digest_algo) { - return format_certificate(own_cert[0], digest_algo); + internal uint8[] get_own_fingerprint(DigestAlgorithm digest_algo) { + return own_fingerprint; } - public void set_peer_fingerprint(string fingerprint) { + public void set_peer_fingerprint(uint8[] fingerprint, DigestAlgorithm digest_algo) { this.peer_fingerprint = fingerprint; + this.peer_fp_algo = digest_algo; } public uint8[] process_incoming_data(uint component_id, uint8[] data) { @@ -94,10 +98,11 @@ public class DtlsSrtp { cert.sign(cert, private_key); + own_fingerprint = get_fingerprint(cert, DigestAlgorithm.SHA256); own_cert = new X509.Certificate[] { (owned)cert }; } - public async void setup_dtls_connection(bool server) { + public async Xmpp.Xep.Jingle.ContentEncryption setup_dtls_connection(bool server) { InitFlags server_or_client = server ? InitFlags.SERVER : InitFlags.CLIENT; debug("Setting up DTLS connection. We're %s", server_or_client.to_string()); @@ -149,6 +154,7 @@ public class DtlsSrtp { srtp_session.set_encryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, client_key.extract(), client_salt.extract()); srtp_session.set_decryption_key(Crypto.Srtp.AES_CM_128_HMAC_SHA1_80, server_key.extract(), server_salt.extract()); } + return new Xmpp.Xep.Jingle.ContentEncryption() { encryption_ns=Xmpp.Xep.JingleIceUdp.DTLS_NS_URI, encryption_name = "DTLS-SRTP", our_key=own_fingerprint, peer_key=peer_fingerprint }; } private static ssize_t pull_function(void* transport_ptr, uint8[] buffer) { @@ -226,24 +232,40 @@ public class DtlsSrtp { X509.Certificate peer_cert = X509.Certificate.create(); peer_cert.import(ref cert_datums[0], CertificateFormat.DER); - string peer_fp_str = format_certificate(peer_cert, DigestAlgorithm.SHA256); - if (peer_fp_str.down() != this.peer_fingerprint.down()) { - warning("First cert in peer cert list doesn't equal advertised one %s vs %s", peer_fp_str, this.peer_fingerprint); + uint8[] real_peer_fp = get_fingerprint(peer_cert, peer_fp_algo); + + if (real_peer_fp.length != this.peer_fingerprint.length) { + warning("Fingerprint lengths not equal %i vs %i", real_peer_fp.length, peer_fingerprint.length); return false; } + for (int i = 0; i < real_peer_fp.length; i++) { + if (real_peer_fp[i] != this.peer_fingerprint[i]) { + warning("First cert in peer cert list doesn't equal advertised one: %s vs %s", format_fingerprint(real_peer_fp), format_fingerprint(peer_fingerprint)); + return false; + } + } + return true; } - private string format_certificate(X509.Certificate certificate, DigestAlgorithm digest_algo) { + private uint8[] get_fingerprint(X509.Certificate certificate, DigestAlgorithm digest_algo) { uint8[] buf = new uint8[512]; size_t buf_out_size = 512; certificate.get_fingerprint(digest_algo, buf, ref buf_out_size); - var sb = new StringBuilder(); + uint8[] ret = new uint8[buf_out_size]; for (int i = 0; i < buf_out_size; i++) { - sb.append("%02x".printf(buf[i])); - if (i < buf_out_size - 1) { + ret[i] = buf[i]; + } + return ret; + } + + private string format_fingerprint(uint8[] fingerprint) { + var sb = new StringBuilder(); + for (int i = 0; i < fingerprint.length; i++) { + sb.append("%02x".printf(fingerprint[i])); + if (i < fingerprint.length - 1) { sb.append(":"); } } diff --git a/plugins/ice/src/transport_parameters.vala b/plugins/ice/src/transport_parameters.vala index 2db1ab1b..f95be261 100644 --- a/plugins/ice/src/transport_parameters.vala +++ b/plugins/ice/src/transport_parameters.vala @@ -68,9 +68,11 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport dtls_srtp = setup_dtls(this); this.own_fingerprint = dtls_srtp.get_own_fingerprint(GnuTLS.DigestAlgorithm.SHA256); if (incoming) { - dtls_srtp.set_peer_fingerprint(this.peer_fingerprint); + dtls_srtp.set_peer_fingerprint(this.peer_fingerprint, this.peer_fp_algo == "sha-256" ? GnuTLS.DigestAlgorithm.SHA256 : GnuTLS.DigestAlgorithm.NULL); } else { - dtls_srtp.setup_dtls_connection(true); + dtls_srtp.setup_dtls_connection.begin(true, (_, res) => { + this.content.encryption = dtls_srtp.setup_dtls_connection.end(res); + }); } } @@ -143,7 +145,7 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport base.handle_transport_accept(transport); if (dtls_srtp != null && peer_fingerprint != null) { - dtls_srtp.set_peer_fingerprint(this.peer_fingerprint); + dtls_srtp.set_peer_fingerprint(this.peer_fingerprint, this.peer_fp_algo == "sha-256" ? GnuTLS.DigestAlgorithm.SHA256 : GnuTLS.DigestAlgorithm.NULL); } else { dtls_srtp = null; } @@ -205,7 +207,9 @@ public class Dino.Plugins.Ice.TransportParameters : JingleIceUdp.IceUdpTransport if (incoming && dtls_srtp != null) { Jingle.DatagramConnection rtp_datagram = (Jingle.DatagramConnection) content.get_transport_connection(1); rtp_datagram.notify["ready"].connect(() => { - dtls_srtp.setup_dtls_connection(false); + dtls_srtp.setup_dtls_connection.begin(false, (_, res) => { + this.content.encryption = dtls_srtp.setup_dtls_connection.end(res); + }); }); } base.create_transport_connection(stream, content); diff --git a/xmpp-vala/src/module/xep/0166_jingle/content.vala b/xmpp-vala/src/module/xep/0166_jingle/content.vala index 67c13dd8..bce03a7b 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/content.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/content.vala @@ -34,6 +34,8 @@ public class Xmpp.Xep.Jingle.Content : Object { public weak Session session; public Map component_connections = new HashMap(); // TODO private + public ContentEncryption? encryption { get; set; } + // INITIATE_SENT | INITIATE_RECEIVED | CONNECTING public Set tried_transport_methods = new HashSet(); @@ -233,4 +235,11 @@ public class Xmpp.Xep.Jingle.Content : Object { public void send_transport_info(StanzaNode transport) { session.send_transport_info(this, transport); } +} + +public class Xmpp.Xep.Jingle.ContentEncryption : Object { + public string encryption_ns { get; set; } + public string encryption_name { get; set; } + public uint8[] our_key { get; set; } + public uint8[] peer_key { get; set; } } \ No newline at end of file diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala index ff3d31f4..ac65f88c 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/content_parameters.vala @@ -116,6 +116,9 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { remote_crypto = null; local_crypto = null; } + if (remote_crypto != null && local_crypto != null) { + content.encryption = new Xmpp.Xep.Jingle.ContentEncryption() { encryption_ns = "", encryption_name = "SRTP", our_key=local_crypto.key, peer_key=remote_crypto.key }; + } this.stream = parent.create_stream(content); rtp_datagram.datagram_received.connect(this.stream.on_recv_rtp_data); diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala index 730ce9f8..adae11f5 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala @@ -1,5 +1,7 @@ public abstract class Xmpp.Xep.JingleRtp.Stream : Object { + public Jingle.Content content { get; protected set; } + public string name { get { return content.content_name; }} diff --git a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala index 4b7c7a36..5211e3a9 100644 --- a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala +++ b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/jingle_ice_udp_module.vala @@ -5,6 +5,7 @@ using Xmpp; namespace Xmpp.Xep.JingleIceUdp { private const string NS_URI = "urn:xmpp:jingle:transports:ice-udp:1"; +public const string DTLS_NS_URI = "urn:xmpp:jingle:apps:dtls:0"; public abstract class Module : XmppStreamModule, Jingle.Transport { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "0176_jingle_ice_udp"); @@ -12,10 +13,11 @@ public abstract class Module : XmppStreamModule, Jingle.Transport { public override void attach(XmppStream stream) { stream.get_module(Jingle.Module.IDENTITY).register_transport(this); stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); - stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, "urn:xmpp:jingle:apps:dtls:0"); + stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, DTLS_NS_URI); } public override void detach(XmppStream stream) { stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI); + stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, DTLS_NS_URI); } public override string get_ns() { return NS_URI; } diff --git a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala index 3c69d0af..f194f06d 100644 --- a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala +++ b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala @@ -13,8 +13,9 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T public ConcurrentList unsent_local_candidates = new ConcurrentList(Candidate.equals_func); public Gee.List remote_candidates = new ArrayList(Candidate.equals_func); - public string? own_fingerprint = null; - public string? peer_fingerprint = null; + public uint8[]? own_fingerprint = null; + public uint8[]? peer_fingerprint = null; + public string? peer_fp_algo = null; public Jid local_full_jid { get; private set; } public Jid peer_full_jid { get; private set; } @@ -24,7 +25,7 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T public bool incoming { get; private set; default = false; } private bool connection_created = false; - private weak Jingle.Content? content = null; + protected weak Jingle.Content? content = null; protected IceUdpTransportParameters(uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode? node = null) { this.components_ = components; @@ -38,9 +39,10 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T remote_candidates.add(Candidate.parse(candidateNode)); } - StanzaNode? fingerprint_node = node.get_subnode("fingerprint", "urn:xmpp:jingle:apps:dtls:0"); + StanzaNode? fingerprint_node = node.get_subnode("fingerprint", DTLS_NS_URI); if (fingerprint_node != null) { - peer_fingerprint = fingerprint_node.get_deep_string_content(); + peer_fingerprint = fingerprint_to_bytes(fingerprint_node.get_deep_string_content()); + peer_fp_algo = fingerprint_node.get_attribute("hash"); } } } @@ -67,10 +69,10 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T .put_attribute("pwd", local_pwd); if (own_fingerprint != null) { - var fingerprint_node = new StanzaNode.build("fingerprint", "urn:xmpp:jingle:apps:dtls:0") + var fingerprint_node = new StanzaNode.build("fingerprint", DTLS_NS_URI) .add_self_xmlns() .put_attribute("hash", "sha-256") - .put_node(new StanzaNode.text(own_fingerprint)); + .put_node(new StanzaNode.text(format_fingerprint(own_fingerprint))); if (incoming) { fingerprint_node.put_attribute("setup", "active"); } else { @@ -95,9 +97,10 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T remote_candidates.add(Candidate.parse(candidateNode)); } - StanzaNode? fingerprint_node = node.get_subnode("fingerprint", "urn:xmpp:jingle:apps:dtls:0"); + StanzaNode? fingerprint_node = node.get_subnode("fingerprint", DTLS_NS_URI); if (fingerprint_node != null) { - peer_fingerprint = fingerprint_node.get_deep_string_content(); + peer_fingerprint = fingerprint_to_bytes(fingerprint_node.get_deep_string_content()); + peer_fp_algo = fingerprint_node.get_attribute("hash"); } } @@ -138,4 +141,30 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T content.send_transport_info(to_transport_stanza_node()); } } + + + + private string format_fingerprint(uint8[] fingerprint) { + var sb = new StringBuilder(); + for (int i = 0; i < fingerprint.length; i++) { + sb.append("%02x".printf(fingerprint[i])); + if (i < fingerprint.length - 1) { + sb.append(":"); + } + } + return sb.str; + } + + private uint8[] fingerprint_to_bytes(string? fingerprint_) { + if (fingerprint_ == null) return null; + + string fingerprint = fingerprint_.replace(":", "").up(); + + uint8[] bin = new uint8[fingerprint.length / 2]; + const string HEX = "0123456789ABCDEF"; + for (int i = 0; i < fingerprint.length / 2; i++) { + bin[i] = (uint8) (HEX.index_of_char(fingerprint[i*2]) << 4) | HEX.index_of_char(fingerprint[i*2+1]); + } + return bin; + } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 7d2e64769067c1b47e0500f6456dd7e6f4eb435a Mon Sep 17 00:00:00 2001 From: fiaxh Date: Thu, 29 Apr 2021 15:56:22 +0200 Subject: Improve call wording, cleanup --- main/src/ui/call_window/call_bottom_bar.vala | 1 - main/src/ui/call_window/call_window.vala | 4 +- .../src/ui/call_window/call_window_controller.vala | 30 +++--- .../ui/conversation_content_view/call_widget.vala | 2 +- main/src/ui/conversation_titlebar/call_entry.vala | 5 +- .../omemo/src/dtls_srtp_verification_draft.vala | 1 - plugins/rtp/src/module.vala | 12 +-- plugins/rtp/src/stream.vala | 4 +- .../src/module/xep/0166_jingle/component.vala | 12 ++- xmpp-vala/src/module/xep/0166_jingle/content.vala | 4 +- .../xep/0167_jingle_rtp/jingle_rtp_module.vala | 18 ++-- .../0176_jingle_ice_udp/transport_parameters.vala | 2 +- .../src/module/xep/0234_jingle_file_transfer.vala | 20 ++-- .../module/xep/0260_jingle_socks5_bytestreams.vala | 118 +++++++++++---------- .../xep/0261_jingle_in_band_bytestreams.vala | 2 +- 15 files changed, 125 insertions(+), 110 deletions(-) (limited to 'main/src/ui/conversation_content_view') diff --git a/main/src/ui/call_window/call_bottom_bar.vala b/main/src/ui/call_window/call_bottom_bar.vala index a9fee8c3..a3e4b93b 100644 --- a/main/src/ui/call_window/call_bottom_bar.vala +++ b/main/src/ui/call_window/call_bottom_bar.vala @@ -25,7 +25,6 @@ public class Dino.Ui.CallBottomBar : Gtk.Box { private MenuButton video_settings_button = new MenuButton() { halign=Align.END, valign=Align.END }; public VideoSettingsPopover? video_settings_popover; - private EventBox encryption_event_box = new EventBox() { visible=true }; private MenuButton encryption_button = new MenuButton() { relief=ReliefStyle.NONE, height_request=30, width_request=30, margin_start=20, margin_bottom=25, halign=Align.START, valign=Align.END }; private Image encryption_image = new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON) { visible=true }; diff --git a/main/src/ui/call_window/call_window.vala b/main/src/ui/call_window/call_window.vala index 572f73b6..3b3d4dc2 100644 --- a/main/src/ui/call_window/call_window.vala +++ b/main/src/ui/call_window/call_window.vala @@ -158,13 +158,13 @@ namespace Dino.Ui { public void set_status(string state) { switch (state) { case "requested": - header_bar.subtitle = _("Sending a call request…"); + header_bar.subtitle = _("Calling…"); break; case "ringing": header_bar.subtitle = _("Ringing…"); break; case "establishing": - header_bar.subtitle = _("Establishing a (peer-to-peer) connection…"); + header_bar.subtitle = _("Connecting…"); break; default: header_bar.subtitle = null; diff --git a/main/src/ui/call_window/call_window_controller.vala b/main/src/ui/call_window/call_window_controller.vala index 616e341d..0a223d72 100644 --- a/main/src/ui/call_window/call_window_controller.vala +++ b/main/src/ui/call_window/call_window_controller.vala @@ -3,8 +3,6 @@ using Gtk; public class Dino.Ui.CallWindowController : Object { - public signal void terminated(); - private CallWindow call_window; private Call call; private Conversation conversation; @@ -40,8 +38,16 @@ public class Dino.Ui.CallWindowController : Object { call_window.set_status("requested"); } - call_window.bottom_bar.hang_up.connect(end_call); - call_window.destroy.connect(end_call); + call_window.bottom_bar.hang_up.connect(() => { + calls.end_call(conversation, call); + call_window.close(); + call_window.destroy(); + this.dispose(); + }); + call_window.destroy.connect(() => { + calls.end_call(conversation, call); + this.dispose(); + }); call_window.bottom_bar.notify["audio-enabled"].connect(() => { calls.mute_own_audio(call, !call_window.bottom_bar.audio_enabled); @@ -116,16 +122,6 @@ public class Dino.Ui.CallWindowController : Object { this.window_width = this.call_window.get_allocated_width(); } - private void end_call() { - call.notify["state"].disconnect(on_call_state_changed); - calls.call_terminated.disconnect(on_call_terminated); - - calls.end_call(conversation, call); - call_window.close(); - call_window.destroy(); - terminated(); - } - private void on_call_state_changed() { if (call.state == Call.State.IN_PROGRESS) { call_window.set_status(""); @@ -234,4 +230,10 @@ public class Dino.Ui.CallWindowController : Object { call_window.unset_own_video(); } } + + public override void dispose() { + base.dispose(); + call.notify["state"].disconnect(on_call_state_changed); + calls.call_terminated.disconnect(on_call_terminated); + } } \ No newline at end of file diff --git a/main/src/ui/conversation_content_view/call_widget.vala b/main/src/ui/conversation_content_view/call_widget.vala index 66788e28..74525d11 100644 --- a/main/src/ui/conversation_content_view/call_widget.vala +++ b/main/src/ui/conversation_content_view/call_widget.vala @@ -154,7 +154,7 @@ namespace Dino.Ui { case Call.State.FAILED: image.set_from_icon_name("dino-phone-hangup-symbolic", IconSize.LARGE_TOOLBAR); title_label.label = _("Call failed"); - subtitle_label.label = "This call failed to establish"; + subtitle_label.label = "Call failed to establish"; break; } } diff --git a/main/src/ui/conversation_titlebar/call_entry.vala b/main/src/ui/conversation_titlebar/call_entry.vala index e1d10e5c..9353f631 100644 --- a/main/src/ui/conversation_titlebar/call_entry.vala +++ b/main/src/ui/conversation_titlebar/call_entry.vala @@ -92,9 +92,6 @@ namespace Dino.Ui { call_window.present(); update_button_state(); - call_controller.terminated.connect(() => { - update_button_state(); - }); } public new void set_conversation(Conversation conversation) { @@ -119,7 +116,7 @@ namespace Dino.Ui { if (conversation.type_ == Conversation.Type.CHAT) { Conversation conv_bak = conversation; bool audio_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_audio_calls_async(conversation); - bool video_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_audio_calls_async(conversation); + bool video_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_video_calls_async(conversation); if (conv_bak != conversation) return; visible = audio_works; diff --git a/plugins/omemo/src/dtls_srtp_verification_draft.vala b/plugins/omemo/src/dtls_srtp_verification_draft.vala index e2441670..66a31954 100644 --- a/plugins/omemo/src/dtls_srtp_verification_draft.vala +++ b/plugins/omemo/src/dtls_srtp_verification_draft.vala @@ -65,7 +65,6 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft { stream.get_flag(Xep.Jingle.Flag.IDENTITY).get_session.begin(jingle_sid, (_, res) => { Xep.Jingle.Session? session = stream.get_flag(Xep.Jingle.Flag.IDENTITY).get_session.end(res); - if (session != null) print(@"$(session.contents_map.has_key(content_name))\n"); if (session == null || !session.contents_map.has_key(content_name)) return; var encryption = new OmemoContentEncryption() { encryption_ns=NS_URI, encryption_name="OMEMO", our_key=new uint8[0], peer_key=new uint8[0], peer_device_id=device_id_by_jingle_sid[jingle_sid] }; session.contents_map[content_name].encryptions[NS_URI] = encryption; diff --git a/plugins/rtp/src/module.vala b/plugins/rtp/src/module.vala index 13a21cd8..19a7501d 100644 --- a/plugins/rtp/src/module.vala +++ b/plugins/rtp/src/module.vala @@ -216,9 +216,9 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { } public override JingleRtp.Crypto? generate_local_crypto() { - uint8[] keyAndSalt = new uint8[30]; - Crypto.randomize(keyAndSalt); - return JingleRtp.Crypto.create(JingleRtp.Crypto.AES_CM_128_HMAC_SHA1_80, keyAndSalt); + uint8[] key_and_salt = new uint8[30]; + Crypto.randomize(key_and_salt); + return JingleRtp.Crypto.create(JingleRtp.Crypto.AES_CM_128_HMAC_SHA1_80, key_and_salt); } public override JingleRtp.Crypto? pick_remote_crypto(Gee.List cryptos) { @@ -230,8 +230,8 @@ public class Dino.Plugins.Rtp.Module : JingleRtp.Module { public override JingleRtp.Crypto? pick_local_crypto(JingleRtp.Crypto? remote) { if (remote == null || !remote.is_valid) return null; - uint8[] keyAndSalt = new uint8[30]; - Crypto.randomize(keyAndSalt); - return remote.rekey(keyAndSalt); + uint8[] key_and_salt = new uint8[30]; + Crypto.randomize(key_and_salt); + return remote.rekey(key_and_salt); } } \ No newline at end of file diff --git a/plugins/rtp/src/stream.vala b/plugins/rtp/src/stream.vala index 23634aa3..bd8a279f 100644 --- a/plugins/rtp/src/stream.vala +++ b/plugins/rtp/src/stream.vala @@ -256,7 +256,7 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } private void prepare_local_crypto() { - if (local_crypto != null && !crypto_session.has_encrypt) { + if (local_crypto != null && local_crypto.is_valid && !crypto_session.has_encrypt) { crypto_session.set_encryption_key(local_crypto.crypto_suite, local_crypto.key, local_crypto.salt); debug("Setting up encryption with key params %s", local_crypto.key_params); } @@ -396,7 +396,7 @@ public class Dino.Plugins.Rtp.Stream : Xmpp.Xep.JingleRtp.Stream { } private void prepare_remote_crypto() { - if (remote_crypto != null && !crypto_session.has_decrypt) { + if (remote_crypto != null && remote_crypto.is_valid && !crypto_session.has_decrypt) { crypto_session.set_decryption_key(remote_crypto.crypto_suite, remote_crypto.key, remote_crypto.salt); debug("Setting up decryption with key params %s", remote_crypto.key_params); } diff --git a/xmpp-vala/src/module/xep/0166_jingle/component.vala b/xmpp-vala/src/module/xep/0166_jingle/component.vala index 544bcd69..5d573522 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/component.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/component.vala @@ -31,7 +31,11 @@ namespace Xmpp.Xep.Jingle { protected Gee.Promise promise = new Gee.Promise(); private string? terminated = null; - public async void init(IOStream stream) { + public async void set_stream(IOStream? stream) { + if (stream == null) { + promise.set_exception(new IOError.FAILED("Jingle connection failed")); + return; + } assert(!this.stream.ready); promise.set_value(stream); if (terminated != null) { @@ -39,11 +43,17 @@ namespace Xmpp.Xep.Jingle { } } + public void set_error(GLib.Error? e) { + promise.set_exception(e); + } + public override async void terminate(bool we_terminated, string? reason_name = null, string? reason_text = null) { if (terminated == null) { terminated = (reason_name ?? "") + " - " + (reason_text ?? "") + @"we terminated? $we_terminated"; if (stream.ready) { yield stream.value.close_async(); + } else { + promise.set_exception(new IOError.FAILED("Jingle connection failed")); } } } diff --git a/xmpp-vala/src/module/xep/0166_jingle/content.vala b/xmpp-vala/src/module/xep/0166_jingle/content.vala index befe02f4..41310aeb 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/content.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/content.vala @@ -36,7 +36,7 @@ public class Xmpp.Xep.Jingle.Content : Object { public HashMap encryptions = new HashMap(); - public Set tried_transport_methods = new HashSet(); + private Set tried_transport_methods = new HashSet(); public Content.initiate_sent(string content_name, Senders senders, @@ -109,7 +109,7 @@ public class Xmpp.Xep.Jingle.Content : Object { transport_params.dispose(); foreach (ComponentConnection connection in component_connections.values) { - connection.terminate(we_terminated, reason_name, reason_text); + connection.terminate.begin(we_terminated, reason_name, reason_text); } } diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala index 6eb6289b..6b55cbe6 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/jingle_rtp_module.vala @@ -83,7 +83,7 @@ public abstract class Module : XmppStreamModule { } } - public async Jingle.Content add_outgoing_video_content(XmppStream stream, Jingle.Session session) { + public async Jingle.Content add_outgoing_video_content(XmppStream stream, Jingle.Session session) throws Jingle.Error { Jid my_jid = session.local_full_jid; Jid receiver_full_jid = session.peer_full_jid; @@ -168,7 +168,7 @@ public class Crypto { public string? session_params { get; private set; } public string tag { get; private set; } - public uint8[] key_and_salt { owned get { + public uint8[]? key_and_salt { owned get { if (!key_params.has_prefix("inline:")) return null; int endIndex = key_params.index_of("|"); if (endIndex < 0) endIndex = key_params.length; @@ -221,30 +221,30 @@ public class Crypto { case AES_CM_128_HMAC_SHA1_80: case AES_CM_128_HMAC_SHA1_32: case F8_128_HMAC_SHA1_80: - return key_and_salt.length == 30; + return key_and_salt != null && key_and_salt.length == 30; } return false; }} - public uint8[] key { owned get { - uint8[] key_and_salt = key_and_salt; + public uint8[]? key { owned get { + uint8[]? key_and_salt = key_and_salt; switch(crypto_suite) { case AES_CM_128_HMAC_SHA1_80: case AES_CM_128_HMAC_SHA1_32: case F8_128_HMAC_SHA1_80: - if (key_and_salt.length >= 16) return key_and_salt[0:16]; + if (key_and_salt != null && key_and_salt.length >= 16) return key_and_salt[0:16]; break; } return null; }} - public uint8[] salt { owned get { - uint8[] keyAndSalt = key_and_salt; + public uint8[]? salt { owned get { + uint8[]? key_and_salt = key_and_salt; switch(crypto_suite) { case AES_CM_128_HMAC_SHA1_80: case AES_CM_128_HMAC_SHA1_32: case F8_128_HMAC_SHA1_80: - if (keyAndSalt.length >= 30) return keyAndSalt[16:30]; + if (key_and_salt != null && key_and_salt.length >= 30) return key_and_salt[16:30]; break; } return null; diff --git a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala index 83da296b..07b599ee 100644 --- a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala +++ b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala @@ -152,7 +152,7 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T return sb.str; } - private uint8[] fingerprint_to_bytes(string? fingerprint_) { + private uint8[]? fingerprint_to_bytes(string? fingerprint_) { if (fingerprint_ == null) return null; string fingerprint = fingerprint_.replace(":", "").up(); diff --git a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala index 07b158bc..4581019f 100644 --- a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala +++ b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala @@ -268,13 +268,19 @@ public class FileTransfer : Object { content.accept(); Jingle.StreamingConnection connection = content.component_connections.values.to_array()[0] as Jingle.StreamingConnection; - IOStream? io_stream = yield connection.stream.wait_async(); - FileTransferInputStream ft_stream = new FileTransferInputStream(io_stream.input_stream, size); - io_stream.output_stream.close(); - ft_stream.closed.connect(() => { - session.terminate(Jingle.ReasonElement.SUCCESS, null, null); - }); - this.stream = ft_stream; + try { + IOStream io_stream = yield connection.stream.wait_async(); + FileTransferInputStream ft_stream = new FileTransferInputStream(io_stream.input_stream, size); + io_stream.output_stream.close(); + ft_stream.closed.connect(() => { + session.terminate(Jingle.ReasonElement.SUCCESS, null, null); + }); + this.stream = ft_stream; + } catch (FutureError.EXCEPTION e) { + warning("Error accepting Jingle file-transfer: %s", connection.stream.exception.message); + } catch (FutureError e) { + warning("FutureError accepting Jingle file-transfer: %s", e.message); + } } public void reject(XmppStream stream) { diff --git a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala index 6edebbbc..47c243e8 100644 --- a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala @@ -28,6 +28,7 @@ public class Module : Jingle.Transport, XmppStreamModule { public string ns_uri { get { return NS_URI; } } public Jingle.TransportType type_ { get { return Jingle.TransportType.STREAMING; } } public int priority { get { return 1; } } + private Gee.List get_proxies(XmppStream stream) { Gee.List result = new ArrayList(); int i = 1 << 15; @@ -37,6 +38,7 @@ public class Module : Jingle.Transport, XmppStreamModule { } return result; } + private Gee.List start_local_listeners(XmppStream stream, Jid local_full_jid, string dstaddr, out LocalListener? local_listener) { Gee.List result = new ArrayList(); SocketListener listener = new SocketListener(); @@ -62,15 +64,17 @@ public class Module : Jingle.Transport, XmppStreamModule { } return result; } + private void select_candidates(XmppStream stream, Jid local_full_jid, string dstaddr, Parameters result) { result.local_candidates.add_all(get_proxies(stream)); - //result.local_candidates.add_all(start_local_listeners(stream, local_full_jid, dstaddr, out result.listener)); + result.local_candidates.add_all(start_local_listeners(stream, local_full_jid, dstaddr, out result.listener)); result.local_candidates.sort((c1, c2) => { if (c1.priority < c2.priority) { return 1; } if (c1.priority > c2.priority) { return -1; } return 0; }); } + public Jingle.TransportParameters create_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid) { assert(components == 1); Parameters result = new Parameters.create(local_full_jid, peer_full_jid, random_uuid()); @@ -78,6 +82,7 @@ public class Module : Jingle.Transport, XmppStreamModule { select_candidates(stream, local_full_jid, dstaddr, result); return result; } + public Jingle.TransportParameters parse_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode transport) throws Jingle.IqError { Parameters result = Parameters.parse(local_full_jid, peer_full_jid, transport); string dstaddr = calculate_dstaddr(result.sid, local_full_jid, peer_full_jid); @@ -146,6 +151,7 @@ public class Candidate : Socks5Bytestreams.Proxy { public Candidate.build(string cid, string host, Jid jid, int port, int local_priority, CandidateType type) { this(cid, host, jid, port, type.type_preference() + local_priority, type); } + public Candidate.proxy(string cid, Socks5Bytestreams.Proxy proxy, int local_priority) { this.build(cid, proxy.host, proxy.jid, proxy.port, local_priority, CandidateType.PROXY); } @@ -170,6 +176,7 @@ public class Candidate : Socks5Bytestreams.Proxy { return new Candidate(cid, host, jid, port, priority, type); } + public StanzaNode to_xml() { return new StanzaNode.build("candidate", NS_URI) .put_attribute("cid", cid) @@ -210,6 +217,7 @@ class LocalListener { this.inner = inner; this.dstaddr = dstaddr; } + public LocalListener.empty() { this.inner = null; this.dstaddr = ""; @@ -233,6 +241,7 @@ class LocalListener { handle_conn.begin(((StringWrapper)cid).str, conn); } } + async void handle_conn(string cid, SocketConnection conn) { conn.socket.timeout = NEGOTIATION_TIMEOUT; size_t read; @@ -418,39 +427,39 @@ class Parameters : Jingle.TransportParameters, Object { } public void handle_transport_info(StanzaNode transport) throws Jingle.IqError { - StanzaNode? candidate_error = transport.get_subnode("candidate-error", NS_URI); - StanzaNode? candidate_used = transport.get_subnode("candidate-used", NS_URI); - StanzaNode? activated = transport.get_subnode("activated", NS_URI); - StanzaNode? proxy_error = transport.get_subnode("proxy-error", NS_URI); - int num_children = 0; - if (candidate_error != null) { num_children += 1; } - if (candidate_used != null) { num_children += 1; } - if (activated != null) { num_children += 1; } - if (proxy_error != null) { num_children += 1; } - if (num_children == 0) { - throw new Jingle.IqError.UNSUPPORTED_INFO("unknown transport-info"); - } else if (num_children > 1) { - throw new Jingle.IqError.BAD_REQUEST("transport-info with more than one child"); - } - if (candidate_error != null) { - handle_remote_candidate(null); - } - if (candidate_used != null) { - string? cid = candidate_used.get_attribute("cid"); - if (cid == null) { - throw new Jingle.IqError.BAD_REQUEST("missing cid"); - } - handle_remote_candidate(cid); - } - if (activated != null) { - string? cid = activated.get_attribute("cid"); - if (cid == null) { - throw new Jingle.IqError.BAD_REQUEST("missing cid"); - } - handle_activated(cid); + ArrayList socks5_nodes = new ArrayList(); + foreach (StanzaNode node in transport.sub_nodes) { + if (node.ns_uri == NS_URI) socks5_nodes.add(node); } - if (proxy_error != null) { - handle_proxy_error(); + if (socks5_nodes.is_empty) { warning("No socks5 subnodes in transport node"); return; } + if (socks5_nodes.size > 1) { warning("Too many socks5 subnodes in transport node"); return; } + + StanzaNode node = socks5_nodes[0]; + + switch (node.name) { + case "activated": + string? cid = node.get_attribute("cid"); + if (cid == null) { + throw new Jingle.IqError.BAD_REQUEST("missing cid"); + } + handle_activated(cid); + break; + case "candidate-used": + string? cid = node.get_attribute("cid"); + if (cid == null) { + throw new Jingle.IqError.BAD_REQUEST("missing cid"); + } + handle_remote_candidate(cid); + break; + case "candidate-error": + handle_remote_candidate(null); + break; + case "proxy-error": + handle_proxy_error(); + break; + default: + warning("Unknown transport-info: %s", transport.to_string()); + break; } } @@ -499,32 +508,22 @@ class Parameters : Jingle.TransportParameters, Object { return; } - Candidate? remote = remote_selected_candidate; - Candidate? local = local_selected_candidate; - - int num_candidates = 0; - if (remote != null) { num_candidates += 1; } - if (local != null) { num_candidates += 1; } - - if (num_candidates == 0) { - // Notify Jingle of the failed transport. - content_set_transport_connection(null); + if (remote_selected_candidate == null && local_selected_candidate == null) { + content_set_transport_connection_error(new IOError.FAILED("No candidates")); return; } bool remote_wins; - if (num_candidates == 1) { - remote_wins = remote != null; - } else { - if (local.priority < remote.priority) { - remote_wins = true; - } else if (local.priority > remote.priority) { - remote_wins = false; - } else { + if (remote_selected_candidate != null && local_selected_candidate != null) { + if (local_selected_candidate.priority == remote_selected_candidate.priority) { // equal priority -> XEP-0260 says that the candidate offered // by the initiator wins, so the one that the remote chose remote_wins = role == Jingle.Role.INITIATOR; + } else { + remote_wins = local_selected_candidate.priority < remote_selected_candidate.priority; } + } else { + remote_wins = remote_selected_candidate != null; } if (!remote_wins) { @@ -545,8 +544,7 @@ class Parameters : Jingle.TransportParameters, Object { } SocketConnection? conn = listener.get_connection(remote_selected_candidate.cid); if (conn == null) { - // Remote hasn't actually connected to us?! - content_set_transport_connection(null); + content_set_transport_connection_error(new IOError.FAILED("Remote hasn't actually connected to us?!")); return; } content_set_transport_connection(conn); @@ -569,7 +567,7 @@ class Parameters : Jingle.TransportParameters, Object { if (!waiting_for_activation_error) { content_set_transport_connection(conn); } else { - content_set_transport_connection(null); + content_set_transport_connection_error(new IOError.FAILED("waiting_for_activation_error")); } } @@ -620,7 +618,7 @@ class Parameters : Jingle.TransportParameters, Object { .put_attribute("sid", sid) .put_node(new StanzaNode.build("proxy-error", NS_URI)) ); - content_set_transport_connection(null); + content_set_transport_connection_error(new IOError.FAILED("Connect to local candidate error: %s", e.message)); } } @@ -745,15 +743,19 @@ class Parameters : Jingle.TransportParameters, Object { private Jingle.StreamingConnection connection = new Jingle.StreamingConnection(); - private void content_set_transport_connection(IOStream? ios) { - IOStream? iostream = ios; + private void content_set_transport_connection(IOStream ios) { + IOStream iostream = ios; Jingle.Content? strong_content = content; if (strong_content == null) return; if (strong_content.security_params != null) { iostream = strong_content.security_params.wrap_stream(iostream); } - connection.init.begin(iostream); + connection.set_stream.begin(iostream); + } + + private void content_set_transport_connection_error(Error e) { + connection.set_error(e); } public void create_transport_connection(XmppStream stream, Jingle.Content content) { diff --git a/xmpp-vala/src/module/xep/0261_jingle_in_band_bytestreams.vala b/xmpp-vala/src/module/xep/0261_jingle_in_band_bytestreams.vala index f7c77544..09eaf711 100644 --- a/xmpp-vala/src/module/xep/0261_jingle_in_band_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0261_jingle_in_band_bytestreams.vala @@ -98,7 +98,7 @@ class Parameters : Jingle.TransportParameters, Object { if (content.security_params != null) { iostream = content.security_params.wrap_stream(iostream); } - connection.init.begin(iostream); + connection.set_stream.begin(iostream); debug("set transport conn ibb"); content.set_transport_connection(connection, 1); } -- cgit v1.2.3-70-g09d2