aboutsummaryrefslogtreecommitdiffstats
path: root/spec/scansion
diff options
context:
space:
mode:
Diffstat (limited to 'spec/scansion')
-rw-r--r--spec/scansion/basic.scs18
-rw-r--r--spec/scansion/basic_message.scs174
-rw-r--r--spec/scansion/basic_roster.scs73
-rw-r--r--spec/scansion/issue1224.scs115
-rw-r--r--spec/scansion/issue505.scs79
-rw-r--r--spec/scansion/issue978-multi.scs111
-rw-r--r--spec/scansion/issue978.scs85
-rw-r--r--spec/scansion/muc_affiliation_notify.scs137
-rw-r--r--spec/scansion/muc_mediated_invite.scs76
-rw-r--r--spec/scansion/muc_password.scs143
-rw-r--r--spec/scansion/muc_register.scs506
-rw-r--r--spec/scansion/muc_whois_anyone_member.scs101
-rw-r--r--spec/scansion/pep_nickname.scs72
-rw-r--r--spec/scansion/pep_publish_subscribe.scs210
-rw-r--r--spec/scansion/prosody.cfg.lua82
-rw-r--r--spec/scansion/pubsub_advanced.scs167
-rw-r--r--spec/scansion/pubsub_basic.scs104
-rw-r--r--spec/scansion/pubsub_config.scs205
-rw-r--r--spec/scansion/pubsub_createdelete.scs63
-rw-r--r--spec/scansion/vcard_temp.scs80
20 files changed, 2601 insertions, 0 deletions
diff --git a/spec/scansion/basic.scs b/spec/scansion/basic.scs
new file mode 100644
index 00000000..43c9831a
--- /dev/null
+++ b/spec/scansion/basic.scs
@@ -0,0 +1,18 @@
+# Basic login and initial presence
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+Romeo sends:
+ <presence/>
+
+Romeo receives:
+ <presence/>
+
+Romeo disconnects
+
diff --git a/spec/scansion/basic_message.scs b/spec/scansion/basic_message.scs
new file mode 100644
index 00000000..fb21c465
--- /dev/null
+++ b/spec/scansion/basic_message.scs
@@ -0,0 +1,174 @@
+# Basic message routing and delivery
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost
+ password: password
+
+[Client] Juliet's phone
+ jid: juliet@localhost
+ password: password
+ resource: mobile
+
+---------
+
+# Act 1, scene 1
+# The clients connect
+
+Romeo connects
+
+Juliet connects
+
+Juliet's phone connects
+
+# Romeo publishes his presence. Juliet has not, and so does not receive presence.
+
+Romeo sends:
+ <presence/>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}" />
+
+# Romeo sends a message to Juliet's full JID
+
+Romeo sends:
+ <message to="${Juliet's full JID}" type="chat">
+ <body>Hello Juliet!</body>
+ </message>
+
+Juliet receives:
+ <message to="${Juliet's full JID}" from="${Romeo's full JID}" type="chat">
+ <body>Hello Juliet!</body>
+ </message>
+
+# Romeo sends a message to Juliet's phone
+
+Romeo sends:
+ <message to="${Juliet's phone's full JID}" type="chat">
+ <body>Hello Juliet, on your phone.</body>
+ </message>
+
+Juliet's phone receives:
+ <message to="${Juliet's phone's full JID}" from="${Romeo's full JID}" type="chat">
+ <body>Hello Juliet, on your phone.</body>
+ </message>
+
+# Scene 2
+# This requires the server to support offline messages (which is optional).
+
+# Romeo sends a message to Juliet's bare JID. This is not immediately delivered, as she
+# has not published presence on either of her resources.
+
+Romeo sends:
+ <message to="juliet@localhost" type="chat">
+ <body>Hello Juliet, are you there?</body>
+ </message>
+
+# Juliet sends presence on her phone, and should receive the message there
+
+Juliet's phone sends:
+ <presence/>
+
+Juliet's phone receives:
+ <presence/>
+
+Juliet's phone receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>Hello Juliet, are you there?</body>
+ <delay xmlns='urn:xmpp:delay' from='localhost' stamp='{scansion:any}' />
+ </message>
+
+# Romeo sends another bare-JID message, it should be delivered
+# instantly to Juliet's phone
+
+Romeo sends:
+ <message to="juliet@localhost" type="chat">
+ <body>Oh, hi!</body>
+ </message>
+
+Juliet's phone receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>Oh, hi!</body>
+ </message>
+
+# Juliet's laptop goes online, but with a negative priority
+
+Juliet sends:
+ <presence>
+ <priority>-1</priority>
+ </presence>
+
+Juliet receives:
+ <presence from="${Juliet's full JID}">
+ <priority>-1</priority>
+ </presence>
+
+Juliet's phone receives:
+ <presence from="${Juliet's full JID}">
+ <priority>-1</priority>
+ </presence>
+
+# Again, Romeo sends a message to her bare JID, but it should
+# only get delivered to her phone:
+
+Romeo sends:
+ <message to="juliet@localhost" type="chat">
+ <body>How are you?</body>
+ </message>
+
+Juliet's phone receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>How are you?</body>
+ </message>
+
+# Romeo sends direct to Juliet's full JID, and she should receive it
+
+Romeo sends:
+ <message to="${Juliet's full JID}" type="chat">
+ <body>Are you hiding?</body>
+ </message>
+
+Juliet receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>Are you hiding?</body>
+ </message>
+
+# Juliet publishes non-negative presence
+
+Juliet sends:
+ <presence/>
+
+Juliet receives:
+ <presence from="${Juliet's full JID}"/>
+
+Juliet's phone receives:
+ <presence from="${Juliet's full JID}"/>
+
+# And now Romeo's bare JID messages get delivered to both resources
+# (server behaviour may vary here)
+
+Romeo sends:
+ <message to="juliet@localhost" type="chat">
+ <body>There!</body>
+ </message>
+
+Juliet receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>There!</body>
+ </message>
+
+Juliet's phone receives:
+ <message from="${Romeo's full JID}" type="chat">
+ <body>There!</body>
+ </message>
+
+# The End
+
+Romeo disconnects
+
+Juliet disconnects
+
+Juliet's phone disconnects
diff --git a/spec/scansion/basic_roster.scs b/spec/scansion/basic_roster.scs
new file mode 100644
index 00000000..2e292083
--- /dev/null
+++ b/spec/scansion/basic_roster.scs
@@ -0,0 +1,73 @@
+# Basic roster test
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+Juliet connects
+
+Romeo sends:
+ <presence/>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}" />
+
+Romeo sends:
+ <iq type="get" id="roster1">
+ <query xmlns='jabber:iq:roster'/>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="roster1">
+ <query ver='{scansion:any}' xmlns="jabber:iq:roster"/>
+ </iq>
+
+# Add nurse to roster
+
+Romeo sends:
+ <iq type="set" id="roster2">
+ <query xmlns="jabber:iq:roster">
+ <item jid='nurse@localhost'/>
+ </query>
+ </iq>
+
+# Receive the roster add result
+
+Romeo receives:
+ <iq type="result" id="roster2"/>
+
+# Receive the roster push
+
+Romeo receives:
+ <iq type="set" id="{scansion:any}">
+ <query xmlns='jabber:iq:roster' ver='{scansion:any}'>
+ <item jid='nurse@localhost' subscription='none'/>
+ </query>
+ </iq>
+
+Romeo sends:
+ <iq type="result" id="fixme"/>
+
+# Fetch the roster, it should include nurse now
+
+Romeo sends:
+ <iq type="get" id="roster3">
+ <query xmlns='jabber:iq:roster'/>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="roster3">
+ <query xmlns='jabber:iq:roster' ver="{scansion:any}">
+ <item subscription='none' jid='nurse@localhost'/>
+ </query>
+ </iq>
+
+Romeo disconnects
diff --git a/spec/scansion/issue1224.scs b/spec/scansion/issue1224.scs
new file mode 100644
index 00000000..b75cfbd1
--- /dev/null
+++ b/spec/scansion/issue1224.scs
@@ -0,0 +1,115 @@
+# MUC: Handle affiliation changes from buggy clients
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Juliet's JID}" />
+ </query>
+ </iq>
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's JID}" affiliation='member' xmlns='http://jabber.org/protocol/muc#user'/>
+ </x>
+ </message>
+
+Romeo receives:
+ <iq from='room@conference.localhost' id='member1' type='result'/>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Romeo makes Juliet a member of the room, however his client is buggy and only
+# specifies her nickname
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' nick='Juliet' />
+ </query>
+ </iq>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' role='participant' jid="${Juliet's full JID}">
+ <actor jid="${Romeo's full JID}" nick='Romeo'/>
+ </item>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq type='result' id='member1' from='room@conference.localhost' />
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' role='participant' jid="${Juliet's full JID}">
+ <actor nick='Romeo' />
+ </item>
+ <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
+ </x>
+ </presence>
diff --git a/spec/scansion/issue505.scs b/spec/scansion/issue505.scs
new file mode 100644
index 00000000..24fbeb72
--- /dev/null
+++ b/spec/scansion/issue505.scs
@@ -0,0 +1,79 @@
+# Issue 505: mod_muc doesn’t forward part statuses
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet sends:
+ <presence type='unavailable' to='room@conference.localhost'>
+ <status>Farewell</status>
+ </presence>
+
+Romeo receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet'>
+ <status>Farewell</status>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='none' role='none'/>
+ </x>
+ </presence>
diff --git a/spec/scansion/issue978-multi.scs b/spec/scansion/issue978-multi.scs
new file mode 100644
index 00000000..d8f99228
--- /dev/null
+++ b/spec/scansion/issue978-multi.scs
@@ -0,0 +1,111 @@
+# Issue 978: MUC does not carry error into occupant leave status (multiple clients)
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+[Client] Juliet's phone
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user' scansion:strict='false'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Juliet's phone connects, and joins the room
+Juliet's phone connects
+
+Juliet's phone sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet's phone receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet's phone receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet's phone receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user' scansion:strict='false'>
+ <item affiliation='none' jid="${Juliet's phone's full JID}" role='participant'/>
+ <item affiliation='none' jid="${Juliet's full JID}" role='participant'/>
+ </x>
+ </presence>
+
+# Juliet leaves with an error
+Juliet sends:
+ <presence type='error' to='room@conference.localhost'>
+ <error type='cancel'>
+ <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Test error</text>
+ </error>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's phone's full JID}" affiliation='none' role='participant'/>
+ </x>
+ </presence>
diff --git a/spec/scansion/issue978.scs b/spec/scansion/issue978.scs
new file mode 100644
index 00000000..59db8335
--- /dev/null
+++ b/spec/scansion/issue978.scs
@@ -0,0 +1,85 @@
+# Issue 978: MUC does not carry error into occupant leave status (single client)
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_whois'>
+ <value>anyone</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet sends:
+ <presence type='error' to='room@conference.localhost'>
+ <error type='cancel'>
+ <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ <text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Test error</text>
+ </error>
+ </presence>
+
+Romeo receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet'>
+ <status>Kicked: service unavailable: Test error</status>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='333'/>
+ <item jid="${Juliet's full JID}" affiliation='none' role='none'/>
+ </x>
+ </presence>
diff --git a/spec/scansion/muc_affiliation_notify.scs b/spec/scansion/muc_affiliation_notify.scs
new file mode 100644
index 00000000..e7d21fcd
--- /dev/null
+++ b/spec/scansion/muc_affiliation_notify.scs
@@ -0,0 +1,137 @@
+# MUC: Notification of affiliation changes of non-occupants
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+[Client] Rosaline
+ jid: user3@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Promote Juliet to member
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Juliet's JID}" />
+ </query>
+ </iq>
+
+# Juliet is not in the room, so an affiliation change message is received
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's JID}" affiliation='member' xmlns='http://jabber.org/protocol/muc#user'/>
+ </x>
+ </message>
+
+# The affiliation change succeeded
+
+Romeo receives:
+ <iq from='room@conference.localhost' id='member1' type='result'/>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# To check the status of the room is as expected, Romeo requests the member list
+
+Romeo sends:
+ <iq id='member3' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member'/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq from='room@conference.localhost' type='result' id='member3'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Juliet's JID}" />
+ </query>
+ </iq>
+
+# Romeo grants membership to Rosaline, who is not in the room
+
+Romeo sends:
+ <iq id='member2' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Rosaline's JID}" />
+ </query>
+ </iq>
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Rosaline's JID}" affiliation='member' xmlns='http://jabber.org/protocol/muc#user'/>
+ </x>
+ </message>
+
+Romeo receives:
+ <iq type='result' id='member2' from='room@conference.localhost' />
+
+Romeo sends:
+ <message type="groupchat" to="room@conference.localhost">
+ <body>Finished!</body>
+ </message>
+
+Juliet receives:
+ <message type="groupchat" from="room@conference.localhost/Romeo">
+ <body>Finished!</body>
+ </message>
diff --git a/spec/scansion/muc_mediated_invite.scs b/spec/scansion/muc_mediated_invite.scs
new file mode 100644
index 00000000..340aefc7
--- /dev/null
+++ b/spec/scansion/muc_mediated_invite.scs
@@ -0,0 +1,76 @@
+# MUC: Mediated invites
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects
+Juliet connects
+
+Juliet sends:
+ <presence/>
+
+Juliet receives:
+ <presence/>
+
+
+# Romeo invites Juliet to join the room
+
+Romeo sends:
+ <message to="room@conference.localhost" id="invite1">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <invite to="${Juliet's JID}" />
+ </x>
+ </message>
+
+Juliet receives:
+ <message from="room@conference.localhost" id="invite1">
+ <x xmlns="http://jabber.org/protocol/muc#user">
+ <invite from="room@conference.localhost/Romeo">
+ <reason/>
+ </invite>
+ </x>
+ <body>room@conference.localhost/Romeo invited you to the room room@conference.localhost</body>
+ <x xmlns="jabber:x:conference" jid="room@conference.localhost"/>
+ </message>
diff --git a/spec/scansion/muc_password.scs b/spec/scansion/muc_password.scs
new file mode 100644
index 00000000..82611183
--- /dev/null
+++ b/spec/scansion/muc_password.scs
@@ -0,0 +1,143 @@
+# MUC: Password-protected rooms
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_roomsecret'>
+ <value>cauldronburn</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+# Juliet connects, and tries to join the room (password-protected)
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" type="error">
+ <error type="auth" code="401">
+ <not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </presence>
+
+# Retry with the correct password
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc">
+ <password>cauldronburn</password>
+ </x>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Ok, now Juliet leaves, and Romeo unsets the password
+
+Juliet sends:
+ <presence type="unavailable" to="room@conference.localhost"/>
+
+Romeo receives:
+ <presence type="unavailable" from="room@conference.localhost/Juliet"/>
+
+Juliet receives:
+ <presence type="unavailable" from="room@conference.localhost/Juliet"/>
+
+# Remove room password
+Romeo sends:
+ <iq id='config2' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_roomsecret'>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+# Config change success
+Romeo receives:
+ <iq id="config2" from="room@conference.localhost" type="result">
+ </iq>
+
+# Notification of room configuration update
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='104'/>
+ </x>
+ </message>
+
+# Juliet tries to join (should succeed)
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+# Notification of Romeo's presence in the room
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Room topic
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
diff --git a/spec/scansion/muc_register.scs b/spec/scansion/muc_register.scs
new file mode 100644
index 00000000..98af33f1
--- /dev/null
+++ b/spec/scansion/muc_register.scs
@@ -0,0 +1,506 @@
+# MUC: Room registration and reserved nicknames
+
+[Client] Romeo
+ jid: user@localhost
+ password: password
+
+[Client] Juliet
+ jid: user2@localhost
+ password: password
+
+[Client] Rosaline
+ jid: user3@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to="room@conference.localhost/Romeo">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's full JID}" affiliation='owner' role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+# Submit config form
+Romeo sends:
+ <iq id='config1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq id="config1" from="room@conference.localhost" type="result">
+ </iq>
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Juliet's JID}" />
+ </query>
+ </iq>
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's JID}" affiliation='member' />
+ </x>
+ </message>
+
+Romeo receives:
+ <iq from='room@conference.localhost' id='member1' type='result'/>
+
+# Juliet connects, and joins the room
+Juliet connects
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Juliet retrieves the registration form
+
+Juliet sends:
+ <iq id='jw81b36f' to='room@conference.localhost' type='get'>
+ <query xmlns='jabber:iq:register'/>
+ </iq>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='jw81b36f'>
+ <query xmlns='jabber:iq:register'>
+ <x type='form' xmlns='jabber:x:data'>
+ <field type='hidden' var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field type='text-single' label='Nickname' var='muc#register_roomnick'/>
+ </x>
+ </query>
+ </iq>
+
+Juliet sends:
+ <iq id='nv71va54' to='room@conference.localhost' type='set'>
+ <query xmlns='jabber:iq:register'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field var='muc#register_roomnick'>
+ <value>Juliet</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' jid="${Juliet's full JID}" role='participant'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='nv71va54'/>
+
+# Juliet discovers her reserved nick
+
+Juliet sends:
+ <iq id='getnick1' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/>
+ </iq>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='getnick1'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'>
+ <identity category='conference' name='Juliet' type='text'/>
+ </query>
+ </iq>
+
+# Juliet leaves the room:
+
+Juliet sends:
+ <presence type="unavailable" to="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='member' role='none'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's full JID}" affiliation='member' role='participant'/>
+ </x>
+ </presence>
+
+# Rosaline connect and tries to join the room as Juliet
+
+Rosaline connects
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Rosaline receives:
+ <presence type='error' from='room@conference.localhost/Juliet'>
+ <error type='cancel'>
+ <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ <x xmlns='http://jabber.org/protocol/muc'/>
+ </presence>
+
+# In a heated moment, Juliet unregisters from the room
+
+Juliet sends:
+ <iq type='set' to='room@conference.localhost' id='unreg1'>
+ <query xmlns='jabber:iq:register'>
+ <remove/>
+ </query>
+ </iq>
+
+Juliet receives:
+ <iq type='result' from='room@conference.localhost' id='unreg1'/>
+
+# Romeo is notified of Juliet's sad decision
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user' scansion:strict='true'>
+ <item jid="${Juliet's JID}" />
+ </x>
+ </message>
+
+# Rosaline attempts once more to sneak into the room, disguised as Juliet
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Rosaline receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='owner' role='moderator'/>
+ </x>
+ </presence>
+
+Rosaline receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='none' jid="${Rosaline's full JID}" role='participant'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='none' jid="${Rosaline's full JID}" role='participant'/>
+ </x>
+ </presence>
+
+# On discovering the ruse, Romeo restores Juliet's nick and status within the room
+
+Romeo sends:
+ <iq id='member1' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Juliet's JID}" nick='Juliet' />
+ </query>
+ </iq>
+
+# Rosaline is evicted from the room
+
+Romeo receives:
+ <presence from='room@conference.localhost/Juliet' type='unavailable'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='307'/>
+ <item affiliation='none' role='none' jid="${Rosaline's full JID}">
+ <reason>This nickname is reserved</reason>
+ </item>
+ </x>
+ </presence>
+
+# An out-of-room affiliation change is received for Juliet
+
+Romeo receives:
+ <message from='room@conference.localhost'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Juliet's JID}" affiliation='member' />
+ </x>
+ </message>
+
+Romeo receives:
+ <iq type='result' id='member1' from='room@conference.localhost' />
+
+Rosaline receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='307'/>
+ <item affiliation='none' jid="${Rosaline's full JID}" role='none'>
+ <reason>This nickname is reserved</reason>
+ </item>
+ <status code='110'/>
+ </x>
+ </presence>
+
+# Rosaline, frustrated, attempts to get back into the room...
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+# ...but once again, is denied
+
+Rosaline receives:
+ <presence type='error' from='room@conference.localhost/Juliet'>
+ <error type='cancel'>
+ <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ <x xmlns='http://jabber.org/protocol/muc'/>
+ </presence>
+
+# Juliet, however, quietly joins the room with success
+
+Juliet sends:
+ <presence to="room@conference.localhost/Juliet">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Juliet receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Juliet receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from="room@conference.localhost/Juliet" />
+
+# Romeo checks whether he has reserved his own nick yet
+
+Romeo sends:
+ <iq id='getnick1' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/>
+ </iq>
+
+# But no nick is returned, as he hasn't registered yet!
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='getnick1'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item' scansion:strict='true' />
+ </iq>
+
+# Romeo updates his own registration
+
+Romeo sends:
+ <iq id='jw81b36f' to='room@conference.localhost' type='get'>
+ <query xmlns='jabber:iq:register'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='jw81b36f'>
+ <query xmlns='jabber:iq:register'>
+ <x type='form' xmlns='jabber:x:data'>
+ <field type='hidden' var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field type='text-single' label='Nickname' var='muc#register_roomnick'/>
+ </x>
+ </query>
+ </iq>
+
+Romeo sends:
+ <iq id='nv71va54' to='room@conference.localhost' type='set'>
+ <query xmlns='jabber:iq:register'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field var='muc#register_roomnick'>
+ <value>Romeo</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='owner' jid="${Romeo's full JID}" role='moderator'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='nv71va54'/>
+
+Juliet receives:
+ <presence from='room@conference.localhost/Romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item role='moderator' xmlns='http://jabber.org/protocol/muc#user' affiliation='owner'/>
+ </x>
+ </presence>
+
+# Romeo discovers his reserved nick
+
+Romeo sends:
+ <iq id='getnick1' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' from='room@conference.localhost' id='getnick1'>
+ <query xmlns='http://jabber.org/protocol/disco#info' node='x-roomuser-item'>
+ <identity category='conference' name='Romeo' type='text'/>
+ </query>
+ </iq>
+
+# To check the status of the room is as expected, Romeo requests the member list
+
+Romeo sends:
+ <iq id='member3' to='room@conference.localhost' type='get'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member'/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq from='room@conference.localhost' type='result' id='member3'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item jid="${Juliet's JID}" affiliation='member' nick='Juliet'/>
+ </query>
+ </iq>
+
+Juliet sends:
+ <presence type="unavailable" to="room@conference.localhost/Juliet" />
+
+Juliet receives:
+ <presence from='room@conference.localhost/Juliet' type='unavailable' />
+
+Romeo receives:
+ <presence type='unavailable' from='room@conference.localhost/Juliet' />
+
+# Rosaline joins as herself
+
+Rosaline sends:
+ <presence to="room@conference.localhost/Rosaline">
+ <x xmlns="http://jabber.org/protocol/muc"/>
+ </presence>
+
+Rosaline receives:
+ <presence from="room@conference.localhost/Romeo" />
+
+Rosaline receives:
+ <presence from="room@conference.localhost/Rosaline" />
+
+Rosaline receives:
+ <message type='groupchat' from='room@conference.localhost'><subject/></message>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Rosaline'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Rosaline's full JID}" affiliation='none' role='participant'/>
+ </x>
+ </presence>
+
+# Rosaline tries to register her own nickname, but unaffiliated
+# registration is disabled by default
+
+Rosaline sends:
+ <iq id='reg990' to='room@conference.localhost' type='get'>
+ <query xmlns='jabber:iq:register'/>
+ </iq>
+
+Rosaline receives:
+ <iq type='error' from='room@conference.localhost' id='reg990'>
+ <error type='auth'>
+ <registration-required xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </iq>
+
+Rosaline sends:
+ <iq id='reg991' to='room@conference.localhost' type='set'>
+ <query xmlns='jabber:iq:register'>
+ <x xmlns='jabber:x:data' type='submit'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#register</value>
+ </field>
+ <field var='muc#register_roomnick'>
+ <value>Romeo</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Rosaline receives:
+ <iq id='reg991' type='error'>
+ <error type='auth'>
+ <registration-required xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </iq>
+
+# Romeo reserves her nickname for her
+
+Romeo sends:
+ <iq id='member2' to='room@conference.localhost' type='set'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member' jid="${Rosaline's JID}" nick='Rosaline' />
+ </query>
+ </iq>
+
+Romeo receives:
+ <presence from='room@conference.localhost/Rosaline'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' role='participant' jid="${Rosaline's full JID}">
+ <actor jid="${Romeo's full JID}" nick='Romeo'/>
+ </item>
+ </x>
+ </presence>
+
+Romeo receives:
+ <iq type='result' id='member2' from='room@conference.localhost' />
+
+Rosaline receives:
+ <presence from='room@conference.localhost/Rosaline'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item affiliation='member' role='participant' jid="${Rosaline's full JID}">
+ <actor nick='Romeo' />
+ </item>
+ <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
+ </x>
+ </presence>
diff --git a/spec/scansion/muc_whois_anyone_member.scs b/spec/scansion/muc_whois_anyone_member.scs
new file mode 100644
index 00000000..9a6f7e15
--- /dev/null
+++ b/spec/scansion/muc_whois_anyone_member.scs
@@ -0,0 +1,101 @@
+# MUC: Allow members to fetch the affiliation lists in open non-anonymous rooms
+
+[Client] Romeo
+ jid: romeo@localhost/MsliYo9C
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost/vJrUtY4Z
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence to='issue1230@conference.localhost/romeo'>
+ <x xmlns='http://jabber.org/protocol/muc'/>
+ </presence>
+
+Romeo receives:
+ <presence from='issue1230@conference.localhost/romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='201'/>
+ <item jid="${Romeo's JID}" role='moderator' affiliation='owner'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Romeo receives:
+ <message from='issue1230@conference.localhost' type='groupchat'>
+ <subject/>
+ </message>
+
+Romeo sends:
+ <iq id='lx3' type='set' to='issue1230@conference.localhost'>
+ <query xmlns='http://jabber.org/protocol/muc#owner'>
+ <x type='submit' xmlns='jabber:x:data'>
+ <field var='FORM_TYPE'>
+ <value>http://jabber.org/protocol/muc#roomconfig</value>
+ </field>
+ <field var='muc#roomconfig_whois'>
+ <value>anyone</value>
+ </field>
+ </x>
+ </query>
+ </iq>
+
+Romeo receives:
+ <iq from='issue1230@conference.localhost' type='result' id='lx3'/>
+
+Romeo receives:
+ <message from='issue1230@conference.localhost' type='groupchat'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='172'/>
+ </x>
+ </message>
+
+Juliet connects
+
+Juliet sends:
+ <presence to='issue1230@conference.localhost/juliet'>
+ <x xmlns='http://jabber.org/protocol/muc'/>
+ </presence>
+
+Juliet receives:
+ <presence from='issue1230@conference.localhost/romeo'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <item jid="${Romeo's JID}" role='moderator' affiliation='owner'/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <presence from='issue1230@conference.localhost/juliet'>
+ <x xmlns='http://jabber.org/protocol/muc#user'>
+ <status code='100'/>
+ <item jid="${Juliet's JID}" role='participant' affiliation='none'/>
+ <status code='110'/>
+ </x>
+ </presence>
+
+Juliet receives:
+ <message from='issue1230@conference.localhost' type='groupchat'>
+ <subject/>
+ </message>
+
+Juliet sends:
+ <iq id='lx2' type='get' to='issue1230@conference.localhost'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'>
+ <item affiliation='member'/>
+ </query>
+ </iq>
+
+Juliet receives:
+ <iq from='issue1230@conference.localhost' type='result' id='lx2'>
+ <query xmlns='http://jabber.org/protocol/muc#admin'/>
+ </iq>
+
+Juliet disconnects
+
+Romeo disconnects
+
diff --git a/spec/scansion/pep_nickname.scs b/spec/scansion/pep_nickname.scs
new file mode 100644
index 00000000..f958ec75
--- /dev/null
+++ b/spec/scansion/pep_nickname.scs
@@ -0,0 +1,72 @@
+# Publishing a nickname in PEP and receiving a notification
+
+[Client] Romeo
+ jid: romeo@localhost/nJi7BeTR
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq id="4" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/nick">
+ <item id="current">
+ <nickname xmlns="http://jabber.org/protocol/nick"/>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="4" to="romeo@localhost/nJi7BeTR" type="result">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/nick">
+ <item id="current"/>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo sends:
+ <presence>
+ <c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="http://code.matthewwild.co.uk/clix/" ver="jC32N+FhQoLrZ7nNQtZK3aqR0Fk="/>
+ </presence>
+
+Romeo receives:
+ <iq id="disco" to="romeo@localhost/nJi7BeTR" from="romeo@localhost" type="get">
+ <query xmlns="http://jabber.org/protocol/disco#info" node="http://code.matthewwild.co.uk/clix/#jC32N+FhQoLrZ7nNQtZK3aqR0Fk="/>
+ </iq>
+
+Romeo receives:
+ <presence from="romeo@localhost/nJi7BeTR">
+ <c xmlns="http://jabber.org/protocol/caps" hash="sha-1" node="http://code.matthewwild.co.uk/clix/" ver="jC32N+FhQoLrZ7nNQtZK3aqR0Fk="/>
+ </presence>
+
+Romeo sends:
+ <iq id="disco" type="result" to="romeo@localhost">
+ <query xmlns="http://jabber.org/protocol/disco#info" node="http://code.matthewwild.co.uk/clix/#jC32N+FhQoLrZ7nNQtZK3aqR0Fk=">
+ <identity type="console" name="clix" category="client"/>
+ <feature var="http://jabber.org/protocol/disco#items"/>
+ <feature var="http://jabber.org/protocol/disco#info"/>
+ <feature var="http://jabber.org/protocol/caps"/>
+ <feature var="http://jabber.org/protocol/nick+notify"/>
+ </query>
+ </iq>
+
+Romeo receives:
+ <message type="headline" from="romeo@localhost" to="romeo@localhost/nJi7BeTR">
+ <event xmlns="http://jabber.org/protocol/pubsub#event">
+ <items node="http://jabber.org/protocol/nick">
+ <item id="current">
+ <nickname xmlns="http://jabber.org/protocol/nick"/>
+ </item>
+ </items>
+ </event>
+ </message>
+
+Romeo sends:
+ <presence type="unavailable"/>
+
+Romeo disconnects
+
diff --git a/spec/scansion/pep_publish_subscribe.scs b/spec/scansion/pep_publish_subscribe.scs
new file mode 100644
index 00000000..e8080134
--- /dev/null
+++ b/spec/scansion/pep_publish_subscribe.scs
@@ -0,0 +1,210 @@
+# PEP publish, subscribe and publish-options
+
+[Client] Romeo
+ jid: pep-test-wjebo4kg@localhost
+ password: password
+
+[Client] Juliet
+ jid: pep-test-tqvqu_pv@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <presence>
+ <c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/>
+ </presence>
+
+Romeo receives:
+ <iq type='get' id='disco' from="${Romeo's JID}">
+ <query node='http://code.matthewwild.co.uk/verse/#PDH7CGVPRERS2WUqBD18PHGEzaY=' xmlns='http://jabber.org/protocol/disco#info'/>
+ </iq>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}">
+ <c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/>
+ </presence>
+
+Romeo sends:
+ <iq type='get' id='6'>
+ <query ver='' xmlns='jabber:iq:roster'/>
+ </iq>
+
+Romeo receives:
+ <iq type='result' id='6'>
+ <query ver='1' xmlns='jabber:iq:roster'/>
+ </iq>
+
+Juliet connects
+
+Juliet sends:
+ <presence>
+ <c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/>
+ </presence>
+
+Juliet receives:
+ <iq type='get' id='disco' from="${Juliet's JID}">
+ <query node='http://code.matthewwild.co.uk/verse/#PDH7CGVPRERS2WUqBD18PHGEzaY=' xmlns='http://jabber.org/protocol/disco#info'/>
+ </iq>
+
+Juliet receives:
+ <presence from="${Juliet's full JID}">
+ <c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/>
+ </presence>
+
+Juliet sends:
+ <iq type='get' id='6'>
+ <query ver='' xmlns='jabber:iq:roster'/>
+ </iq>
+
+Juliet receives:
+ <iq type='result' id='6'>
+ <query ver='1' xmlns='jabber:iq:roster'/>
+ </iq>
+
+Romeo sends:
+ <iq type='result' id='disco' to='pep-test-wjebo4kg@localhost'><query xmlns='http://jabber.org/protocol/disco#info' node='http://code.matthewwild.co.uk/verse/#PDH7CGVPRERS2WUqBD18PHGEzaY='><identity type='pc' name='Verse' category='client'/><feature var='http://jabber.org/protocol/disco#info'/><feature var='http://jabber.org/protocol/disco#items'/><feature var='http://jabber.org/protocol/caps'/></query></iq>
+
+Romeo sends:
+ <presence type='subscribe' to="${Juliet's JID}"><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Romeo receives:
+ <iq type='set' id='{scansion:any}'><query ver='1' xmlns='jabber:iq:roster'><item ask='subscribe' jid='pep-test-tqvqu_pv@localhost' subscription='none'/></query></iq>
+
+Romeo receives:
+ <presence type='unavailable' to='pep-test-wjebo4kg@localhost' from='pep-test-tqvqu_pv@localhost'/>
+
+Juliet receives:
+ <presence type='subscribe' from='pep-test-wjebo4kg@localhost' to='pep-test-tqvqu_pv@localhost'><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Juliet sends:
+ <iq type='result' id='disco' to='pep-test-tqvqu_pv@localhost'><query xmlns='http://jabber.org/protocol/disco#info' node='http://code.matthewwild.co.uk/verse/#PDH7CGVPRERS2WUqBD18PHGEzaY='><identity type='pc' name='Verse' category='client'/><feature var='http://jabber.org/protocol/disco#info'/><feature var='http://jabber.org/protocol/disco#items'/><feature var='http://jabber.org/protocol/caps'/></query></iq>
+
+Juliet sends:
+ <presence type='subscribe' to="${Romeo's JID}"><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Juliet receives:
+ <iq type='set' id='{scansion:any}'><query ver='2' xmlns='jabber:iq:roster'><item ask='subscribe' jid='pep-test-wjebo4kg@localhost' subscription='none'/></query></iq>
+
+Juliet receives:
+ <presence type='unavailable' to='pep-test-tqvqu_pv@localhost' from='pep-test-wjebo4kg@localhost'/>
+
+Romeo receives:
+ <presence type='subscribe' from='pep-test-tqvqu_pv@localhost' to='pep-test-wjebo4kg@localhost'><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Romeo sends:
+ <iq type='result' id='fixme'/>
+
+Romeo sends:
+ <presence type='subscribed' to='pep-test-tqvqu_pv@localhost'><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Romeo receives:
+ <iq type='set' id='{scansion:any}'><query ver='3' xmlns='jabber:iq:roster'><item ask='subscribe' jid='pep-test-tqvqu_pv@localhost' subscription='from'/></query></iq>
+
+Juliet receives:
+ <presence type='subscribed' from='pep-test-wjebo4kg@localhost' to='pep-test-tqvqu_pv@localhost'><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Juliet receives:
+ <iq type='set' id='{scansion:any}'><query ver='3' xmlns='jabber:iq:roster'><item jid='pep-test-wjebo4kg@localhost' subscription='to'/></query></iq>
+
+Juliet receives:
+ <presence to='pep-test-tqvqu_pv@localhost' from="${Romeo's full JID}"><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/><delay xmlns='urn:xmpp:delay' stamp='{scansion:any}' from='localhost'/></presence>
+
+Juliet sends:
+ <presence type='subscribed' to='pep-test-wjebo4kg@localhost'><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Juliet receives:
+ <iq type='set' id='{scansion:any}'><query ver='4' xmlns='jabber:iq:roster'><item jid='pep-test-wjebo4kg@localhost' subscription='both'/></query></iq>
+
+Juliet receives:
+ <presence to='pep-test-tqvqu_pv@localhost' from="${Romeo's full JID}"><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/><delay xmlns='urn:xmpp:delay' stamp='{scansion:any}' from='localhost'/></presence>
+
+Romeo receives:
+ <presence type='subscribed' from='pep-test-tqvqu_pv@localhost' to='pep-test-wjebo4kg@localhost'><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Romeo receives:
+ <iq type='set' id='{scansion:any}'><query ver='4' xmlns='jabber:iq:roster'><item jid='pep-test-tqvqu_pv@localhost' subscription='both'/></query></iq>
+
+Romeo receives:
+ <presence to='pep-test-wjebo4kg@localhost' from="${Juliet's full JID}"><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='PDH7CGVPRERS2WUqBD18PHGEzaY=' node='http://code.matthewwild.co.uk/verse/'/><delay xmlns='urn:xmpp:delay' stamp='{scansion:any}' from='localhost'/></presence>
+
+Juliet sends:
+ <iq type='result' id='fixme'/>
+
+Romeo sends:
+ <iq type='result' id='fixme'/>
+
+Romeo sends:
+ <iq type='result' id='fixme'/>
+
+Romeo sends:
+ <presence><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='m/sIsyfzKk8X1okZMtStR43nQQg=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Romeo receives:
+ <iq type='get' id='disco' from='pep-test-wjebo4kg@localhost'><query node='http://code.matthewwild.co.uk/verse/#m/sIsyfzKk8X1okZMtStR43nQQg=' xmlns='http://jabber.org/protocol/disco#info'/></iq>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}"><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='m/sIsyfzKk8X1okZMtStR43nQQg=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Romeo receives:
+ <iq type='get' id='disco' from='pep-test-tqvqu_pv@localhost'><query node='http://code.matthewwild.co.uk/verse/#m/sIsyfzKk8X1okZMtStR43nQQg=' xmlns='http://jabber.org/protocol/disco#info'/></iq>
+
+Juliet receives:
+ <presence from="${Romeo's full JID}"><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='m/sIsyfzKk8X1okZMtStR43nQQg=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Romeo sends:
+ <presence><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='IfQwbaaDB4LEP5tkGArEaB/3Y+s=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Romeo receives:
+ <iq type='get' id='disco' from='pep-test-wjebo4kg@localhost'><query node='http://code.matthewwild.co.uk/verse/#IfQwbaaDB4LEP5tkGArEaB/3Y+s=' xmlns='http://jabber.org/protocol/disco#info'/></iq>
+
+Romeo receives:
+ <presence from="${Romeo's full JID}"><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='IfQwbaaDB4LEP5tkGArEaB/3Y+s=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Romeo receives:
+ <iq type='get' id='disco' from='pep-test-tqvqu_pv@localhost'><query node='http://code.matthewwild.co.uk/verse/#IfQwbaaDB4LEP5tkGArEaB/3Y+s=' xmlns='http://jabber.org/protocol/disco#info'/></iq>
+
+Romeo sends:
+ <iq type='result' id='disco' to='pep-test-wjebo4kg@localhost'><query xmlns='http://jabber.org/protocol/disco#info' node='http://code.matthewwild.co.uk/verse/#m/sIsyfzKk8X1okZMtStR43nQQg='/></iq>
+
+Romeo sends:
+ <iq type='result' id='disco' to='pep-test-tqvqu_pv@localhost'><query xmlns='http://jabber.org/protocol/disco#info' node='http://code.matthewwild.co.uk/verse/#m/sIsyfzKk8X1okZMtStR43nQQg='/></iq>
+
+Romeo sends:
+ <iq type='result' id='disco' to='pep-test-wjebo4kg@localhost'><query xmlns='http://jabber.org/protocol/disco#info' node='http://code.matthewwild.co.uk/verse/#IfQwbaaDB4LEP5tkGArEaB/3Y+s='><identity type='pc' name='Verse' category='client'/><feature var='http://jabber.org/protocol/tune+notify'/><feature var='http://jabber.org/protocol/disco#info'/><feature var='http://jabber.org/protocol/disco#items'/><feature var='http://jabber.org/protocol/caps'/><feature var='http://jabber.org/protocol/mood+notify'/></query></iq>
+
+Juliet receives:
+ <presence from="${Romeo's full JID}"><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' ver='IfQwbaaDB4LEP5tkGArEaB/3Y+s=' node='http://code.matthewwild.co.uk/verse/'/></presence>
+
+Juliet sends:
+ <iq type='result' id='fixme'/>
+
+Juliet sends:
+ <iq type='set' id='7'><pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='http://jabber.org/protocol/tune'><item id='current'><tune xmlns='http://jabber.org/protocol/tune'><title>Beautiful Cedars</title><artist>The Spinners</artist><source>Not Quite Folk</source><track>4</track></tune></item></publish></pubsub></iq>
+
+Juliet receives:
+ <iq type='result' id='7' ><pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='http://jabber.org/protocol/tune'><item id='current'/></publish></pubsub></iq>
+
+Juliet sends:
+ <iq type='set' id='8'><pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='http://jabber.org/protocol/mood'><item><mood xmlns='http://jabber.org/protocol/mood'><happy/></mood></item></publish><publish-options><x type='submit' xmlns='jabber:x:data'><field type='hidden' var='FORM_TYPE'><value>http://jabber.org/protocol/pubsub#publish-options</value></field><field var='pubsub#persist_items'><value>true</value></field><field var='pubsub#access_model'><value>whitelist</value></field></x></publish-options></pubsub></iq>
+
+Juliet receives:
+ <iq type='result' id='8'><pubsub xmlns='http://jabber.org/protocol/pubsub'><publish node='http://jabber.org/protocol/mood'><item id='{scansion:any}'/></publish></pubsub></iq>
+
+Juliet sends:
+ <iq type='result' id='{scansion:any}'/>
+
+Romeo receives:
+ <message type='headline' from='pep-test-tqvqu_pv@localhost'><event xmlns='http://jabber.org/protocol/pubsub#event'><items node='http://jabber.org/protocol/tune'><item id='current'><tune xmlns='http://jabber.org/protocol/tune'><title>Beautiful Cedars</title><artist>The Spinners</artist><source>Not Quite Folk</source><track>4</track></tune></item></items></event></message>
+
+Romeo sends:
+ <iq type='result' id='disco' to='pep-test-tqvqu_pv@localhost'><query xmlns='http://jabber.org/protocol/disco#info' node='http://code.matthewwild.co.uk/verse/#IfQwbaaDB4LEP5tkGArEaB/3Y+s='><identity type='pc' name='Verse' category='client'/><feature var='http://jabber.org/protocol/tune+notify'/><feature var='http://jabber.org/protocol/disco#info'/><feature var='http://jabber.org/protocol/disco#items'/><feature var='http://jabber.org/protocol/caps'/><feature var='http://jabber.org/protocol/mood+notify'/></query></iq>
+
+Romeo receives:
+ <message type='headline' from='pep-test-tqvqu_pv@localhost'><event xmlns='http://jabber.org/protocol/pubsub#event'><items node='http://jabber.org/protocol/tune'><item id='current'><tune xmlns='http://jabber.org/protocol/tune'><title>Beautiful Cedars</title><artist>The Spinners</artist><source>Not Quite Folk</source><track>4</track></tune></item></items></event></message>
+
+Juliet disconnects
+
+Romeo disconnects
diff --git a/spec/scansion/prosody.cfg.lua b/spec/scansion/prosody.cfg.lua
new file mode 100644
index 00000000..94861449
--- /dev/null
+++ b/spec/scansion/prosody.cfg.lua
@@ -0,0 +1,82 @@
+--luacheck: ignore
+
+admins = { "admin@localhost" }
+
+use_libevent = true
+
+modules_enabled = {
+ -- Generally required
+ "roster"; -- Allow users to have a roster. Recommended ;)
+ "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
+ "tls"; -- Add support for secure TLS on c2s/s2s connections
+ "dialback"; -- s2s dialback support
+ "disco"; -- Service discovery
+
+ -- Not essential, but recommended
+ "carbons"; -- Keep multiple clients in sync
+ "pep"; -- Enables users to publish their mood, activity, playing music and more
+ "private"; -- Private XML storage (for room bookmarks, etc.)
+ "blocklist"; -- Allow users to block communications with other users
+ "vcard"; -- Allow users to set vCards
+
+ -- Nice to have
+ "version"; -- Replies to server version requests
+ "uptime"; -- Report how long server has been running
+ "time"; -- Let others know the time here on this server
+ "ping"; -- Replies to XMPP pings with pongs
+ "register"; -- Allow users to register on this server using a client and change passwords
+ --"mam"; -- Store messages in an archive and allow users to access it
+
+ -- HTTP modules
+ --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
+ --"websocket"; -- XMPP over WebSockets
+ --"http_files"; -- Serve static files from a directory over HTTP
+
+ -- Other specific functionality
+ --"limits"; -- Enable bandwidth limiting for XMPP connections
+ --"groups"; -- Shared roster support
+ --"server_contact_info"; -- Publish contact information for this service
+ --"announce"; -- Send announcement to all online users
+ --"welcome"; -- Welcome users who register accounts
+ --"watchregistrations"; -- Alert admins of registrations
+ --"motd"; -- Send a message to users when they log in
+ --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
+ --"proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use
+
+ -- Useful for testing
+ --"scansion_record"; -- Records things that happen in scansion test case format
+}
+
+certificate = "certs"
+
+allow_registration = false
+
+c2s_require_encryption = false
+allow_unencrypted_plain_auth = true
+
+authentication = "insecure"
+insecure_open_authentication = "Yes please, I know what I'm doing!"
+
+storage = "memory"
+
+
+-- For the "sql" backend, you can uncomment *one* of the below to configure:
+--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename.
+--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
+--sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
+
+
+-- Logging configuration
+-- For advanced logging see https://prosody.im/doc/logging
+log = "*console"
+
+daemonize = true
+pidfile = "prosody.pid"
+
+VirtualHost "localhost"
+
+Component "conference.localhost" "muc"
+ storage = "memory"
+
+Component "pubsub.localhost" "pubsub"
+ storage = "memory"
diff --git a/spec/scansion/pubsub_advanced.scs b/spec/scansion/pubsub_advanced.scs
new file mode 100644
index 00000000..c873486e
--- /dev/null
+++ b/spec/scansion/pubsub_advanced.scs
@@ -0,0 +1,167 @@
+# Pubsub: Node creation, publish, subscribe, affiliations and delete
+
+[Client] Balthasar
+ jid: admin@localhost
+ password: password
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='create1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="error" id='create1'>
+ <error type="auth">
+ <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
+ </error>
+ </iq>
+
+Balthasar connects
+
+Balthasar sends:
+ <iq type='set' to='pubsub.localhost' id='create2'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <create node='princely_musings'/>
+ </pubsub>
+ </iq>
+
+Balthasar receives:
+ <iq type="result" id='create2'/>
+
+Balthasar sends:
+ <iq type="set" to="pubsub.localhost" id='create3'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Balthasar receives:
+ <iq type="error" id='create3'>
+ <error type="cancel">
+ <conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </iq>
+
+Juliet connects
+
+Juliet sends:
+ <iq type="set" to="pubsub.localhost" id='sub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <subscribe node="princely_musings" jid="${Romeo's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="error" id='sub1'/>
+
+Juliet sends:
+ <iq type="set" to="pubsub.localhost" id='sub2'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <subscribe node="princely_musings" jid="${Juliet's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="result" id='sub2'>
+ <pubsub xmlns='http://jabber.org/protocol/pubsub'>
+ <subscription jid="${Juliet's full JID}" node='princely_musings' subscription='subscribed'/>
+ </pubsub>
+ </iq>
+
+Balthasar sends:
+ <iq type="get" id='aff1' to='pubsub.localhost'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <affiliations node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Balthasar receives:
+ <iq type="result" id='aff1' from='pubsub.localhost'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <affiliations node="princely_musings">
+ <affiliation affiliation='owner' jid='admin@localhost' xmlns='http://jabber.org/protocol/pubsub#owner'/>
+ </affiliations>
+ </pubsub>
+ </iq>
+
+Balthasar sends:
+ <iq type="set" id='aff2' to='pubsub.localhost'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <affiliations node="princely_musings">
+ <affiliation affiliation='owner' jid='admin@localhost' xmlns='http://jabber.org/protocol/pubsub#owner'/>
+ <affiliation jid="${Romeo's JID}" affiliation="publisher"/>
+ </affiliations>
+ </pubsub>
+ </iq>
+
+Balthasar receives:
+ <iq type="result" id='aff2' from='pubsub.localhost'/>
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='pub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="princely_musings">
+ <item id="current">
+ <entry xmlns="http://www.w3.org/2005/Atom">
+ <title>Soliloquy</title>
+ <summary>Lorem ipsum dolor sit amet</summary>
+ </entry>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <message type="headline" from="pubsub.localhost">
+ <event xmlns="http://jabber.org/protocol/pubsub#event">
+ <items node="princely_musings">
+ <item id="current">
+ <entry xmlns="http://www.w3.org/2005/Atom">
+ <title>Soliloquy</title>
+ <summary>Lorem ipsum dolor sit amet</summary>
+ </entry>
+ </item>
+ </items>
+ </event>
+ </message>
+
+Romeo receives:
+ <iq type="result" id='pub1'/>
+
+Juliet sends:
+ <iq type="set" to="pubsub.localhost" id='unsub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <unsubscribe node="princely_musings" jid="${Juliet's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="result" id='unsub1'/>
+
+Balthasar sends:
+ <iq type="set" to="pubsub.localhost" id='del1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <delete node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Balthasar receives:
+ <iq type="result" from='pubsub.localhost' id='del1'/>
+
+Romeo disconnects
+
+// vim: syntax=xml:
diff --git a/spec/scansion/pubsub_basic.scs b/spec/scansion/pubsub_basic.scs
new file mode 100644
index 00000000..d983ff66
--- /dev/null
+++ b/spec/scansion/pubsub_basic.scs
@@ -0,0 +1,104 @@
+# Pubsub: Basic support
+
+[Client] Romeo
+ jid: admin@localhost
+ password: password
+
+// admin@localhost is assumed to have node creation privileges
+
+[Client] Juliet
+ jid: juliet@localhost
+ password: password
+
+---------
+
+Romeo connects
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='create1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id='create1'/>
+
+Juliet connects
+
+-- Juliet sends:
+-- <iq type="set" to="pubsub.localhost">
+-- <pubsub xmlns="http://jabber.org/protocol/pubsub">
+-- <subscribe node="princely_musings" jid="${Romeo's full JID}"/>
+-- </pubsub>
+-- </iq>
+--
+-- Juliet receives:
+-- <iq type="error"/>
+
+Juliet sends:
+ <iq type="set" to="pubsub.localhost" id='sub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <subscribe node="princely_musings" jid="${Juliet's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="result" id='sub1'/>
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='pub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="princely_musings">
+ <item id="current">
+ <entry xmlns="http://www.w3.org/2005/Atom">
+ <title>Soliloquy</title>
+ <summary>Lorem ipsum dolor sit amet</summary>
+ </entry>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id='pub1'/>
+
+Juliet receives:
+ <message type="headline" from="pubsub.localhost">
+ <event xmlns="http://jabber.org/protocol/pubsub#event">
+ <items node="princely_musings">
+ <item id="current">
+ <entry xmlns="http://www.w3.org/2005/Atom">
+ <title>Soliloquy</title>
+ <summary>Lorem ipsum dolor sit amet</summary>
+ </entry>
+ </item>
+ </items>
+ </event>
+ </message>
+
+Juliet sends:
+ <iq type="set" to="pubsub.localhost" id='unsub1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <unsubscribe node="princely_musings" jid="${Juliet's full JID}"/>
+ </pubsub>
+ </iq>
+
+Juliet receives:
+ <iq type="result" id='unsub1'/>
+
+Juliet disconnects
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='del1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <delete node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id='del1'/>
+
+Romeo disconnects
+
+// vim: syntax=xml:
diff --git a/spec/scansion/pubsub_config.scs b/spec/scansion/pubsub_config.scs
new file mode 100644
index 00000000..d979aca5
--- /dev/null
+++ b/spec/scansion/pubsub_config.scs
@@ -0,0 +1,205 @@
+# pubsub#title as name attribute in disco#items
+# Issue 1226
+
+[Client] Romeo
+ password: password
+ jid: jqpcrbq@localhost
+
+-----
+
+Romeo connects
+
+Romeo sends:
+ <iq id="67eb1f47-1e69-4cb3-91e2-4d5943e72d4c" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/tune">
+ <item id="current">
+ <tune xmlns="http://jabber.org/protocol/tune"/>
+ </item>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="67eb1f47-1e69-4cb3-91e2-4d5943e72d4c" type="result">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <publish node="http://jabber.org/protocol/tune">
+ <item id="current"/>
+ </publish>
+ </pubsub>
+ </iq>
+
+Romeo sends:
+ <iq id="52d74a36-afb0-4028-87ed-b25b988b049e" type="get">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="http://jabber.org/protocol/tune"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="52d74a36-afb0-4028-87ed-b25b988b049e" type="result">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="http://jabber.org/protocol/tune">
+ <x xmlns="jabber:x:data" type="form">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#node_config</value>
+ </field>
+ <field var="pubsub#title" label="Title" type="text-single"/>
+ <field var="pubsub#description" label="Description" type="text-single"/>
+ <field var="pubsub#type" label="The type of node data, usually specified by the namespace of the payload (if any)" type="text-single"/>
+ <field var="pubsub#max_items" label="Max # of items to persist" type="text-single">
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:integer"/>
+ <value>1</value>
+ </field>
+ <field var="pubsub#persist_items" label="Persist items to storage" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#access_model" label="Specify the subscriber model" type="list-single">
+ <option label="authorize">
+ <value>authorize</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <option label="presence">
+ <value>presence</value>
+ </option>
+ <option label="roster">
+ <value>roster</value>
+ </option>
+ <option label="whitelist">
+ <value>whitelist</value>
+ </option>
+ <value>presence</value>
+ </field>
+ <field var="pubsub#publish_model" label="Specify the publisher model" type="list-single">
+ <option label="publishers">
+ <value>publishers</value>
+ </option>
+ <option label="subscribers">
+ <value>subscribers</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <value>publishers</value>
+ </field>
+ <field var="pubsub#deliver_notifications" label="Whether to deliver event notifications" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#deliver_payloads" label="Whether to deliver payloads with event notifications" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notification_type" label="Specify the delivery style for notifications" type="list-single">
+ <option label="Messages of type normal">
+ <value>normal</value>
+ </option>
+ <option label="Messages of type headline">
+ <value>headline</value>
+ </option>
+ <value>headline</value>
+ </field>
+ <field var="pubsub#notify_delete" label="Whether to notify subscribers when the node is deleted" type="boolean">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notify_retract" label="Whether to notify subscribers when items are removed from the node" type="boolean">
+ <value>1</value>
+ </field>
+ </x>
+ </configure>
+ </pubsub>
+ </iq>
+
+Romeo sends:
+ <iq id="a73aac09-74be-4ee2-97e5-571bbdbcd956" type="set">
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <configure node="http://jabber.org/protocol/tune">
+ <x xmlns="jabber:x:data" type="submit">
+ <field var="FORM_TYPE" type="hidden">
+ <value>http://jabber.org/protocol/pubsub#node_config</value>
+ </field>
+ <field var="pubsub#title" type="text-single" label="Title">
+ <value>Nice tunes</value>
+ </field>
+ <field var="pubsub#description" type="text-single" label="Description"/>
+ <field var="pubsub#type" type="text-single" label="The type of node data, usually specified by the namespace of the payload (if any)"/>
+ <field var="pubsub#max_items" type="text-single" label="Max # of items to persist">
+ <validate xmlns="http://jabber.org/protocol/xdata-validate" datatype="xs:integer"/>
+ <value>1</value>
+ </field>
+ <field var="pubsub#persist_items" type="boolean" label="Persist items to storage">
+ <value>1</value>
+ </field>
+ <field var="pubsub#access_model" type="list-single" label="Specify the subscriber model">
+ <option label="authorize">
+ <value>authorize</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <option label="presence">
+ <value>presence</value>
+ </option>
+ <option label="roster">
+ <value>roster</value>
+ </option>
+ <option label="whitelist">
+ <value>whitelist</value>
+ </option>
+ <value>presence</value>
+ </field>
+ <field var="pubsub#publish_model" type="list-single" label="Specify the publisher model">
+ <option label="publishers">
+ <value>publishers</value>
+ </option>
+ <option label="subscribers">
+ <value>subscribers</value>
+ </option>
+ <option label="open">
+ <value>open</value>
+ </option>
+ <value>publishers</value>
+ </field>
+ <field var="pubsub#deliver_notifications" type="boolean" label="Whether to deliver event notifications">
+ <value>1</value>
+ </field>
+ <field var="pubsub#deliver_payloads" type="boolean" label="Whether to deliver payloads with event notifications">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notification_type" type="list-single" label="Specify the delivery style for notifications">
+ <option label="Messages of type normal">
+ <value>normal</value>
+ </option>
+ <option label="Messages of type headline">
+ <value>headline</value>
+ </option>
+ <value>headline</value>
+ </field>
+ <field var="pubsub#notify_delete" type="boolean" label="Whether to notify subscribers when the node is deleted">
+ <value>1</value>
+ </field>
+ <field var="pubsub#notify_retract" type="boolean" label="Whether to notify subscribers when items are removed from the node">
+ <value>1</value>
+ </field>
+ </x>
+ </configure>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq id="a73aac09-74be-4ee2-97e5-571bbdbcd956" type="result"/>
+
+Romeo sends:
+ <iq id="ab0e92d2-c06b-4987-9d45-f9f9e7721709" type="get">
+ <query xmlns="http://jabber.org/protocol/disco#items"/>
+ </iq>
+
+Romeo receives:
+ <iq id="ab0e92d2-c06b-4987-9d45-f9f9e7721709" type="result">
+ <query xmlns="http://jabber.org/protocol/disco#items">
+ <item name="Nice tunes" node="http://jabber.org/protocol/tune" jid="${Romeo's JID}"/>
+ </query>
+ </iq>
+
+Romeo disconnects
+
diff --git a/spec/scansion/pubsub_createdelete.scs b/spec/scansion/pubsub_createdelete.scs
new file mode 100644
index 00000000..a44695e7
--- /dev/null
+++ b/spec/scansion/pubsub_createdelete.scs
@@ -0,0 +1,63 @@
+# Pubsub: Create and delete
+
+[Client] Romeo
+ jid: admin@localhost
+ password: password
+
+// admin@localhost is assumed to have node creation privileges
+
+---------
+
+Romeo connects
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='create1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id='create1'/>
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='create2'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub">
+ <create node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="error" id='create2'>
+ <error type="cancel">
+ <conflict xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </iq>
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='delete1'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <delete node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id='delete1'/>
+
+Romeo sends:
+ <iq type="set" to="pubsub.localhost" id='delete2'>
+ <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
+ <delete node="princely_musings"/>
+ </pubsub>
+ </iq>
+
+Romeo receives:
+ <iq type="error" id='delete2'>
+ <error type="cancel">
+ <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ </error>
+ </iq>
+
+Romeo disconnects
+
+// vim: syntax=xml:
diff --git a/spec/scansion/vcard_temp.scs b/spec/scansion/vcard_temp.scs
new file mode 100644
index 00000000..38c6f755
--- /dev/null
+++ b/spec/scansion/vcard_temp.scs
@@ -0,0 +1,80 @@
+# XEP-0054 vCard-temp writable and readable by anyone
+# mod_scansion_record on host 'localhost' recording started 2018-10-20T15:00:12Z
+
+[Client] Romeo
+ jid: romeo@localhost
+ password: password
+
+[Client] Juliet
+ jid: juliet@localhost
+ password: password
+
+-----
+
+Romeo connects
+
+# Romeo sets his vCard
+# FN and N are required by the schema and mod_vcard_legacy will always inject them
+Romeo sends:
+ <iq id="lx3" type="set">
+ <vCard xmlns="vcard-temp">
+ <FN>Romeo Montague</FN>
+ <N>
+ <FAMILY>Montague</FAMILY>
+ <GIVEN>Romeo</GIVEN>
+ <MIDDLE/>
+ <PREFIX/>
+ <SUFFIX/>
+ </N>
+ </vCard>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="lx3" to="${Romeo's full JID}"/>
+
+Romeo sends:
+ <iq id="lx4" type="get">
+ <vCard xmlns="vcard-temp"/>
+ </iq>
+
+Romeo receives:
+ <iq type="result" id="lx4" to="${Romeo's full JID}">
+ <vCard xmlns="vcard-temp">
+ <FN>Romeo Montague</FN>
+ <N>
+ <FAMILY>Montague</FAMILY>
+ <GIVEN>Romeo</GIVEN>
+ <MIDDLE/>
+ <PREFIX/>
+ <SUFFIX/>
+ </N>
+ </vCard>
+ </iq>
+
+Romeo disconnects
+
+Juliet connects
+
+Juliet sends:
+ <iq type="get" id="lx3" to="romeo@localhost">
+ <vCard xmlns="vcard-temp"/>
+ </iq>
+
+# Juliet can see Romeo's vCard since it's public
+Juliet receives:
+ <iq type="result" from="romeo@localhost" id="lx3" to="${Juliet's full JID}">
+ <vCard xmlns="vcard-temp">
+ <FN>Romeo Montague</FN>
+ <N>
+ <FAMILY>Montague</FAMILY>
+ <GIVEN>Romeo</GIVEN>
+ <MIDDLE/>
+ <PREFIX/>
+ <SUFFIX/>
+ </N>
+ </vCard>
+ </iq>
+
+Juliet disconnects
+
+# recording ended on 2018-10-20T15:02:14Z