phone

    • chevron_right

      Ignite Realtime Blog: Denial of Service Vulnerability in Smack 4.4 if XMPPTCPConnection is used with StAX

      news.movim.eu / PlanetJabber • 3 December, 2022 • 2 minutes

    The fantastic folks behind Jitsi have discovered a Denial of Service (DoS) vulnerability in Smack ( JSA-2022-0002 , JSA-2022-0003 ), which is possible if a combination of Smack components is used. The root of the vulnerability is interesting because it is due to a countermeasure against DoS attacks, namely FEATURE_SECURE_PROCESSING of the Java API for XML Processing (JAXP).

    The DoS is possible because the older XMPPTCPConnection implementation of Smack parses the XMPP stream as one large XML document. Suppose the connection instance uses a parser where FEATURE_SECURE_PROCESSING is enabled. In that case, it is easy for an attacker to craft a stanza that triggers one of the various limits imposed by FEATURE_SECURE_PROCESSING, causing an exception, leaving the parser in an unrecoverable state, and closing the connection.

    This vulnerability was relatively recently introduced in Smack with the addition of the support for JAXP’s Streaming API for XML (StaX) parser. Historically, Smack only used XPP3 as XML pull parser. The default implementation of XPP3 is a fast, lightweight, and, to the best of our knowledge, secure parser. XPP3 is used, for example, by Android. However, with version 4.4.0 ( SMACK-591 ), Smack gained support for using Java’s Streaming API for XML (StAX) in addition to XPP3, to facilitate code-reuse on Java SE platforms and avoiding the XPP3 dependency.

    So this DoS is possible if the XMPP connection is of type XMPPTCPConnection and if the Smack connection instance uses a StAX parser for XMPP parsing.

    On a related note, Smack’s newer modular connection architecture is not affected by this, because it splits the individual top-level XMPP stream elements and parses them as standalone document. The splitting is done very early in the input processing step by XmlSplitter (of jxmpp ), which also enforces size limits for the XML elements. Therefore, the DoS is not possible over connections that are established via Smack’s modern ModularXmppClientToServerConnection .

    If you are affected, then the following countermeasures are possible:

    1. Relax the FEATURE_SECURE_PROCESSING_LIMITS
    2. Switch to XPP3 (smack-xmlparser-xpp3)
    3. Use ModularXmppClientToServerConnection

    Option A has the drawback that it is only possible to relax the limits globally. That is, it will affect XML processing regardless if Smack or some other component performs it. If you still want to go down that route, then

    System.setProperty("jdk.xml.entityExpansionLimit", "0")
    System.setProperty("jdk.xml.maxOccurLimit", "0")
    System.setProperty("jdk.xml.elementAttributeLimit", "0")
    System.setProperty("jdk.xml.totalEntitySizeLimit", "0")
    System.setProperty("jdk.xml.maxXMLNameLimit", "524288")
    System.setProperty("jdk.xml.entityReplacementLimit", "0")
    

    1 post - 1 participant

    Read full topic

    • wifi_tethering open_in_new

      This post is public

      discourse.igniterealtime.org /t/denial-of-service-vulnerability-in-smack-4-4-if-xmpptcpconnection-is-used-with-stax/92314

    • chevron_right

      Gajim: Gajim 1.5.4

      news.movim.eu / PlanetJabber • 3 December, 2022 • 1 minute

    Gajim 1.5.4 comes with a reworked file transfer interface, better URL detection, message selection improvements, and many fixes under the hood. Thank you for all your contributions!

    What’s New

    Gajim’s interface for sending files has been reworked, and should be much easier to use now. For each file you’re about to send, Gajim will generate a preview. This way, you can avoid sending the wrong file to somebody. Regardless of how you start a file transfer, be it drag and drop, pasting a screen shot, or simply clicking the share button, you’ll always be able to check what you’re about to send.

    Gajim’s new file transfer interface

    Gajim’s new file transfer interface

    More Changes

    • Performance: Chat history is now displayed quicker
    • Support for Jingle XTLS has been dropped, since it hasn’t been standardized
    • geo:-URIs are now prettier (thanks, @mjk )
    • Dependencies: pyOpenSSL has been replaced by python-cryptography

    Fixes

    • Fixes for message selection
    • Improvements for recognizing URLs ( @mjk )
    • Many fixes to improve Gajim’s usability

    Over 20 issues have been fixed in this release. Have a look at the changelog for a complete list.

    Gajim

    As always, don’t hesitate to contact us at gajim@conference.gajim.org or open an issue on our Gitlab .

    • wifi_tethering open_in_new

      This post is public

      gajim.org /post/2022-12-03-gajim-1.5.4-released/

    • chevron_right

      Erlang Solutions: Advent of Code 2022 – Every Puzzle Solved in Erlang

      news.movim.eu / PlanetJabber • 1 December, 2022 • 9 minutes

    Day 1

    Christmas is getting closer and with that, the annual Advent of Code begins. For those who do not know, Advent of Code is a fun and inclusive event which provides a new programming puzzle every day. The fun is that these puzzles can be solved in any programming language and are accessible for varying levels of coding experience and skills. The real test is in your problem-solving. This year, we’ll be solving each of the problems in Erlang and publishing the results. We hope you enjoy it – if you’ve come up with a different solution and want to discuss it with us, we encourage you to comment on Twitter.

    The code will be added to the repo here: https://github.com/aleklisi/AdventOfCode2022 as I manage to solve each next puzzle.

    Day 1

    Full problem description: https://adventofcode.com/2022/day/1

    The example input file:

    1000
    2000
    3000
    
    4000
    
    5000
    6000
    
    7000
    8000
    9000
    
    10000
    

    Puzzle 1

    We are given a file with a list of values for calories in snacks carried by elves. The Elves take turns writing down the number of Calories contained in the various snacks that they’ve brought with them, one item per line . Each Elf separates its own inventory from the previous Elf’s inventory (if any) by a blank line. So the first task is to read and parse the data and decide how to represent it.

    I decided to represent the input as a list of two-element tuples, where each tuple stores the elf’s number and a list of snacks. Here is an example data representation for the example file:

    [
      {1,[1000,2000,3000]},
      {2,[4000]},
      {3,[5000,6000]},
      {4,[7000,8000,9000]},
      {5,[10000]}
    ]
    

    Now we just need to define a max function, which compares elements based on the sum of elements in a list of the calories in the snacks. I assumed that the list of elves is not empty, so I start with its first element as the current max and then started comparisons with other elves. Every time I find an elf with a bigger calorie sum I replace my current elf with the new one so that I will end up with the elf with the highest calorie total. Once the elf with the most calories is found, we can return the sum of calories.
    See the code: https://github.com/aleklisi/AdventOfCode2022/blob/main/day1_puzzle1/src/day1_puzzle1.erl#L48-L56 .

    Puzzle 2

    The only difference in puzzle 2 compared to puzzle 1 is that now we need to find 3 elves with the most calories total instead of just 1 elf and sum their calories altogether.

    We can heavily rely on solutions from puzzle 1.

    To find the top 3 elves I will just:

    1. Find the elf with the highest calories and save this elf as the first one.
    2. Remove the first elf from the list of elves.
    3. Find the next top elf with the highest calories from the list of elves (without the first elf) and save this elf as the second one.
    4. Remove the second elf from the list of elves.
    5. Find the next top elf with the highest calories from the list of elves (without the first and second elf) and save this elf as the third one.
    6. Return the sum of calories of the first, second and third elf.

    Voila!

    Day 2

    Day 2 of Advent of Code sees us helping the Elves to score a game of Rock, Paper, Scissors. The Elves have provided us with a strategy guide and it’s our job to help them score their game.

    Puzzle 1

    To complete the task we need to calculate the results of the above games. Since the games are unrelated (the result of previous games does not impact the next games, the best way to approach the problem is to start by implementing a single game’s score count function and then map a list of games with this function, to get the scores for each of the games. To get the final score (which is a sum of the games’ scores) we then sum all of the elements of the list.

    The data structure I decided to use to represent a single game is a two element tuple, where the first element is the opponent’s move and the second element is my move.

    The list of games is parsed into something like this:

    [{rock, rock},
                  {scissors, rock},
                  {scissors, rock},
                  {scissors, rock},
                  {paper, paper},
    	…
    ]
    

    Looking back (after solving the problem) I could have used maps with a structure like the one below:

    	#{my_move = > rock, opponent_move => paper}
    

    It might have helped me debug and avoid errors, which I did when first approaching the problem. That error was to confuse my move with my opponent’s move. In other words, I decoded A, B and C to be my moves and  X, Y, and Z to be my opponent’s moves. It is an obvious mistake when you spot it, but easy oversight when reading the puzzle’s description fast. I obviously had to read the description carefully 2 more times to spot my mistake, so as we say in Polish: “the cunning one loses twice”.

    Both parsing and solving today’s puzzle heavily rely on pattern matching, so let’s see that in action.

    Firstly, let’s take a look at how the data is decoded using pattern matching:

    % "The first column is what your opponent is going to play:
    % A for Rock,
    % B for Paper,
    % C for Scissors.
    translate_opponent_move("A") -> rock;
    translate_opponent_move("B") -> paper;
    translate_opponent_move("C") -> scissors.
    
    % The second column, you reason, must be what you should play in response:
    % X for Rock,
    % Y for Paper,
    % Z for Scissors.
    translate_my_move("X") -> rock;
    translate_my_move("Y") -> paper;
    translate_my_move("Z") -> scissors.
    

    A smart observer might notice that I could have used a single function to handle that translation, but I find dividing the decoding into two separate functions much more readable.

    Now let’s take a look at how scoring can be conveniently calculated:

    count_games_score({OpponentMove, MyMove}) ->
        count_shape_score(MyMove) + count_result_score(OpponentMove, MyMove).
    
    % The score for a single round is the score for the shape you selected (
    % 1 for Rock,
    % 2 for Paper,
    % 3 for Scissors
    count_shape_score(rock) -> 1;
    count_shape_score(paper) -> 2;
    count_shape_score(scissors) -> 3.
    
    % ) plus the score for the outcome of the round (
    % 0 if you lost,
    % 3 if the round was a draw, 
    % 6 if you won
    % ).
    count_result_score(rock, scissors) -> 0;
    count_result_score(paper, rock) -> 0;
    count_result_score(scissors, paper) -> 0;
    count_result_score(OpponentMove, MyMove) when MyMove == OpponentMove -> 3;
    count_result_score(scissors, rock) -> 6;
    count_result_score(rock, paper) -> 6;
    count_result_score(paper, scissors) -> 6.
    

    Again a keen observer might notice that it could be done in a single function, but I think most people will agree that translating the specifications one-to-one is way more convenient and much easier to understand and possibly debug if the need arises.

    The solution can be found here: https://github.com/aleklisi/AdventOfCode2022/tree/main/day2_puzzle1

    Puzzle 2

    Puzzle two introduces a plot twist. It turns out that the second part of the input for each of the games is not what we are supposed to play, but the expected game result. We need to figure out what to play, based on what our opponent’s move is and the expected result. Notice that the game score count does not change, so if we determine what we have played based on the new understanding of input and provide parsing output to follow the same rules as we did in today’s puzzle 1 when doing the parsing, the rest of the code should work correctly without any change.

    Let’s now see how to achieve that in practice.

    In the `read_and_parse_data/1` function I modified the anonymous function inside a map function to translate the predicted result into my move:

            fun(RawGame) ->
                [OpponentMoveRaw, GameResultRaw] = string:split(RawGame, " "),
                OpponentMove = translate_opponent_move(OpponentMoveRaw),
                GameResult = translate_result(GameResultRaw),
                MyMove = find_my_move(OpponentMove, GameResult),
                {OpponentMove, MyMove}
            end

    And this is the implementation of the translating functions:

    % "The first column is what your opponent is going to play:
    % A for Rock,
    % B for Paper,
    % C for Scissors.
    translate_opponent_move("A") -> rock;
    translate_opponent_move("B") -> paper;
    translate_opponent_move("C") -> scissors.
    
    % The second column says how the round needs to end:
    % X means you need to lose,
    % Y means you need to end the round in a draw,
    % Z means you need to win.
    translate_result("X") -> lose;
    translate_result("Y") -> draw;
    translate_result("Z") -> win.
    
    find_my_move(OpponentMove, draw) -> OpponentMove;
    find_my_move(rock, lose) -> scissors;
    find_my_move(paper, lose) -> rock;
    find_my_move(scissors, lose) -> paper;
    find_my_move(rock, win) -> paper;
    find_my_move(paper, win) -> scissors;
    find_my_move(scissors, win) -> rock.
    

    Again they heavily rely on pattern matching.

    The solution can be found here: https://github.com/aleklisi/AdventOfCode2022/tree/main/day2_puzzle2

    Conclusions after completing day 2

    Firstly, ALWAYS carefully read the requirements, and do not skip any part, because you find it “obvious”.

    Secondly, pattern matching is a great tool to have, it allows us to easily implement readable code.

    And last but not least, if you struggle or get stuck with something, it helps to add readable printing/logging to your code. When my implementation of `find_my_move/2` function (when solving puzzle 2) did not work. I added the following printing debug to the parsing data function:

    
    …
    MyMove = find_my_move(OpponentMove, GameResult),
                io:format("OpponentMoveRaw: ~p\t", [OpponentMoveRaw]),
                io:format("OpponentMove: ~p\t", [OpponentMove]),
                io:format("GameResultRaw: ~p\t", [GameResultRaw]),
                io:format("MyMove: ~p\t", [MyMove]),
                io:format("GameResult: ~p\t", [GameResult]),
                io:format("Result Score: ~p\n", [count_games_score({OpponentMove, MyMove})]),
                {OpponentMove, MyMove}
    …
    

    Which for the test file:

    A X
    A Y
    A Z
    B X
    B Y
    B Z
    C X
    C Y
    C Z
    

    Results with the following output:

    OpponentMoveRaw: "A"	OpponentMove: rock	GameResultRaw: "X"	MyMove: scissors		GameResult: lose	Result Score: 3
    OpponentMoveRaw: "A"	OpponentMove: rock	GameResultRaw: "Y"	MyMove: rock		GameResult: draw	Result Score: 4
    OpponentMoveRaw: "A"	OpponentMove: rock	GameResultRaw: "Z"	MyMove: paper		GameResult: win	Result Score: 8
    OpponentMoveRaw: "B"	OpponentMove: paper	GameResultRaw: "X"	MyMove: rock		GameResult: lose	Result Score: 1
    OpponentMoveRaw: "B"	OpponentMove: paper	GameResultRaw: "Y"	MyMove: paper		GameResult: draw	Result Score: 5
    OpponentMoveRaw: "B"	OpponentMove: paper	GameResultRaw: "Z"	MyMove: scissors		GameResult: win	Result Score: 9
    OpponentMoveRaw: "C"	OpponentMove: scissors	GameResultRaw: "X"	MyMove: paper		GameResult: lose	Result Score: 2
    OpponentMoveRaw: "C"	OpponentMove: scissors	GameResultRaw: "Y"	MyMove: scissors		GameResult: draw	Result Score: 6
    OpponentMoveRaw: "C"	OpponentMove: scissors	GameResultRaw: "Z"	MyMove: rock		GameResult: win	Result Score: 7
    

    Which I found extremely helpful when finding the mistake. It turned out that instead of:

    % …
    find_my_move(rock, lose) -> scissors;
    find_my_move(paper, lose) -> rock;
    find_my_move(scissors, lose) -> paper;
    % …
    

    I had:

    % …
    find_my_move(rock, lose) -> paper;
    find_my_move(paper, lose) -> rock;
    find_my_move(scissors, lose) -> paper;
    % …
    

    In the event that I was unable to locate my mistake, I would recommend implementing unit tests, hoping not to duplicate the mistake there.

    That’s it for day 2. Come back on Monday for the solutions to the weekend’s puzzles as well as Monday’s solution.

    The post Advent of Code 2022 – Every Puzzle Solved in Erlang appeared first on Erlang Solutions .

    • chevron_right

      Erlang Solutions: RabbitMQ – An Amazing Message Broker

      news.movim.eu / PlanetJabber • 1 December, 2022 • 6 minutes

    In cloud architectures (or microservices), applications are broken down into smaller independent blocks that can be quickly developed, deployed, and maintained. Imagine you have a cloud architecture that has many services and many requests per second, you have to make sure that no requests are lost and your web service is always ready to receive new requests instead of locked by processing the previous request and must ensure that the services communicate with each other smoothly and efficiently.

    So how do you? How can different applications communicate with each other? The answer is Message Broker!

    Nowadays, there are many Message Broker software that can be listed as AWS Simple Queue Service (SQS), Apache Kafka, Apache ActiveMQ,… But the most popular among the names listed above are RabbitMQ!

    So, What is a message broker? What is RabbitMQ? Why use RabbitMQ?
    Your concerns will be answered by the RabbitMQ team right in the article!


    What is a Message Broker?

    A message broker (an integration broker or interface engine) is an intermediary module that transfers messages from sender to receiver. It is an architectural pattern for inspecting, relaying, and navigating messages; mediating between applications, simplifying communication between them, and maximizing the efficiency of splitting into smaller blocks. The main task of a Message broker is to receive messages from applications and perform some action. Let’s take a look at the sequence diagram below:

    How does Message Broker work?

    During the Initialize process, Service1 and Sevice2 load the proxy and register to the Broker. From there, the Broker will forward the messages to the pre-registered proxy.
    The advantages can see here are:

    • Service1 and Service2 don’t need to know each other and don’t need to speak in the same language. They just need to send the message to the proxy, and from here proxy will forward the message to the Broker. The Broker will take care of validating, transforming, and routing messages between Service1 and Service2.
    • With this design pattern, we can set up \an asynchronous mechanism. Service1 doesn’t need to care when a message is delivered to Service2 and when Service2 finishes handling the message, everything Sevice1 should do is send the message to the Broker, Service2 will pick up the message whenever it wants.

    To put it simply, a message broker is an intermediary program developed to serve the needs of easy communication between different applications. You can also understand message broker as a message broker software program.

    Some highlights about RabbitMQ

    RabbitMQ is an open-source message broker. In the beginning, it was used for Advanced Message Queueing Protocol (AMQP), and after that growing up to support the Streaming Text Oriented Messaging Protocol (STOMP), Message Queuing Telemetry Transport (MQTT), and other protocols.

    In a word, RabbitMQ is like an intermediary message carrier or queue manager. RabbitMQ presented in Erlang language gives programmers an intermediary means to exchange data between members of the system and securely stores the data before it is pushed to another location.

    It can be understood simply, as a large-scale system, the exchange of messages between components is more and more complex. Therefore, RabbitMQ was born as an effective solution in the system structure. Not only that, but the capacity of RabbitMQ is also quite light, programmers can deploy it on both fixed and cloud environments.

    RabbitMQ also supports multiple Messaging protocols, develops in a distributed, federated environment, and meets the need for scalability. RabbitMQ also provides a wide range of tools for the most popular languages such as C++, C#, Java, PHP, Python,…

    The most outstanding features of RabbitMQ

    As the software of choice is used so much, it must contain many outstanding features:

    • Interface :

    RabbitMQ has a fairly simple interface, easy to use. Users can easily manage, monitor and control all problems in the programs.

    • Reliability :

    RabbitMQ offers a variety of features to let you trade off performance with reliability, including persistence, delivery acknowledgments, publisher confirms, and high availability.

    • When a connection fails, messages may be in transit between the client and server. They may be stuck in the middle of being decoded or encoded on either side, stuck in the TCP buffer, … In such events, messages in transit will not be delivered, they will need to be retransmitted. The RabbitMQ Acknowledgements and Confirms feature will let the server and clients know when to do this.
    • RabbitMQ can detect dead TCP connections with the Heartbeats feature.
    • With some messaging protocols supported by RabbitMQ, applications can control the durability of queues and messages . In order to avoid losing messages, durable queue is the recommended option, and messages are published as persistent by publishers (Delivery mode property).
    • In a RabbitMQ cluster, all definitions (of exchanges, bindings, users, etc) are replicated across the entire cluster. Queues may be located on a single node, or replicate their content for higher availability.

    Quorum queues is a modern replicated queue type that focuses on data safety. They provide a more understandable, in some cases less resource intensive, new implementation for achieving replicated queues and high availability.

    Streams is another replicated messaging data type that allows for repeatable consumption.

    • Flexibility:

    The message is routed through the Exchange before reaching the Queue. RabbitMQ provides some commonly used Exchange types, we can also define our own Exchange as a plugin. For more complex routing you can bind exchanges together .

    RabbitMQ enables users to control the trade-offs between messages, throughput, and performance. All the messages in the queues can specify where they should be saved to a disc before their delivery. Queues in a cluster can span multiple servers while ensuring that no messages are lost in the case of a server failure.

    • Multi-protocol, the multi-language feature creates a diversity of users.
    • Lightweight:

    RabbitMQ is lightweight and requires less than 40MB of RAM to run the application core and plugins like the Management UI.

    • High availability of queues:

    With queues in RabbitMQ, users can replicate it on several different machines in the same cluster. This will help to keep the messages safe even if the hardware fails.

    • Traceability :

    If messaging is not working properly, RabbitMQ will step in and take action. Thanks to its traceability , users can track the system’s operating status or tell if the system has any problems.

    • Plugins system :

    RabbitMQ also supports plugin extension through many forms. If you have the ability, you can also create these utilities yourself. For more information on how to develop a RabbitMQ Plugin, please refer to the Plugin Development Basics page.

    • Commercial Services :

    Support sales with training and consulting programs offered on Pivotal.

    RabbitMQ’s application

    RabbitMQ can be used when the web server needs to quickly respond to requests. This eliminates the need to perform resource-intensive operations while the user waits for results. RabbitMQ is also used to transport messages to many different recipients for processing or to share the load among highly loaded workers (20K+ messages/sec).

    RabbitMQ can be used for:

    • Applications need to support legacy protocols, such as STOMP, MQTT, AMQP, 0-9-1.
    • Fine-grained control over consistency/set of guarantees on a per-message basis
    • Complex routing to consumers
    • Applications need multiple to publish/subscribe, point-to-point request/reply messaging capabilities.

    Who is using RabbitMQ?

    RabbitMQ is an open-source tool with 10.1K GitHub stars and 3.7K GitHub forks. Here’s a link to RabbitMQ’s open-source repository on GitHub .

    Explore RabbitMQ’s Story .

    1972 companies reportedly use RabbitMQ in their tech stacks, including Robinhood, Reddit, and Tech Stack .

    Reference resources

    https://www.rabbitmq.com/

    https://github.com/rabbitmq/rabbitmq-server

    https://www.erlang-solutions.com/blog/an-introduction-to-rabbitmq-what-is-rabbitmq/

    https://www.erlang-solutions.com/blog/rabbitmq-quorum-queues-explained-what-you-need-to-know/

    The post RabbitMQ – An Amazing Message Broker appeared first on Erlang Solutions .

    • chevron_right

      JMP: Writing a Chat Client from Scratch

      news.movim.eu / PlanetJabber • 1 December, 2022 • 28 minutes

    There are a lot of things that go into building a chat system, such as client, server, and protocol.  Even for only making a client there are lots of areas of focus, such as user experience, features, and performance.  To keep this post a manageable size, we will just be building a client and will use an existing server and protocol (accessing Jabber network services using the XMPP protocol).  We’ll make a practical GUI so we can test things, but not spend too much time on polish, and look at getting to a useful baseline of features.

    You can find all the code for this post in git .  All code licensed AGPL3+ .

    Use a Library

    As with most large programming tasks, if we wanted to do every single thing ourselves we would spend a lot more time, so we should find some good libraries.  There is another reason to use a library: any improvements we make to the library benefits others.  While releasing our code might help someone else if they choose to read it, a library improvement can be picked up by users of that library right away.

    We need to speak the XMPP protocol so let’s choose Blather .  We need a GUI so we can see this working, but don’t really want to futz with it much so let’s choose Glimmer .  The code here will use these libraries and be written in the Ruby programming language, but these ideas are general purpose to the task and hopefully we won’t get too bogged down in syntax specifics.

    One little language-specific thing you will need to create is a description of which ruby packages are being used, so let’s make that file (named Gemfile ):

    Gemfile

    source "https://rubygems.org"
    
    gem "blather", git: "https://github.com/adhearsion/blather", branch: "develop"
    gem "glimmer-dsl-libui", "~> 0.5.24"

    Run this to get the packages installed:

    bundle install --path=.gems

    Let’s get the bare minimum: a connection to a Jabber service and a window.

    client.rb

    require "glimmer-dsl-libui"
    require "blather/client"
    
    BLATHER = self
    include Glimmer
    
    Thread.new do
    	window("Contacts") {
    		on_destroy {
    			BLATHER.shutdown
    		}
    	}
    end

    When required in this way, Blather will automatically set up a connection with event processing on the main thread, and will process command line arguments to get connection details.  So we put the GUI on a second thread to not have them block each other.  When the window is closed ( on_destroy ), be sure to disconnect from the server too.  You can run this barely-a-client like this:

    bundle exec ruby client.rb user@example.com password

    The arguments are a Jabber ID (which you can get from many existing services ), and the associated password.

    You should get a blank window and no errors in your terminal.  If you wanted to you could even look in another client and confirm that it is connected to the account by seeing it come online.

    Show a Contact List

    Let’s fetch the user’s contacts from the server and show them in the window (if you use this with a new, blank test account there won’t be any contacts yet of course, but still).

    $roster = [["", ""]]
    
    Thread.new do
    	window("Contacts") {
    		vertical_box {
    			table {
    				button_column("Contact") {
    				}
    				editable false
    				cell_rows $roster
    			}
    		}
    
    		on_destroy {
    			BLATHER.shutdown
    		}
        }.show
    end
    
    after(:roster) do
    	LibUI.queue_main do
    		$roster.clear
    		my_roster.each do |item|
    			$roster << [item.name || item.jid, item.jid]
    		end
    	end
    end

    In a real app you would probably want some kind of singleton object to represent the contacts window and the contact list (“roster”) etc.  For simplicity here we just use a global variable for the roster, starting with some dummy data so that the GUI framework knows what it will look like, etc.

    We fill out the window from before a little bit to have a table with a column of buttons, one for each contact.  The button_column is the first (and in this case, only) column definition so it will source data from the first element of each item in cell_rows .  It’s not an editable table, and it gets its data from the global variable.

    We then add an event handler to our XMPP connection to say that once the roster has been loaded from the server, we hand control over to the GUI thread and there we clear out the global variable and fill it up with the roster as we now see it.  The first item in each row is the name that will be shown on the button (either item.name or item.jid if there is no name set), the second item is the Jabber ID which won’t be shown because we didn’t define that column when we made the window.  Any updates to the global variable will be automatically painted into the GUI so we’re done.

    One Window Per Conversation

    For simplicity, let’s say we want to show one window per conversation, like so:

    $conversations = {}
    
    class Conversation
    	include Glimmer
    
    	def self.open(jid, m=nil)
    		return if $conversations[jid]
    
    		($conversations[jid] = new(jid, m)).launch
    	end
    
    	def initialize(jid, m=nil)
    		@jid = jid
    		@messages = [["", ""]]
    		new_message(m) if m
    	end
    
    	def launch
    		window("Conversation With #{@jid}") {
    			vertical_box {
    				table {
    					text_column("Sender")
    					text_column("Message")
    					editable false
    					cell_rows @messages
    					@messages.clear
    				}
    
    				horizontal_box {
    					stretchy false
    
    					@message_entry = entry
    					button("Send") {
    						stretchy false
    
    						on_clicked do
    							BLATHER.say(@jid, @message_entry.text)
    							@messages << [ARGV[0], @message_entry.text]
    							@message_entry.text = ""
    						end
    					}
    				}
    			}
    
    			on_closing do
    				$conversations.delete(@jid)
    			end
    		}.show
    	end
    
    	def format_sender(jid)
    		BLATHER.my_roster[jid]&.name || jid
    	end
    
    	def message_row(m)
    		[
    			format_sender(m.from&.stripped || BLATHER.jid.stripped),
    			m.body
    		]
    	end
    
    	def new_message(m)
    		@messages << message_row(m)
    	end
    end
    
    message :body do |m|
    	LibUI.queue_main do
    		conversation = $conversations[m.from.stripped.to_s]
    		if conversation
    			conversation.new_message(m)
    		else
    			Conversation.open(m.from.stripped.to_s, m)
    		end
    	end
    end

    Most of this is the window definition again, with a table of the messages in this conversation sourced from an instance variable @messages .  At the bottom of the window is an entry box to type in text and a button to trigger sending it as a message.  When the button is clicked, send that message to the contact this conversation is with, add it to the list of messages so that it shows up in the GUI, and make the entry box empty again.  When the window closes ( on_closing this time because it’s not the “main” window) delete the object from the global set of open conversations.

    This object also has a helper to open a conversation window if there isn’t already one with a given Jabber ID (jid), some helpers to format message objects into table rows by extracting the sender and body (including format_sender which gets the roster item if there is one, uses &.name to get the name if there was a roster item or else nil , and if there was no roster item or no name just show jid ) and a helper that adds new messages into the GUI.

    Finally we add a new XMPP event handler for incoming messages that have a body.  Any such incoming message we look up in the global if there is a conversation open already, if so we pass the new message there to have it appended to the GUI table, otherwise we open the conversation with this message as the first thing it will show.

    Getting from the Contact List to a Conversation

    Now we wire up the contact list to the conversation view:

    button_column("Contact") {
    	on_clicked do |row|
    		Conversation.open($roster[row][1].to_s)
    	end
    }

    When a contact button is clicked, grab the Jabber ID from the hidden end of the table row that we had stashed there, and open the conversation.

    horizontal_box {
    	stretchy false
    
    	jid_entry = entry {
    		label("Jabber ID")
    	}
    
    	button("Start Conversation") {
    		stretchy false
    
    		on_clicked do
    			Conversation.open(jid_entry.text)
    		end
    	}
    }

    And let’s provide a way to start a new conversation with an address that isn’t a contact too.  An entry to type in a Jabber ID and a button that opens the conversation.

    Adding a Contact

    Might as well add a button to the main window that re-uses that entry box to allow adding a contact as well:

    button("Add Contact") {
    	stretchy false
    
    	on_clicked do
    		BLATHER.my_roster << jid_entry.text
    	end
    }

    Handling Multiple Devices

    In many chat protocols, it is common to have multiple devices or apps connected simultaneously. It is often desirable to show messages sent to or from one device on all the others as well.  So let’s implement that.  First, a helper for creating XML structures we may need:

    def xml_child(parent, name, namespace)
    	child = Niceogiri::XML::Node.new(name, parent.document, namespace)
    	parent << child
    	child
    end

    We need to tell the server that we support this feature:

    when_ready do
    	self << Blather::Stanza::Iq.new(:set).tap { |iq|
    		xml_child(iq, :enable, "urn:xmpp:carbons:2")
    	}
    end

    We will be handling live messages from multiple event handlers so let’s pull the live message handling out into a helper:

    def handle_live_message(m, counterpart: m.from.stripped.to_s)
    	LibUI.queue_main do
    		conversation = $conversations[counterpart]
    		if conversation
    			conversation.new_message(m)
    		else
    			Conversation.open(counterpart, m)
    		end
    	end
    end

    And the helper that will handle messages from other devices of ours:

    def handle_carbons(fwd, counterpart:)
    	fwd = fwd.first if fwd.is_a?(Nokogiri::XML::NodeSet)
    	return unless fwd
    
    	m = Blather::XMPPNode.import(fwd)
    	return unless m.is_a?(Blather::Stanza::Message) && m.body.present?
    
    	handle_live_message(m, counterpart: counterpart.call(m))
    end

    This takes in the forwarded XML object (allowing for it to be a set of which we take the first one) and imports it with Blather’s logic to become hopefully a Message object.  If it’s not a Message or has no body, we don’t really care so we stop there. Otherwise we can handle this extracted message as though we had received it ourselves.

    And then wire up the event handlers:

    message(
    	"./carbon:received/fwd:forwarded/*[1]",
    	carbon: "urn:xmpp:carbons:2",
    	fwd: "urn:xmpp:forward:0"
    ) do |_, fwd|
    	handle_carbons(fwd, counterpart: ->(m) { m.from.stripped.to_s })
    end

    Because XMPP is just XML, we can use regular XPath stuff to extract from incoming messages.  Here we say that if the message contains a forwarded element inside a carbons received element, then we should handle this with the carbons handler instead of just the live messages handler.  The XML that matches our XPath comes in as the second argument and that is what we pass to the handler to get converted into a Message object.

    message(
    	"./carbon:sent/fwd:forwarded/*[1]",
    	carbon: "urn:xmpp:carbons:2",
    	fwd: "urn:xmpp:forward:0"
    ) do |_, fwd|
    	handle_carbons(fwd, counterpart: ->(m) { m.to.stripped.to_s })
    end

    This handler is for messages sent by other devices instead of received by other devices.  It is pretty much the same, except that we know the “other side of the conversation” (here called counterpart) is in the to not the from.

    message :body do |m|
    	handle_live_message(m)
    end

    And our old message-with-body handler now just needs to call the helper.

    History

    So far our client only processes and displays live messages.  If you close the app, or even close a conversation window, the history is gone.  If you chat with another client or device, you can’t see that when you re-open this one.  To fix that we’ll need to store messages persistently, and also fetch any history from while we were disconnected from the server.  We will need a few more lines in our Gemfile first:

    gem "sqlite3"
    gem "xdg"

    And then to set up a basic database schema:

    require "securerandom"
    require "sqlite3"
    require "xdg"
    
    DATA_DIR = XDG::Data.new.home + "jabber-client-demo"
    DATA_DIR.mkpath
    DB = SQLite3::Database.new(DATA_DIR + "db.sqlite3")
    
    if DB.user_version < 1
    	DB.execute(<<~SQL)
    		CREATE TABLE messages (
    			mam_id TEXT PRIMARY KEY,
    			stanza_id TEXT NOT NULL,
    			conversation TEXT NOT NULL,
    			created_at INTEGER NOT NULL,
    			stanza TEXT NOT NULL
    		)
    	SQL
    	DB.execute("CREATE TABLE data (key TEXT PRIMARY KEY, value TEXT)")
    	DB.user_version = 1
    end

    user_version is a SQLite feature that allows storing a simple integer alongside the database.  It starts at 0 if never set, and so here we use it to check if our schema has been created or not.  We store the database in a new directory created according to the XDG Base Directory specification .  There are two relevant IDs for most XMPP operations: the MAM ID (the ID in the server’s archive) and the Stanza ID (which was usually selected by the original sender).  We also create a data table for storing basic key-value stuff, which we’ll use in a minute to remember where we have sync’d up to so far.  Let’s edit the Conversation object to store messages as we send them, updating the send button on_clicked handler:

    def message
    	Blather::Stanza::Message.new(@jid, @message_entry.text, :chat).tap { |m|
    		m.id = SecureRandom.uuid
    	}
    end

    on_clicked do
    	m = message
    	EM.defer do
    		BLATHER << m
    		DB.execute(<<~SQL, [nil, m.id, @jid, m.to_s])
    			INSERT INTO messages
    			(mam_id, stanza_id, conversation, created_at, stanza)
    			VALUES (?,?,?,unixepoch(),?)
    		SQL
    	end
    	@messages << message_row(m)
    	@message_entry.text = ""
    end

    When we send a message we don’t yet know the server’s archive ID, so we set that to nil for now.  We set mam_id to be the primary key, but SQLite allows multiple rows to have NULL in there so this will work.  We don’t want to block the GUI thread while doing database work so we use EM.defer to move this to a worker pool.  We also want to store messages when we receive them live, so add this to the start of handle_live_message :

    mam_id = m.xpath("./ns:stanza-id", ns: "urn:xmpp:sid:0").find { |el|
    	el["by"] == jid.stripped.to_s
    }&.[]("id")
    delay = m.delay&.stamp&.to_i || Time.now.to_i
    DB.execute(<<~SQL, [mam_id, m.id, counterpart, delay, m.to_s])
    	INSERT INTO messages (mam_id, stanza_id, conversation, created_at, stanza)
        VALUES (?,?,?,?,?)
    SQL

    Here we extract the server archive’s ID for the message (added by the server in a stanza-id with by="Your Jabber ID" ) and figure out what time the message was originally sent (usually this is just right now for a live message, but if it is coming from offline storage because every client was offline or similar, then there can be a “delay” set on it which we can use).  Now that we have stored the history of message we received we need to load them into the GUI when we start up a Conversation so add this at the end of initialize:

    EM.defer do
    	mam_messages = []
    	query = <<~SQL
    		SELECT stanza
    		FROM messages
    		WHERE conversation=?
    		ORDER BY created_at
    	SQL
    	DB.execute(query, [@jid]) do |row|
    		m = Blather::XMPPNode.import(
    			Nokogiri::XML.parse(row[0]).root
    		)
    		mam_messages << m
    	end
    
    	LibUI.queue_main do
    		mam_messages.map! { |m| message_row(m) }
    		@messages.replace(mam_messages + @messages)
    	end
    end

    In the worker pool we load up all the stored messages for the current conversation in order, then we take the XML stored as a string in the database and parse it into a Blather Message object.  Once we’ve done as much of the work as we can in we worker pool we use queue_main to switch back to the GUI thread and actually build the rows for the table and replace them into the GUI.

    With these changes, we are now storing all messages we see while connected and displaying them in the conversation.  But what about messages sent or received by other devices or clients while we were not connected?  For that we need to sync with the server’s archive, fetching messages at a reasonable page size from whatever we already have until the end.

    def sync_mam(last_id)
    	start_mam = Blather::Stanza::Iq.new(:set).tap { |iq|
    		xml_child(iq, :query, "urn:xmpp:mam:2").tap do |query|
    			xml_child(query, :set, "http://jabber.org/protocol/rsm").tap do |rsm|
    				xml_child(rsm, :max, "http://jabber.org/protocol/rsm").tap do |max|
    					max.content = (EM.threadpool_size * 5).to_s
    				end
    				next unless last_id
    
    				xml_child(rsm, :after, "http://jabber.org/protocol/rsm").tap do |after|
    					after.content = last_id
    				end
    			end
    		end
    	}
    
    	client.write_with_handler(start_mam) do |reply|
    		next if reply.error?
    
    		fin = reply.find_first("./ns:fin", ns: "urn:xmpp:mam:2")
    		next unless fin
    
    		handle_rsm_reply_when_idle(fin)
    	end
    end

    The first half of this creates the XML stanza to request a page from the server’s archive. We create a query with a max page size based on the size of our worker threadpool, and ask for messages only after the last known id (if we have one, which we won’t on first run). Then we use write_with_handler to send this request to the server and wait for a reply. The reply is sent after all messages have been sent down (sent seperately, not returned in this reply, see below), but we may still be processing some of them in the worker pool so we next create a helper to wait for the worker pool to be done:

    def handle_rsm_reply_when_idle(fin)
    	unless EM.defers_finished?
    		EM.add_timer(0.1) { handle_rsm_reply_when_idle(fin) }
    		return
    	end
    
    	last = fin.find_first(
    		"./ns:set/ns:last",
    		ns: "http://jabber.org/protocol/rsm"
    	)&.content
    
    	if last
    		DB.execute(<<~SQL, [last, last])
    			INSERT INTO data VALUES ('last_mam_id', ?)
    			ON CONFLICT(key) DO UPDATE SET value=? WHERE key='last_mam_id'
    		SQL
    	end
    	return if fin["complete"].to_s == "true"
    
    	sync_mam(last)
    end

    Poll with a timer until the worker pool is all done so that we aren’t fetching new pages before we have handled the last one.  Get the value of the last archive ID that was part of the page just processed and store it in the database for next time we start up.  If this was the last page (that is, complete="true" ) then we’re all done, otherwise get the next page.  We need to make sure we actually start this sync process inside the when_ready handler:

    last_mam_id = DB.execute(<<~SQL)[0]&.first
    	SELECT value FROM data WHERE key='last_mam_id' LIMIT 1
    SQL
    sync_mam(last_mam_id)

    And also, we need to actually handle the messages as they come down from the server archive:

    message "./ns:result", ns: "urn:xmpp:mam:2" do |_, result|
    	fwd = result.xpath("./ns:forwarded", ns: "urn:xmpp:forward:0").first
    	fwd = fwd.find_first("./ns:message", ns: "jabber:client")
    	m = Blather::XMPPNode.import(fwd)
    	next unless m.is_a?(Blather::Stanza::Message) && m.body.present?
    
    	mam_id = result.first["id"]&.to_s
    	# Can't really race because we're checking for something from the past
    	# Any new message inserted isn't the one we're looking for here anyway
    	sent = DB.execute(<<~SQL, [m.id])[0][0]
    		SELECT count(*) FROM messages WHERE stanza_id=? AND mam_id IS NULL
    	SQL
    	if sent < 1
    		counterpart = if m.from.stripped.to_s == jid.stripped.to_s
    			m.to.stripped.to_s
    		else
    			m.from.stripped.to_s
    		end
    		delay =
    			fwd.find_first("./ns:delay", ns: "urn:xmpp:delay")
    			&.[]("stamp")&.then(Time.method(:parse))
    		delay = delay&.to_i || m.delay&.stamp&.to_i || Time.now.to_i
    		DB.execute(<<~SQL, [mam_id, m.id, counterpart, delay, m.to_s])
    			INSERT OR IGNORE INTO messages
    			(mam_id, stanza_id, conversation, created_at, stanza)
    			VALUES (?,?,?,?,?)
    		SQL
    	else
    		DB.execute(<<~SQL, [mam_id, m.id])
    			UPDATE messages SET mam_id=? WHERE stanza_id=?
    		SQL
    	end
    end

    Any message which contains a MAM (server archive) result will get handled here.  Just like with carbons we extract the forwarded message and import, making sure it ends up as a Blather Message object with a body.

    Remember how when we stored a sent message we didn’t know the archive ID yet?  Here we check if there is anything in our database already with this stanza ID and no archive ID, if no we will insert it as a new message, but otherwise we can update the row we already have to store the server archive ID on it, which we now know.

    And with that, our client now stores and syncs all history with the server, to give the user a full view of their conversation no matter where or when it happened.

    Display Names

    If a user is added to the contact list with a name, we already show that name instead of their address in conversations.  What if a user is not a contact yet, or we haven’t set a name for them?  It might be useful to be able to fetch any display name they advertise for themselves and show that.  First we add a simple helper to expose write_with_handler outside of the main object:

    public def write_with_handler(stanza, &block)
    	client.write_with_handler(stanza, &block)
    end

    We need an attribute on the Conversation to hold the nickname:

    attr_accessor :nickname

    And then we can use this in Conversation#initialize to fetch the other side’s nickname if they advertise one and we don’t have one for them yet:

    self.nickname = BLATHER.my_roster[jid]&.name || jid
    return unless nickname.to_s == jid.to_s
    
    BLATHER.write_with_handler(
    	Blather::Stanza::PubSub::Items.new(:get).tap { |iq|
    		iq.node = "http://jabber.org/protocol/nick"
    		iq.to = jid
    	}
    ) do |reply|
    	self.nickname = reply.items.first.payload_node.text rescue self.nickname
    end

    Inside the window declaration we can use this as the window title:

    title <=> [self, :nickname]

    and in format_sender we can use this as well:

    return nickname if jid.to_s == @jid.to_s

    Avatars

    Names are nice, but what about pictures?  Can we have nice avatar images that go with each user?  What should we display if they don’t have an avatar set?  Well not only is there a protocol to get an avatar, but a specification that allows all clients to use the same colours to represent things, so we can use a block of that if there is no avatar set.  Let’s generate the colour blocks first.  Add this to Gemfile :

    gem "hsluv"

    Require the library at the top:

    require "hsluv"
    
    $avatars = {}

    And a method on Conversation to use this:

    def default_avatar(string)
    	hue = (Digest::SHA1.digest(string).unpack1("v").to_f / 65536) * 360
    	rgb = Hsluv.rgb_prepare(Hsluv.hsluv_to_rgb(hue, 100, 50))
    	rgba = rgb.pack("CCC") + "xff".b
    	image { image_part(rgba * 32 * 32, 32, 32, 4) }
    end

    This takes the SHA-1 of a string, unpacks the first two bytes as a 16-bit little-endian integer, converts the range from 0 to MAX_SHORT into the range from 0 to 360 for hue degrees, then passes to the library we added to convert from HSV to RGB colour formats.  The GUI library expects images as a byte string where every 4 bytes are 0 to 255 for red, then green, then blue, then transparency.  Because we want a square of all one colour, we can create the byte string for one pixel and then multiply the string by the width and height (multiplying a string by a number in Ruby make a new string with that many copies repeated) to get the whole image.

    In Conversation#initialize we can use this to make a default avatar on the dummy message row then the window first opens:

    @messages = [[default_avatar(""), "", ""]]

    And we will need to add a new column definition to be beginning of the table { block:

    image_column("Avatar")

    And actually add the image to message_row :

    def message_row(m)
    	from = m.from&.stripped || BLATHER.jid.stripped
    	[
    		$avatars[from.to_s] || default_avatar(from.to_s),
    		format_sender(from),
    		m.body
    	]
    end

    If you run this you should now see a coloured square next to each message.  We would now like to get actual avatars, so add this somewhere at the top level to advertise support for this:

    set_caps(
    	"https://git.singpolyma.net/jabber-client-demo",
    	[],
    	["urn:xmpp:avatar:metadata+notify"]
    )

    Then in the when_ready block make sure to send it to the server:

    send_caps

    And handle the avatars as they come in:

    pubsub_event(
    	"//ns:items[@node='urn:xmpp:avatar:metadata']",
    	ns: "http://jabber.org/protocol/pubsub#event"
    ) do |m|
    	id = m.items.first&.payload_node&.children&.first&.[]("id")
    	next $avatars.delete(m.from.stripped.to_s) unless id
    
    	path = DATA_DIR + id.to_s
    	key = m.from.stripped.to_s
    	if path.exist?
    		LibUI.queue_main { $avatars[key] = image(path.to_s, 32, 32) rescue nil }
    	else
    		write_with_handler(
    			Blather::Stanza::PubSub::Items.new(:get).tap { |iq|
    				iq.node = "urn:xmpp:avatar:data"
    				iq.to = m.from
    			}
    		) do |reply|
    			next if reply.error?
    
    			data = Base64.decode64(reply.items.first&.payload_node&.text.to_s)
    			path.write(data)
    			LibUI.queue_main { $avatars[key] = image(path.to_s, 32, 32) rescue nil }
    		end
    	end
    end

    When an avatar metadata event comes in, we check what it is advertising as the ID of the avatar for this user.  If there is none, that means they don’t have an avatar anymore so delete anything we may have in the global cache for them, otherwise create a file path in the same folder as the database based on this ID.  If that file exists already, then no need to fetch it again, create the image from that path on the GUI thread and set it into our global in-memory cache.  If the file does not exist, then use write_with_handler to request their avatar data.  It comes back Base64 encoded, so decode it and then write it to the file.

    If you run this you should now see avatars next to messages for anyone who has one set.

    Delivery Receipts

    The Internet is a wild place, and sometimes things don’t work out how you’d hope.  Sometimes something goes wrong, or perhaps just all of a user’s devices are turned off.  Whatever the reason, it can be useful to see if a message has been delivered to at least one of the intended user’s devices yet or not.  We’ll need a new database column to store that status, add after the end of the DB.user_version < 1 if block:

    if DB.user_version < 2
    	DB.execute(<<~SQL)
    		ALTER TABLE messages ADD COLUMN delivered INTEGER NOT NULL DEFAULT 0
    	SQL
    	DB.user_version = 2
    end

    Let’s advertise support for the feature:

    set_caps(
    	"https://git.singpolyma.net/jabber-client-demo",
    	[],
    	["urn:xmpp:avatar:metadata+notify", "urn:xmpp:receipts"]
    )

    We need to add delivery status and stanza id to the dummy row for the messages table:

    @messages = [[default_avatar(""), "", "", false, nil]]

    And make sure we select the status out of the database when loading up messages:

    SELECT stanza,delivered FROM messages WHERE conversation=? ORDER BY created_at

    And pass that through when building the message rows

    mam_messages << [m, row[1]]

    mam_messages.map! { |args| message_row(*args) }

    Update the messages table to expect the new data model:

    table {
    	image_column("Avatar")
    	text_column("Sender")
    	text_column("Message")
    	checkbox_column("Delivered")
    	editable false
    	cell_rows @messages
    	@messages.clear if @messages.length == 1 && @messages.first.last.nil?
    }

    And update the row builder to include this new data:

    def message_row(m, delivered=false)
    	from = m.from&.stripped || BLATHER.jid.stripped
    	[
    		$avatars[from.to_s] || default_avatar(from.to_s),
    		format_sender(from),
    		m.body,
    		delivered,
    		m.id
    	]
    end

    Inbound messages are always considered delivered, since we have them:

    def new_message(m)
    	@messages << message_row(m, true)
    end

    And a method to allow signalling that a delivery receipt should be displayed, using the fact that we now hide the stanza id off the end of the rows in the table to find the relevant message to update:

    def delivered_message(id)
    	row = @messages.find_index { |r| r.last == id }
    	return unless row
    
    	@messages[row] = @messages[row][0..-3] + [true, id]
    end

    In the Send button’s on_clicked handler we need to actually request that others send us receipts:

    m = message
    xml_child(m, :request, "urn:xmpp:receipts")

    And we need to handle the receipts when they arrive:

    message "./ns:received", ns: "urn:xmpp:receipts" do |m, received|
    	DB.execute(<<~SQL, [received.first["id"].to_s])
    		UPDATE messages SET delivered=1 WHERE stanza_id=?
    	SQL
    
    	conversation = $conversations[m.from.stripped.to_s]
    	return unless conversation
    
    	LibUI.queue_main do
    		conversation.delivered_message(received.first["id"].to_s)
    	end
    end

    When we get a received receipt, we get the id attribute off of it, which represents a stanza ID that this receipt is for.  We update the database, and inform any open conversation window so the GUI can be updated.

    Finally, if someone requests a receipt from us we should send it to them:

    message :body do |m|
    	handle_live_message(m)
    
    	if m.id && m.at("./ns:request", ns: "urn:xmpp:receipts")
    		self << m.reply(remove_children: true).tap { |receipt|
    			xml_child(receipt, :received, "urn:xmpp:receipts").tap { |received|
    				received["id"] = m.id
    			}
    		}
    	end
    end

    If the stanza has an id and a receipt request, we construct a reply that contains just the received receipt and send it.

    Message Correction

    Sometimes people send a message with a mistake in it and want to send another to fix it.  It is convenvient for the GUI to support this and render only the new version of the message.  So let’s implement that.  First we add it to the list of things we advertise support for:

    set_caps(
    	"https://git.singpolyma.net/jabber-client-demo",
    	[],
    	[
    		"urn:xmpp:avatar:metadata+notify",
    		"urn:xmpp:receipts",
    		"urn:xmpp:message-correct:0"
    	]
    )

    Then we need a method on Conversation to process incoming corrections and update the GUI:

    def new_correction(replace_id, m)
    	row = @messages.find_index { |r| r.last == replace_id }
    	return unless row
    
    	@messages[row] = message_row(m, true)
    end

    We look up the message row on the stanza id, just as we did for delivery receipts, and just completely replace it with a row based on the new incoming message.  That’s it for the GUI.  Corrections may come from live messages, from carbons, or even from the server archive if they happened while we were disconnected, so we create a new insert_message helper to handle any case we previously did the SQL INSERT for an incoming message:

    def insert_message(
    	m,
    	mam_id:,
    	counterpart: m.from.stripped.to_s,
    	delay: m.delay&.stamp&.to_i
    )
    	if (replace = m.at("./ns:replace", ns: "urn:xmpp:message-correct:0"))
    		DB.execute(<<~SQL, [m.to_s, counterpart, replace["id"].to_s])
    			UPDATE messages SET stanza=? WHERE conversation=? AND stanza_id=?
    		SQL
    	else
    		delay ||= Time.now.to_i
    		DB.execute(<<~SQL, [mam_id, m.id, counterpart, delay, m.to_s])
    			INSERT OR IGNORE INTO messages
    			(mam_id, stanza_id, conversation, created_at, stanza, delivered)
    			VALUES (?,?,?,?,?,1)
    		SQL
    	end
    end

    The else case here is the same as the INSERTs we’ve been using up to this point, but we also check first for an element that signals this as a replacement and if that is the case we issue an UPDATE instead to correct our internal archive to the new version.

    Then in handle_live_message we also signal the possibly-open GUI:

    if (replace = m.at("./ns:replace", ns: "urn:xmpp:message-correct:0"))
    	conversation.new_correction(replace["id"].to_s, m)
    else
    	conversation.new_message(m)
    end

    We can now display incoming corrections, but it would also be nice to be able to send them.  Add a second button after the Send button in Conversation that can re-use the @message_entry box to correct the most recently sent message:

    button("Correct") {
    	stretchy false
    
    	on_clicked do
    		replace_row = @messages.rindex { |message|
    			message[1] == format_sender(BLATHER.jid.stripped)
    		}
    		next unless replace_row
    
    		m = message
    		m << xml_child(m, :replace, "urn:xmpp:message-correct:0").tap { |replace|
    			replace["id"] = @messages[replace_row].last
    		}
    		EM.defer do
    			BLATHER << m
    			DB.execute(<<~SQL, [m.to_s, @jid, @messages[replace_row].last])
    				UPDATE messages SET stanza=? WHERE conversation=? AND stanza_id=?
    			SQL
    		end
    		@messages[replace_row] = message_row(m, @messages[replace_row][-2])
    		@message_entry.text = ""
    	end
    }

    When the button is clicked we find the row for the most recently sent message, construct a message to send just as in the Send case but add the message correction replace child with the id matching the stanza id of the most recently sent message.  We send that message and also update our own local copy of the stanza both in the database and in the memory model rendered in the GUI.

    Conclusion

    There are a lot more features that a chat system can implement, but hopefully this gives you a useful taste of how each one can be incrementally layered in, and what the considerations might be for a wide variety of different kinds of features.  All the code for the working application developed in this article is available in git under AGPLv3+, with commits that corrospond to the path we took here .

    • wifi_tethering open_in_new

      This post is public

      blog.jmp.chat /b/2022-chat-client-from-scratch

    • chevron_right

      Ignite Realtime Blog: HTTP File Upload plugin 1.2.0 released

      news.movim.eu / PlanetJabber • 30 November, 2022

    We have now released version 1.2.0 of the HTTP File Upload plugin!

    This plugin adds functionality to Openfire that allows clients to share files, as defined in the XEP-0363 ‘HTTP File Upload’ specification .

    This release primarily enhances functionality when running in an Openfire cluster. All changes can be reviewed in the changelog for this release of the plugin.

    As always, your instance of Openfire should automatically display the availability of the update. Alternatively, you can download the new release of the plugin at the HTTP File Upload plugin’s archive page .

    For other release announcements and news follow us on Twitter

    1 post - 1 participant

    Read full topic

    • chevron_right

      Prosodical Thoughts: Bringing FASTer authentication to Prosody and XMPP

      news.movim.eu / PlanetJabber • 28 November, 2022 • 9 minutes

    As our work continues on modernizing XMPP authentication , we have some more new milestones to share with you. Until now our work has mostly been focused on internal Prosody improvements, such as the new roles and permissions framework . Now we are starting to extend our work to the actual client-to-server protocol in XMPP.

    Prosody and Snikket are both regularly used from mobile devices, which have intermittent connectivity. Even if it’s only a change between networks, or when driving through a tunnel for a few minutes, these things can temporarily break your connection - requiring a new one to be established.

    We’ve had solutions and optimizations in the XMPP protocol for this situation for years (really… the first version of XEP-0198 was published in 2004!). XEP-0198 allows a client to reconnect to the server as soon as the network comes back, easily discover if anything failed to be sent/received due to the network interruption, and then resync any lost packets in either direction.

    This effectively allows resuming and repairing the session as if no disconnect occurred, while skipping a bunch of traffic that would usually be exchanged when establishing a new session (instead, everything is simply cached from the old session).

    However, there is one important thing we don’t allow the client to skip. To keep this resumption step secure, we require authentication. It’s a new connection, and we need to prove it’s from who it claims to be from.

    Authentication in XMPP today

    The most common authentication method for XMPP connections today is SCRAM. This is a neat password-based authentication mechanism that has many nice properties, such as allowing both the client and the server to store only a hash of the password. It also allows the client to determine that the server really knows the user’s password, and supports channel binding. These features allow the client to detect various kinds of attack.

    Even though we have been using SCRAM in XMPP for many years now, it still offers more protective features today than the vast majority of online services you use - which generally all send your password to the server in plain text, albeit within TLS or HTTPS.

    A new SCRAM alternative is currently being developed, known as OPAQUE, which adds even more nice properties. But that’s for future blog post… :)

    However, there are some drawbacks of SCRAM (and similar mechanisms, including OPAQUE) that can’t realistically be solved. To adequately protect your password, it requires some back-and-forth negotiation with the server. In protocol speak, we refer to such situations as “round trips” - every time the client sends something to the server and has to wait for a response before it can proceed. On a very slow network, round trips can add a bunch of latency , and as anyone who has used audio/video calls or gaming online knows, latency can be frustrating and hard to eliminate from a connection.

    Simpler authentication methods just have the client say “here are my credentials”, and the server say “your credentials look great, you’re authenticated!“. That’s how HTTP and most websites work today. Such approaches are quick and easy, but they don’t protect your credentials as well as SCRAM does.

    Passwords are the problem

    SCRAM’s protections are important for passwords. Passwords are (unfortunately) often chosen by users to be the same or similar across multiple services, and even if they are strong and unique they can be vulnerable to phishing. If leaked, many memorable passwords contain private information about the user.

    We don’t want to drop any of our important password security features just to improve connection speed. So instead we found a better solution: drop passwords!

    Our new solution allows the client to log in initially using a password (or any other method the XMPP server supports). After that, it can upgrade to a strong unique authentication token provided by the server, which it can use to quickly re-authenticate on future connections.

    Tokens are the answer

    Tokens have many advantages compared to passwords:

    • They are unique to the service that generated them , so cross-service attacks like credential stuffing are useless against tokens.
    • Tokens don’t need to be memorable , so they can be very long and random (both desirable properties for increasing account security!).
    • As they are not memorized by the user, they can be rotated frequently without any inconvenience.
    • Different tokens can be generated for each of a user’s devices , instead of sharing the user’s password across all of them. This also allows selectively removing a device’s access from the user’s account, e.g. if it gets lost or stolen.

    With these security advantages, we suddenly unlock the ability to use simpler authentication mechanisms without risking the security of the user’s account or password.

    Still, we can do a bit better than just sending the token to the server as plain text. Fortunately, just the kind of modern token authentication method we need has already been in development by Florian Schmaus: the SASL HT mechanism family .

    HT mechanisms have the following properties:

    • The actual token itself is not exchanged over the connection during authentication.
    • And yet, the server receives proof that the client has the full correct token .
    • The client also receives proof that the server has the full correct token (and isn’t just impersonating the real server).
    • Finally, if channel binding is used, both sides receive proof that no MITM or relay attack being performed .

    And… all this can be completed within a single round trip!

    The protocol to achieve this has been submitted to the XSF as “Fast Authentication Streamlining Tokens” . It is in the acceptance queue, so doesn’t have a XEP number assigned yet.

    Updating and integrating with SASL2

    If FAST authentication was the only thing we had been working on recently, we would be happy enough. But there’s more…

    In collaboration with Thilo Molitor from the Monal project , a new version of XEP-0388 (SASL 2) has been submitted . SASL 2 was originally proposed back in 2017, and it defines a new authentication protocol for XMPP (still based on SASL, so we can reuse all the existing mechanisms we already have in place).

    Several features of SASL 2 are very relevant to our work. For example, it allows negotiation of session features in parallel with the authentication process. The old way required the client to authenticate, and then proceed to negotiate whatever features and parameters it wanted for the new session. With SASL2 the client can provide this information at the same time it provides its credentials. This saves yet more round trips.

    As well as SASL 2, we’ve also updated a related proposal from around the same time, XEP-0386 (Bind 2). This is also a critical piece of session establishment that integrates with SASL 2.

    With the work we’ve done across these three specifications - XEP-0388, XEP-0386 and FAST - we’ve essentially overhauled the entire authentication and session establishment protocol of XMPP . Even with all our additional authentication security features, it’s now possible for a client to connect, authenticate, and resume or create a session in a single request and response.

    This post shouldn’t be taken as being entirely about performance improvements. It’s nice to be able to (re)connect to the server in the blink of an eye. But there are other reasons to be working on this.

    As anyone who used XMPP in 2012 and 2022 knows, XMPP has been continuously evolving as both the internet and the way people use it has changed. Over time we have “bolted on” various features to the connection process to achieve this evolution.

    Now, with these new changes, we are bringing all these enhancements together into a single framework that was designed for them to fit neatly into. Not only are we reducing round trips, we are also simplifying connection establishment for the next generation of XMPP developers.

    When can I use all this?

    Even though this is all cutting edge stuff, you’ll be able to use it much sooner than you might think!

    Prosody has support for the new SASL 2, Bind 2 and FAST protocols. They are all available as community modules right now, though we intend for them to become part of the main Prosody distribution eventually.

    To get started, you’ll need a Prosody trunk nightly build , and simply enable the following community modules:

    To take advantage of the new features, you’ll need a compatible client. FAST is already implemented in multiple clients, and will be available from Conversations 2.11 for Android, as well as the next major versions of Monal , Siskin and Beagle for iOS and MacOS.

    Gajim already has SASL 2 implemented, and other client developers have also already expressed an interest in support.

    If you’re a client or library developer interested in supporting any of this, we have a test server available that you are welcome to use. Just let us know!

    Do remember that all this is still very new and experimental . The relevant protocol specifications are still working their way through the XSF standards process and there may be changes to come in the future. There may also be undiscovered bugs. We encourage brave souls to help test it all in real world deployments, but if your priority is keeping a stable setup, you should probably wait a little longer before deploying any of this.

    TCP Fast Open

    While this post is not just about performance improvements, we’ve talked a lot about performance improvements. Therefore it’s worth noting an extra little side feature at this point.

    Prosody trunk builds, when used with the new LuaSocket 3.1.0, support something known as TCP Fast Open . This is a low-level TCP extension that allows new connections to skip a round trip, by exchanging initial data packets while the connection is being established.

    It’s disabled for servers by default on Linux, but you can enable it on most modern systems by creating the file /etc/sysctl.d/tcp-fastopen.conf with the contents:

    net.ipv4.tcp_fastopen=3
    

    Run systemctl restart systemd-sysctl.service to apply the changes. More information on the sysctl configuration can be found in the Linux kernel documentation .

    In Prosody’s config, add the following in the global section:

    network_settings = {
        tcp_fastopen = 256;
    }
    

    Restart Prosody to apply these changes. Be aware that some networks and routers have been reported to be incompatible with TCP Fast Open (support was removed from Firefox for this reason). Although Linux has built-in recovery mechanisms that should work around such issues, if you experience trouble connecting to your server from certain networks, you may want to try turning this off again.

    We’re also looking at support for TLS 1.3’s 0-RTT mode, which can be combined with FAST authentication and TCP Fast Open to achieve full connection establishment within a single round-trip. Pretty impressive!

    Next steps

    These protocol changes are yet another step on our XMPP authentication modernization journey. With the new protocols now written and implemented, we can start looking forward to the next milestones for the project.

    In the coming months, we’ll be working on the ability to sign in to your XMPP account from third-party clients and services without sharing your password with them. Subscribe to our blog or Mastodon account and keep an eye out for that future post!

    • wifi_tethering open_in_new

      This post is public

      blog.prosody.im /fast-auth/

    • chevron_right

      Jérôme Poisson: Libervia progress note 2022-W45

      news.movim.eu / PlanetJabber • 24 November, 2022 • 8 minutes

    Hello, it's time for a long overdue progress note.

    I'll talk here about the work made on ActivityPub (AP) gateway and on end-to-end encryption around pubsub.

    Oh, and if everything goes well, this blog post should be accessible from XMPP and ActivityPub (and HTTP and ATOM feed), using the same identifier goffi@goffi.org .

    Forewords

    The work made on the AP gateway has been possible thanks to a NLnet/NGI0 grant (with financial support from the European Commission's Next Generation Internet programme).

    I especially appreciated that the team was really there to help bring the ideas to life, and not once did they get in the way: little paperwork, no unnecessary pressure, caring, contacts when help was needed, etc.

    I wish there were more organizations like this one that really help develop libre projects for the common good.

    So once again I want to thank them for all that.

    XMPP ⬌ ActivityPub Gateway

    There is probably no need to explain here what is ActivityPub , we can simply write that it is an open protocol that allows to do things that XMPP also allows doing, and that until now these 2 protocols could not communicate together. The work on the ActivityPub gateway aims to allow software implementing one of these 2 protocols to communicate as easily as possible. I firmly believe that all open protocols should be able to communicate which each other, to avoid creating more silos, proprietary software is already good enough at that.

    To be useful, a gateway must use the full potential of both protocols. A simple bot transcribing messages as we see too often, using unsuitable features (such as instant messaging for blog posts), or using a very limited set of features to ensure compatibility are flaws that I have tried to avoid. Building a good gateway is a difficult and time-consuming task. If done right, the gateway should be as invisible as possible to the end user.

    XMPP is featuring blogging since long before AP, however the set of features is not exactly the same. Current use of AP is clearly inspired from commercial "social" networks, and metadata such as subscribers/subscribed nodes (or followers/following in AP terms) are highlighted, feature such as like/favourite were missing in XMPP, and some implementation such as Pleroma do implement reactions. To integrate that in the gateway, I've been working on new specifications:

    • Pubsub Public Subscriptions : a way to publicly announce subscriptions, in an opt-in way. With this it's possible to implement followers/following features in a way respectful of privacy.

    • Pubsub Attachments : a generic way to attach any kind of data to a pubsub item. It's notably used to implements noticed/favourite button (see here and reactions.

    • Calendar Events : handling of events and all the RSVP mechanism. Libervia was handling events for years, but it was an experimental implementation, this specification is a next step in the effort to make it a standard.

    Note that this XEP and the others linked below have been accepted but are not yet visible in official list.

    You may wonder why there is a specification for Calendar Events… It's because the AP gateway also handles them, making it compatible with Mobilizon . The gateway may evolve in the future to support other non (micro)blogging use cases.

    The gateway is now finished in terms of functionalities, however the code is clearly of an alpha quality for the moment. Now the goal in the coming months will be to stabilize and possibly implement other features if there is a demand for it.

    Early adopters are encouraged to try and test it as long as they keep in mind that it's not stable. So if you do try it, I recommend keeping a separate ActivityPub account in whatever stable implementation you use at the moment, this way you can check if messages or media are missing, if there is any inconsistency or other bugs, and report them to me. If you test it, please join the XMPP room libervia@chat.jabberfr.org ( click here to connect from your browser ) for help and feedback. Stabilization will probably take weeks, but I hope to have it done by early 2023.

    Installation instructions and details on how the conversion between protocols is done is available in the documentation and notably here

    A question I've been asked a lot: yes, you can use the same identifier for XMPP (JID) and AP ( WebFinger actor handle) as long as you use "simple" characters (i.e. alphanumeric ASCII chars, _ , . and - ). If you use something more complicated, you'll have to use the escaping mechanism explained in the doc (this is due to constraints with some AP implementations).

    As for blogs on pubsub nodes (what Movim calls "communities"), I made it simple: you can use directly the name of the node that holds the blog in the local part (i.e. before the "@") of your actor handle: a blog named community_bog at the XMPP pubsub service pubsub.example.org can thus be addressed with the AP actor community_blog@pubsub.example.org . This way you can use a rather user-friendly identifier to share your blog with people who are only on ActivityPub.

    This gateway should work with any XMPP server, and any client that implement blogging features (Only Libervia itself and Movim implement it for now, but I have heard that other clients are planning support for it). To enjoy the whole feature set of the gateway, the new specifications need to be implemented by the clients, so you can start to fill feature requests…

    With this gateway, the door is open to have a client able to talk to the ActivityPub network, while having the feature of XMPP, including e2e encrypted private messages (e2e encrypted only if you communicate with an XMPP account, not with an AP one).

    Oh, and please update your graphics, drawing and other texts to include XMPP in the fediverse ;)

    End-to-End Encryption

    Much effort has also gone into end-to-end encryption.

    OMEMO implementation has been updated (OMEMO:2 is now used), including Stanza Content Encryption which allows encrypting arbitrary elements instead of only the \<body/> of the message, I believe that Libervia is the first XMPP client to implement it. OpenPGP for XMPP (or "OX") has also been implemented, all that thanks to the work of Tim Henkes "Syndace", the author of python-omemo .

    Beside instant messaging, end-to-end encryption has also been introduced to pubsub. I've made specifications for two methods:

    • An OpenPGP profile for pubsub which is thought to encrypt a whole node, with a system of secret sharing/rotation/revocation. With it, it is easy to give access to new entities after publication, and to retrieve old items for newcomers. This specification can be used to encrypt any pubsub based features: (micro)blogging, calendar events, lists, etc.

    • Pubsub Targeted Encryption which is a way to apply the same cryptographic system used in instant messaging to pubsub. This way, OMEMO can be used with its forward secrecy property. It is not a good option to use this specification to encrypt a whole node, as archive is then not accessible to newcomers, and to add access to a new entity you have to re-encrypt all items, but it's an interesting option to encrypt an element occasionally, for instance to restrict access of a specific post in an otherwise public blog.

    Specifications have also been written to sign a pubsub item in a backward compatible way (client which don't implement those specifications can still work normally):

    All those specifications are already implemented in Libervia, but they are only usable from CLI frontend at the moment. All you have to do is to use the --encrypt and/or --sign options from pubsub or blog commands (check documentation for details).

    Uploaded files were already encrypted with OMEMO Media Sharing which is what is commonly used these days, but this method has not been accepted as a standard as it was a workaround for limitation of legacy OMEMO implementation. The proper way is now specified with Stateless File Sharing and is encrypted with Encryption For Stateless File Sharing . Those methods are currently only usable when OMEMO:2 is implemented in the peer client, and with them metadata on the shared file can be attached, including thumbnails.

    Encryption has also been implemented for Jingle ( XEP-0391 and XEP-0396 ), which is notably used for Jingle File Transfer (specially useful for large files transfers).

    So to summarize, nearly everything (instant messaging, files uploaded, large file transfers, all pubsub related features) can now be e2e encrypted with Libervia.

    Possible Future

    With the AP gateway permitting to reach the whole AP network, all the new features implemented, and the work done on e2e encryption, Libervia has everything to be a solid option for communication. After the recent events regarding a famous commercial network, we see a breakthrough of ActivityPub that will hopefully last over time. We can now access AP from XMPP, while having the possibility to have e2e encrypted private conversations or even blogs or calendar events.

    As far as I know this is, so far, something unique for a Libre decentralized software. However, there is still work to do on stabilization on UI/UX update before this is really usable.

    Those feature were planned for very long (years), but the lack of resources made them slow to come. The grant has made it possible to greatly accelerate the pace of development, and I doubt that it would have been possible to have all that without it.

    Regarding how large the project is, and my family life, it's not possible any more to develop seriously this project on my free time alone (and I would like to do other things, sometimes, of my free time).

    In other words, I need to find a way to sustain the development of Libervia for the years to come, so I can work full-time on it, and with some luck, build a team. I'm thinking very seriously about it these days, I'll probably write on this topic in a little while. If you are willing to help in any way, please contact me (on the Libervia room linked above for instance).

    That's all for this progress note. I'm now working on stabilization and UI/UX update on the web frontend.

    • wifi_tethering open_in_new

      This post is public

      www.goffi.org /b/libervia-progress-note-2022-w45-MTdL

    • chevron_right

      Ignite Realtime Blog: Openfire Monitoring Service plugin 2.4.0 release

      news.movim.eu / PlanetJabber • 22 November, 2022

    Earlier today, we have released version 2.4.0 of the Openfire Monitoring Service plugin. This plugin adds both statistics, as well as message archiving functionality to Openfire.

    In this release, compatibility with future versions of Openfire is added. A bug that affects MSSQL users has been fixed, and the dreaded “Unable to save XML properties” error message has been resolved. A few other minor tweaks have been added.

    As always, your instance of Openfire should automatically display the availability of the update. Alternatively, you can download the new release of the plugin at the Monitoring plugin’s archive page .

    For other release announcements and news follow us on Twitter

    1 post - 1 participant

    Read full topic