diff options
Diffstat (limited to 'xmpp-vala/src')
18 files changed, 790 insertions, 39 deletions
diff --git a/xmpp-vala/src/module/presence/flag.vala b/xmpp-vala/src/module/presence/flag.vala index 77bc0b5f..8e13d0ad 100644 --- a/xmpp-vala/src/module/presence/flag.vala +++ b/xmpp-vala/src/module/presence/flag.vala @@ -20,6 +20,17 @@ public class Flag : XmppStreamFlag { return presences[full_jid]; } + public Gee.List<Presence.Stanza> get_presences(Jid jid) { + Gee.List<Presence.Stanza> ret = new ArrayList<Presence.Stanza>(); + Gee.List<Jid>? jid_res = resources[jid]; + if (jid_res == null) return ret; + + foreach (Jid full_jid in jid_res) { + ret.add(presences[full_jid]); + } + return ret; + } + public void add_presence(Presence.Stanza presence) { if (!resources.has_key(presence.from)) { resources[presence.from] = new ArrayList<Jid>(Jid.equals_func); 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..c4c299c5 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<Crypto> remote_cryptos = new ArrayList<Crypto>(); 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<PayloadType> 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(); @@ -138,6 +151,7 @@ public class Xmpp.Xep.JingleRtp.Parameters : Jingle.ContentParameters, Object { } this.stream = parent.create_stream(content); + this.stream.weak_ref(() => this.stream = null); rtp_datagram.datagram_received.connect(this.stream.on_recv_rtp_data); rtcp_datagram.datagram_received.connect(this.stream.on_recv_rtcp_data); this.stream.on_send_rtp_data.connect(rtp_datagram.send_datagram); @@ -202,6 +216,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<PayloadType> 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<PayloadType> payloads); public abstract Crypto? generate_local_crypto(); public abstract Crypto? pick_remote_crypto(Gee.List<Crypto> cryptos); @@ -28,7 +29,7 @@ public abstract class Module : XmppStreamModule { public abstract Gee.List<HeaderExtension> 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<Jingle.Content> contents = new ArrayList<Jingle.Content>(); // 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/0167_jingle_rtp/stream.vala b/xmpp-vala/src/module/xep/0167_jingle_rtp/stream.vala index 65be8a0a..031f0ad0 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,4 @@ public abstract class Xmpp.Xep.JingleRtp.Stream : Object { - public Jingle.Content content { get; protected set; } public string name { get { @@ -54,6 +53,13 @@ public abstract class Xmpp.Xep.JingleRtp.Stream : Object { return false; }} + // Receiver Estimated Maximum Bitrate + public bool remb_enabled { get { + return payload_type != null ? payload_type.rtcp_fbs.any_match((it) => it.type_ == "goog-remb") : false; + }} + public uint target_receive_bitrate { get; set; default=256; } + public uint target_send_bitrate { get; set; default=256; } + protected Stream(Jingle.Content content) { this.content = content; } 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/0177_jingle_raw_udp.vala b/xmpp-vala/src/module/xep/0177_jingle_raw_udp.vala new file mode 100644 index 00000000..200cdfa9 --- /dev/null +++ b/xmpp-vala/src/module/xep/0177_jingle_raw_udp.vala @@ -0,0 +1,118 @@ +using Gee; +using Xmpp.Xep; +using Xmpp; + +namespace Xmpp.Xep.JingleRawUdp { + + public const string NS_URI = "urn:xmpp:jingle:transports:raw-udp:1"; + + public delegate Gee.List<string> GetLocalIpAddresses(); + + public class Module : XmppStreamModule, Jingle.Transport { + public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0177_jingle_raw_udp"); + + private GetLocalIpAddresses? get_local_ip_addresses_impl = null; + + 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); + } + public override void detach(XmppStream stream) { + stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI); + } + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } + + public async bool is_transport_available(XmppStream stream, uint8 components, Jid full_jid) { + return yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, full_jid, NS_URI); + } + + public string ns_uri{ get { return NS_URI; } } + public Jingle.TransportType type_{ get { return Jingle.TransportType.DATAGRAM; } } + public int priority { get { return 1; } } + + public void set_local_ip_address_handler(owned GetLocalIpAddresses get_local_ip_addresses) { + get_local_ip_addresses_impl = (owned)get_local_ip_addresses; + } + + public Gee.List<string> get_local_ip_addresses() { + if (get_local_ip_addresses_impl == null) { + return Gee.List.empty(); + } + return get_local_ip_addresses_impl(); + } + + public Jingle.TransportParameters create_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid) { + return new TransportParameters(components, null); + } + + public Jingle.TransportParameters parse_transport_parameters(XmppStream stream, uint8 components, Jid local_full_jid, Jid peer_full_jid, StanzaNode transport) throws Jingle.IqError { + return new TransportParameters(components, transport); + } + } + + public class TransportParameters : Jingle.TransportParameters, Object { + public string ns_uri { get { return NS_URI; } } + public uint8 components { get; } + + public Gee.List<Candidate> remote_candidates = new ArrayList<Candidate>(); + public Gee.List<Candidate> own_candidates = new ArrayList<Candidate>(); + + public TransportParameters(uint8 components, StanzaNode? node = null) { +// this.components = components; + if (node != null) { + foreach (StanzaNode candidate_node in node.get_subnodes("candidate")) { + Candidate candidate = new Candidate(); + string component_str = candidate_node.get_attribute("component"); + candidate.component = int.parse(component_str); + string generation_str = candidate_node.get_attribute("generation"); + candidate.generation = int.parse(generation_str); + candidate.id = candidate_node.get_attribute("generation"); + string ip_str = candidate_node.get_attribute("ip"); + candidate.ip = new InetAddress.from_string(ip_str); + string port_str = candidate_node.get_attribute("port"); + candidate.port = int.parse(port_str); + + remote_candidates.add(candidate); + } + } + } + + public void set_content(Jingle.Content content) { + + } + + public StanzaNode to_transport_stanza_node(string action_type) { + StanzaNode transport_node = new StanzaNode.build("transport", NS_URI).add_self_xmlns(); + foreach (Candidate candidate in own_candidates) { + transport_node.put_node(new StanzaNode.build("candidate", NS_URI) + .put_attribute("generation", candidate.generation.to_string()) + .put_attribute("id", candidate.id) + .put_attribute("ip", candidate.ip.to_string()) + .put_attribute("port", candidate.port.to_string())); + } + return transport_node; + } + + public void handle_transport_accept(StanzaNode transport) throws Jingle.IqError { + + } + + public void handle_transport_info(StanzaNode transport) throws Jingle.IqError { + + } + + public void create_transport_connection(XmppStream stream, Jingle.Content content) { + + } + } + + public class Candidate : Object { + public int component { get; set; } + public int generation { get; set; } + public string id { get; set; } + public InetAddress ip { get; set; } + public uint port { get; set; } + } +}
\ No newline at end of file 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/0272_muji.vala b/xmpp-vala/src/module/xep/0272_muji.vala new file mode 100644 index 00000000..2bdc068e --- /dev/null +++ b/xmpp-vala/src/module/xep/0272_muji.vala @@ -0,0 +1,286 @@ +using Gee; +namespace Xmpp.Xep.Muji { + + public const string NS_URI = "http://telepathy.freedesktop.org/muji"; + + public class Module : XmppStreamModule { + public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "0272_muji"); + + public async GroupCall? join_call(XmppStream stream, Jid muc_jid, bool video) { + StanzaNode initial_muji_node = new StanzaNode.build("muji", NS_URI).add_self_xmlns() + .put_node(new StanzaNode.build("preparing", NS_URI)); + + var group_call = new GroupCall(muc_jid); + stream.get_flag(Flag.IDENTITY).calls[muc_jid] = group_call; + + group_call.our_nick = "%08x".printf(Random.next_int()); + debug(@"[%s] MUJI joining as %s", stream.get_flag(Bind.Flag.IDENTITY).my_jid.to_string(), group_call.our_nick); + Xep.Muc.JoinResult? result = yield stream.get_module(Muc.Module.IDENTITY).enter(stream, muc_jid, group_call.our_nick, null, null, initial_muji_node); + if (result == null || result.nick == null) return null; + debug(@"[%s] MUJI joining as %s done", stream.get_flag(Bind.Flag.IDENTITY).my_jid.to_string(), group_call.our_nick); + + // Determine all participants that have finished preparation. Those are the ones we have to initiate the call with. + Gee.List<Presence.Stanza> other_presences = yield wait_for_preparing_peers(stream, muc_jid); + var finished_real_jids = new ArrayList<Jid>(Jid.equals_func); + foreach (Presence.Stanza presence in other_presences) { + if (presence.stanza.get_deep_subnode(NS_URI + ":muji", NS_URI + ":preparing") != null) continue; + Jid? real_jid = stream.get_flag(Muc.Flag.IDENTITY).get_real_jid(presence.from); + if (real_jid == null) { + warning("Don't know the real jid for %s", presence.from.to_string()); + continue; + } + finished_real_jids.add(real_jid); + } + group_call.peers_to_connect_to.add_all(finished_real_jids); + + // Build+send our own MUJI presence + StanzaNode muji_node = new StanzaNode.build("muji", NS_URI).add_self_xmlns(); + + foreach (string media in video ? new string[] { "audio", "video" } : new string[] { "audio" }) { + StanzaNode content_node = new StanzaNode.build("content", Xep.Jingle.NS_URI).add_self_xmlns() + .put_attribute("name", media); + StanzaNode description_node = new StanzaNode.build("description", Xep.JingleRtp.NS_URI).add_self_xmlns() + .put_attribute("media", media); + content_node.put_node(description_node); + + Gee.List<Xep.JingleRtp.PayloadType> payload_types = null; + if (other_presences.is_empty) { + payload_types = yield stream.get_module(Xep.JingleRtp.Module.IDENTITY).get_supported_payloads(media); + } else { + yield compute_payload_intersection(stream, group_call, media); + payload_types = group_call.current_payload_intersection[media]; + } + foreach (Xep.JingleRtp.PayloadType payload_type in payload_types) { + description_node.put_node(payload_type.to_xml().add_self_xmlns()); + } + muji_node.put_node(content_node); + } + + Presence.Stanza presence_stanza = new Presence.Stanza() { to=muc_jid.with_resource(group_call.our_nick) }; + presence_stanza.stanza.put_node(muji_node); + stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence_stanza); + + return group_call; + } + + private async Gee.List<Presence.Stanza> wait_for_preparing_peers(XmppStream stream, Jid muc_jid) { + var promise = new Promise<Gee.List<Presence.Stanza>>(); + + ArrayList<Jid> preparing_peers = new ArrayList<Jid>(Jid.equals_func); + + Gee.List<Presence.Stanza> presences = get_other_presences(stream, muc_jid); + + foreach (Presence.Stanza presence in presences) { + StanzaNode? preparing_node = presence.stanza.get_deep_subnode(NS_URI + ":muji", NS_URI + ":preparing"); + if (preparing_node != null) { + preparing_peers.add(presence.from); + } + } + + debug("[%s] MUJI waiting for %i/%i peers", stream.get_flag(Bind.Flag.IDENTITY).my_jid.to_string(), preparing_peers.size, presences.size); + + if (preparing_peers.is_empty) { + return presences; + } + + GroupCall group_call = stream.get_flag(Flag.IDENTITY).calls[muc_jid]; + group_call.waiting_for_finish_prepares[promise] = preparing_peers; + + return yield promise.future.wait_async(); + } + + private async void compute_payload_intersection(XmppStream stream, GroupCall group_call, string media) { + Gee.List<Presence.Stanza> presences = get_other_presences(stream, group_call.muc_jid); + if (presences.is_empty) return; + + Gee.List<Xep.JingleRtp.PayloadType> intersection = parse_payload_types(stream, media, presences[0]); + var remove_payloads = new ArrayList<Xep.JingleRtp.PayloadType>(); + + // Check if all peers support the payloads + foreach (Presence.Stanza presence in presences) { + Gee.List<Xep.JingleRtp.PayloadType> peer_payload_types = parse_payload_types(stream, media, presence); + + foreach (Xep.JingleRtp.PayloadType payload_type in intersection) { + if (!peer_payload_types.contains(payload_type)) { + remove_payloads.add(payload_type); + } + } + } + // Check if we support the payloads + foreach (Xep.JingleRtp.PayloadType payload_type in intersection) { + if (!yield stream.get_module(Xep.JingleRtp.Module.IDENTITY).is_payload_supported(media, payload_type)) { + remove_payloads.add(payload_type); + } + } + // Remove payloads not supported by everyone + foreach (Xep.JingleRtp.PayloadType payload_type in remove_payloads) { + intersection.remove(payload_type); + } + + // Check if the payload intersection changed (if so: notify) + bool changed = !group_call.current_payload_intersection.has_key(media) || + !group_call.current_payload_intersection[media].contains_all(intersection) || + !intersection.contains_all(group_call.current_payload_intersection[media]); + + if (changed) { + group_call.current_payload_intersection[media] = intersection; + group_call.codecs_changed(intersection); + } + } + + private Gee.List<Xep.JingleRtp.PayloadType> parse_payload_types(XmppStream stream, string media, Presence.Stanza presence) { + Gee.List<Xep.JingleRtp.PayloadType> ret = new ArrayList<Xep.JingleRtp.PayloadType>(Xep.JingleRtp.PayloadType.equals_func); + + foreach (StanzaNode content_node in presence.stanza.get_deep_subnodes(NS_URI + ":muji", Xep.Jingle.NS_URI + ":content")) { + StanzaNode? description_node = content_node.get_subnode("description", Xep.JingleRtp.NS_URI); + if (description_node == null) continue; + + if (description_node.get_attribute("media") == media) { + Gee.List<StanzaNode> payload_nodes = description_node.get_subnodes("payload-type", Xep.JingleRtp.NS_URI); + foreach (StanzaNode payload_node in payload_nodes) { + Xep.JingleRtp.PayloadType payload_type = Xep.JingleRtp.PayloadType.parse(payload_node); + ret.add(payload_type); + } + } + } + return ret; + } + + private void on_received_available(XmppStream stream, Presence.Stanza presence) { + StanzaNode? muji_node = presence.stanza.get_subnode("muji", NS_URI); + if (muji_node == null) return; + + var flag = stream.get_flag(Flag.IDENTITY); + GroupCall? group_call = flag.calls.get(presence.from.bare_jid); + if (group_call == null) return; + + if (presence.from.resourcepart == group_call.our_nick) return; + + foreach (StanzaNode content_node in muji_node.get_subnodes("content", Xep.Jingle.NS_URI)) { + StanzaNode? description_node = content_node.get_subnode("description", Xep.JingleRtp.NS_URI); + if (description_node == null) continue; + + string? media = description_node.get_attribute("media"); + if (media == null) continue; + + compute_payload_intersection.begin(stream, group_call, media); + } + + StanzaNode? prepare_node = muji_node.get_subnode("preparing", NS_URI); + if (prepare_node == null) { + on_jid_finished_preparing(stream, presence.from, group_call); + + if (!group_call.peers.contains(presence.from)) { + // A new peer finished preparing + Jid? real_jid = stream.get_flag(Muc.Flag.IDENTITY).get_real_jid(presence.from); + if (real_jid == null) { + warning("Don't know the real jid for %s", presence.from.to_string()); + return; + } + debug("Muji peer joined %s / %s\n", real_jid.to_string(), presence.from.to_string()); + group_call.peers.add(presence.from); + group_call.real_jids[presence.from] = real_jid; + group_call.peer_joined(real_jid); + } + } + } + + private void on_received_unavailable(XmppStream stream, Presence.Stanza presence) { + Flag flag = stream.get_flag(Flag.IDENTITY); + GroupCall? group_call = flag.calls[presence.from.bare_jid]; + if (group_call == null) return; + + debug("Muji peer left %s / %s", group_call.real_jids.has_key(presence.from) ? group_call.real_jids[presence.from].to_string() : "Unknown real JID", presence.from.to_string()); + on_jid_finished_preparing(stream, presence.from, group_call); + group_call.peers.remove(presence.from); + group_call.peers_to_connect_to.remove(presence.from); + if (group_call.real_jids.has_key(presence.from)) { + group_call.peer_left(group_call.real_jids[presence.from]); + } + group_call.real_jids.remove(presence.from); + } + + private void on_jid_finished_preparing(XmppStream stream, Jid jid, GroupCall group_call) { + debug("Muji peer finished preparing %s", jid.to_string()); + foreach (Promise<Gee.List<Presence.Stanza>> promise in group_call.waiting_for_finish_prepares.keys) { + debug("Waiting for finish prepares %i", group_call.waiting_for_finish_prepares[promise].size); + Gee.List<Jid> outstanding_prepares = group_call.waiting_for_finish_prepares[promise]; + if (outstanding_prepares.contains(jid)) { + outstanding_prepares.remove(jid); + debug("Waiting for finish prepares %i", group_call.waiting_for_finish_prepares[promise].size); + + if (outstanding_prepares.is_empty) { + Gee.List<Presence.Stanza> presences = get_other_presences(stream, jid.bare_jid); + promise.set_value(presences); + } + } + } + } + + private Gee.List<Presence.Stanza> get_other_presences(XmppStream stream, Jid muc_jid) { + Gee.List<Presence.Stanza> presences = stream.get_flag(Presence.Flag.IDENTITY).get_presences(muc_jid); + string? own_nick = stream.get_flag(Flag.IDENTITY).calls[muc_jid].our_nick; + + var remove_presences = new ArrayList<Presence.Stanza>(); + foreach (Presence.Stanza presence in presences) { + if (presence.from.resourcepart == own_nick) { + remove_presences.add(presence); + } + StanzaNode? muji_node = presence.stanza.get_subnode("muji", NS_URI); + if (muji_node == null) { + remove_presences.add(presence); + } + } + presences.remove_all(remove_presences); + return presences; + } + + public override void attach(XmppStream stream) { + stream.add_flag(new Flag()); + stream.get_module(Presence.Module.IDENTITY).received_available.connect(on_received_available); + stream.get_module(Presence.Module.IDENTITY).received_unavailable.connect(on_received_unavailable); + } + + public override void detach(XmppStream stream) { } + + public override string get_ns() { + return NS_URI; + } + + public override string get_id() { + return IDENTITY.id; + } + } + + public class GroupCall { + public string our_nick; + public Jid muc_jid; + public ArrayList<Jid> peers_to_connect_to = new ArrayList<Jid>(Jid.equals_func); + public ArrayList<Jid> peers = new ArrayList<Jid>(Jid.equals_func); + public HashMap<Jid, Jid> real_jids = new HashMap<Jid, Jid>(Jid.hash_func, Jid.equals_func); + public HashMap<Promise, Gee.List<Jid>> waiting_for_finish_prepares = new HashMap<Promise, Gee.List<Jid>>(); + public HashMap<string, Gee.List<Xep.JingleRtp.PayloadType>> current_payload_intersection = new HashMap<string, Gee.List<Xep.JingleRtp.PayloadType>>(); + + public signal void peer_joined(Jid real_jid); + public signal void peer_left(Jid real_jid); + public signal void codecs_changed(Gee.List<Xep.JingleRtp.PayloadType> payload_types); + + public GroupCall(Jid muc_jid) { + this.muc_jid = muc_jid; + } + + public void leave(XmppStream stream) { + stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, muc_jid); + stream.get_flag(Flag.IDENTITY).calls.unset(muc_jid); + } + } + + public class Flag : XmppStreamFlag { + public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "muji"); + + public HashMap<Jid, GroupCall> calls = new HashMap<Jid, GroupCall>(Jid.hash_bare_func, Jid.equals_bare_func); + + public override string get_ns() { return NS_URI; } + public override string get_id() { return IDENTITY.id; } + } +}
\ No newline at end of file diff --git a/xmpp-vala/src/module/xep/0298_coin.vala b/xmpp-vala/src/module/xep/0298_coin.vala new file mode 100644 index 00000000..58b3b055 --- /dev/null +++ b/xmpp-vala/src/module/xep/0298_coin.vala @@ -0,0 +1,136 @@ +using Gee; + +namespace Xmpp.Xep.Coin { + private const string NS_RFC = "urn:ietf:params:xml:ns:conference-info"; + + public class Module : XmppStreamModule, Iq.Handler { + public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_RFC, "0298_coin"); + + public signal void coin_info_received(Jid jid, ConferenceInfo info); + + public async override void on_iq_set(XmppStream stream, Iq.Stanza iq) { + ConferenceInfo? info = parse_node(iq.stanza.get_subnode("conference-info", NS_RFC), null); + if (info == null) return; + + coin_info_received(iq.from, info); + } + + public override void attach(XmppStream stream) { + stream.get_module(Iq.Module.IDENTITY).register_for_namespace(NS_RFC, this); + } + + public override void detach(XmppStream stream) { } + + public override string get_ns() { return NS_RFC; } + + public override string get_id() { return IDENTITY.id; } + } + + public ConferenceInfo? parse_node(StanzaNode conference_node, ConferenceInfo? previous_conference_info) { + string? version_str = conference_node.get_attribute("version"); + string? conference_state = conference_node.get_attribute("state"); + if (version_str == null || conference_state == null) return null; + + int version = int.parse(version_str); + if (previous_conference_info != null && version <= previous_conference_info.version) return null; + + ConferenceInfo conference_info = previous_conference_info ?? new ConferenceInfo(); + conference_info.version = version; + + Gee.List<StanzaNode> user_nodes = conference_node.get_deep_subnodes(NS_RFC + ":users", NS_RFC + ":user"); + foreach (StanzaNode user_node in user_nodes) { + string? jid_string = user_node.get_attribute("entity"); + if (jid_string == null) continue; +// if (!jid_string.has_prefix("xmpp:")) continue; // silk does this wrong + Jid? jid = null; + try { + jid = new Jid(jid_string.substring(4)); + } catch (Error e) { + continue; + } + string user_state = user_node.get_attribute("state"); + if (conference_state == "full" && user_state != "full") return null; + + if (user_state == "deleted") { + conference_info.users.unset(jid); + continue; + } + + ConferenceUser user = new ConferenceUser(); + user.jid = jid; + user.display_text = user_node.get_deep_string_content(NS_RFC + ":display-text"); + + Gee.List<StanzaNode> endpoint_nodes = user_node.get_subnodes("endpoint"); + foreach (StanzaNode entpoint_node in endpoint_nodes) { + Gee.List<StanzaNode> media_nodes = entpoint_node.get_subnodes("media"); + foreach (StanzaNode media_node in media_nodes) { + string? id = media_node.get_attribute("id"); + string? ty = media_node.get_deep_string_content(NS_RFC + ":type"); + string? src_id_str = media_node.get_deep_string_content(NS_RFC + ":src-id"); + + if (id == null) continue; + + ConferenceMedia media = new ConferenceMedia(); + media.id = id; + media.src_id = src_id_str != null ? int.parse(src_id_str) : -1; + media.ty = ty; + user.medias[id] = media; + } + + conference_info.users[user.jid] = user; + } + } + return conference_info; + } + + public class ConferenceInfo { + public int version = -1; + public HashMap<Jid, ConferenceUser> users = new HashMap<Jid, ConferenceUser>(Jid.hash_func, Jid.equals_func); + + public StanzaNode to_xml() { + StanzaNode ret = new StanzaNode.build("conference-info", NS_RFC).add_self_xmlns() + .put_attribute("version", this.version.to_string()) + .put_attribute("state", "full"); + StanzaNode users_node = new StanzaNode.build("users", NS_RFC); + + foreach (ConferenceUser user in this.users.values) { + users_node.put_node(user.to_xml()); + } + ret.put_node(users_node); + return ret; + } + } + + public class ConferenceUser { + public Jid jid; + public string? display_text; + public HashMap<string, ConferenceMedia> medias = new HashMap<string, ConferenceMedia>(); + + public StanzaNode to_xml() { + StanzaNode user_node = new StanzaNode.build("user", NS_RFC) + .put_attribute("entity", jid.to_string()); + foreach (ConferenceMedia media in medias.values) { + user_node.put_node(media.to_xml()); + } + return user_node; + } + } + + public class ConferenceMedia { + public string id; + public string? ty; + public int src_id = -1; + + public StanzaNode to_xml() { + StanzaNode media_node = new StanzaNode.build("media", NS_RFC) + .put_attribute("id", id); + if (ty != null) { + media_node.put_node(new StanzaNode.build("type", NS_RFC).put_node(new StanzaNode.text(ty))); + } + if (src_id != -1) { + media_node.put_node(new StanzaNode.build("src-id", NS_RFC).put_node(new StanzaNode.text(src_id.to_string()))); + } + return media_node; + } + } +}
\ No newline at end of file diff --git a/xmpp-vala/src/module/xep/0353_jingle_message_initiation.vala b/xmpp-vala/src/module/xep/0353_jingle_message_initiation.vala index 71e16a95..ac1d8329 100644 --- a/xmpp-vala/src/module/xep/0353_jingle_message_initiation.vala +++ b/xmpp-vala/src/module/xep/0353_jingle_message_initiation.vala @@ -8,7 +8,7 @@ namespace Xmpp.Xep.JingleMessageInitiation { public signal void session_proposed(Jid from, Jid to, string sid, Gee.List<StanzaNode> descriptions); public signal void session_retracted(Jid from, Jid to, string sid); - public signal void session_accepted(Jid from, string sid); + public signal void session_accepted(Jid from, Jid to, string sid); public signal void session_rejected(Jid from, Jid to, string sid); public void send_session_propose_to_peer(XmppStream stream, Jid to, string sid, Gee.List<StanzaNode> descriptions) { @@ -65,7 +65,7 @@ namespace Xmpp.Xep.JingleMessageInitiation { switch (mi_node.name) { case "accept": case "proceed": - session_accepted(message.from, mi_node.get_attribute("id")); + session_accepted(message.from, message.to, mi_node.get_attribute("id")); break; case "propose": ArrayList<StanzaNode> descriptions = new ArrayList<StanzaNode>(); 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..89a0e8de --- /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<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "muji_meta"); + + public signal void call_proposed(Jid from, Jid to, Jid muc_jid, Gee.List<StanzaNode> descriptions, string message_type); + public signal void call_retracted(Jid from, Jid to, Jid muc_jid, string message_type); + public signal void call_accepted(Jid from, Jid muc_jid, string message_type); + public signal void call_rejected(Jid from, Jid to, Jid muc_jid, string message_type); + + public void send_invite(XmppStream stream, Jid invitee, Jid muc_jid, bool video, string? message_type = null) { + 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_=message_type }; + 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, string? message_type = null) { + send_jmi_message(stream, "retract", invitee, muc_jid, message_type); + } + + public void send_invite_accept_to_peer(XmppStream stream, Jid invitor, Jid muc_jid, string? message_type = null) { + send_jmi_message(stream, "accept", invitor, muc_jid, message_type); + } + + 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, string? message_type = null) { + send_jmi_message(stream, "reject", invitor, muc_jid, message_type); + } + + 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, string? message_type = null) { + 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_= message_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, message.type_); + break; + case "propose": + ArrayList<StanzaNode> descriptions = new ArrayList<StanzaNode>(); + + 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, message.type_); + } + break; + case "retract": + call_retracted(message.from, message.to, muc_jid, message.type_); + break; + case "reject": + call_rejected(message.from, message.to, muc_jid, message.type_); + 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 |