From 26d10d1dcb95f11b65611473c9840e13683cb5ec Mon Sep 17 00:00:00 2001 From: fiaxh Date: Thu, 4 Nov 2021 17:33:08 +0100 Subject: Add multiparty call support to libdino and xmpp-vala --- xmpp-vala/src/module/xep/0045_muc/module.vala | 32 ++++-- .../src/module/xep/0166_jingle/component.vala | 2 + xmpp-vala/src/module/xep/0166_jingle/content.vala | 15 ++- xmpp-vala/src/module/xep/0166_jingle/session.vala | 5 +- .../xep/0167_jingle_rtp/content_parameters.vala | 16 +++ .../xep/0167_jingle_rtp/jingle_rtp_module.vala | 17 +-- .../module/xep/0167_jingle_rtp/payload_type.vala | 19 +++- .../0176_jingle_ice_udp/transport_parameters.vala | 17 ++- .../src/module/xep/0234_jingle_file_transfer.vala | 6 +- .../module/xep/0260_jingle_socks5_bytestreams.vala | 4 + .../src/module/xep/0384_omemo/omemo_decryptor.vala | 15 ++- xmpp-vala/src/module/xep/muji_meta.vala | 117 +++++++++++++++++++++ 12 files changed, 229 insertions(+), 36 deletions(-) create mode 100644 xmpp-vala/src/module/xep/muji_meta.vala (limited to 'xmpp-vala/src/module/xep') diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala index 4cab9b6a..c9445f7e 100644 --- a/xmpp-vala/src/module/xep/0045_muc/module.vala +++ b/xmpp-vala/src/module/xep/0045_muc/module.vala @@ -58,6 +58,7 @@ public class JoinResult { public MucEnterError? muc_error; public string? stanza_error; public string? nick; + public bool newly_created = false; } public class Module : XmppStreamModule { @@ -80,7 +81,7 @@ public class Module : XmppStreamModule { received_pipeline_listener = new ReceivedPipelineListener(this); } - public async JoinResult? enter(XmppStream stream, Jid bare_jid, string nick, string? password, DateTime? history_since) { + public async JoinResult? enter(XmppStream stream, Jid bare_jid, string nick, string? password, DateTime? history_since, StanzaNode? additional_node) { try { Presence.Stanza presence = new Presence.Stanza(); presence.to = bare_jid.with_resource(nick); @@ -96,6 +97,10 @@ public class Module : XmppStreamModule { } presence.stanza.put_node(x_node); + if (additional_node != null) { + presence.stanza.put_node(additional_node); + } + stream.get_flag(Flag.IDENTITY).start_muc_enter(bare_jid, presence.id); query_room_info.begin(stream, bare_jid); @@ -210,11 +215,15 @@ public class Module : XmppStreamModule { stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq); } - public void change_affiliation(XmppStream stream, Jid jid, string nick, string new_affiliation) { - StanzaNode query = new StanzaNode.build("query", NS_URI_ADMIN).add_self_xmlns(); - query.put_node(new StanzaNode.build("item", NS_URI_ADMIN).put_attribute("nick", nick, NS_URI_ADMIN).put_attribute("affiliation", new_affiliation, NS_URI_ADMIN)); - Iq.Stanza iq = new Iq.Stanza.set(query) { to=jid }; - stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq); + public async void change_affiliation(XmppStream stream, Jid muc_jid, Jid? user_jid, string? nick, string new_affiliation) { + StanzaNode item_node = new StanzaNode.build("item", NS_URI_ADMIN) + .put_attribute("affiliation", new_affiliation, NS_URI_ADMIN); + if (user_jid != null) item_node.put_attribute("jid", user_jid.to_string(), NS_URI_ADMIN); + if (nick != null) item_node.put_attribute("nick", nick, NS_URI_ADMIN); + + StanzaNode query = new StanzaNode.build("query", NS_URI_ADMIN).add_self_xmlns().put_node(item_node); + Iq.Stanza iq = new Iq.Stanza.set(query) { to=muc_jid }; + yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); } public async DataForms.DataForm? get_config_form(XmppStream stream, Jid jid) { @@ -229,19 +238,19 @@ public class Module : XmppStreamModule { return null; } - public void set_config_form(XmppStream stream, Jid jid, DataForms.DataForm data_form) { + public async void set_config_form(XmppStream stream, Jid jid, DataForms.DataForm data_form) { StanzaNode stanza_node = new StanzaNode.build("query", NS_URI_OWNER); stanza_node.add_self_xmlns().put_node(data_form.get_submit_node()); Iq.Stanza set_iq = new Iq.Stanza.set(stanza_node) { to=jid }; - stream.get_module(Iq.Module.IDENTITY).send_iq(stream, set_iq); + yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, set_iq); } public override void attach(XmppStream stream) { stream.add_flag(new Flag()); stream.get_module(MessageModule.IDENTITY).received_message.connect(on_received_message); stream.get_module(MessageModule.IDENTITY).received_pipeline.connect(received_pipeline_listener); - stream.get_module(Presence.Module.IDENTITY).received_presence.connect(check_for_enter_error); stream.get_module(Presence.Module.IDENTITY).received_available.connect(on_received_available); + stream.get_module(Presence.Module.IDENTITY).received_presence.connect(check_for_enter_error); stream.get_module(Presence.Module.IDENTITY).received_unavailable.connect(on_received_unavailable); stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); } @@ -249,8 +258,8 @@ public class Module : XmppStreamModule { public override void detach(XmppStream stream) { stream.get_module(MessageModule.IDENTITY).received_message.disconnect(on_received_message); stream.get_module(MessageModule.IDENTITY).received_pipeline.disconnect(received_pipeline_listener); - stream.get_module(Presence.Module.IDENTITY).received_presence.disconnect(check_for_enter_error); stream.get_module(Presence.Module.IDENTITY).received_available.disconnect(on_received_available); + stream.get_module(Presence.Module.IDENTITY).received_presence.disconnect(check_for_enter_error); stream.get_module(Presence.Module.IDENTITY).received_unavailable.disconnect(on_received_unavailable); stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI); } @@ -339,7 +348,8 @@ public class Module : XmppStreamModule { query_affiliation.begin(stream, bare_jid, "owner"); flag.finish_muc_enter(bare_jid); - flag.enter_futures[bare_jid].set_value(new JoinResult() {nick=presence.from.resourcepart}); + var join_result = new JoinResult() { nick=presence.from.resourcepart, newly_created=status_codes.contains(StatusCode.NEW_ROOM_CREATED) }; + flag.enter_futures[bare_jid].set_value(join_result); } flag.set_muc_nick(presence.from); diff --git a/xmpp-vala/src/module/xep/0166_jingle/component.vala b/xmpp-vala/src/module/xep/0166_jingle/component.vala index 5d573522..e30175d5 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/component.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/component.vala @@ -2,6 +2,8 @@ namespace Xmpp.Xep.Jingle { public abstract class ComponentConnection : Object { public uint8 component_id { get; set; default = 0; } + public ulong bytes_sent { get; protected set; default=0; } + public ulong bytes_received { get; protected set; default=0; } public abstract async void terminate(bool we_terminated, string? reason_name = null, string? reason_text = null); public signal void connection_closed(); public signal void connection_error(IOError e); diff --git a/xmpp-vala/src/module/xep/0166_jingle/content.vala b/xmpp-vala/src/module/xep/0166_jingle/content.vala index b51bb26d..31d8f9fc 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/content.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/content.vala @@ -95,16 +95,27 @@ public class Xmpp.Xep.Jingle.Content : Object { } public void accept() { + if (state != State.PENDING) { + warning("accepting a non-pending content"); + return; + } state = State.WANTS_TO_BE_ACCEPTED; - session.accept_content(this); } public void reject() { + if (state != State.PENDING) { + warning("rejecting a non-pending content"); + return; + } session.reject_content(this); } public void terminate(bool we_terminated, string? reason_name, string? reason_text) { + if (state == State.PENDING) { + warning("terminating a pending call"); + return; + } content_params.terminate(we_terminated, reason_name, reason_text); transport_params.dispose(); @@ -137,7 +148,7 @@ public class Xmpp.Xep.Jingle.Content : Object { this.content_params.handle_accept(stream, this.session, this, content_node.description); } - private async void select_new_transport() { + public async void select_new_transport() { XmppStream stream = session.stream; Transport? new_transport = yield stream.get_module(Module.IDENTITY).select_transport(stream, transport.type_, transport_params.components, peer_full_jid, tried_transport_methods); if (new_transport == null) { diff --git a/xmpp-vala/src/module/xep/0166_jingle/session.vala b/xmpp-vala/src/module/xep/0166_jingle/session.vala index a45fc6db..af913aab 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/session.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/session.vala @@ -264,10 +264,7 @@ public class Xmpp.Xep.Jingle.Session : Object { warning("Received invalid session accept: %s", e.message); } } - // TODO(hrxi): more sanity checking, perhaps replace who we're talking to - if (!responder.is_full()) { - throw new IqError.BAD_REQUEST("invalid responder JID"); - } + foreach (ContentNode content_node in content_nodes) { handle_content_accept(content_node); } 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 eadc1c8b..9022547d 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 @@ -21,6 +21,10 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { public Gee.List remote_cryptos = new ArrayList(); public Crypto? local_crypto = null; public Crypto? remote_crypto = null; + public Jid? muji_muc = null; + + public bool rtp_ready { get; private set; default=false; } + public bool rtcp_ready { get; private set; default=false; } public weak Stream? stream { get; private set; } @@ -28,6 +32,7 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { public Parameters(Module parent, string media, Gee.List payload_types, + Jid? muji_muc, string? ssrc = null, bool rtcp_mux = false, string? bandwidth = null, string? bandwidth_type = null, bool encryption_required = false, Crypto? local_crypto = null @@ -41,6 +46,7 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { this.encryption_required = encryption_required; this.payload_types = payload_types; this.local_crypto = local_crypto; + this.muji_muc = muji_muc; } public Parameters.from_node(Module parent, StanzaNode node) throws Jingle.IqError { @@ -61,6 +67,10 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { foreach (StanzaNode subnode in node.get_subnodes(HeaderExtension.NAME, HeaderExtension.NS_URI)) { this.header_extensions.add(HeaderExtension.parse(subnode)); } + string? muji_muc_str = node.get_deep_attribute(Xep.Muji.NS_URI + ":muji", "muc"); + if (muji_muc_str != null) { + muji_muc = new Jid(muji_muc_str); + } } public async void handle_proposed_content(XmppStream stream, Jingle.Session session, Jingle.Content content) { @@ -95,6 +105,7 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { ulong rtcp_ready_handler_id = 0; rtcp_ready_handler_id = rtcp_datagram.notify["ready"].connect((rtcp_datagram, _) => { this.stream.on_rtcp_ready(); + this.rtcp_ready = true; ((Jingle.DatagramConnection)rtcp_datagram).disconnect(rtcp_ready_handler_id); rtcp_ready_handler_id = 0; @@ -103,8 +114,10 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { ulong rtp_ready_handler_id = 0; rtp_ready_handler_id = rtp_datagram.notify["ready"].connect((rtp_datagram, _) => { this.stream.on_rtp_ready(); + this.rtp_ready = true; if (rtcp_mux) { this.stream.on_rtcp_ready(); + this.rtcp_ready = true; } connection_ready(); @@ -202,6 +215,9 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { if (rtcp_mux) { ret.put_node(new StanzaNode.build("rtcp-mux", NS_URI)); } + if (muji_muc != null) { + ret.put_node(new StanzaNode.build("muji", Xep.Muji.NS_URI).add_self_xmlns().put_attribute("muc", muji_muc.to_string())); + } return ret; } } 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 6b55cbe6..9dab5dc2 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 @@ -19,6 +19,7 @@ public abstract class Module : XmppStreamModule { } public abstract async Gee.List get_supported_payloads(string media); + public abstract async bool is_payload_supported(string media, JingleRtp.PayloadType payload_type); public abstract async PayloadType? pick_payload_type(string media, Gee.List payloads); public abstract Crypto? generate_local_crypto(); public abstract Crypto? pick_remote_crypto(Gee.List cryptos); @@ -28,7 +29,7 @@ public abstract class Module : XmppStreamModule { public abstract Gee.List get_suggested_header_extensions(string media); public abstract void close_stream(Stream stream); - public async Jingle.Session start_call(XmppStream stream, Jid receiver_full_jid, bool video, string? sid = null) throws Jingle.Error { + public async Jingle.Session start_call(XmppStream stream, Jid receiver_full_jid, bool video, string sid, Jid? muji_muc) throws Jingle.Error { Jingle.Module jingle_module = stream.get_module(Jingle.Module.IDENTITY); @@ -40,7 +41,7 @@ public abstract class Module : XmppStreamModule { ArrayList contents = new ArrayList(); // Create audio content - Parameters audio_content_parameters = new Parameters(this, "audio", yield get_supported_payloads("audio")); + Parameters audio_content_parameters = new Parameters(this, "audio", yield get_supported_payloads("audio"), muji_muc); audio_content_parameters.local_crypto = generate_local_crypto(); audio_content_parameters.header_extensions.add_all(get_suggested_header_extensions("audio")); Jingle.Transport? audio_transport = yield jingle_module.select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty()); @@ -48,7 +49,7 @@ public abstract class Module : XmppStreamModule { throw new Jingle.Error.NO_SHARED_PROTOCOLS("No suitable audio transports"); } Jingle.TransportParameters audio_transport_params = audio_transport.create_transport_parameters(stream, content_type.required_components, my_jid, receiver_full_jid); - Jingle.Content audio_content = new Jingle.Content.initiate_sent("voice", Jingle.Senders.BOTH, + Jingle.Content audio_content = new Jingle.Content.initiate_sent("audio", Jingle.Senders.BOTH, content_type, audio_content_parameters, audio_transport, audio_transport_params, null, null, @@ -58,7 +59,7 @@ public abstract class Module : XmppStreamModule { Jingle.Content? video_content = null; if (video) { // Create video content - Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video")); + Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video"), muji_muc); video_content_parameters.local_crypto = generate_local_crypto(); video_content_parameters.header_extensions.add_all(get_suggested_header_extensions("video")); Jingle.Transport? video_transport = yield stream.get_module(Jingle.Module.IDENTITY).select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty()); @@ -66,7 +67,7 @@ public abstract class Module : XmppStreamModule { throw new Jingle.Error.NO_SHARED_PROTOCOLS("No suitable video transports"); } Jingle.TransportParameters video_transport_params = video_transport.create_transport_parameters(stream, content_type.required_components, my_jid, receiver_full_jid); - video_content = new Jingle.Content.initiate_sent("webcam", Jingle.Senders.BOTH, + video_content = new Jingle.Content.initiate_sent("video", Jingle.Senders.BOTH, content_type, video_content_parameters, video_transport, video_transport_params, null, null, @@ -83,7 +84,7 @@ public abstract class Module : XmppStreamModule { } } - public async Jingle.Content add_outgoing_video_content(XmppStream stream, Jingle.Session session) throws Jingle.Error { + public async Jingle.Content add_outgoing_video_content(XmppStream stream, Jingle.Session session, Jid? muji_muc) throws Jingle.Error { Jid my_jid = session.local_full_jid; Jid receiver_full_jid = session.peer_full_jid; @@ -100,7 +101,7 @@ public abstract class Module : XmppStreamModule { if (content == null) { // Content for video does not yet exist -> create it - Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video")); + Parameters video_content_parameters = new Parameters(this, "video", yield get_supported_payloads("video"), muji_muc); video_content_parameters.local_crypto = generate_local_crypto(); video_content_parameters.header_extensions.add_all(get_suggested_header_extensions("video")); Jingle.Transport? video_transport = yield stream.get_module(Jingle.Module.IDENTITY).select_transport(stream, content_type.required_transport_type, content_type.required_components, receiver_full_jid, Set.empty()); @@ -108,7 +109,7 @@ public abstract class Module : XmppStreamModule { throw new Jingle.Error.NO_SHARED_PROTOCOLS("No suitable video transports"); } Jingle.TransportParameters video_transport_params = video_transport.create_transport_parameters(stream, content_type.required_components, my_jid, receiver_full_jid); - content = new Jingle.Content.initiate_sent("webcam", + content = new Jingle.Content.initiate_sent("video", session.we_initiated ? Jingle.Senders.INITIATOR : Jingle.Senders.RESPONDER, content_type, video_content_parameters, video_transport, video_transport_params, diff --git a/xmpp-vala/src/module/xep/0167_jingle_rtp/payload_type.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/payload_type.vala index faba38c9..73bc9800 100644 --- a/xmpp-vala/src/module/xep/0167_jingle_rtp/payload_type.vala +++ b/xmpp-vala/src/module/xep/0167_jingle_rtp/payload_type.vala @@ -64,12 +64,27 @@ public class Xmpp.Xep.JingleRtp.PayloadType { } public static bool equals_func(PayloadType a, PayloadType b) { - return a.id == b.id && + bool simple = a.id == b.id && a.name == b.name && a.channels == b.channels && a.clockrate == b.clockrate && a.maxptime == b.maxptime && - a.ptime == b.ptime; + a.ptime == b.ptime && + a.parameters.size == b.parameters.size && + a.rtcp_fbs.size == b.rtcp_fbs.size; + if (!simple) return false; + foreach (string key in a.parameters.keys) { + if (!b.parameters.has_key(key)) return false; + if (a.parameters[key] != b.parameters[key]) return false; + } + foreach (RtcpFeedback fb in a.rtcp_fbs) { + if (!b.rtcp_fbs.any_match((it) => it.type_ == fb.type_ && it.subtype == fb.subtype)) return false; + } + return true; + } + + public static uint hash_func(PayloadType payload_type) { + return payload_type.to_xml().to_string().hash(); } } 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 07b599ee..680d7c80 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 @@ -28,6 +28,7 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T private bool connection_created = false; protected weak Jingle.Content? content = null; + protected bool use_raw = false; protected IceUdpTransportParameters(uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode? node = null) { this.components_ = components; @@ -80,10 +81,16 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T node.put_node(fingerprint_node); } - foreach (Candidate candidate in unsent_local_candidates) { + if (action_type != "transport-info") { + foreach (Candidate candidate in unsent_local_candidates) { + node.put_node(candidate.to_xml()); + } + unsent_local_candidates.clear(); + } else if (!unsent_local_candidates.is_empty) { + Candidate candidate = unsent_local_candidates.first(); node.put_node(candidate.to_xml()); + unsent_local_candidates.remove(candidate); } - unsent_local_candidates.clear(); return node; } @@ -128,15 +135,15 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T local_candidates.add(candidate); if (this.content != null && (this.connection_created || !this.incoming)) { - Timeout.add(50, () => { + Idle.add( () => { check_send_transport_info(); - return false; + return Source.REMOVE; }); } } private void check_send_transport_info() { - if (this.content != null && unsent_local_candidates.size > 0) { + if (this.content != null && !unsent_local_candidates.is_empty) { content.send_transport_info(to_transport_stanza_node("transport-info")); } } 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 7b213ca5..f85049ac 100644 --- a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala +++ b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala @@ -100,7 +100,11 @@ public class Module : Jingle.ContentType, XmppStreamModule { yield; // Send the file data - Jingle.StreamingConnection connection = content.component_connections.values.to_array()[0] as Jingle.StreamingConnection; + Jingle.StreamingConnection connection = content.component_connections[1] as Jingle.StreamingConnection; + if (connection == null || connection.stream == null) { + warning("Connection or stream not null"); + return; + } IOStream io_stream = yield connection.stream.wait_async(); yield io_stream.input_stream.close_async(); yield io_stream.output_stream.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); 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 32ba8bb9..0fe9ce5f 100644 --- a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala @@ -759,6 +759,10 @@ class Parameters : Jingle.TransportParameters, Object { } private void content_set_transport_connection_error(Error e) { + Jingle.Content? strong_content = content; + if (strong_content == null) return; + + strong_content.select_new_transport.begin(); connection.set_error(e); } diff --git a/xmpp-vala/src/module/xep/0384_omemo/omemo_decryptor.vala b/xmpp-vala/src/module/xep/0384_omemo/omemo_decryptor.vala index 8e3213ae..a8ca5016 100644 --- a/xmpp-vala/src/module/xep/0384_omemo/omemo_decryptor.vala +++ b/xmpp-vala/src/module/xep/0384_omemo/omemo_decryptor.vala @@ -18,16 +18,25 @@ namespace Xmpp.Xep.Omemo { ParsedData ret = new ParsedData(); StanzaNode? header_node = encrypted_node.get_subnode("header"); - if (header_node == null) return null; + if (header_node == null) { + warning("Can't parse OMEMO node: No header node"); + return null; + } ret.sid = header_node.get_attribute_int("sid", -1); - if (ret.sid == -1) return null; + if (ret.sid == -1) { + warning("Can't parse OMEMO node: No sid"); + return null; + } string? payload_str = encrypted_node.get_deep_string_content("payload"); if (payload_str != null) ret.ciphertext = Base64.decode(payload_str); string? iv_str = header_node.get_deep_string_content("iv"); - if (iv_str == null) return null; + if (iv_str == null) { + warning("Can't parse OMEMO node: No iv"); + return null; + } ret.iv = Base64.decode(iv_str); foreach (StanzaNode key_node in header_node.get_subnodes("key")) { diff --git a/xmpp-vala/src/module/xep/muji_meta.vala b/xmpp-vala/src/module/xep/muji_meta.vala new file mode 100644 index 00000000..4452e611 --- /dev/null +++ b/xmpp-vala/src/module/xep/muji_meta.vala @@ -0,0 +1,117 @@ +using Gee; +namespace Xmpp.Xep.MujiMeta { + + public const string NS_URI = "http://telepathy.freedesktop.org/muji"; + + public class Module : XmppStreamModule { + public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "muji_meta"); + + public signal void call_proposed(Jid from, Jid to, Jid muc_jid, Gee.List descriptions); + public signal void call_retracted(Jid from, Jid to, Jid muc_jid); + public signal void call_accepted(Jid from, Jid muc_jid); + public signal void call_rejected(Jid from, Jid to, Jid muc_jid); + + public void send_invite(XmppStream stream, Jid invitee, Jid muc_jid, bool video) { + var invite_node = new StanzaNode.build("propose", NS_URI).put_attribute("muc", muc_jid.to_string()); + invite_node.put_node(new StanzaNode.build("description", Xep.JingleRtp.NS_URI).add_self_xmlns().put_attribute("media", "audio")); + if (video) { + invite_node.put_node(new StanzaNode.build("description", Xep.JingleRtp.NS_URI).add_self_xmlns().put_attribute("media", "video")); + } + var muji_node = new StanzaNode.build("muji", NS_URI).add_self_xmlns().put_node(invite_node); + MessageStanza invite_message = new MessageStanza() { to=invitee, type_=MessageStanza.TYPE_CHAT }; + invite_message.stanza.put_node(muji_node); + stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, invite_message); + } + + public void send_invite_retract_to_peer(XmppStream stream, Jid invitee, Jid muc_jid) { + send_jmi_message(stream, "retract", invitee, muc_jid); + } + + public void send_invite_accept_to_peer(XmppStream stream, Jid invitor, Jid muc_jid) { + send_jmi_message(stream, "accept", invitor, muc_jid); + } + + public void send_invite_accept_to_self(XmppStream stream, Jid muc_jid) { + send_jmi_message(stream, "accept", Bind.Flag.get_my_jid(stream).bare_jid, muc_jid); + } + + public void send_invite_reject_to_peer(XmppStream stream, Jid invitor, Jid muc_jid) { + send_jmi_message(stream, "reject", invitor, muc_jid); + } + + public void send_invite_reject_to_self(XmppStream stream, Jid muc_jid) { + send_jmi_message(stream, "reject", Bind.Flag.get_my_jid(stream).bare_jid, muc_jid); + } + + private void send_jmi_message(XmppStream stream, string name, Jid to, Jid muc) { + var jmi_node = new StanzaNode.build(name, NS_URI).add_self_xmlns().put_attribute("muc", muc.to_string()); + var muji_node = new StanzaNode.build("muji", NS_URI).add_self_xmlns().put_node(jmi_node); + + MessageStanza accepted_message = new MessageStanza() { to=to, type_=MessageStanza.TYPE_CHAT }; + accepted_message.stanza.put_node(muji_node); + stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, accepted_message); + } + + private void on_received_message(XmppStream stream, MessageStanza message) { + Xep.MessageArchiveManagement.MessageFlag? mam_flag = Xep.MessageArchiveManagement.MessageFlag.get_flag(message); + if (mam_flag != null) return; + + var muji_node = message.stanza.get_subnode("muji", NS_URI); + if (muji_node == null) return; + + StanzaNode? mi_node = null; + foreach (StanzaNode node in muji_node.sub_nodes) { + if (node.ns_uri == NS_URI) { + mi_node = node; + } + } + if (mi_node == null) return; + + string? jid_str = mi_node.get_attribute("muc"); + if (jid_str == null) return; + + Jid muc_jid = null; + try { + muc_jid = new Jid(jid_str); + } catch (Error e) { + return; + } + + switch (mi_node.name) { + case "accept": + case "proceed": + call_accepted(message.from, muc_jid); + break; + case "propose": + ArrayList descriptions = new ArrayList(); + + foreach (StanzaNode node in mi_node.sub_nodes) { + if (node.name != "description") continue; + descriptions.add(node); + } + + if (descriptions.size > 0) { + call_proposed(message.from, message.to, muc_jid, descriptions); + } + break; + case "retract": + call_retracted(message.from, message.to, muc_jid); + break; + case "reject": + call_rejected(message.from, message.to, muc_jid); + break; + } + } + + public override void attach(XmppStream stream) { + stream.get_module(MessageModule.IDENTITY).received_message.connect(on_received_message); + } + + public override void detach(XmppStream stream) { + stream.get_module(MessageModule.IDENTITY).received_message.disconnect(on_received_message); + } + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2