├── .github
└── workflows
│ ├── ci.yml
│ ├── clippy-fmt.yml
│ └── update-doc.yml
├── .gitignore
├── .rustfmt.toml
├── Cargo.toml
├── PROTOCOL.md
├── README.md
├── credentials
├── Cargo.toml
└── src
│ └── lib.rs
├── docker-compose.yml
├── examples
└── server
│ ├── Cargo.toml
│ ├── config
│ └── default.toml
│ ├── server.crt
│ ├── server.key
│ └── src
│ └── main.rs
├── proto
├── Cargo.toml
└── src
│ ├── lib.rs
│ ├── non_stanza.rs
│ ├── non_stanza
│ ├── auth.rs
│ ├── bind.rs
│ ├── close_stream.rs
│ ├── open_stream.rs
│ ├── proceed_tls.rs
│ ├── sasl_success.rs
│ ├── start_tls.rs
│ ├── stream_error.rs
│ └── stream_features.rs
│ ├── ns.rs
│ ├── packet.rs
│ ├── stanza.rs
│ └── stanza
│ ├── generic_iq.rs
│ ├── message.rs
│ └── presence.rs
├── server
├── Cargo.toml
└── src
│ ├── authentication.rs
│ ├── config.rs
│ ├── lib.rs
│ ├── listeners.rs
│ ├── listeners
│ ├── tcp.rs
│ ├── tcp
│ │ ├── listener.rs
│ │ └── session.rs
│ └── ws.rs
│ ├── messages.rs
│ ├── messages
│ ├── system.rs
│ └── tcp.rs
│ ├── packet.rs
│ ├── parser.rs
│ ├── parser
│ ├── codec.rs
│ └── sink.rs
│ ├── router.rs
│ ├── sessions.rs
│ ├── sessions
│ ├── manager.rs
│ ├── state.rs
│ └── unauthenticated.rs
│ ├── tests.rs
│ └── tests
│ ├── executor.rs
│ ├── fixtures
│ ├── server.crt
│ └── server.key
│ ├── rfc6120.rs
│ └── rfc6120
│ ├── namespaces.rs
│ ├── starttls.rs
│ └── stream_attribute.rs
├── src
└── lib.rs
└── xml
├── Cargo.toml
├── src
├── children.rs
├── element.rs
├── error.rs
├── lib.rs
├── libold.rs
├── namespace.rs
├── options.rs
├── position.rs
├── qname.rs
└── xml_atom.rs
└── tests
└── test_basic.rs
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 |
3 | name: CI
4 |
5 | jobs:
6 | build_and_test:
7 | strategy:
8 | fail-fast: false
9 | matrix:
10 | target:
11 | - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu }
12 | - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
13 | - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
14 | version:
15 | - stable
16 | - nightly
17 |
18 | name: ${{ matrix.target.name }} / ${{ matrix.version }}
19 | runs-on: ${{ matrix.target.os }}
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 |
24 | - name: Install ${{ matrix.version }}
25 | uses: actions-rs/toolchain@v1
26 | with:
27 | toolchain: ${{ matrix.version }}-${{ matrix.target.triple }}
28 | profile: minimal
29 | override: true
30 |
31 | - name: Generate Cargo.lock
32 | uses: actions-rs/cargo@v1
33 | with:
34 | command: generate-lockfile
35 | - name: Cache Dependencies
36 | uses: Swatinem/rust-cache@v1.2.0
37 |
38 | - name: Install cargo-hack
39 | uses: actions-rs/cargo@v1
40 | with:
41 | command: install
42 | args: cargo-hack
43 |
44 | - name: check minimal
45 | uses: actions-rs/cargo@v1
46 | with:
47 | command: hack
48 | args: --clean-per-run check --workspace --no-default-features --tests
49 |
50 | - name: check full
51 | uses: actions-rs/cargo@v1
52 | with:
53 | command: check
54 | args: --workspace --bins --examples --tests
55 |
56 | - name: tests
57 | uses: actions-rs/cargo@v1
58 | with:
59 | command: test
60 | args: -v --workspace --all-features --no-fail-fast -- --nocapture
61 |
62 | - name: Generate coverage file
63 | if: >
64 | matrix.target.os == 'ubuntu-latest'
65 | && matrix.version == 'stable'
66 | && github.ref == 'refs/heads/master'
67 | run: |
68 | cargo install cargo-tarpaulin --vers "^0.13"
69 | cargo tarpaulin --all --out Xml --verbose
70 | - name: Upload to Codecov
71 | if: >
72 | matrix.target.os == 'ubuntu-latest'
73 | && matrix.version == 'stable'
74 | && github.ref == 'refs/heads/master'
75 | uses: codecov/codecov-action@v1
76 | with:
77 | file: cobertura.xml
78 |
79 | - name: Clear the cargo caches
80 | run: |
81 | cargo install cargo-cache --no-default-features --features ci-autoclean
82 | cargo-cache
83 |
84 |
--------------------------------------------------------------------------------
/.github/workflows/clippy-fmt.yml:
--------------------------------------------------------------------------------
1 | name: Lint
2 |
3 | on:
4 | pull_request:
5 | types: [opened, synchronize, reopened]
6 |
7 | jobs:
8 | fmt:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 |
13 | - name: Install Rust
14 | uses: actions-rs/toolchain@v1
15 | with:
16 | toolchain: stable
17 | components: rustfmt
18 | - name: Check with rustfmt
19 | uses: actions-rs/cargo@v1
20 | with:
21 | command: fmt
22 | args: --all -- --check
23 |
24 | clippy:
25 | runs-on: ubuntu-latest
26 | steps:
27 | - uses: actions/checkout@v2
28 |
29 | - name: Install Rust
30 | uses: actions-rs/toolchain@v1
31 | with:
32 | toolchain: stable
33 | components: clippy
34 | override: true
35 | - name: Check with Clippy
36 | uses: actions-rs/clippy-check@v1
37 | with:
38 | token: ${{ secrets.GITHUB_TOKEN }}
39 | args: --workspace --tests --all-features
40 |
--------------------------------------------------------------------------------
/.github/workflows/update-doc.yml:
--------------------------------------------------------------------------------
1 | name: Upload Documentation
2 |
3 | on:
4 | push:
5 | branches: [master]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v2
13 |
14 | - name: Install Rust
15 | uses: actions-rs/toolchain@v1
16 | with:
17 | toolchain: nightly-x86_64-unknown-linux-gnu
18 | profile: minimal
19 | override: true
20 |
21 | - name: Build Docs
22 | uses: actions-rs/cargo@v1
23 | with:
24 | command: doc
25 | args: --workspace --all-features --no-deps
26 |
27 | - name: Tweak HTML
28 | run: echo '' > target/doc/index.html
29 |
30 | - name: Deploy to GitHub Pages
31 | uses: JamesIves/github-pages-deploy-action@3.7.1
32 | with:
33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34 | BRANCH: gh-pages
35 | FOLDER: target/doc
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | Cargo.lock
3 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | max_width = 200
2 | format_macro_matchers = true
3 | format_macro_bodies = true
4 | use_try_shorthand = true
5 | unstable_features = true
6 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xmpp-rs"
3 | version = "0.1.3"
4 | description = """
5 | xmpp-rs is an implementation of the Extensible Messaging and Presence Protocol (XMPP).
6 |
7 | Based on tokio-rs and futures-rs. It's goal is to be fully tested and usable.
8 | """
9 |
10 | edition = "2018"
11 | license = "MPL-2.0"
12 | documentation = "https://docs.rs/xmpp-rs"
13 | authors = ["Freyskeyd "]
14 | repository = "https://github.com/Freyskeyd/xmpp-rs"
15 | readme = "README.md"
16 | keywords = ["xmpp", "tokio", "jabber", "IM", "instant-messaging"]
17 | categories = ["network-programming"]
18 | exclude = [
19 | "derive",
20 | ]
21 | [badges]
22 | travis-ci = { repository = "freyskeyd/xmpp-rs", branch = "master" }
23 |
24 | [lib]
25 | name = "xmpp"
26 | path = "src/lib.rs"
27 |
28 | [dev-dependencies]
29 | env_logger = "0"
30 | futures = "0.1"
31 | log = "0"
32 | tokio-core = "0.1"
33 |
34 | xml-rs = "*"
35 | circular = "*"
36 |
37 | [dependencies]
38 | xmpp-credentials = { path = "credentials" }
39 | xmpp-proto = { path = "proto" }
40 | xmpp-server = { path = "server" }
41 |
42 | [workspace]
43 | members = ["proto", "credentials/", "server/", "examples/server"]
44 |
45 |
--------------------------------------------------------------------------------
/PROTOCOL.md:
--------------------------------------------------------------------------------
1 | # PROTOCOL
2 |
3 | - [ ] [RFC-6120](https://datatracker.ietf.org/doc/rfc6120/)
4 |
5 |
6 | Implementation details
7 |
8 |
9 | ```
10 | C: = a client
11 | E: = any XMPP entity
12 | I: = an initiating entity
13 | P: = a peer server
14 | R: = a receiving entity
15 | S: = a server
16 | S1: = server1
17 | S2: = server2
18 | ```
19 |
20 | - [x] TCP Binding
21 | - [ ] XML Streams
22 | - [x] Opening a Stream
23 | - [ ] Stream Negotiation
24 | - [x] Restarts
25 | - [x] Resending Features
26 | - [ ] Completion of Stream Negotiation
27 | - [ ] Determination of Addresses
28 | - [ ] Closing a Stream
29 | - [ ] Directionality
30 | - [ ] Handling of Silent Peers
31 | - [ ] Dead Connection
32 | - [ ] Broken Stream
33 | - [ ] Idle Peer
34 | - [ ] Use of Checking Methods
35 | - [ ] Stream Attributes
36 | - [ ] from
37 | - [x] to
38 | - [x] id
39 | - [ ] xml:lang
40 | - [x] version
41 | - [ ] XML Namespaces
42 | - [ ] Stream Namespace
43 | - [ ] Content Namespace
44 | - [ ] XMPP Content Namespaces
45 | - [ ] Other Namespaces
46 | - [ ] Namespace Declarations and Prefixes
47 | - [ ] Stream Errors
48 | - [x] Rules
49 | - [x] Stream Errors Are Unrecoverable
50 | - [x] Stream Errors Can Occur During Setup
51 | - [x] Stream Errors When the Host Is Unspecified or Unknown
52 | - [x] Where Stream Errors Are Sent
53 | - [x] Syntax
54 | - [ ] Stream Error Conditions
55 | - [ ] bad-format -> not applied
56 | - [x] bad-namespace-prefix
57 | - [ ] conflict
58 | - [ ] connection-timeout
59 | - [ ] host-gone
60 | - [x] host-unknown
61 | - [ ] improper-addressing
62 | - [ ] internal-server-error
63 | - [ ] invalid-from
64 | - [x] invalid-namespace
65 | - [ ] invalid-xml
66 | - [x] not-authorized
67 | - [ ] not-well-formed
68 | - [ ] policy-violation
69 | - [ ] remote-connection-failed
70 | - [ ] reset
71 | - [ ] resource-constraint
72 | - [ ] restricted-xml
73 | - [ ] see-other-host
74 | - [ ] system-shutdown
75 | - [ ] undefined-condition
76 | - [x] unsupported-encoding
77 | - [ ] unsupported-feature
78 | - [ ] unsupported-stanza-type
79 | - [ ] unsupported-version
80 | - [ ] Application-Specific Conditions
81 | - [ ] Simplified Stream Examples
82 |
83 | - [ ] STARTTLS Negotiation
84 | - [x] Fundamentals
85 | - [x] Support
86 | - [ ] Stream Negotiation Rules
87 | - [ ] Mandatory-to-Negotiate
88 | - [ ] Restart
89 | - [ ] Data Formatting
90 | - [ ] Order of TLS and SASL Negotiations
91 | - [ ] TLS Renegotiation
92 | - [ ] TLS Extensions
93 | - [ ] Process
94 | - [ ] Exchange of Stream Headers and Stream Features
95 | - [ ] Initiation of STARTTLS Negotiation
96 | - [ ] STARTTLS Command
97 | - [ ] Failure Case
98 | - [ ] Proceed Case
99 | - [ ] TLS Negotiation
100 | - [ ] Rules
101 | - [ ] TLS Failure
102 | - [ ] TLS Success
103 | - [ ] SASL Negotiation
104 | - [ ] Fundamentals
105 | - [ ] Support
106 | - [ ] Stream Negotiation Rules
107 | - [ ] Mandatory-to-Negotiate
108 | - [ ] Restart
109 | - [ ] Mechanism Preferences
110 | - [ ] Mechanism Offers
111 | - [ ] Data Formatting
112 | - [ ] Security Layers
113 | - [ ] Simple User Name
114 | - [ ] Authorization Identity
115 | - [ ] Realms
116 | - [ ] Round Trips
117 | - [ ] Process
118 | - [ ] Exchange of Stream Headers and Stream Features
119 | - [ ] Initiation
120 | - [ ] Challenge-Response Sequence
121 | - [ ] Abort
122 | - [ ] SASL Failure
123 | - [ ] SASL Success
124 | - [ ] SASL Errors
125 | - [ ] aborted
126 | - [ ] account-disabled
127 | - [ ] credentials-expired
128 | - [ ] encryption-required
129 | - [ ] incorrect-encoding
130 | - [ ] invalid-authzid
131 | - [ ] invalid-mechanism
132 | - [ ] malformed-request
133 | - [ ] mechanism-too-weak
134 | - [ ] not-authorized
135 | - [ ] temporary-auth-failure
136 | - [ ] SASL Definition
137 | - [ ] Resource Binding
138 | - [ ] Fundamentals
139 | - [ ] Support
140 | - [ ] Stream Negotiation Rules
141 | - [ ] Mandatory-to-Negotiate
142 | - [ ] Restart
143 | - [ ] Advertising Support
144 | - [ ] Generation of Resource Identifiers
145 | - [ ] Server-Generated Resource Identifier
146 | - [ ] Success Case
147 | - [ ] Error Cases
148 | - [ ] Resource Constraint
149 | - [ ] Not Allowed
150 | - [ ] Client-Submitted Resource Identifier
151 | - [ ] Success Case
152 | - [ ] Error Cases
153 | - [ ] Bad Request
154 | - [ ] Conflict
155 | - [ ] Retries
156 | - [ ] XML Stanzas
157 | - [ ] Common Attributes
158 | - [ ] to
159 | - [ ] Client-to-Server Streams
160 | - [ ] Server-to-Server Streams
161 | - [ ] from
162 | - [ ] Client-to-Server Streams
163 | - [ ] Server-to-Server Streams
164 | - [ ] id
165 | - [ ] type
166 | - [ ] xml:lang
167 | - [ ] Basic Semantics
168 | - [ ] Message Semantics
169 | - [ ] Presence Semantics
170 | - [ ] IQ Semantics
171 | - [ ] Stanza Errors
172 | - [ ] Rules
173 | - [ ] Syntax
174 | - [ ] Defined Conditions
175 | - [ ] bad-request
176 | - [ ] conflict
177 | - [ ] feature-not-implemented
178 | - [ ] forbidden
179 | - [ ] gone
180 | - [ ] internal-server-error
181 | - [ ] item-not-found
182 | - [ ] jid-malformed
183 | - [ ] not-acceptable
184 | - [ ] not-allowed
185 | - [ ] not-authorized
186 | - [ ] policy-violation
187 | - [ ] recipient-unavailable
188 | - [ ] redirect
189 | - [ ] registration-required
190 | - [ ] remote-server-not-found
191 | - [ ] remote-server-timeout
192 | - [ ] resource-constraint
193 | - [ ] service-unavailable
194 | - [ ] subscription-required
195 | - [ ] undefined-condition
196 | - [ ] unexpected-request
197 | - [ ] Application-Specific Conditions
198 | - [ ] Extended Content
199 | - [ ] Detailed Examples
200 | - [ ] Client-to-Server Examples
201 | - [ ] TLS
202 | - [ ] SASL
203 | - [ ] Resource Binding
204 | - [ ] Stanza Exchange
205 | - [ ] Close
206 | - [ ] Server-to-Server Examples
207 | - [ ] TLS
208 | - [ ] SASL
209 | - [ ] Stanza Exchange
210 | - [ ] Close
211 | - [ ] Server Rules for Processing XML Stanzas
212 | - [ ] In-Order Processing
213 | - [ ] General Considerations
214 | - [ ] No 'to' Address
215 | - [ ] Message
216 | - [ ] Presence
217 | - [ ] IQ
218 | - [ ] Remote Domain
219 | - [ ] Existing Stream
220 | - [ ] No Existing Stream
221 | - [ ] Error Handling
222 | - [ ] Local Domain
223 | - [ ] domainpart
224 | - [ ] domainpart/resourcepart
225 | - [ ] localpart@domainpart
226 | - [ ] No Such User
227 | - [ ] User Exists
228 | - [ ] localpart@domainpart/resourcepart
229 | - [ ] XML Usage
230 | - [ ] XML Restrictions
231 | - [ ] XML Namespace Names and Prefixes
232 | - [ ] Well-Formedness
233 | - [ ] Validation
234 | - [ ] Inclusion of XML Declaration
235 | - [ ] Character Encoding
236 | - [ ] Whitespace
237 | - [ ] XML Versions
238 | - [ ] Internationalization Considerations
239 | - [ ] Security Considerations
240 | - [ ] Fundamentals
241 | - [ ] Threat Model
242 | - [ ] Order of Layers
243 | - [ ] Confidentiality and Integrity
244 | - [ ] Peer Entity Authentication
245 | - [ ] Strong Security
246 | - [ ] Certificates
247 | - [ ] Certificate Generation
248 | - [ ] General Considerations
249 | - [ ] Server Certificates
250 | - [ ] Client Certificates
251 | - [ ] XmppAddr Identifier Type
252 | - [ ] Certificate Validation
253 | - [ ] Server Certificates
254 | - [ ] Client Certificates
255 | - [ ] Checking of Certificates in Long-Lived Streams
256 | - [ ] Use of Certificates in XMPP Extensions
257 | - [ ] Mandatory-to-Implement TLS and SASL Technologies
258 | - [ ] For Authentication Only
259 | - [ ] For Confidentiality Only
260 | - [ ] For Confidentiality and Authentication with Passwords
261 | - [ ] For Confidentiality and Authentication without Passwords
262 | - [ ] Technology Reuse
263 | - [ ] Use of Base 64 in SASL
264 | - [ ] Use of DNS
265 | - [ ] Use of Hash Functions
266 | - [ ] Use of SASL
267 | - [ ] Use of TLS
268 | - [ ] Use of UTF-8
269 | - [ ] Use of XML
270 | - [ ] Information Leaks
271 | - [ ] IP Addresses
272 | - [ ] Presence Information
273 | - [ ] Directory Harvesting
274 | - [ ] Denial of Service
275 | - [ ] Firewalls
276 | - [ ] Interdomain Federation
277 | - [ ] Non-Repudiation
278 | - [ ] IANA Considerations
279 | - [ ] XML Namespace Name for TLS Data
280 | - [ ] XML Namespace Name for SASL Data
281 | - [ ] XML Namespace Name for Stream Errors
282 | - [ ] XML Namespace Name for Resource Binding
283 | - [ ] XML Namespace Name for Stanza Errors
284 | - [ ] GSSAPI Service Name
285 | - [ ] Port Numbers and Service Names
286 | - [ ] Conformance Requirements
287 | - [ ] References
288 | - [ ] Normative References
289 | - [ ] Informative References
290 | > Appendix A. XML Schemas . . . . . . . . . . . . . . . . . . . . 190
291 | > A.1. Stream Namespace . . . . . . . . . . . . . . . . . . . . 190
292 | > A.2. Stream Error Namespace . . . . . . . . . . . . . . . . . 192
293 | > A.3. STARTTLS Namespace . . . . . . . . . . . . . . . . . . . 193
294 | > A.4. SASL Namespace . . . . . . . . . . . . . . . . . . . . . 194
295 | > A.5. Client Namespace . . . . . . . . . . . . . . . . . . . . 196
296 | > A.6. Server Namespace . . . . . . . . . . . . . . . . . . . . 201
297 | > A.7. Resource Binding Namespace . . . . . . . . . . . . . . . 206
298 | > A.8. Stanza Error Namespace . . . . . . . . . . . . . . . . . 206
299 | > Appendix B. Contact Addresses . . . . . . . . . . . . . . . . . 208
300 | > Appendix C. Account Provisioning . . . . . . . . . . . . . . . . 208
301 | > Appendix D. Differences from RFC 3920 . . . . . . . . . . . . . 208
302 | > Appendix E. Acknowledgements . . . . . . . . . . . . . . . . . . 210
303 |
304 |
305 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # xmpp-rs
2 |
3 | `xmpp-rs` is an XMPP Server.
4 |
5 | [](https://github.com/Freyskeyd/xmpp-rs/actions/workflows/ci.yml)
6 | [](https://github.com/Freyskeyd/xmpp-rs/actions/workflows/update-doc.yml)
7 | [](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2FFreyskeyd%2Fxmpp-rs?ref=badge_shield)
8 | ## Install
9 |
10 | *Under construction*
11 |
12 | ## Build
13 |
14 | To build xmpp-rs:
15 |
16 | `cargo build --release`
17 |
18 | ## ROADMAP
19 |
20 | - [ ] :rocket: Root implementation
21 | - [ ] **TCP Connection**: Able to accept TCP connection from clients
22 | - [ ] **Open stream reading**: Listen to an open `stream` stanza and respond to it
23 | - [ ] **TLS Connection and negociation**
24 | - [ ] **PLAIN authentication**: Authentification with a PLAIN mechanism must be possible.
25 |
26 | ## XEPs
27 |
28 | - [ ] [RFC-6122: (XMPP): Address Format](https://tools.ietf.org/html/rfc6122)
29 | - [ ] [RFC-7590: Use of TLS](https://tools.ietf.org/html/rfc7590)
30 | - [ ] [XEP-0368: SRV records for XMPP over TLS](https://xmpp.org/extensions/xep-0368.html)
31 | - [ ] [XEP-0199: XMPP Ping](https://xmpp.org/extensions/xep-0199.html)
32 | - [ ] [XEP-0004: Data Forms](https://xmpp.org/extensions/xep-0004.html)
33 | - [ ] [XEP-0030: Service Discovery](https://xmpp.org/extensions/xep-0030.html)
34 | - [ ] [XEP-0048: Bookmarks](https://xmpp.org/extensions/xep-0048.html)
35 | - [ ] [XEP-0049: Private XML Storage](https://xmpp.org/extensions/xep-0049.html)
36 |
37 | - [ ] [Stanza errors](https://tools.ietf.org/html/rfc6120#section-8.3)
38 |
39 | ## License
40 |
41 | xmpp-rs is primarily distributed under the terms of both the MIT license
42 | and the Apache License (Version 2.0), with portions covered by various
43 | BSD-like licenses.
44 |
45 | See LICENSE-APACHE, and LICENSE-MIT for details.
46 |
--------------------------------------------------------------------------------
/credentials/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xmpp-credentials"
3 | version = "0.1.3"
4 | license = "MPL-2.0"
5 |
6 | description = """
7 | xmpp-rs is an implementation of the Extensible Messaging and Presence Protocol (XMPP).
8 |
9 | Based on tokio-rs and futures-rs. It's goal is to be fully tested and usable.
10 | """
11 | repository = "https://github.com/Freyskeyd/xmpp-rs"
12 | keywords = ["xmpp", "tokio", "jabber", "IM", "instant-messaging"]
13 | categories = ["network-programming"]
14 |
15 | [dependencies]
16 | sasl = "0.4.1"
17 | jid = "0"
18 |
--------------------------------------------------------------------------------
/credentials/src/lib.rs:
--------------------------------------------------------------------------------
1 | extern crate jid;
2 | extern crate sasl;
3 |
4 | use jid::Jid;
5 | use sasl::common::Credentials as SaslCredentials;
6 | use std::str::FromStr;
7 |
8 | /// Define Credentials used to authenticate a user
9 | #[derive(Clone, Debug, PartialEq)]
10 | pub struct Credentials {
11 | pub jid: Jid,
12 | pub password: String,
13 | }
14 |
15 | impl Credentials {
16 | pub fn format(&self) -> SaslCredentials {
17 | SaslCredentials::default().with_username(self.jid.to_string()).with_password(self.password.to_string())
18 | }
19 | }
20 |
21 | impl Default for Credentials {
22 | fn default() -> Credentials {
23 | Credentials {
24 | jid: Jid::from_str("guest").unwrap(),
25 | password: "guest".to_string(),
26 | }
27 | }
28 | }
29 |
30 | #[cfg(test)]
31 | mod tests {
32 | use super::*;
33 |
34 | #[test]
35 | fn check_default_values() {
36 | let c = Credentials { ..Credentials::default() };
37 |
38 | assert_eq!(c.jid, Jid::from_str("guest").unwrap());
39 | assert_eq!(c.password, "guest");
40 | }
41 |
42 | #[test]
43 | fn creation() {
44 | let c = Credentials {
45 | jid: Jid::from_str("guest").unwrap(),
46 | password: "guest".into(),
47 | };
48 | assert_eq!(c.jid, Jid::from_str("guest").unwrap());
49 | assert_eq!(c.password, "guest");
50 | }
51 |
52 | #[test]
53 | fn equality() {
54 | let c = Credentials {
55 | jid: Jid::from_str("guest").unwrap(),
56 | password: "guest".into(),
57 | };
58 | let mut d = c.clone();
59 |
60 | assert!(c == d);
61 |
62 | d.jid = Jid::from_str("guest2").unwrap();
63 | d.password = "guest".into();
64 |
65 | assert!(c != d);
66 | }
67 |
68 | #[test]
69 | fn format() {
70 | let c = Credentials {
71 | jid: Jid::from_str("guest").unwrap(),
72 | password: "guest".into(),
73 | };
74 |
75 | let check = SaslCredentials::default().with_username("guest").with_password("guest");
76 |
77 | assert!(c.format().identity == check.identity);
78 | assert!(c.format().secret == check.secret);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | ejabberd:
2 | image: rroemhild/ejabberd
3 | ports:
4 | - 5222:5222
5 | - 5269:5269
6 | - 5280:5280
7 | volumes:
8 | - ssl:/opt/ejabberd/ssl
9 | environment:
10 | - ERLANG_NODE=ejabberd
11 | - EJABBERD_LOGLEVEL=5
12 | - XMPP_DOMAIN=example.com
13 | # - EJABBERD_ADMINS=admin@example.com
14 | - EJABBERD_USERS=alice@example.com:test user1@example.com:test
15 |
--------------------------------------------------------------------------------
/examples/server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "server"
3 | version = "0.1.0"
4 | authors = ["Freyskeyd "]
5 | edition = "2018"
6 |
7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8 |
9 | [dependencies]
10 | actix = "0.11.0"
11 | xmpp-rs = {path = "../../"}
12 |
13 | env_logger = "*"
14 |
--------------------------------------------------------------------------------
/examples/server/config/default.toml:
--------------------------------------------------------------------------------
1 | authenticators = ["sql", "personal"]
2 |
3 | [vhosts.localhost]
4 | name = "locahost"
5 |
6 | [[listeners]]
7 | [listeners.Tcp]
8 | port = 5222
9 | ip = "127.0.0.1"
10 | # starttls = "Unavailable"
11 |
12 | # [[listeners]]
13 |
14 | # [listeners.Tcp]
15 | # port = 5223
16 | # ip = "127.0.0.1"
17 |
18 | [listeners.Tcp.starttls.Required]
19 | cert_path = "server.crt"
20 | key_path = "server.key"
21 |
--------------------------------------------------------------------------------
/examples/server/server.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICljCCAX4CCQCCkuNb7meOCTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJm
3 | cjAeFw0yMTAxMDUwODIwMjRaFw0yMjAxMDUwODIwMjRaMA0xCzAJBgNVBAYTAmZy
4 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzEWH6yE14a5mDgF7aTxd
5 | aDkH42jsEMCO8b2cRGfUrU+eSRk9WCMaSL/zPhiBq7cWYKZqa9ML/myHQCFX8FVn
6 | 8rLuUCDtb+Z4fNwdccHQmiZmQz6nTKdnyz9bvnGyTj5QNnxeOmsxl/9PI32qoM/3
7 | wrRS5CQf22NgKkPGEo/nKXGkd/zIZ856c+ZXBMHnpzUQlogyJSpS/5HU6j97GpKg
8 | Vx/ETeWLLwbjEQVOWbil7uRn2YhfDVdD9G5qu4jmyKIW7v+Mq8O8ccFcJgiCEz3i
9 | 6Gz50YeUim0dRXQA/or9ix/b2eilNtn9q5DCFP2EkaBexpccBW0SLTjZTl+iOyyR
10 | AwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDGOD6IDqymUrIthvt/xoMlEGUTR0QT
11 | X62iP456RXy5s1jYFAU6f/ullQMz0CynSPiWFcShMoUUCG+fjqpDAWiplV7zQKMY
12 | dvctoSfLSyQAENu8UUhNo4s+0j/JdEve4LZENnQoY3GTZtV3qcKT8pgFIN0xJMc+
13 | MFVNifzv83Buex2Hti/UfBBpXZyIqZLHx0NhZ4VIjj593oQqSYcDWDf6UeVqkkOb
14 | DxLt5WOHwpPWyTFyyp/yMfISXqJw/7n7FjZZJIjjP5I4T1y58lMKTDj8UNMXsy+J
15 | 8uiaqEu5wZSVglIHtTvpHkB9YXtzo6/UlgWty6fe68IRwXgZhaM7X/0c
16 | -----END CERTIFICATE-----
17 |
--------------------------------------------------------------------------------
/examples/server/server.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMRYfrITXhrmYO
3 | AXtpPF1oOQfjaOwQwI7xvZxEZ9StT55JGT1YIxpIv/M+GIGrtxZgpmpr0wv+bIdA
4 | IVfwVWfysu5QIO1v5nh83B1xwdCaJmZDPqdMp2fLP1u+cbJOPlA2fF46azGX/08j
5 | faqgz/fCtFLkJB/bY2AqQ8YSj+cpcaR3/Mhnznpz5lcEweenNRCWiDIlKlL/kdTq
6 | P3sakqBXH8RN5YsvBuMRBU5ZuKXu5GfZiF8NV0P0bmq7iObIohbu/4yrw7xxwVwm
7 | CIITPeLobPnRh5SKbR1FdAD+iv2LH9vZ6KU22f2rkMIU/YSRoF7GlxwFbRItONlO
8 | X6I7LJEDAgMBAAECggEADoy1Ta4j5FtDsaOxYqGGFbJaOXxztA3DILzcsJKkt2OA
9 | ZryBfhGiAaSKctXUBqMX2PKBigSSSiD40TyOwvOSX9sW1mZCA0JrOpmDD2M6tIAf
10 | sJJ54B/caMGuizYV+TS/CeJ547dW5Piubly5FpM5loi5jr5z9nBxnREOvqu5T15E
11 | MoQ9yhQtd6xJfOHPbMhyqAQeW2RwEPfzwRNWm0fH7kmQSnNgzSfBSG7R5SHXFViT
12 | v1BzjTXfapqGp6xrGzXdQCetij86GzvMMwjjgkN/Q3GExB9d/pTTAu7abZZ21d7C
13 | QjOj9lA274SS5S2FymIKFsevQ7Lr0QDlSReYZ7vNQQKBgQDyno1mkOlxsJUwTobu
14 | 0d7VrXRNxKohdCHrkyItKRLs/SWadUnc9c3YgWmLgvKlxFFtfs7kY4nbxuVAlGpC
15 | zaOf7naI7c27P7ihdfnu3GgrbkvIlTfa+LxIk98j1zERN2mmMKpoqvXf+pT+QDGm
16 | SsMIN7AqSxkRlpEAjeFZkedhOQKBgQDXiY/U4RjUV7tCuf+By3jrsaUzmEkpkgRh
17 | 72d3stk+wTdxzB9XU0v+C/u3NxbvUdv3gjMR70NkNAKf1OiGKsM19YQYeepb4MOo
18 | +62x+M8NTqb+eNle3TenTFn/yhJi/FETKwDqLVWt1FQJflfhWmXCAKffxg3U8FZW
19 | HsW3Cf/QGwKBgCcW8pNG38XIfJD25fiOacellap9+CdBrcFlyEjcaEc0lh1nrkni
20 | dJLgbt/ibjPVCIWKu8zCWNDHH+KixugSd71pz0FKhy4XGykwd99LNaFhuOYNXJ10
21 | G+nZoUcGAcrTUbtL9fi9KrY2ilDYiOdQ/lFRn5mA1f4mcyBSu68RueTJAoGBAM8v
22 | 5tkFwC5uXw3XaatFAmnejCU6oCmbuSbDUTyY6YgPh9KWGxKAea4tqrwF8r/+empD
23 | 9/+ndaqe7F7j9Njzxk7aQS6eExBa0PphZCiOOcpa7t/zH1C3acYh+OmPP3lzfiPk
24 | 1K5HAfNlBZtSnft8QBDrPHQ5GBa3KOcEEZ7Pt949AoGAQ2Wouhs7pBuRq+6RFQ1F
25 | 8KPNHsq8v8ieGBaNTJ+2AilkkKb4Zad5eS8JLpZqR1uq87BAIAehX3hk1ZR9YIez
26 | n2/uG8Lk6oEdpJBGyHQ7w6ulOyawSaXVqKvMfB7xUmMzf4SEpVXY+2DCg/0YIMvR
27 | YSIlsBWilw9Cu8ybuAEG/C8=
28 | -----END PRIVATE KEY-----
29 |
--------------------------------------------------------------------------------
/examples/server/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::env;
2 | use xmpp::server::Server;
3 |
4 | #[actix::main]
5 | async fn main() {
6 | env::set_var("RUST_LOG", "actix=trace,xmpp_server=trace");
7 | env_logger::init();
8 |
9 | let _ = Server::build().launch().await;
10 | }
11 |
--------------------------------------------------------------------------------
/proto/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xmpp-proto"
3 | version = "0.1.3"
4 | authors = ["Freyskeyd "]
5 | documentation = "https://docs.rs/xmpp-proto"
6 | edition = "2018"
7 | license = "MPL-2.0"
8 | description = """
9 | xmpp-rs is an implementation of the Extensible Messaging and Presence Protocol (XMPP).
10 |
11 | Based on tokio-rs and futures-rs. It's goal is to be fully tested and usable.
12 | """
13 | repository = "https://github.com/Freyskeyd/xmpp-rs"
14 | readme = "../README.md"
15 | keywords = ["xmpp", "tokio", "jabber", "IM", "instant-messaging"]
16 | categories = ["network-programming"]
17 |
18 | [dependencies]
19 | # base64 = "0"
20 | # bytes = "0"
21 | # circular = "0"
22 | # futures = "*"
23 | log = "0"
24 | # native-tls = "0"
25 | # tokio-core = "0"
26 | # tokio-io = "0"
27 | # tokio-tls = "0.1.2"
28 | uuid = { version = "0.4", features = ["v4"] }
29 | # xml-rs = {git = "https://github.com/Freyskeyd/xml-rs"}
30 | # xmpp-derive = {path = "../derive", version = "0" }
31 | # sasl = "0.4.1"
32 | # clippy = {version = "*", optional = true}
33 |
34 | xmpp-credentials = { path = "../credentials" }
35 | jid = "0"
36 | xmpp-xml = { path = "../xml" }
37 |
38 | actix = { version = "0.11.0", default_feature = false }
39 | circular = "*"
40 | derive_builder = "0.9.0"
41 | # xmpp-events = { path = "../events" }
42 | [features]
43 | default = []
44 |
--------------------------------------------------------------------------------
/proto/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod non_stanza;
2 | pub mod ns;
3 | mod packet;
4 | mod stanza;
5 |
6 | use jid::Jid;
7 |
8 | pub use non_stanza::*;
9 | pub use packet::*;
10 | pub use stanza::*;
11 |
12 | use xmpp_xml::Element;
13 |
14 | pub trait NonStanzaTrait {}
15 |
16 | impl ToXmlElement for Element {
17 | type Error = std::io::Error;
18 |
19 | fn to_element(&self) -> Result {
20 | Ok(self.clone())
21 | }
22 | }
23 |
24 | /// FromXmlElement is used to transform an Element to an object
25 | pub trait FromXmlElement {
26 | type Error;
27 | fn from_element(e: &Element) -> Result
28 | where
29 | Self: Sized;
30 | }
31 |
32 | pub trait ToXmlElement {
33 | type Error;
34 |
35 | fn to_element(&self) -> Result;
36 | }
37 |
--------------------------------------------------------------------------------
/proto/src/non_stanza.rs:
--------------------------------------------------------------------------------
1 | use xmpp_xml::Element;
2 |
3 | mod auth;
4 | mod bind;
5 | mod close_stream;
6 | mod open_stream;
7 | mod proceed_tls;
8 | mod sasl_success;
9 | mod start_tls;
10 | mod stream_error;
11 | mod stream_features;
12 |
13 | pub use auth::*;
14 | pub use bind::*;
15 | pub use close_stream::*;
16 | pub use open_stream::*;
17 | pub use proceed_tls::*;
18 | pub use sasl_success::*;
19 | pub use start_tls::*;
20 | pub use stream_error::*;
21 | pub use stream_features::*;
22 |
23 | use crate::ToXmlElement;
24 |
25 | /// Define a sub part of a Packet, a NonStanza is the representation of an XML Stream event.
26 | /// It's used by the system to deal with the communication between entities over a network.
27 | #[derive(Debug, Clone)]
28 | pub enum NonStanza {
29 | // TODO: Rename to Initial stream header?
30 | OpenStream(OpenStream),
31 | ProceedTls(ProceedTls),
32 | StartTls(StartTls),
33 | SASLSuccess(SASLSuccess),
34 | StreamFeatures(StreamFeatures),
35 | Auth(Auth),
36 | Bind(Bind),
37 | StreamError(StreamError),
38 | CloseStream(CloseStream),
39 | }
40 |
41 | impl ToXmlElement for NonStanza {
42 | type Error = std::io::Error;
43 |
44 | fn to_element(&self) -> Result {
45 | match self {
46 | NonStanza::Auth(_) => Err(std::io::Error::new(std::io::ErrorKind::Other, "shouldn't be sent back")),
47 | NonStanza::Bind(s) => s.to_element(),
48 | NonStanza::OpenStream(s) => s.to_element(),
49 | NonStanza::ProceedTls(s) => s.to_element(),
50 | NonStanza::SASLSuccess(s) => s.to_element(),
51 | NonStanza::StartTls(_) => Err(std::io::Error::new(std::io::ErrorKind::Other, "shouldn't be sent back")),
52 | NonStanza::StreamFeatures(s) => s.to_element(),
53 | NonStanza::StreamError(s) => s.to_element(),
54 | NonStanza::CloseStream(s) => s.to_element(),
55 | }
56 | }
57 | }
58 |
59 | #[cfg(test)]
60 | mod tests {
61 | use super::*;
62 |
63 | #[test]
64 | fn to_element() {
65 | assert!(matches!(NonStanza::OpenStream(OpenStream::default()).to_element(), Ok(_)));
66 | assert!(matches!(NonStanza::StreamFeatures(StreamFeatures::default()).to_element(), Ok(_)));
67 | assert!(matches!(NonStanza::StartTls(StartTls::default()).to_element(), Err(_)));
68 | assert!(matches!(NonStanza::ProceedTls(ProceedTls::default()).to_element(), Ok(_)));
69 | assert!(matches!(NonStanza::SASLSuccess(SASLSuccess::default()).to_element(), Ok(_)));
70 | assert!(matches!(NonStanza::Auth(Auth::default()).to_element(), Err(_)));
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/proto/src/non_stanza/auth.rs:
--------------------------------------------------------------------------------
1 | use xmpp_xml::Element;
2 |
3 | use crate::{FromXmlElement, NonStanza, Packet};
4 |
5 | #[derive(Default, Debug, Clone)]
6 | pub struct Auth {
7 | mechanism: Option,
8 | challenge: Option,
9 | }
10 |
11 | impl Auth {
12 | /// Get a reference to the auth's challenge.
13 | pub fn challenge(&self) -> &Option {
14 | &self.challenge
15 | }
16 |
17 | /// Get a reference to the auth's mechanism.
18 | pub fn mechanism(&self) -> Option<&str> {
19 | self.mechanism.as_ref().map(|v| v.as_ref())
20 | }
21 | }
22 |
23 | impl From for Packet {
24 | fn from(s: Auth) -> Self {
25 | NonStanza::Auth(s).into()
26 | }
27 | }
28 |
29 | impl FromXmlElement for Auth {
30 | type Error = std::io::Error;
31 | fn from_element(e: &Element) -> Result {
32 | let p = Self {
33 | mechanism: e.get_attr("mechanism").map(|mechanism| mechanism.to_string()),
34 | challenge: Some(e.text().to_string()),
35 | };
36 |
37 | Ok(p)
38 | }
39 | }
40 |
41 | #[cfg(test)]
42 | mod tests {
43 | use std::io::Write;
44 |
45 | use circular::Buffer;
46 | use xmpp_xml::xml::{reader::XmlEvent, ParserConfig};
47 |
48 | use super::*;
49 |
50 | const EXPECTED_AUTH: &'static str = "AGp1bGlldAByMG0zMG15cjBtMzA=";
51 |
52 | #[test]
53 | fn parse_proceed_plain() {
54 | let element = Element::from_reader(EXPECTED_AUTH.as_bytes()).unwrap();
55 | let proceed = Auth::from_element(&element).unwrap();
56 |
57 | assert_eq!(proceed.mechanism.unwrap(), "PLAIN");
58 | assert_eq!(proceed.challenge.unwrap(), "AGp1bGlldAByMG0zMG15cjBtMzA=");
59 | }
60 |
61 | #[test]
62 | fn from() {
63 | let mut cfg = ParserConfig::new().whitespace_to_characters(true);
64 | cfg.ignore_end_of_stream = true;
65 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096));
66 |
67 | reader.source_mut().write(EXPECTED_AUTH.as_bytes()).unwrap();
68 | let _ = reader.next().unwrap();
69 | let x = reader.next().unwrap();
70 |
71 | assert!(matches!(x, XmlEvent::StartElement { .. }));
72 |
73 | if let XmlEvent::StartElement { name, attributes, namespace } = x {
74 | let packet = Packet::parse(&mut reader, name, namespace, attributes);
75 | assert!(
76 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::Auth(_))),
77 | "Packet wasn't an Auth, it was: {:?}",
78 | packet
79 | );
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/proto/src/non_stanza/bind.rs:
--------------------------------------------------------------------------------
1 | use uuid::Uuid;
2 | use xmpp_xml::Element;
3 |
4 | use crate::{ns, FromXmlElement, NonStanza, Packet, ToXmlElement};
5 |
6 | #[derive(Default, Debug, Clone)]
7 | pub struct Bind {
8 | pub resource: Option,
9 | }
10 |
11 | impl From for Packet {
12 | fn from(s: Bind) -> Self {
13 | NonStanza::Bind(s).into()
14 | }
15 | }
16 |
17 | impl FromXmlElement for Bind {
18 | type Error = std::io::Error;
19 | fn from_element(e: &Element) -> Result {
20 | let p = Self {
21 | resource: e.find((ns::BIND, "resource")).map(|e| e.text().to_owned()),
22 | };
23 |
24 | Ok(p)
25 | }
26 | }
27 |
28 | impl ToXmlElement for Bind {
29 | type Error = std::io::Error;
30 | fn to_element(&self) -> Result {
31 | let mut bind = Element::new((ns::BIND, "bind"));
32 | bind.append_new_child((ns::BIND, "jid"))
33 | .set_text(format!("SOME@localhost/{}", self.resource.clone().unwrap_or_else(|| Uuid::new_v4().to_string())));
34 |
35 | Ok(bind)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/proto/src/non_stanza/close_stream.rs:
--------------------------------------------------------------------------------
1 | use crate::Packet;
2 | use crate::ToXmlElement;
3 | use crate::{ns, NonStanza};
4 | use xmpp_xml::Element;
5 |
6 | #[derive(Debug, Clone)]
7 | pub struct CloseStream {}
8 |
9 | impl From for Packet {
10 | fn from(s: CloseStream) -> Self {
11 | NonStanza::CloseStream(s).into()
12 | }
13 | }
14 |
15 | impl ToXmlElement for CloseStream {
16 | type Error = std::io::Error;
17 | fn to_element(&self) -> Result {
18 | let root = Element::new((ns::STREAM, "stream"));
19 |
20 | Ok(root)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/proto/src/non_stanza/open_stream.rs:
--------------------------------------------------------------------------------
1 | use std::str::FromStr;
2 |
3 | use crate::{ns, NonStanza, Packet, PacketParsingError, ToXmlElement};
4 | use jid::Jid;
5 | use uuid::Uuid;
6 | use xmpp_xml::{xml::attribute::OwnedAttribute, Element, QName};
7 |
8 | /// Define an OpenStream NonStanza packet.
9 | ///
10 | /// Mostly used when negociating connection between parties.
11 | #[derive(derive_builder::Builder, Default, Debug, Clone)]
12 | #[builder(setter(into))]
13 | pub struct OpenStream {
14 | /// An Id generated by the server.
15 | ///
16 | /// The 'id' attribute specifies a unique identifier for the stream, called a "stream ID". The stream ID MUST be generated by the receiving entity when it sends a response stream header and MUST BE unique within the receiving application (normally a server).
17 | ///
18 | /// See [RFC-6120]( https://xmpp.org/rfcs/rfc6120.html#streams-attr-id )
19 | #[builder(setter(into), default)]
20 | pub id: Option,
21 | /// The 'xml:lang' attribute specifies an entity's preferred or default language for any human-readable XML character data to be sent over the stream
22 | ///
23 | /// See [RFC-6120]( https://xmpp.org/rfcs/rfc6120.html#streams-attr-xmllang )
24 | #[builder(setter(into), default = "String::from(\"en\")")]
25 | pub lang: String,
26 | /// The inclusion of the version attribute set to a value of at least "1.0" signals support for the stream-related protocols defined in this specification, including TLS negotiation, SASL negotiation, stream features, and stream errors.
27 | ///
28 | /// See [RFC-6120]( https://xmpp.org/rfcs/rfc6120.html#streams-attr-version )
29 | pub version: String,
30 | #[builder(setter(into), default)]
31 | pub to: Option,
32 | #[builder(setter(into), default)]
33 | pub from: Option,
34 | }
35 |
36 | impl OpenStream {
37 | pub fn from_start_element(attributes: Vec) -> Result {
38 | attributes
39 | .into_iter()
40 | .fold(&mut OpenStreamBuilder::default(), |packet, attribute| match attribute.name.local_name.as_ref() {
41 | "to" if attribute.name.namespace.is_none() => packet.to(Jid::from_str(&attribute.value).ok()),
42 | "from" if attribute.name.namespace.is_none() => packet.from(Jid::from_str(&attribute.value).ok()),
43 | "lang" if attribute.name.namespace == Some(ns::XML_URI.to_string()) => packet.lang(attribute.value),
44 | "version" if attribute.name.namespace.is_none() => packet.version(attribute.value),
45 | "id" if attribute.name.namespace.is_none() => packet.id(attribute.value),
46 | _ => packet,
47 | })
48 | .build()
49 | .or(Err(PacketParsingError::Unknown))
50 | }
51 | }
52 |
53 | impl From for Packet {
54 | fn from(s: OpenStream) -> Self {
55 | NonStanza::OpenStream(s).into()
56 | }
57 | }
58 |
59 | impl ToXmlElement for OpenStream {
60 | type Error = std::io::Error;
61 | fn to_element(&self) -> Result {
62 | let mut element = Element::new((ns::CLIENT, "stream")).write_end_tag(false);
63 |
64 | let _ = element.set_namespace_prefix(ns::STREAM, "stream");
65 |
66 | let qname_stream = QName::from_ns_name(Some(ns::STREAM), "stream");
67 | element
68 | .set_tag(&qname_stream)
69 | .set_attr("version", self.version.to_string())
70 | .set_attr((ns::XML_URI, "lang"), self.lang.to_string())
71 | .set_attr("id", self.id.clone().unwrap_or_else(|| Uuid::new_v4().to_string()));
72 |
73 | if let Some(ref from) = self.from {
74 | element.set_attr("from", from.to_string());
75 | }
76 |
77 | if let Some(ref to) = self.to {
78 | element.set_attr("to", to.to_string());
79 | }
80 |
81 | Ok(element)
82 | }
83 | }
84 |
85 | #[cfg(test)]
86 | mod tests {
87 | use super::*;
88 |
89 | use circular::Buffer;
90 | use std::io::Write;
91 |
92 | use xmpp_xml::xml::{reader::XmlEvent, ParserConfig};
93 |
94 | #[test]
95 | fn from() {
96 | let initial_stream = "";
97 |
98 | let mut cfg = ParserConfig::new().whitespace_to_characters(true);
99 | cfg.ignore_end_of_stream = true;
100 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096));
101 |
102 | reader.source_mut().write(initial_stream.as_bytes()).unwrap();
103 | let _ = reader.next().unwrap();
104 | let x = reader.next().unwrap();
105 |
106 | assert!(matches!(x, XmlEvent::StartElement { .. }));
107 |
108 | if let XmlEvent::StartElement { name, attributes, namespace } = x {
109 | let packet = Packet::parse(&mut reader, name, namespace, attributes);
110 | assert!(
111 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::OpenStream(_))),
112 | "Packet wasn't an OpenStream, it was: {:?}",
113 | packet
114 | );
115 | }
116 | }
117 |
118 | #[test]
119 | fn to_element() {
120 | let id = Uuid::new_v4().to_string();
121 | let lang: String = "en".into();
122 | let version: String = "1.0".into();
123 | let to: Jid = Jid::from_str("jid@localhost").unwrap();
124 | let from: Jid = Jid::from_str("localhost").unwrap();
125 | let expected = format!(
126 | r#""#,
127 | id = id,
128 | lang = lang,
129 | from = from,
130 | to = to,
131 | version = version
132 | );
133 |
134 | let open_stream = OpenStreamBuilder::default().id(id).lang(lang).version(version).to(to).from(from).build();
135 |
136 | assert!(open_stream.is_ok());
137 |
138 | assert!(matches!(open_stream, Ok(OpenStream { .. })));
139 |
140 | let mut output: Vec = Vec::new();
141 | let _ = open_stream.unwrap().to_element().unwrap().to_writer(&mut output);
142 |
143 | let generated = String::from_utf8(output).unwrap();
144 |
145 | assert!(expected == generated, "{} != {}", expected, generated);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/proto/src/non_stanza/proceed_tls.rs:
--------------------------------------------------------------------------------
1 | use xmpp_xml::Element;
2 |
3 | use crate::{ns, FromXmlElement, NonStanza, Packet, ToXmlElement};
4 |
5 | #[derive(Default, Debug, Clone)]
6 | pub struct ProceedTls {
7 | mechanism: Option,
8 | challenge: Option,
9 | }
10 |
11 | impl From for Packet {
12 | fn from(s: ProceedTls) -> Self {
13 | NonStanza::ProceedTls(s).into()
14 | }
15 | }
16 |
17 | impl ToXmlElement for ProceedTls {
18 | type Error = std::io::Error;
19 | fn to_element(&self) -> Result {
20 | let root = Element::new((ns::TLS, "proceed"));
21 |
22 | Ok(root)
23 | }
24 | }
25 |
26 | impl FromXmlElement for ProceedTls {
27 | type Error = std::io::Error;
28 | fn from_element(e: &Element) -> Result {
29 | let p = Self {
30 | mechanism: e.get_attr("mechanism").map(|mechanism| mechanism.to_string()),
31 | challenge: Some(e.text().to_string()),
32 | };
33 |
34 | Ok(p)
35 | }
36 | }
37 |
38 | #[cfg(test)]
39 | mod tests {
40 | use std::io::Write;
41 |
42 | use circular::Buffer;
43 | use xmpp_xml::{
44 | xml::{reader::XmlEvent, ParserConfig},
45 | WriteOptions,
46 | };
47 |
48 | use super::*;
49 |
50 | const EXPECTED_PROCEEDTLS: &'static str = r#""#;
51 |
52 | #[test]
53 | fn to_element() {
54 | let proceed = ProceedTls::default();
55 |
56 | let mut output: Vec = Vec::new();
57 | let _ = proceed.to_element().unwrap().to_writer_with_options(&mut output, WriteOptions::new().set_xml_prolog(None));
58 |
59 | let generated = String::from_utf8(output).unwrap();
60 |
61 | assert!(EXPECTED_PROCEEDTLS == generated);
62 | }
63 |
64 | #[test]
65 | fn from() {
66 | let mut cfg = ParserConfig::new().whitespace_to_characters(true);
67 | cfg.ignore_end_of_stream = true;
68 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096));
69 |
70 | reader.source_mut().write(EXPECTED_PROCEEDTLS.as_bytes()).unwrap();
71 | let _ = reader.next().unwrap();
72 | let x = reader.next().unwrap();
73 |
74 | assert!(matches!(x, XmlEvent::StartElement { .. }));
75 |
76 | if let XmlEvent::StartElement { name, attributes, namespace } = x {
77 | let packet = Packet::parse(&mut reader, name, namespace, attributes);
78 | assert!(
79 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::ProceedTls(_))),
80 | "Packet wasn't an ProceedTls, it was: {:?}",
81 | packet
82 | );
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/proto/src/non_stanza/sasl_success.rs:
--------------------------------------------------------------------------------
1 | use xmpp_xml::Element;
2 |
3 | use crate::{ns, NonStanza, Packet, ToXmlElement};
4 |
5 | #[derive(Default, Debug, Clone)]
6 | pub struct SASLSuccess {}
7 |
8 | impl From for Packet {
9 | fn from(s: SASLSuccess) -> Self {
10 | NonStanza::SASLSuccess(s).into()
11 | }
12 | }
13 |
14 | impl ToXmlElement for SASLSuccess {
15 | type Error = std::io::Error;
16 | fn to_element(&self) -> Result {
17 | Ok(Element::new((ns::SASL, "success")))
18 | }
19 | }
20 |
21 | #[cfg(test)]
22 | mod tests {
23 | use xmpp_xml::WriteOptions;
24 |
25 | use super::*;
26 |
27 | const EXPECTED_SUCCESS: &'static str = r#""#;
28 |
29 | #[test]
30 | fn from() {
31 | let packet: Packet = SASLSuccess::default().into();
32 | assert!(
33 | matches!(packet, Packet::NonStanza(ref stanza) if matches!(**stanza, NonStanza::SASLSuccess(_))),
34 | "Packet wasn't an SASLSuccess",
35 | );
36 | }
37 |
38 | #[test]
39 | fn to() {
40 | let mut output: Vec = Vec::new();
41 | let _ = SASLSuccess::default()
42 | .to_element()
43 | .unwrap()
44 | .to_writer_with_options(&mut output, WriteOptions::new().set_xml_prolog(None));
45 |
46 | let generated = String::from_utf8(output).unwrap();
47 |
48 | assert!(EXPECTED_SUCCESS == generated);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/proto/src/non_stanza/start_tls.rs:
--------------------------------------------------------------------------------
1 | use xmpp_xml::Element;
2 |
3 | use crate::{FromXmlElement, NonStanza, Packet, PacketParsingError};
4 |
5 | #[derive(Debug, Default, Clone)]
6 | pub struct StartTls {}
7 |
8 | impl From for Packet {
9 | fn from(s: StartTls) -> Self {
10 | NonStanza::StartTls(s).into()
11 | }
12 | }
13 |
14 | impl FromXmlElement for StartTls {
15 | type Error = PacketParsingError;
16 |
17 | fn from_element(_: &Element) -> Result
18 | where
19 | Self: Sized,
20 | {
21 | Ok(Self {})
22 | }
23 | }
24 |
25 | #[cfg(test)]
26 | mod tests {
27 | use std::io::Write;
28 |
29 | use circular::Buffer;
30 | use xmpp_xml::xml::{reader::XmlEvent, ParserConfig};
31 |
32 | use super::*;
33 |
34 | const EXPECTED_STARTTLS: &'static str = "";
35 |
36 | #[test]
37 | fn from() {
38 | let mut cfg = ParserConfig::new().whitespace_to_characters(true);
39 | cfg.ignore_end_of_stream = true;
40 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096));
41 |
42 | reader.source_mut().write(EXPECTED_STARTTLS.as_bytes()).unwrap();
43 | let _ = reader.next().unwrap();
44 | let x = reader.next().unwrap();
45 |
46 | assert!(matches!(x, XmlEvent::StartElement { .. }));
47 |
48 | if let XmlEvent::StartElement { name, attributes, namespace } = x {
49 | let packet = Packet::parse(&mut reader, name, namespace, attributes);
50 | assert!(
51 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::StartTls(_))),
52 | "Packet wasn't an StartTls, it was: {:?}",
53 | packet
54 | );
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/proto/src/non_stanza/stream_error.rs:
--------------------------------------------------------------------------------
1 | use crate::Packet;
2 | use crate::ToXmlElement;
3 | use crate::{ns, NonStanza};
4 | use xmpp_xml::Element;
5 |
6 | #[derive(Debug, Clone)]
7 | pub struct StreamError {
8 | pub kind: StreamErrorKind,
9 | //TODO: Implement error text
10 | // pub text: String
11 | }
12 |
13 | #[derive(Debug, Clone)]
14 | pub enum StreamErrorKind {
15 | BadNamespacePrefix,
16 | HostUnknown,
17 | InvalidNamespace,
18 | NotAuthorized,
19 | UnsupportedEncoding,
20 | UnsupportedVersion,
21 | }
22 |
23 | impl From for Packet {
24 | fn from(s: StreamError) -> Self {
25 | NonStanza::StreamError(s).into()
26 | }
27 | }
28 |
29 | impl ToXmlElement for StreamError {
30 | type Error = std::io::Error;
31 | fn to_element(&self) -> Result {
32 | let mut root = Element::new((ns::STREAM, "error"));
33 |
34 | match self.kind {
35 | StreamErrorKind::BadNamespacePrefix => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "bad-namespace-prefix")),
36 | StreamErrorKind::HostUnknown => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "host-unknown")),
37 | StreamErrorKind::InvalidNamespace => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "invalid-namespace")),
38 | StreamErrorKind::NotAuthorized => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "not-authorized")),
39 | StreamErrorKind::UnsupportedEncoding => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "unsupported-encoding")),
40 | StreamErrorKind::UnsupportedVersion => root.append_new_child(("urn:ietf:params:xml:ns:xmpp-streams", "unsupported-version")),
41 | };
42 |
43 | Ok(root)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/proto/src/non_stanza/stream_features.rs:
--------------------------------------------------------------------------------
1 | use xmpp_xml::Element;
2 |
3 | use crate::{ns, FromXmlElement, NonStanza, Packet, ToXmlElement};
4 |
5 | #[derive(derive_builder::Builder, Default, Debug, Clone)]
6 | #[builder(setter(into))]
7 | pub struct StreamFeatures {
8 | pub features: Features,
9 | }
10 |
11 | impl FromXmlElement for StreamFeatures {
12 | type Error = std::io::Error;
13 |
14 | fn from_element(e: &Element) -> Result
15 | where
16 | Self: Sized,
17 | {
18 | Ok(Self {
19 | features: e
20 | .children()
21 | .map(|child| match Features::from(child.tag().name()) {
22 | Features::Mechanisms(_) => Features::Mechanisms(child.children().map(|m| m.text().to_string()).collect()),
23 | feature => feature,
24 | })
25 | .collect::>()
26 | .first()
27 | .cloned()
28 | .unwrap_or(Features::Unknown),
29 | })
30 | }
31 | }
32 |
33 | impl From for Packet {
34 | fn from(s: StreamFeatures) -> Self {
35 | NonStanza::StreamFeatures(s).into()
36 | }
37 | }
38 |
39 | impl ToXmlElement for StreamFeatures {
40 | type Error = std::io::Error;
41 | fn to_element(&self) -> Result {
42 | let mut root = Element::new("stream:features");
43 |
44 | match self.features {
45 | Features::StartTls => {
46 | let starttls = root.append_new_child((ns::TLS, "starttls"));
47 | starttls.append_new_child((ns::TLS, "required"));
48 | }
49 | Features::Bind => {
50 | root.append_new_child((ns::BIND, "bind"));
51 | }
52 | Features::Mechanisms(ref mechanisms) => {
53 | let node = root.append_new_child((ns::SASL, "mechanisms"));
54 | mechanisms.iter().for_each(|mech| {
55 | node.append_new_child((ns::SASL, "mechanism")).set_text(mech);
56 | });
57 | }
58 | Features::Unknown => {}
59 | }
60 |
61 | Ok(root)
62 | }
63 | }
64 |
65 | #[derive(Debug, Clone, PartialEq)]
66 | pub enum Features {
67 | StartTls,
68 | Bind,
69 | Mechanisms(Vec),
70 | Unknown,
71 | }
72 |
73 | impl Default for Features {
74 | fn default() -> Self {
75 | Self::Unknown
76 | }
77 | }
78 |
79 | impl From<&str> for Features {
80 | fn from(e: &str) -> Self {
81 | match e {
82 | "starttls" => Features::StartTls,
83 | "bind" => Features::Bind,
84 | "mechanisms" => Features::Mechanisms(vec![]),
85 | _ => Features::Unknown,
86 | }
87 | }
88 | }
89 |
90 | #[cfg(test)]
91 | mod tests {
92 | use super::*;
93 |
94 | use circular::Buffer;
95 | use std::io::Write;
96 |
97 | use xmpp_xml::{
98 | xml::{reader::XmlEvent, ParserConfig},
99 | WriteOptions,
100 | };
101 |
102 | const INITIAL_STREAM: &'static str = "";
103 | const EXPECTED_STARTTLS: &'static str = r#""#;
104 | const EXPECTED_BIND: &'static str = r#""#;
105 | const EXPECTED_MECHANISMS: &'static str = r#"EXTERNALSCRAM-SHA-1-PLUSSCRAM-SHA-1PLAIN"#;
106 |
107 | #[test]
108 | fn parse_starttls() {
109 | let stream_feature = StreamFeaturesBuilder::default().features(Features::StartTls).build();
110 |
111 | let mut output: Vec = Vec::new();
112 | let _ = stream_feature
113 | .unwrap()
114 | .to_element()
115 | .unwrap()
116 | .to_writer_with_options(&mut output, WriteOptions::new().set_xml_prolog(None));
117 |
118 | let generated = String::from_utf8(output).unwrap();
119 |
120 | assert!(EXPECTED_STARTTLS == generated);
121 |
122 | let mut cfg = ParserConfig::new().whitespace_to_characters(true);
123 | cfg.ignore_end_of_stream = true;
124 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096));
125 |
126 | reader.source_mut().write(INITIAL_STREAM.as_bytes()).unwrap();
127 | reader.source_mut().write(EXPECTED_STARTTLS.as_bytes()).unwrap();
128 |
129 | let _ = reader.next().unwrap();
130 | let _ = reader.next().unwrap();
131 | let x = reader.next().unwrap();
132 |
133 | assert!(matches!(x, XmlEvent::StartElement { .. }));
134 |
135 | if let XmlEvent::StartElement { name, attributes, namespace } = x {
136 | let packet = Packet::parse(&mut reader, name, namespace, attributes);
137 | assert!(
138 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::StreamFeatures(StreamFeatures { features: Features::StartTls }))),
139 | "Packet wasn't an StreamFeatures::StartTls, it was: {:?}",
140 | packet
141 | );
142 | }
143 | }
144 |
145 | #[test]
146 | fn parse_bind() {
147 | let stream_feature = StreamFeaturesBuilder::default().features(Features::Bind).build();
148 |
149 | let mut output: Vec = Vec::new();
150 | let _ = stream_feature
151 | .unwrap()
152 | .to_element()
153 | .unwrap()
154 | .to_writer_with_options(&mut output, WriteOptions::new().set_xml_prolog(None));
155 |
156 | let generated = String::from_utf8(output).unwrap();
157 |
158 | assert!(EXPECTED_BIND == generated);
159 |
160 | let mut cfg = ParserConfig::new().whitespace_to_characters(true);
161 | cfg.ignore_end_of_stream = true;
162 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096));
163 |
164 | reader.source_mut().write(INITIAL_STREAM.as_bytes()).unwrap();
165 | reader.source_mut().write(EXPECTED_BIND.as_bytes()).unwrap();
166 |
167 | let _ = reader.next().unwrap();
168 | let _ = reader.next().unwrap();
169 | let x = reader.next().unwrap();
170 |
171 | assert!(matches!(x, XmlEvent::StartElement { .. }));
172 |
173 | if let XmlEvent::StartElement { name, attributes, namespace } = x {
174 | let packet = Packet::parse(&mut reader, name, namespace, attributes);
175 | assert!(
176 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::StreamFeatures(StreamFeatures { features: Features::Bind }))),
177 | "Packet wasn't an StreamFeatures::Bind, it was: {:?}",
178 | packet
179 | );
180 | }
181 | }
182 |
183 | #[test]
184 | fn parse_mechanisms() {
185 | let mechs: Vec = "EXTERNAL SCRAM-SHA-1-PLUS SCRAM-SHA-1 PLAIN".split_whitespace().map(String::from).collect();
186 | let stream_feature = StreamFeaturesBuilder::default().features(Features::Mechanisms(mechs.clone())).build();
187 |
188 | let mut output: Vec = Vec::new();
189 | let _ = stream_feature
190 | .unwrap()
191 | .to_element()
192 | .unwrap()
193 | .to_writer_with_options(&mut output, WriteOptions::new().set_xml_prolog(None));
194 |
195 | let generated = String::from_utf8(output).unwrap();
196 |
197 | assert!(EXPECTED_MECHANISMS == generated);
198 |
199 | let mut cfg = ParserConfig::new().whitespace_to_characters(true);
200 | cfg.ignore_end_of_stream = true;
201 | let mut reader = cfg.create_reader(Buffer::with_capacity(4096));
202 |
203 | reader.source_mut().write(INITIAL_STREAM.as_bytes()).unwrap();
204 | reader.source_mut().write(EXPECTED_MECHANISMS.as_bytes()).unwrap();
205 |
206 | let _ = reader.next().unwrap();
207 | let _ = reader.next().unwrap();
208 | let x = reader.next().unwrap();
209 |
210 | assert!(matches!(x, XmlEvent::StartElement { .. }));
211 |
212 | if let XmlEvent::StartElement { name, attributes, namespace } = x {
213 | let packet = Packet::parse(&mut reader, name, namespace, attributes);
214 | assert!(
215 | matches!(packet, Ok(Packet::NonStanza(ref stanza)) if matches!(**stanza, NonStanza::StreamFeatures(StreamFeatures { features: Features::Mechanisms(ref m) }) if m == &mechs)),
216 | "Packet wasn't an StreamFeatures::Mechanisms, it was: {:?}",
217 | packet
218 | );
219 | }
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/proto/src/ns.rs:
--------------------------------------------------------------------------------
1 | pub const XML_URI: &str = "http://www.w3.org/XML/1998/namespace";
2 | pub const CLIENT: &str = "jabber:client";
3 | pub const SERVER: &str = "jabber:server";
4 | pub const STREAM: &str = "http://etherx.jabber.org/streams";
5 | pub const TLS: &str = "urn:ietf:params:xml:ns:xmpp-tls";
6 | pub const SASL: &str = "urn:ietf:params:xml:ns:xmpp-sasl";
7 | pub const BIND: &str = "urn:ietf:params:xml:ns:xmpp-bind";
8 | pub const SESSION: &str = "urn:ietf:params:xml:ns:xmpp-session";
9 | pub const STANZAS: &str = "urn:ietf:params:xml:ns:xmpp-stanzas";
10 | pub const PING: &str = "urn:xmpp:ping";
11 |
--------------------------------------------------------------------------------
/proto/src/packet.rs:
--------------------------------------------------------------------------------
1 | use crate::{non_stanza::StartTls, ns, stanza::GenericIq, Auth, OpenStream, StreamErrorKind, StreamFeatures, ToXmlElement};
2 | use crate::{FromXmlElement, ProceedTls};
3 | use crate::{NonStanza, NonStanzaTrait, Stanza};
4 |
5 | use actix::Message;
6 | use circular::Buffer;
7 | use log::error;
8 | use std::{
9 | convert::{TryFrom, TryInto},
10 | io::{ErrorKind, Write},
11 | };
12 | use xmpp_xml::WriteOptions;
13 | use xmpp_xml::{
14 | xml::{attribute::OwnedAttribute, name::OwnedName, namespace::Namespace, EventReader},
15 | Element,
16 | };
17 |
18 | #[derive(Debug, Message, Clone)]
19 | #[rtype(result = "Result, ()>")]
20 | pub enum Packet {
21 | /// Represent a packet which is an XML Stream
22 | NonStanza(Box),
23 | /// Represent a packet which isn't an XML Stanza
24 | Stanza(Box),
25 | InvalidPacket(Box),
26 | }
27 |
28 | impl From for Packet
29 | where
30 | T: NonStanzaTrait,
31 | {
32 | fn from(s: T) -> Self {
33 | s.into()
34 | }
35 | }
36 |
37 | impl From for Packet {
38 | fn from(s: NonStanza) -> Self {
39 | Packet::NonStanza(Box::new(s))
40 | }
41 | }
42 |
43 | impl From for Packet {
44 | fn from(s: Stanza) -> Self {
45 | Packet::Stanza(Box::new(s))
46 | }
47 | }
48 |
49 | #[derive(Debug)]
50 | pub enum PacketParsingError {
51 | Final,
52 | Xml(xmpp_xml::Error),
53 | Io,
54 | Unknown,
55 | InvalidNamespace,
56 | }
57 |
58 | impl From for PacketParsingError {
59 | fn from(e: xmpp_xml::Error) -> Self {
60 | PacketParsingError::Xml(e)
61 | }
62 | }
63 |
64 | impl From for PacketParsingError {
65 | fn from(_: std::io::Error) -> Self {
66 | PacketParsingError::Io
67 | }
68 | }
69 |
70 | impl TryFrom for Packet {
71 | type Error = PacketParsingError;
72 |
73 | fn try_from(element: Element) -> Result {
74 | match (element.tag().ns(), element.tag().name()) {
75 | (Some(ns::STREAM), "features") => Ok(StreamFeatures::from_element(&element)?.into()),
76 | (Some(ns::SASL), "auth") => Ok(Auth::from_element(&element)?.into()),
77 | (Some(ns::TLS), "starttls") => Ok(StartTls::from_element(&element)?.into()),
78 | (Some(ns::TLS), "proceed") => Ok(ProceedTls::from_element(&element)?.into()),
79 | (Some(ns::CLIENT), "iq") => Ok(GenericIq::from_element(&element)?.into()),
80 | (Some(ns::CLIENT), "message") => Ok(Stanza::Message(element).into()),
81 | (Some(ns::CLIENT), "presence") => Ok(Stanza::Presence(element).into()),
82 | (_, name) if ["features", "auth", "starttls", "proceed", "iq", "message", "presence"].contains(&name) => Ok(Packet::InvalidPacket(Box::new(StreamErrorKind::BadNamespacePrefix))),
83 | e => {
84 | error!("{:?}", e);
85 | Err(PacketParsingError::Unknown)
86 | }
87 | }
88 | }
89 | }
90 |
91 | impl Packet {
92 | pub fn write_to_stream(&self, mut stream: W) -> Result<(), std::io::Error> {
93 | match self {
94 | Packet::NonStanza(s) if matches!(**s, NonStanza::CloseStream(_)) => {
95 | stream.write_all(b"")?;
96 | Ok(())
97 | }
98 | Packet::NonStanza(s) if matches!(**s, NonStanza::OpenStream(_)) => Ok(s.to_element()?.to_writer(stream)?),
99 | Packet::NonStanza(s) => Ok(s.to_element()?.to_writer_with_options(stream, WriteOptions::new().set_xml_prolog(None))?),
100 | Packet::Stanza(s) => Ok(s.to_element()?.to_writer_with_options(stream, WriteOptions::new().set_xml_prolog(None))?),
101 | // TODO: should we fail the write or ignore it ?
102 | Packet::InvalidPacket(_) => Err(std::io::Error::new(ErrorKind::InvalidData, "Invalid packet shouldn't be written back")),
103 | }
104 | }
105 |
106 | pub fn parse(buffer: &mut EventReader, name: OwnedName, namespace: Namespace, attributes: Vec) -> Result {
107 | match (name.namespace_ref(), name.prefix_ref(), name.local_name.as_ref()) {
108 | // open stream isn't an element
109 | (Some(ns::STREAM), Some("stream"), "stream") => Ok(OpenStream::from_start_element(attributes)?.into()),
110 | (_, Some("stream"), "stream") => Ok(Packet::InvalidPacket(Box::new(StreamErrorKind::InvalidNamespace))),
111 | (_, _, "stream") => Ok(Packet::InvalidPacket(Box::new(StreamErrorKind::BadNamespacePrefix))),
112 | _ => Element::from_start_element(name, attributes, namespace, None, buffer)?.try_into(),
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/proto/src/stanza.rs:
--------------------------------------------------------------------------------
1 | use xmpp_xml::Element;
2 |
3 | use crate::ToXmlElement;
4 |
5 | pub use self::generic_iq::*;
6 |
7 | mod generic_iq;
8 |
9 | /// Define a sub part of a Packet, a Stanza is the representation of an Xmpp Stanza which can be a
10 | /// Presence, an IQ or a Message.
11 | #[derive(Debug, Clone)]
12 | pub enum Stanza {
13 | IQ(GenericIq),
14 | Message(Element),
15 | Presence(Element),
16 | }
17 |
18 | impl ToXmlElement for Stanza {
19 | type Error = std::io::Error;
20 |
21 | fn to_element(&self) -> Result {
22 | match self {
23 | Stanza::IQ(iq) => iq.to_element(),
24 | Stanza::Message(s) => s.to_element(),
25 | Stanza::Presence(s) => s.to_element(),
26 | }
27 | }
28 | }
29 |
30 | #[cfg(test)]
31 | mod tests {
32 | use uuid::Uuid;
33 |
34 | use crate::FromXmlElement;
35 |
36 | use super::*;
37 |
38 | #[test]
39 | fn to_element() {
40 | let mut element = Element::new("iq");
41 | element.set_attr("id", GenericIq::unique_id()).set_attr("type", "get");
42 | element.append_child(Element::new("test"));
43 | let iq = GenericIq::from_element(&element).unwrap();
44 |
45 | assert!(matches!(Stanza::IQ(iq).to_element(), Ok(_)));
46 | assert!(matches!(Stanza::Message(Element::new("message")).to_element(), Ok(_)));
47 | assert!(matches!(Stanza::Presence(Element::new("presence")).to_element(), Ok(_)));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/proto/src/stanza/generic_iq.rs:
--------------------------------------------------------------------------------
1 | use super::Stanza;
2 | use crate::{FromXmlElement, Jid, Packet, ToXmlElement};
3 |
4 | use std::fmt;
5 | use std::io;
6 | use std::str::FromStr;
7 | use uuid::Uuid;
8 | use xmpp_xml::Element;
9 |
10 | #[derive(Debug, Clone)]
11 | pub struct GenericIq {
12 | id: String,
13 | iq_type: IqType,
14 | to: Option,
15 | from: Option,
16 | element: Option,
17 | // error: Option,
18 | }
19 |
20 | impl From for Packet {
21 | fn from(s: GenericIq) -> Self {
22 | Stanza::IQ(s).into()
23 | }
24 | }
25 | impl Default for GenericIq {
26 | fn default() -> Self {
27 | Self::new("", IqType::Get)
28 | }
29 | }
30 |
31 | #[derive(Debug, Copy, Clone, PartialEq)]
32 | pub enum IqType {
33 | Get,
34 | Set,
35 | Result,
36 | Error,
37 | }
38 |
39 | impl From for String {
40 | fn from(iq_type: IqType) -> String {
41 | match iq_type {
42 | IqType::Get => "get".to_string(),
43 | IqType::Set => "set".to_string(),
44 | IqType::Result => "result".to_string(),
45 | IqType::Error => "error".to_string(),
46 | }
47 | }
48 | }
49 |
50 | impl GenericIq {
51 | pub fn new(id: &T, iq_type: IqType) -> GenericIq {
52 | GenericIq {
53 | id: id.to_string(),
54 | iq_type,
55 | to: None,
56 | from: None,
57 | element: None,
58 | }
59 | }
60 |
61 | pub fn unique_id() -> String {
62 | Uuid::new_v4().to_string()
63 | }
64 |
65 | pub fn get_element(&self) -> Option<&Element> {
66 | self.element.as_ref()
67 | }
68 |
69 | pub fn set_type(&mut self, iq_type: IqType) -> &mut Self {
70 | self.iq_type = iq_type;
71 | self
72 | }
73 |
74 | pub fn get_type(&self) -> IqType {
75 | self.iq_type
76 | }
77 |
78 | pub fn get_id(&self) -> &str {
79 | self.id.as_ref()
80 | }
81 |
82 | pub fn set_id(&mut self, id: &T) -> &mut Self {
83 | self.id = id.to_string();
84 | self
85 | }
86 |
87 | pub fn set_to(&mut self, jid: Option) -> &mut Self {
88 | self.to = jid;
89 |
90 | self
91 | }
92 |
93 | pub fn get_to(&self) -> Option<&Jid> {
94 | self.to.as_ref()
95 | }
96 |
97 | pub fn set_from(&mut self, jid: Option) -> &mut Self {
98 | self.from = jid;
99 |
100 | self
101 | }
102 |
103 | pub fn get_from(&self) -> Option<&Jid> {
104 | self.from.as_ref()
105 | }
106 | }
107 | impl ToXmlElement for GenericIq {
108 | type Error = io::Error;
109 |
110 | fn to_element(&self) -> Result {
111 | Ok(self.element.clone().unwrap())
112 | }
113 | }
114 |
115 | impl FromXmlElement for GenericIq {
116 | type Error = io::Error;
117 | fn from_element(e: &Element) -> Result {
118 | let id = match e.get_attr("id") {
119 | Some(id) => id.to_string(),
120 | None => return Err(io::Error::new(io::ErrorKind::InvalidInput, "ID is required")),
121 | };
122 |
123 | let iq_type = match e.get_attr("type") {
124 | Some(t) => match IqType::from_str(t) {
125 | Ok(t) => t,
126 | Err(e) => return Err(e),
127 | },
128 | None => return Err(io::Error::new(io::ErrorKind::InvalidInput, "TYPE is required")),
129 | };
130 |
131 | // Validation types
132 | match iq_type {
133 | IqType::Result => {
134 | if e.child_count() > 1 {
135 | return Err(io::Error::new(io::ErrorKind::InvalidInput, "An IQ stanza of type \"result\" MUST include zero or one child elements."));
136 | }
137 | }
138 |
139 | // An error stanza MUST contain an child element
140 | IqType::Error => {
141 | if e.find("{jabber:client}error").is_none() {
142 | return Err(io::Error::new(
143 | io::ErrorKind::InvalidInput,
144 | "An IQ stanza of type \"error\" SHOULD include the child element contained in the associated \"get\" or \"set\" and MUST include an child",
145 | ));
146 | }
147 | }
148 | IqType::Set | IqType::Get => {
149 | if e.child_count() != 1 {
150 | // https://xmpp.org/rfcs/rfc3920.html#stanzas
151 | return Err(io::Error::new(io::ErrorKind::InvalidInput, "IqType Get/Set MUST contain one and only one child"));
152 | }
153 | }
154 | }
155 |
156 | // An child MUST NOT be included if the 'type' attribute has a value other than "error"
157 | match iq_type {
158 | IqType::Set | IqType::Get | IqType::Result => {
159 | if e.find("error").is_some() {
160 | return Err(io::Error::new(
161 | io::ErrorKind::InvalidInput,
162 | "An child MUST NOT be included if the 'type' attribute has a value other than \"error\"",
163 | ));
164 | }
165 | }
166 | _ => {}
167 | }
168 |
169 | let to = {
170 | if let Some(t) = e.get_attr("to") {
171 | match Jid::from_str(t) {
172 | Ok(j) => Some(j),
173 | Err(_) => None,
174 | }
175 | } else {
176 | None
177 | }
178 | };
179 |
180 | let from = {
181 | if let Some(f) = e.get_attr("from") {
182 | match Jid::from_str(f) {
183 | Ok(j) => Some(j),
184 | Err(_) => None,
185 | }
186 | } else {
187 | None
188 | }
189 | };
190 |
191 | // let error = {
192 | // if iq_type == IqType::Error {
193 | // match StanzaError::from_element(e.find("error").unwrap().to_owned()) {
194 | // Ok(error_element) => Some(error_element),
195 | // Err(_) => return Err(io::Error::new(io::ErrorKind::InvalidInput, "Unparsable error element")),
196 | // }
197 | // } else {
198 | // None
199 | // }
200 | // };
201 |
202 | Ok(GenericIq {
203 | id,
204 | iq_type,
205 | to,
206 | from,
207 | element: Some(e.clone()),
208 | // error,
209 | })
210 | }
211 | }
212 |
213 | impl FromStr for IqType {
214 | type Err = io::Error;
215 | fn from_str(s: &str) -> Result {
216 | match s.to_lowercase().as_ref() {
217 | "get" => Ok(IqType::Get),
218 | "set" => Ok(IqType::Set),
219 | "result" => Ok(IqType::Result),
220 | "error" => Ok(IqType::Error),
221 | _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "Unsupported IqType")),
222 | }
223 | }
224 | }
225 | impl fmt::Display for IqType {
226 | // This trait requires `fmt` with this exact signature.
227 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
228 | // Write strictly the first element into the supplied output
229 | // stream: `f`. Returns `fmt::Result` which indicates whether the
230 | // operation succeeded or failed. Note that `write!` uses syntax which
231 | // is very similar to `println!`.
232 | write!(
233 | f,
234 | "{}",
235 | match *self {
236 | IqType::Get => "get".to_string(),
237 | IqType::Set => "set".to_string(),
238 | IqType::Result => "result".to_string(),
239 | IqType::Error => "error".to_string(),
240 | }
241 | )
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/proto/src/stanza/message.rs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Freyskeyd/xmpp-rs/3e9545c134d33059099c2f079c459047fdd01918/proto/src/stanza/message.rs
--------------------------------------------------------------------------------
/proto/src/stanza/presence.rs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Freyskeyd/xmpp-rs/3e9545c134d33059099c2f079c459047fdd01918/proto/src/stanza/presence.rs
--------------------------------------------------------------------------------
/server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xmpp-server"
3 | version = "0.1.3"
4 | license = "MPL-2.0"
5 | documentation = "https://docs.rs/xmpp-server"
6 | authors = ["Freyskeyd "]
7 |
8 | edition = "2018"
9 | description = """
10 | xmpp-rs is an implementation of the Extensible Messaging and Presence Protocol (XMPP).
11 |
12 | Based on tokio-rs and futures-rs. It's goal is to be fully tested and usable.
13 | """
14 | repository = "https://github.com/Freyskeyd/xmpp-rs"
15 | keywords = ["xmpp", "tokio", "jabber", "IM", "instant-messaging"]
16 | categories = ["network-programming"]
17 |
18 | [dependencies]
19 | actix = { version = "0.12.0" }
20 | actix-codec = "0.4.0-beta.1"
21 | actix-http = { version = "3.0.0-beta.4", default-features = false }
22 | actix-rt = { version = "2.2", default-features = false }
23 | actix-service = "2.0.0-beta.5"
24 | actix-tls = "3.0.0-beta.5"
25 |
26 | # actix = "0.11.0"
27 | # actix-tls = "*"
28 | actix-web = "4.0.0-beta.8"
29 | actix-web-actors = "4.0.0-beta.6"
30 | # actix-web-actors = { git = "https://github.com/actix/actix-web" }
31 | # actix-net = "*"
32 | # actix-server = "*"
33 | # actix-service = "*"
34 | # actix-codec = "*"
35 | byteorder = "*"
36 | futures = "*"
37 | futures-util = "*"
38 | log = "*"
39 | bytes = "1"
40 | # byteorder = "1.3"
41 | serde = "*"
42 | serde_json = "*"
43 | tokio = { version = "1", features = ["full"] }
44 | # # tokio = { version = "0.2" }
45 | tokio-util = { version = "0.6", features = ["codec"] }
46 | tokio-rustls = "*"
47 | uuid = { version = "0.4", features = ["v4"] }
48 |
49 | circular = "*"
50 | xml-rs = "*"
51 | xmpp-xml = { path = "../xml" }
52 | xmpp-proto = { path = "../proto" }
53 | sasl = "*"
54 |
55 | derive_builder = "0.10.0"
56 |
57 | jid = "0"
58 | config = "0.11"
59 | async-trait ="*"
60 | lazy_static = "1"
61 | tower-service = "0.3.1"
62 | base64 = "0.13.0"
63 |
64 | [dev-dependencies]
65 | demonstrate = "0.4.5"
66 |
67 | [features]
68 | default = ["auth_method_memory"]
69 | auth_method_memory = []
70 | auth_method_sql = []
71 |
--------------------------------------------------------------------------------
/server/src/authentication.rs:
--------------------------------------------------------------------------------
1 | use crate::messages::AuthenticationRequest;
2 | use crate::CONFIG;
3 | use actix::prelude::*;
4 | use base64::decode;
5 | use jid::Jid;
6 | use log::trace;
7 | use sasl::common::Identity;
8 | use sasl::common::Identity::Username;
9 | use sasl::secret;
10 | use sasl::server::mechanisms::Plain as ServerPlain;
11 | use sasl::server::Mechanism;
12 | use sasl::server::Response;
13 | use sasl::server::Validator;
14 | use sasl::server::ValidatorError;
15 | use std::collections::HashMap;
16 | use std::str::FromStr;
17 |
18 | type Vhost = String;
19 |
20 | pub struct AuthenticationManager {
21 | authenticators: HashMap>>,
22 | }
23 |
24 | impl Default for AuthenticationManager {
25 | fn default() -> Self {
26 | Self { authenticators: HashMap::new() }
27 | }
28 | }
29 | impl AuthenticationManager {
30 | pub(crate) fn register(mut self, authenticators: &HashMap>) -> Self {
31 | CONFIG.vhosts.iter().for_each(|(vhost, config)| {
32 | config.authenticators.iter().for_each(|authenticator| match authenticator.as_ref() {
33 | "memory" => {}
34 | custom => {
35 | if let Some(recipient) = authenticators.get(custom) {
36 | self.authenticators.entry(vhost.clone()).or_insert_with(Vec::new).push(recipient.clone());
37 | }
38 | }
39 | });
40 | });
41 |
42 | self
43 | }
44 | }
45 |
46 | impl Supervised for AuthenticationManager {}
47 | impl SystemService for AuthenticationManager {}
48 |
49 | impl Actor for AuthenticationManager {
50 | type Context = Context;
51 |
52 | fn started(&mut self, _ctx: &mut Self::Context) {
53 | trace!("AuthenticationManager started");
54 | }
55 | }
56 |
57 | impl Handler for AuthenticationManager {
58 | type Result = Result;
59 |
60 | fn handle(&mut self, msg: AuthenticationRequest, _ctx: &mut Self::Context) -> Self::Result {
61 | if let Some("PLAIN") = msg.packet.mechanism() {
62 | trace!("AUTHENT WITH PLAIN");
63 | let challenge = decode(msg.packet.challenge().as_ref().unwrap()).unwrap();
64 |
65 | let mut mech = ServerPlain::new(MyValidator);
66 | let username = match mech.respond(&challenge) {
67 | Ok(Response::Success(Username(username), _)) => username,
68 | _ => {
69 | return Err(());
70 | }
71 | };
72 |
73 | Ok(Jid::from_str(&format!("{}@localhost", username)).unwrap())
74 | } else {
75 | Err(())
76 | }
77 | }
78 | }
79 |
80 | const USERNAME: &str = "local";
81 | const PASSWORD: &str = "admin";
82 |
83 | struct MyValidator;
84 | impl Validator for MyValidator {
85 | fn validate(&self, identity: &Identity, value: &secret::Plain) -> Result<(), ValidatorError> {
86 | let &secret::Plain(ref password) = value;
87 | if identity != &Identity::Username(USERNAME.to_owned()) || password != PASSWORD {
88 | Err(ValidatorError::AuthenticationFailed)
89 | } else {
90 | Ok(())
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/server/src/config.rs:
--------------------------------------------------------------------------------
1 | use config::{Config, ConfigError, File};
2 | use serde::Deserialize;
3 | use std::collections::HashMap;
4 |
5 | #[derive(Debug, Deserialize)]
6 | pub(crate) struct Settings {
7 | pub(crate) listeners: Vec,
8 | pub(crate) authenticators: Vec,
9 | pub(crate) vhosts: HashMap,
10 | }
11 |
12 | //TODO: Rethink how to access per vhost config and config inheritance
13 | impl Settings {
14 | pub(crate) fn new() -> Result {
15 | let mut s = Config::default();
16 |
17 | s.merge(File::with_name("config/default"))?;
18 |
19 | let default_authenticators = s.get::>("authenticators")?;
20 |
21 | let vhosts = s.get_table("vhosts")?;
22 |
23 | vhosts.iter().for_each(|(vhost, _)| {
24 | let target = format!("vhosts.{}.authenticators", vhost);
25 |
26 | let _ = s.set_default(&target, default_authenticators.clone());
27 | });
28 |
29 | println!("vhosts : {:?}", s.get::>("vhosts"));
30 | s.try_into()
31 | }
32 | }
33 |
34 | #[derive(Debug, Deserialize)]
35 | pub(crate) enum ListenerConfig {
36 | Tcp(TcpListenerConfig),
37 | // Ws,
38 | // Udp
39 | }
40 |
41 | #[derive(Debug, Deserialize)]
42 | pub(crate) struct TcpListenerConfig {
43 | pub(crate) port: i32,
44 | pub(crate) ip: String,
45 | pub(crate) starttls: StartTLSConfig,
46 | }
47 |
48 | #[derive(Debug, Deserialize)]
49 | pub(crate) struct Cert {
50 | pub(crate) cert_path: String,
51 | pub(crate) key_path: String,
52 | }
53 |
54 | #[derive(Debug, Deserialize)]
55 | pub(crate) enum StartTLSConfig {
56 | Unavailable,
57 | Available(Cert),
58 | Required(Cert),
59 | }
60 |
61 | impl std::fmt::Display for StartTLSConfig {
62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 | match self {
64 | StartTLSConfig::Unavailable => write!(f, "starttls unavailable"),
65 | StartTLSConfig::Available(_) => write!(f, "starttls available"),
66 | StartTLSConfig::Required(_) => write!(f, "starttls required"),
67 | }
68 | }
69 | }
70 |
71 | #[derive(Debug, Deserialize)]
72 | pub(crate) struct VhostConfig {
73 | pub(crate) authenticators: Vec,
74 | }
75 |
--------------------------------------------------------------------------------
/server/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate lazy_static;
3 |
4 | use crate::config::Settings;
5 | use crate::{authentication::AuthenticationManager, sessions::manager::SessionManager};
6 | use actix::{Actor, Recipient};
7 | use log::info;
8 | use router::Router;
9 | use std::collections::HashMap;
10 |
11 | mod authentication;
12 | mod config;
13 | mod listeners;
14 | mod messages;
15 | mod packet;
16 | mod parser;
17 | mod router;
18 | mod sessions;
19 | #[cfg(test)]
20 | mod tests;
21 |
22 | pub use messages::AuthenticationRequest;
23 |
24 | lazy_static! {
25 | static ref CONFIG: Settings = Settings::new().unwrap();
26 | }
27 |
28 | pub trait Service {
29 | type Config;
30 |
31 | fn create_with_config(config: &Self::Config) -> Self;
32 | }
33 |
34 | pub struct Server {}
35 |
36 | impl Server {
37 | pub fn build() -> ServerBuilder {
38 | ServerBuilder::default()
39 | }
40 | }
41 |
42 | #[derive(Default)]
43 | pub struct ServerBuilder {
44 | cert: Option,
45 | keys: Option,
46 | authenticators: HashMap>,
47 | }
48 |
49 | impl ServerBuilder {
50 | pub fn cert>(mut self, cert: T) -> Self {
51 | self.cert = Some(cert.into());
52 |
53 | self
54 | }
55 |
56 | pub fn keys>(mut self, keys: T) -> Self {
57 | self.keys = Some(keys.into());
58 |
59 | self
60 | }
61 |
62 | pub fn add_authenticator(mut self, authenticator_name: &str, authenticator: Recipient) -> Self {
63 | self.authenticators.insert(authenticator_name.into(), authenticator);
64 | self
65 | }
66 |
67 | pub async fn launch(self) -> std::io::Result<()> {
68 | println!("CONFIG: {:?}", *CONFIG);
69 | SessionManager::new().start();
70 | AuthenticationManager::default().register(&self.authenticators).start();
71 | // Starting systemd
72 | // Starting hooks
73 | // Starting clustering
74 | // Starting translation
75 | // Starting access permissions
76 | // Starting ctl
77 | // Starting commands
78 | // Starting admin
79 | // Starting Router
80 | let router = Router::new().start();
81 | // Starting all listener (tcp, ws)
82 | for listener_cfg in CONFIG.listeners.iter() {
83 | match listener_cfg {
84 | config::ListenerConfig::Tcp(tcp_config) => {
85 | let _tcp_listener = crate::listeners::tcp::listener::TcpListener::start(tcp_config, router.clone());
86 | }
87 | }
88 | }
89 | // let _ws_listener = crate::listeners::ws::ws_listener();
90 | // Starting pkix
91 | // Starting ACL
92 | // Starting Shaper
93 | // Starting DB
94 | // Starting Backend
95 | // Starting Sql
96 | // Starting IQ
97 | // Starting router multicast
98 | // Starting local
99 | // Starting Session Manager
100 | // Starting s2s_in
101 | // Starting s2s_out
102 | // Starting s2s
103 | // Starting service
104 | // Starting captcha
105 | // Starting ext_mod
106 | // Starting acme
107 | // Starting auth
108 | // Starting oauth
109 |
110 | // Start API
111 |
112 | tokio::signal::ctrl_c().await.unwrap();
113 | info!("Ctrl-C received, shutting down");
114 |
115 | Ok(())
116 | }
117 | }
118 |
119 | pub trait Authenticator: actix::Actor {
120 | // type Future;
121 | // type Error;
122 |
123 | // fn poll_ready(&mut self, cx: Context<'_>) -> Poll>;
124 | // fn authenticate(&mut self, request: AuthenticationRequest) -> Self::Future;
125 | }
126 |
--------------------------------------------------------------------------------
/server/src/listeners.rs:
--------------------------------------------------------------------------------
1 | // TCP
2 | pub(crate) mod tcp;
3 | // WS
4 | pub(crate) mod ws;
5 |
6 | use actix_codec::AsyncRead;
7 | use tokio::io::AsyncWrite;
8 |
9 | pub trait XmppStream: AsyncRead + AsyncWrite + Unpin + Send {}
10 | pub struct XmppStreamHolder {
11 | inner: Box,
12 | }
13 |
--------------------------------------------------------------------------------
/server/src/listeners/tcp.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | listeners::{XmppStream, XmppStreamHolder},
3 | messages::{tcp::TcpOpenStream, SessionManagementPacketResult},
4 | packet::PacketHandler,
5 | parser::codec::XmppCodec,
6 | sessions::{
7 | state::{SessionState, StaticSessionState},
8 | unauthenticated::UnauthenticatedSession,
9 | },
10 | };
11 | use actix::prelude::*;
12 | use actix_codec::{Decoder, Encoder};
13 | use bytes::BytesMut;
14 | use log::{error, trace};
15 | use std::io;
16 | use tokio::io::{AsyncReadExt, AsyncWriteExt};
17 |
18 | pub(crate) mod listener;
19 | pub(crate) mod session;
20 |
21 | impl XmppStream for tokio::net::TcpStream {}
22 | impl XmppStream for tokio_rustls::server::TlsStream {}
23 |
24 | impl Handler for UnauthenticatedSession {
25 | type Result = ResponseFuture>;
26 |
27 | fn handle(&mut self, msg: TcpOpenStream, _ctx: &mut Self::Context) -> Self::Result {
28 | trace!("Opening TCP");
29 | let mut stream = msg.stream;
30 | let acceptor = msg.acceptor;
31 | let mut buf = BytesMut::with_capacity(4096);
32 | let mut codec = XmppCodec::new();
33 |
34 | let mut state = StaticSessionState::builder().build().unwrap();
35 | let fut = async move {
36 | loop {
37 | stream.readable().await.unwrap();
38 |
39 | match stream.read_buf(&mut buf).await {
40 | Ok(0) => {}
41 | Ok(_) => {
42 | while let Ok(Some(packet)) = codec.decode(&mut buf) {
43 | let r = Self::handle_packet(state.clone(), &packet).await;
44 | if let Ok(SessionManagementPacketResult { session_state, packets, .. }) = r {
45 | state.state = session_state.state;
46 |
47 | packets.into_iter().for_each(|packet| {
48 | if let Err(e) = codec.encode(packet, &mut buf) {
49 | error!("Error in proceed_packet: {:?}", e);
50 | }
51 | });
52 |
53 | if let Err(e) = stream.write_buf(&mut buf).await {
54 | error!("{:?}", e);
55 | }
56 |
57 | if let Err(e) = stream.flush().await {
58 | error!("{:?}", e);
59 | }
60 | }
61 | }
62 | match state.state {
63 | SessionState::Negociating => break,
64 | SessionState::Closing => {
65 | // TODO: remove unwrap
66 | let _ = stream.into_std().unwrap().shutdown(std::net::Shutdown::Both);
67 | return Err(());
68 | }
69 | _ => {}
70 | }
71 | }
72 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
73 | continue;
74 | }
75 | Err(e) => {
76 | error!("err: {:?}", e);
77 | break;
78 | }
79 | }
80 | }
81 |
82 | match acceptor {
83 | Some(acceptor) => {
84 | trace!("Session switching to TLS");
85 | let mut tls_stream = acceptor.accept(stream).await.unwrap();
86 | state.state = SessionState::Negociated;
87 | let mut buf = BytesMut::with_capacity(4096);
88 |
89 | loop {
90 | match tls_stream.read_buf(&mut buf).await {
91 | Ok(0) => {}
92 | Ok(_) => {
93 | while let Ok(Some(packet)) = codec.decode(&mut buf) {
94 | if let Ok(SessionManagementPacketResult { packets, session_state, .. }) = Self::handle_packet(state.clone(), &packet).await {
95 | state.state = session_state.state;
96 | state.jid = session_state.jid;
97 |
98 | packets.into_iter().for_each(|packet| {
99 | if let Err(e) = codec.encode(packet, &mut buf) {
100 | error!("Error in proceed_packet: {:?}", e);
101 | }
102 | });
103 |
104 | if let Err(e) = tls_stream.write_buf(&mut buf).await {
105 | error!("{:?}", e);
106 | }
107 |
108 | if let Err(e) = tls_stream.flush().await {
109 | error!("{:?}", e);
110 | }
111 | }
112 | }
113 |
114 | if state.state == SessionState::Authenticated {
115 | break;
116 | }
117 | if state.state == SessionState::Closing {
118 | // TODO: remove unwrap
119 | let (inner_stream, _) = tls_stream.into_inner();
120 | let _ = inner_stream.into_std().unwrap().shutdown(std::net::Shutdown::Both);
121 | return Err(());
122 | }
123 | }
124 | Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
125 | continue;
126 | }
127 | Err(e) => {
128 | error!("err: {:?}", e);
129 | break;
130 | }
131 | };
132 | }
133 |
134 | Ok((XmppStreamHolder { inner: Box::new(tls_stream) }, state))
135 | }
136 | None => Ok((XmppStreamHolder { inner: Box::new(stream) }, state)),
137 | }
138 | };
139 | Box::pin(fut)
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/server/src/listeners/tcp/listener.rs:
--------------------------------------------------------------------------------
1 | use super::session::TcpSession;
2 | use crate::sessions::state::StaticSessionState;
3 | use crate::{config::StartTLSConfig, listeners::tcp::TcpOpenStream};
4 | use crate::{config::TcpListenerConfig, messages::tcp::NewSession};
5 | use crate::{listeners::XmppStreamHolder, parser::codec::XmppCodec, sessions::unauthenticated::UnauthenticatedSession};
6 | use crate::{router::Router, sessions::Session};
7 | use actix::{prelude::*, spawn};
8 | use log::{error, info, trace};
9 | use std::{
10 | fs::File,
11 | io::{self, BufReader},
12 | net::SocketAddr,
13 | path::Path,
14 | str::FromStr,
15 | sync::Arc,
16 | };
17 | use tokio_rustls::{
18 | rustls::{
19 | internal::pemfile::{certs, pkcs8_private_keys},
20 | Certificate, NoClientAuth, PrivateKey, ServerConfig,
21 | },
22 | TlsAcceptor,
23 | };
24 | use tokio_util::codec::FramedRead;
25 |
26 | pub(crate) struct TcpListener {
27 | acceptor: Option,
28 | sessions: Vec>,
29 | }
30 |
31 | impl TcpListener {
32 | pub(crate) fn new(acceptor: Option) -> Self {
33 | Self { acceptor, sessions: Vec::new() }
34 | }
35 |
36 | pub(crate) fn start(config: &TcpListenerConfig, router: Addr) -> Result, ()> {
37 | let ip = format!("{}:{}", config.ip, config.port);
38 | let socket_addr = SocketAddr::from_str(&ip).unwrap();
39 |
40 | let acceptor = match config.starttls {
41 | StartTLSConfig::Unavailable => None,
42 | StartTLSConfig::Required(ref cert_cfg) | StartTLSConfig::Available(ref cert_cfg) => {
43 | let cert = Path::new(&cert_cfg.cert_path);
44 | let keys = Path::new(&cert_cfg.key_path);
45 |
46 | let certs = load_certs(cert).unwrap();
47 | let mut keys = load_keys(keys).unwrap();
48 |
49 | let mut config = ServerConfig::new(NoClientAuth::new());
50 | config.set_single_cert(certs, keys.remove(0)).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err)).unwrap();
51 | let acceptor = TlsAcceptor::from(Arc::new(config));
52 |
53 | Some(acceptor)
54 | }
55 | };
56 |
57 | let addr = Self::create(|_ctx| Self::new(acceptor));
58 | let tcp_listener = addr.clone();
59 |
60 | trace!("Starting new TCP listener on {} with {}", ip, config.starttls);
61 | spawn(async move {
62 | // Openning TCP to prepare for STARTLS
63 | let listener = tokio::net::TcpListener::bind(&socket_addr).await.unwrap();
64 |
65 | while let Ok((stream, socket_addr)) = listener.accept().await {
66 | tcp_listener.do_send(NewSession(stream, socket_addr, router.clone()));
67 | }
68 | });
69 |
70 | Ok(addr)
71 | }
72 | }
73 |
74 | impl Actor for TcpListener {
75 | type Context = Context;
76 |
77 | fn started(&mut self, _ctx: &mut Self::Context) {}
78 | }
79 |
80 | impl Handler for TcpListener {
81 | type Result = ResponseActFuture;
82 | fn handle(&mut self, msg: NewSession, _ctx: &mut Self::Context) -> Self::Result {
83 | info!("New TCP Session asked");
84 |
85 | let router = msg.2.clone();
86 | let acceptor = self.acceptor.clone();
87 | let session = UnauthenticatedSession::default().start();
88 |
89 | Box::pin(
90 | async move { session.send(TcpOpenStream { stream: msg.0, acceptor }).await.unwrap().map_err(|_| ()) }
91 | .into_actor(self)
92 | .map(|res: Result<(XmppStreamHolder, StaticSessionState), ()>, act: &mut TcpListener, _ctx| match res {
93 | Ok((stream, state)) => {
94 | trace!("Session succeed");
95 | let session = TcpSession::create(|ctx| {
96 | let (r, w) = tokio::io::split(stream.inner);
97 |
98 | let session = Session::create(|session_ctx| Session::new(state.clone(), session_ctx.address(), ctx.address().recipient()));
99 | TcpSession::add_stream(FramedRead::new(r, XmppCodec::new()), ctx);
100 | TcpSession::new(state, 0, actix::io::FramedWrite::new(Box::pin(w), XmppCodec::new(), ctx), session)
101 | });
102 |
103 | act.sessions.push(session)
104 | }
105 |
106 | Err(e) => {
107 | error!("Session failed {:?}", e);
108 | }
109 | })
110 | .map(|_, _, _| {
111 | trace!("Session killed");
112 | }),
113 | )
114 | }
115 | }
116 |
117 | // Move to utils?
118 | fn load_certs(path: &Path) -> io::Result> {
119 | certs(&mut BufReader::new(File::open(path)?)).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert"))
120 | }
121 |
122 | fn load_keys(path: &Path) -> io::Result> {
123 | let f = File::open(path)?;
124 | pkcs8_private_keys(&mut BufReader::new(f)).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key"))
125 | }
126 |
--------------------------------------------------------------------------------
/server/src/listeners/tcp/session.rs:
--------------------------------------------------------------------------------
1 | use crate::messages::system::{PacketIn, PacketsOut, SessionCommand, SessionCommandAction};
2 | use crate::sessions::state::{SessionState, StaticSessionState};
3 | use crate::{parser::codec::XmppCodec, sessions::Session};
4 | use actix::{io::FramedWrite, prelude::*};
5 | use log::trace;
6 | use std::time::Duration;
7 | use std::{io, pin::Pin};
8 | use tokio::io::AsyncWrite;
9 | use xmpp_proto::Packet;
10 |
11 | pub(crate) struct TcpSession {
12 | _id: usize,
13 | session: Addr,
14 | state: SessionState,
15 | #[allow(dead_code)]
16 | sink: FramedWrite>, XmppCodec>,
17 | timeout_handler: Option,
18 | }
19 |
20 | impl TcpSession {
21 | pub(crate) fn new(_state: StaticSessionState, id: usize, sink: FramedWrite>, XmppCodec>, session: Addr) -> Self {
22 | Self {
23 | _id: id,
24 | state: SessionState::Opening,
25 | sink,
26 | session,
27 | timeout_handler: None,
28 | }
29 | }
30 |
31 | fn reset_timeout(&mut self, ctx: &mut ::Context) {
32 | if let Some(handler) = self.timeout_handler {
33 | if ctx.cancel_future(handler) {
34 | trace!("Timeout handler resetted for session");
35 | } else {
36 | trace!("Unable to reset timeout handler for session");
37 | ctx.set_mailbox_capacity(0);
38 | self.state = SessionState::Closing;
39 | ctx.stop()
40 | }
41 | }
42 |
43 | self.timeout_handler = Some(ctx.run_later(Duration::from_secs(120), move |actor, ctx| {
44 | trace!("Duration ended");
45 | ctx.set_mailbox_capacity(0);
46 | actor.state = SessionState::Closing;
47 | let fut = actor.session.send(SessionCommand(SessionCommandAction::Kill)).into_actor(actor).map(|_, _, _| ());
48 | ctx.wait(fut);
49 | // ctx.stop();
50 | }));
51 | }
52 | }
53 |
54 | impl Actor for TcpSession {
55 | type Context = Context;
56 |
57 | fn started(&mut self, _ctx: &mut Self::Context) {
58 | trace!("Starting TcpSession");
59 | }
60 |
61 | fn stopping(&mut self, _ctx: &mut Self::Context) -> actix::Running {
62 | trace!("Stopping TcpSession");
63 | if self.state != SessionState::Closing {
64 | self.session.do_send(SessionCommand(SessionCommandAction::Kill));
65 | }
66 | actix::Running::Stop
67 | }
68 |
69 | fn stopped(&mut self, _ctx: &mut Self::Context) {
70 | trace!("TcpSession Stopped");
71 | }
72 | }
73 |
74 | impl actix::io::WriteHandler for TcpSession {}
75 |
76 | impl StreamHandler> for TcpSession {
77 | fn handle(&mut self, packet: Result, ctx: &mut Context) {
78 | self.reset_timeout(ctx);
79 |
80 | if let Ok(packet) = packet {
81 | let _ = self.session.try_send(PacketIn(packet));
82 | }
83 | }
84 | }
85 |
86 | impl Handler for TcpSession {
87 | type Result = Result<(), ()>;
88 |
89 | fn handle(&mut self, msg: PacketsOut, _ctx: &mut Self::Context) -> Self::Result {
90 | msg.0.into_iter().for_each(|packet| {
91 | self.sink.write(packet);
92 | });
93 |
94 | Ok(())
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/server/src/listeners/ws.rs:
--------------------------------------------------------------------------------
1 | use actix::{Actor, ActorContext, StreamHandler};
2 | use actix_web_actors::ws;
3 |
4 | /// websocket connection is long running connection, it easier
5 | /// to handle with an actor
6 | struct MyWebSocket {
7 | // hb: Instant,
8 | }
9 |
10 | impl Actor for MyWebSocket {
11 | type Context = ws::WebsocketContext;
12 |
13 | // fn started(&mut self, ctx: &mut Self::Context) {
14 | // self.hb(ctx);
15 | // }
16 | }
17 |
18 | /// Handler for `ws::Message`
19 | impl StreamHandler> for MyWebSocket {
20 | fn handle(&mut self, msg: Result, ctx: &mut Self::Context) {
21 | // process websocket messages
22 | match msg {
23 | // Ok(ws::Message::Ping(msg)) => {
24 | // self.hb = Instant::now();
25 | // ctx.pong(&msg);
26 | // }
27 | // Ok(ws::Message::Pong(_)) => {
28 | // self.hb = Instant::now();
29 | // }
30 | Ok(ws::Message::Text(text)) => ctx.text(text),
31 | Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
32 | Ok(ws::Message::Close(reason)) => {
33 | ctx.close(reason);
34 | ctx.stop();
35 | }
36 | _ => ctx.stop(),
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/server/src/messages.rs:
--------------------------------------------------------------------------------
1 | use crate::sessions::state::StaticSessionState;
2 | use actix::Message;
3 | use jid::Jid;
4 | use std::{error::Error, fmt};
5 | use xmpp_proto::{Auth, Packet, Stanza};
6 |
7 | pub(crate) mod system;
8 | pub(crate) mod tcp;
9 |
10 | #[derive(Debug, Message)]
11 | #[rtype(result = "Result<(), ()>")]
12 | pub(crate) struct SessionPacket {
13 | pub(crate) packet: Packet,
14 | }
15 |
16 | #[derive(Message, derive_builder::Builder, Debug, Clone)]
17 | #[builder(setter(into, strip_option))]
18 | #[rtype("()")]
19 | pub(crate) struct SessionManagementPacketResult {
20 | #[builder(default = "Vec::new()", setter(each = "packet", into = "true"))]
21 | pub(crate) packets: Vec,
22 | pub(crate) session_state: StaticSessionState,
23 | }
24 |
25 | #[derive(Debug)]
26 | pub(crate) enum SessionManagementPacketError {
27 | Unknown,
28 | }
29 |
30 | impl Error for SessionManagementPacketError {
31 | fn description(&self) -> &str {
32 | "error"
33 | }
34 | }
35 |
36 | impl fmt::Display for SessionManagementPacketError {
37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38 | write!(f, "error")
39 | }
40 | }
41 |
42 | impl From for SessionManagementPacketError {
43 | fn from(_: actix::MailboxError) -> Self {
44 | Self::Unknown
45 | }
46 | }
47 |
48 | impl From for SessionManagementPacketError {
49 | fn from(_: SessionManagementPacketResultBuilderError) -> Self {
50 | Self::Unknown
51 | }
52 | }
53 |
54 | #[derive(Message)]
55 | #[rtype("Result")]
56 | pub struct AuthenticationRequest {
57 | pub(crate) packet: Auth,
58 | }
59 |
60 | impl AuthenticationRequest {
61 | pub(crate) fn new(packet: Auth) -> Self {
62 | Self { packet }
63 | }
64 | }
65 |
66 | #[derive(Message)]
67 | #[rtype("()")]
68 | pub(crate) struct StanzaEnvelope {
69 | pub(crate) stanza: Stanza,
70 | pub(crate) from: StaticSessionState,
71 | }
72 |
--------------------------------------------------------------------------------
/server/src/messages/system.rs:
--------------------------------------------------------------------------------
1 | use actix::{Message, Recipient};
2 | use jid::Jid;
3 | use xmpp_proto::{Features, Packet};
4 |
5 | #[derive(Debug, Message)]
6 | #[rtype("Result<(),()>")]
7 | pub(crate) struct RegisterSession {
8 | pub(crate) jid: Jid,
9 | pub(crate) referer: Recipient,
10 | }
11 |
12 | #[derive(Debug, Message)]
13 | #[rtype("Result<(),()>")]
14 | pub(crate) struct UnregisterSession {
15 | pub(crate) jid: Jid,
16 | }
17 |
18 | #[derive(Debug, Message)]
19 | #[rtype("Result")]
20 | pub(crate) struct GetMechanisms(pub(crate) String);
21 |
22 | #[derive(Debug, Message)]
23 | #[rtype("Result<(),()>")]
24 | pub(crate) struct SessionCommand(pub(crate) SessionCommandAction);
25 |
26 | #[derive(Debug)]
27 | pub(crate) enum SessionCommandAction {
28 | Kill,
29 | SendPacket(Packet),
30 | }
31 |
32 | /// PacketOut represents a set of packets to be sent throught the sink.
33 | /// This kind of packet must be exchange between raw session and Session.
34 | #[derive(Debug, Message)]
35 | #[rtype("Result<(),()>")]
36 | pub(crate) struct PacketsOut(pub(crate) Vec);
37 |
38 | /// PacketIn represents a packet received in a stream.
39 | /// This kind of packet must be exchange between raw session and Session.
40 | #[derive(Debug, Message)]
41 | #[rtype("()")]
42 | pub(crate) struct PacketIn(pub(crate) Packet);
43 |
--------------------------------------------------------------------------------
/server/src/messages/tcp.rs:
--------------------------------------------------------------------------------
1 | use crate::listeners::XmppStreamHolder;
2 | use crate::router::Router;
3 | use crate::sessions::state::StaticSessionState;
4 | use actix::{prelude::*, Message};
5 | use tokio::net::TcpStream;
6 |
7 | #[derive(Message)]
8 | #[rtype("()")]
9 | pub(crate) struct NewSession(pub TcpStream, pub std::net::SocketAddr, pub Addr);
10 |
11 | #[derive(Message)]
12 | #[rtype("Result<(XmppStreamHolder, StaticSessionState), ()>")]
13 | pub(crate) struct TcpOpenStream {
14 | pub(crate) stream: TcpStream,
15 | pub(crate) acceptor: Option,
16 | }
17 |
--------------------------------------------------------------------------------
/server/src/packet.rs:
--------------------------------------------------------------------------------
1 | use xmpp_proto::{CloseStream, OpenStream, Packet, StreamError, StreamErrorKind};
2 |
3 | use crate::messages::SessionManagementPacketResultBuilder;
4 | use crate::messages::{SessionManagementPacketError, SessionManagementPacketResult};
5 | use crate::sessions::state::{SessionState, StaticSessionState};
6 |
7 | #[async_trait::async_trait]
8 | pub(crate) trait PacketHandler {
9 | type Result;
10 |
11 | async fn handle_packet(state: StaticSessionState, stanza: &Packet) -> Self::Result;
12 |
13 | fn handle_invalid_packet(
14 | session_state: StaticSessionState,
15 | invalid_packet: &StreamErrorKind,
16 | response: &mut SessionManagementPacketResultBuilder,
17 | ) -> Result {
18 | if matches!(*invalid_packet, StreamErrorKind::UnsupportedEncoding) && SessionState::Opening.eq(&session_state.state) {
19 | return Ok(response.session_state(SessionState::UnsupportedEncoding).build()?);
20 | }
21 |
22 | if SessionState::Opening == session_state.state || SessionState::UnsupportedEncoding == session_state.state {
23 | response.packet(OpenStream::default().into());
24 | }
25 |
26 | Self::close(response.packet(StreamError { kind: invalid_packet.clone() }.into()))
27 | }
28 |
29 | fn not_authorized_and_close(response: &mut SessionManagementPacketResultBuilder) -> Result {
30 | Self::close(response.packet(StreamError { kind: StreamErrorKind::NotAuthorized }.into()))
31 | }
32 |
33 | fn close(response: &mut SessionManagementPacketResultBuilder) -> Result {
34 | Ok(response.packet(CloseStream {}.into()).session_state(SessionState::Closing).build()?)
35 | }
36 | }
37 |
38 | #[async_trait::async_trait]
39 | pub(crate) trait StanzaHandler {
40 | async fn handle(state: StaticSessionState, stanza: &T) -> Result;
41 | }
42 |
--------------------------------------------------------------------------------
/server/src/parser.rs:
--------------------------------------------------------------------------------
1 | pub(crate) mod codec;
2 | pub(crate) mod sink;
3 |
--------------------------------------------------------------------------------
/server/src/parser/codec.rs:
--------------------------------------------------------------------------------
1 | use crate::parser::sink::PacketSink;
2 | use bytes::BufMut;
3 | use circular::Buffer;
4 | use log::trace;
5 | use std::io::{self, Write};
6 | use tokio_util::codec::{Decoder, Encoder};
7 | use xml::ParserConfig;
8 | use xmpp_proto::Packet;
9 |
10 | /// XmppCodec deals with incoming bytes. You can feed the parser with bytes and try to detect new
11 | /// event.
12 | pub struct XmppCodec {
13 | pub sink: PacketSink,
14 | }
15 |
16 | impl XmppCodec {
17 | pub fn new() -> Self {
18 | Self { sink: Self::new_sink() }
19 | }
20 |
21 | fn new_sink() -> PacketSink {
22 | let mut cfg = ParserConfig::new().whitespace_to_characters(true);
23 | cfg.ignore_end_of_stream = true;
24 | PacketSink {
25 | parser: cfg.create_reader(Buffer::with_capacity(4096)),
26 | }
27 | }
28 | }
29 |
30 | impl Decoder for XmppCodec {
31 | type Item = Packet;
32 |
33 | type Error = io::Error;
34 |
35 | fn decode(&mut self, buf: &mut bytes::BytesMut) -> Result