├── .gitignore ├── CHANGES.md ├── Cargo.toml ├── Jenkinsfile ├── LICENSE ├── Makefile ├── README.md ├── docs ├── Makefile ├── fastpacket.svg └── fastpacket.tex ├── examples ├── fastcall.rs └── fastserve.rs ├── rustfmt.toml ├── src ├── client.rs ├── lib.rs ├── protocol.rs └── server.rs └── tests └── client_server_test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /docs/*.aux 4 | /docs/*.dvi 5 | /docs/*.log 6 | Cargo.lock 7 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.3.0 4 | 5 | Change the package name to fast-rpc to avoid naming conflict when publishing to 6 | crates.io. 7 | 8 | ## 0.2.0 9 | 10 | Update fast protocol version to 2 to be compatible with node-fast version 3.0.0 11 | servers. 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fast-rpc" 3 | version = "0.3.0" 4 | authors = ["Kelly McLaughlin "] 5 | description = """ 6 | streaming JSON RPC over TCP 7 | """ 8 | repository = "https://github.com/joyent/rust-fast" 9 | keywords = ["protocol","joyent"] 10 | readme = "./README.md" 11 | license = "MPL-2.0" 12 | edition = "2018" 13 | 14 | [dependencies] 15 | bytes = "0.4.12" 16 | byteorder = "1.2.6" 17 | chrono = { version = "0.4.6", features = ["serde"] } 18 | crc16 = "0.4.0" 19 | num = "0.2" 20 | num-derive = "0.3" 21 | num-traits = "0.2" 22 | quickcheck = "0.8.0" 23 | rand = "0.6.4" 24 | serde = "1.0.84" 25 | serde_derive = "1.0.84" 26 | serde_json = "1.0.36" 27 | slog = "2.4.1" 28 | slog-stdlog = "3" 29 | tokio = "0.1.14" 30 | tokio-codec = "0.1.1" 31 | tokio-io = "0.1.11" 32 | tokio-uds = "0.2.5" 33 | 34 | [dev-dependencies] 35 | clap = "2.32" 36 | slog-term = "2.4.0" 37 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | @Library('jenkins-joylib@v1.0.4') _ 2 | 3 | pipeline { 4 | 5 | agent { 6 | label joyCommonLabels(image_ver: '19.4.0') 7 | } 8 | 9 | options { 10 | buildDiscarder(logRotator(numToKeepStr: '10')) 11 | timestamps() 12 | } 13 | 14 | stages { 15 | stage('check') { 16 | steps{ 17 | sh('make check') 18 | } 19 | } 20 | stage('test') { 21 | steps{ 22 | sh('make test') 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Joyent, Inc 3 | # 4 | 5 | # 6 | # Variables 7 | # 8 | 9 | NAME = rust-cueball 10 | CARGO ?= cargo 11 | RUST_CLIPPY_ARGS ?= -- -D clippy::all 12 | 13 | # 14 | # Repo-specific targets 15 | # 16 | .PHONY: all 17 | all: build-cueball 18 | 19 | .PHONY: build-cueball 20 | build-cueball: 21 | $(CARGO) build --release 22 | 23 | .PHONY: test 24 | test: 25 | $(CARGO) test 26 | 27 | .PHONY: check 28 | check: 29 | $(CARGO) clean && $(CARGO) clippy $(RUST_CLIPPY_ARGS) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fast-rpc: streaming JSON RPC over TCP 2 | 3 | Fast is a simple RPC protocol used in Joyent's 4 | [Triton](http://github.com/joyent/triton) and 5 | [Manta](https://github.com/joyent/manta) systems, particularly in the 6 | [Moray](https://github.com/joyent/moray) key-value store. This README contains 7 | usage notes. 8 | 9 | The original implementation is 10 | [node-fast](https://github.com/joyent/node-fast). This is the rust 11 | implementation of the Fast protocol. 12 | 13 | This crate includes: 14 | 15 | * client library interface 16 | * server library interface 17 | * `fastserve`, An example Fast server for demo and testing 18 | * `fastcall`, An example command-line tool for making Fast RPC requests 19 | 20 | ## Synopsis 21 | 22 | Start the rust Fast server: 23 | 24 | $ cargo run --example fastserve 25 | 26 | Use the `fastcall` example program to invoke the `date` RPC method inside the 27 | client: 28 | 29 | ``` 30 | cargo run --example fastcall -- --args '[]' --method date 31 | 32 | ``` 33 | 34 | The `fastcall` program in the [`node-fast`](https://github.com/joyent/node-fast) 35 | repo will also work: 36 | 37 | $ fastcall 127.0.0.1 2030 date '[]' 38 | {"timestamp":1457475515355,"iso8601":"2016-03-08T22:18:35.355Z"} 39 | 40 | Or try the `yes` method, an RPC version of yes(1): 41 | 42 | $ fastcall 127.0.0.1 2030 yes '[ { "value": { "hello": "world" }, "count": 3 } ]' 43 | {"hello":"world"} 44 | {"hello":"world"} 45 | {"hello":"world"} 46 | 47 | ## Documentation 48 | 49 | Further information is available in the rustdocs. These can be generated locally 50 | by running `cargo doc` in the cloned repository. 51 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Joyent, Inc. 3 | # 4 | 5 | # Create a DVI from fastpacket.tex and then convert that DVI to into an SVG for 6 | # use in the rustdocs 7 | # 8 | # Building this requires a working TeX installation. Here are a few examples of how to do this: 9 | # 10 | # Mac 11 | # 12 | # $ brew cask install mactex-no-gui 13 | # $ export PATH=/Library/TeX/texbin:$PATH 14 | # $ cd docs ; make 15 | # 16 | # Arch Linux 17 | # 18 | # $ sudo pacman -S texlive-most 19 | # $ cd docs ; make 20 | 21 | .PHONY: all 22 | all: 23 | latex fastpacket && dvisvgm --no-fonts fastpacket.dvi fastpacket.svg 24 | -------------------------------------------------------------------------------- /docs/fastpacket.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /docs/fastpacket.tex: -------------------------------------------------------------------------------- 1 | \documentclass{standalone} 2 | \usepackage{bytefield} 3 | \begin{document} 4 | 5 | \begin{bytefield}[bitwidth=1.1em]{32} 6 | \bitheader{0,7,8,15,16,23,24,31} \\ 7 | \bitbox{8}{VERSION=1} & \bitbox{8}{TYPE} & \bitbox{8}{STATUS}& \bitbox{8}{MSGID~0} \\ 8 | \bitbox{8}{MSGID~1} & \bitbox{8}{MSGID~2} & \bitbox{8}{MSGID~3} & \bitbox{8}{CRC~0} \\ 9 | \bitbox{8}{CRC~1} & \bitbox{8}{CRC~2} & \bitbox{8}{CRC~3} & \bitbox{8}{DLEN~0} \\ 10 | \bitbox{8}{DLEN~1} & \bitbox{8}{DLEN~2} & \bitbox{8}{DLEN~3} & \bitbox{8}{DATA~0} \\ 11 | \bitbox{8}{DATA~1} & \bitbox{8}{DATA~2} & \bitbox{8}{DATA~3} & \bitbox{8}{DATA~4} \\ 12 | \bitbox[]{16}{$\vdots$ \\[1ex]} &\bitbox[]{16}{$\vdots$ \\[1ex]} \\ 13 | \bitbox{8}{DATA~$N-3$} & \bitbox{8}{DATA~$N-2$} & \bitbox{8}{DATA~$N-1$} & \bitbox{8}{DATA~$N$} \\ 14 | \end{bytefield} 15 | 16 | \end{document} 17 | -------------------------------------------------------------------------------- /examples/fastcall.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joyent, Inc. 2 | 3 | use std::io::Error; 4 | use std::net::{SocketAddr, TcpStream}; 5 | use std::process; 6 | 7 | use clap::{crate_version, value_t, App, Arg, ArgMatches}; 8 | use serde_json::Value; 9 | 10 | use fast_rpc::client; 11 | use fast_rpc::protocol::{FastMessage, FastMessageId}; 12 | 13 | static APP: &'static str = "fastcall"; 14 | static DEFAULT_HOST: &'static str = "127.0.0.1"; 15 | const DEFAULT_PORT: u32 = 2030; 16 | 17 | pub fn parse_opts<'a, 'b>(app: String) -> ArgMatches<'a> { 18 | App::new(app) 19 | .about("Command-line tool for making a node-fast RPC method call") 20 | .version(crate_version!()) 21 | .arg( 22 | Arg::with_name("host") 23 | .help("DNS name or IP address for remote server") 24 | .long("host") 25 | .short("h") 26 | .takes_value(true) 27 | .required(false), 28 | ) 29 | .arg( 30 | Arg::with_name("port") 31 | .help("TCP port for remote server (Default: 2030)") 32 | .long("port") 33 | .short("p") 34 | .takes_value(true), 35 | ) 36 | .arg( 37 | Arg::with_name("method") 38 | .help("Name of remote RPC method call") 39 | .long("method") 40 | .short("m") 41 | .takes_value(true) 42 | .required(true), 43 | ) 44 | .arg( 45 | Arg::with_name("args") 46 | .help("JSON-encoded arguments for RPC method call") 47 | .long("args") 48 | .takes_value(true) 49 | .required(true), 50 | ) 51 | .arg( 52 | Arg::with_name("abandon") 53 | .long("abandon-immediately") 54 | .short("a") 55 | .takes_value(false), 56 | ) 57 | .arg( 58 | Arg::with_name("leave_open") 59 | .long("leave-conn-open") 60 | .short("c") 61 | .takes_value(false), 62 | ) 63 | .get_matches() 64 | } 65 | 66 | fn stdout_handler(msg: &FastMessage) { 67 | println!("{}", msg.data.d); 68 | } 69 | 70 | fn response_handler(msg: &FastMessage) -> Result<(), Error> { 71 | match msg.data.m.name.as_str() { 72 | "date" | "echo" | "yes" | "getobject" | "putobject" => { 73 | stdout_handler(msg) 74 | } 75 | _ => println!("Received {} response", msg.data.m.name), 76 | } 77 | 78 | Ok(()) 79 | } 80 | 81 | fn main() { 82 | let matches = parse_opts(APP.to_string()); 83 | let host = String::from(matches.value_of("host").unwrap_or(DEFAULT_HOST)); 84 | let port = value_t!(matches, "port", u32).unwrap_or(DEFAULT_PORT); 85 | let addr = [host, String::from(":"), port.to_string()] 86 | .concat() 87 | .parse::() 88 | .unwrap_or_else(|e| { 89 | eprintln!( 90 | "Failed to parse host and port as valid socket address: \ 91 | {}", 92 | e 93 | ); 94 | process::exit(1) 95 | }); 96 | let method = 97 | String::from(matches.value_of("method").unwrap_or_else(|| { 98 | eprintln!("Failed to parse method argument as String"); 99 | process::exit(1) 100 | })); 101 | let args = value_t!(matches, "args", Value).unwrap_or_else(|e| e.exit()); 102 | 103 | let mut stream = TcpStream::connect(&addr).unwrap_or_else(|e| { 104 | eprintln!("Failed to connect to server: {}", e); 105 | process::exit(1) 106 | }); 107 | 108 | let mut msg_id = FastMessageId::new(); 109 | 110 | let result = client::send(method, args, &mut msg_id, &mut stream).and_then( 111 | |_bytes_written| client::receive(&mut stream, response_handler), 112 | ); 113 | 114 | if let Err(e) = result { 115 | eprintln!("Error: {}", e); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /examples/fastserve.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joyent, Inc. 2 | 3 | use std::env; 4 | use std::io::{Error, ErrorKind}; 5 | use std::net::SocketAddr; 6 | use std::sync::Mutex; 7 | use std::thread; 8 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 9 | 10 | use chrono::prelude::*; 11 | use serde_derive::{Deserialize, Serialize}; 12 | use serde_json::{json, Value}; 13 | use slog::{debug, error, info, o, Drain, Logger}; 14 | use tokio::net::TcpListener; 15 | use tokio::prelude::*; 16 | 17 | use fast_rpc::protocol::{FastMessage, FastMessageData}; 18 | use fast_rpc::server; 19 | 20 | #[derive(Serialize, Deserialize)] 21 | struct YesPayload { 22 | value: Value, 23 | count: u32, 24 | } 25 | 26 | #[derive(Serialize, Deserialize)] 27 | struct DatePayload { 28 | timestamp: u64, 29 | iso8601: DateTime, 30 | } 31 | 32 | #[derive(Serialize, Deserialize)] 33 | struct FastBenchPayload { 34 | echo: Value, 35 | delay: Option, 36 | } 37 | 38 | impl DatePayload { 39 | fn new() -> DatePayload { 40 | //TODO: Do this only with chrono and time libs 41 | let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 42 | let now_micros = now.as_secs() * 1_000 + now.subsec_millis() as u64; 43 | let now2 = Utc::now(); 44 | DatePayload { 45 | timestamp: now_micros, 46 | iso8601: now2, 47 | } 48 | } 49 | } 50 | 51 | fn other_error(msg: &str) -> Error { 52 | Error::new(ErrorKind::Other, String::from(msg)) 53 | } 54 | 55 | fn date_handler( 56 | msg: &FastMessage, 57 | mut response: Vec, 58 | log: &Logger, 59 | ) -> Result, Error> { 60 | debug!(log, "handling date function request"); 61 | let date_payload_result = serde_json::to_value(vec![DatePayload::new()]); 62 | match date_payload_result { 63 | Ok(date_payload) => { 64 | response.push(FastMessage::data( 65 | msg.id, 66 | FastMessageData::new(msg.data.m.name.clone(), date_payload), 67 | )); 68 | Ok(response) 69 | } 70 | Err(_) => Err(other_error( 71 | "Failed to parse JSON data as payload for date function", 72 | )), 73 | } 74 | } 75 | 76 | fn echo_handler( 77 | msg: &FastMessage, 78 | mut response: Vec, 79 | log: &Logger, 80 | ) -> Result, Error> { 81 | debug!(log, "handling echo function request"); 82 | response.push(FastMessage::data(msg.id, msg.data.clone())); 83 | Ok(response) 84 | } 85 | 86 | fn yes_handler( 87 | msg: &FastMessage, 88 | mut response: Vec, 89 | log: &Logger, 90 | ) -> Result, Error> { 91 | debug!(log, "handling yes function request"); 92 | 93 | //TODO: Too much nesting, need to refactor 94 | match msg.data.d { 95 | Value::Array(_) => { 96 | let data_clone = msg.data.clone(); 97 | let payload_result: Result, _> = 98 | serde_json::from_value(data_clone.d); 99 | match payload_result { 100 | Ok(payloads) => { 101 | if payloads.len() == 1 { 102 | for _i in 0..payloads[0].count { 103 | let value = 104 | Value::Array(vec![payloads[0].value.clone()]); 105 | let yes_data = FastMessage::data( 106 | msg.id, 107 | FastMessageData::new( 108 | msg.data.m.name.clone(), 109 | value, 110 | ), 111 | ); 112 | response.push(yes_data); 113 | } 114 | Ok(response) 115 | } else { 116 | Err(other_error( 117 | "Expected JSON array with a single element", 118 | )) 119 | } 120 | } 121 | Err(_) => Err(other_error( 122 | "Failed to parse JSON data as payload for yes function", 123 | )), 124 | } 125 | } 126 | _ => Err(other_error("Expected JSON array")), 127 | } 128 | } 129 | 130 | fn fastbench_handler( 131 | msg: &FastMessage, 132 | mut response: Vec, 133 | log: &Logger, 134 | ) -> Result, Error> { 135 | debug!(log, "handling fastbench function request"); 136 | 137 | match msg.data.d { 138 | Value::Array(_) => { 139 | let data_clone = msg.data.clone(); 140 | let payload_result: Result, _> = 141 | serde_json::from_value(data_clone.d); 142 | match payload_result { 143 | Ok(payloads) => { 144 | if payloads.len() == 1 { 145 | if payloads[0].delay.is_some() { 146 | let delay_duration = Duration::from_millis( 147 | payloads[0] 148 | .delay 149 | .expect("failed to unwrap delay value"), 150 | ); 151 | thread::sleep(delay_duration); 152 | } 153 | let echo_payloads = 154 | payloads[0].echo.as_array().unwrap(); 155 | let mut resp_payloads = Vec::new(); 156 | for i in echo_payloads { 157 | let echo_response = json!({"value": i.clone()}); 158 | resp_payloads.push(echo_response); 159 | } 160 | let resp = FastMessage::data( 161 | msg.id, 162 | FastMessageData::new( 163 | msg.data.m.name.clone(), 164 | Value::Array(resp_payloads), 165 | ), 166 | ); 167 | response.push(resp); 168 | Ok(response) 169 | } else { 170 | Err(other_error( 171 | "Expected JSON array with a single element", 172 | )) 173 | } 174 | } 175 | Err(_) => Err(other_error( 176 | "Failed to parse JSON data as payload for yes function", 177 | )), 178 | } 179 | } 180 | _ => Err(other_error("Expected JSON array")), 181 | } 182 | } 183 | 184 | fn msg_handler( 185 | msg: &FastMessage, 186 | log: &Logger, 187 | ) -> Result, Error> { 188 | let response: Vec = vec![]; 189 | 190 | match msg.data.m.name.as_str() { 191 | "date" => date_handler(msg, response, &log), 192 | "echo" => echo_handler(msg, response, &log), 193 | "yes" => yes_handler(msg, response, &log), 194 | "fastbench" => fastbench_handler(msg, response, &log), 195 | _ => Err(Error::new( 196 | ErrorKind::Other, 197 | format!("Unsupported function: {}", msg.data.m.name), 198 | )), 199 | } 200 | } 201 | 202 | fn main() { 203 | let plain = slog_term::PlainSyncDecorator::new(std::io::stdout()); 204 | let root_log = Logger::root( 205 | Mutex::new(slog_term::FullFormat::new(plain).build()).fuse(), 206 | o!("build-id" => "0.1.0"), 207 | ); 208 | 209 | let addr = env::args().nth(1).unwrap_or("127.0.0.1:2030".to_string()); 210 | let addr = addr.parse::().unwrap(); 211 | 212 | let listener = TcpListener::bind(&addr).expect("failed to bind"); 213 | info!(root_log, "listening for fast requests"; "address" => addr); 214 | 215 | tokio::run({ 216 | let process_log = root_log.clone(); 217 | let err_log = root_log.clone(); 218 | listener 219 | .incoming() 220 | .map_err(move |e| error!(&err_log, "failed to accept socket"; "err" => %e)) 221 | .for_each(move |socket| { 222 | let task = server::make_task(socket, msg_handler, Some(&process_log)); 223 | tokio::spawn(task) 224 | }) 225 | }); 226 | } 227 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joyent, Inc. 2 | 3 | //! This module provides the interface for creating Fast clients. 4 | 5 | use std::io::{Error, ErrorKind}; 6 | use std::net::TcpStream; 7 | 8 | use bytes::BytesMut; 9 | use serde_json::Value; 10 | use tokio::prelude::*; 11 | 12 | use crate::protocol; 13 | use crate::protocol::{ 14 | FastMessage, FastMessageData, FastMessageId, FastMessageServerError, 15 | FastMessageStatus, FastParseError, 16 | }; 17 | 18 | enum BufferAction { 19 | Keep, 20 | Trim(usize), 21 | Done, 22 | } 23 | 24 | /// Send a message to a Fast server using the provided TCP stream. 25 | pub fn send( 26 | method: String, 27 | args: Value, 28 | msg_id: &mut FastMessageId, 29 | stream: &mut TcpStream, 30 | ) -> Result { 31 | // It is safe to call unwrap on the msg_id iterator because the 32 | // implementation of Iterator for FastMessageId will only ever return 33 | // Some(id). The Option return type is required by the Iterator trait. 34 | let msg = FastMessage::data( 35 | msg_id.next().unwrap() as u32, 36 | FastMessageData::new(method, args), 37 | ); 38 | let mut write_buf = BytesMut::new(); 39 | match protocol::encode_msg(&msg, &mut write_buf) { 40 | Ok(_) => stream.write(write_buf.as_ref()), 41 | Err(err_str) => Err(Error::new(ErrorKind::Other, err_str)), 42 | } 43 | } 44 | 45 | /// Receive a message from a Fast server on the provided TCP stream and call 46 | /// `response_handler` on the response. 47 | pub fn receive( 48 | stream: &mut TcpStream, 49 | mut response_handler: F, 50 | ) -> Result 51 | where 52 | F: FnMut(&FastMessage) -> Result<(), Error>, 53 | { 54 | let mut stream_end = false; 55 | let mut msg_buf: Vec = Vec::new(); 56 | let mut total_bytes = 0; 57 | let mut result = Ok(total_bytes); 58 | 59 | while !stream_end { 60 | let mut read_buf = [0; 128]; 61 | match stream.read(&mut read_buf) { 62 | Ok(0) => { 63 | result = Err(Error::new( 64 | ErrorKind::UnexpectedEof, 65 | "Received EOF (0 bytes) from server", 66 | )); 67 | stream_end = true; 68 | } 69 | Ok(byte_count) => { 70 | total_bytes += byte_count; 71 | msg_buf.extend_from_slice(&read_buf[0..byte_count]); 72 | match parse_and_handle_messages( 73 | msg_buf.as_slice(), 74 | &mut response_handler, 75 | ) { 76 | Ok(BufferAction::Keep) => (), 77 | Ok(BufferAction::Trim(rest_offset)) => { 78 | let truncate_bytes = msg_buf.len() - rest_offset; 79 | msg_buf.rotate_left(rest_offset); 80 | msg_buf.truncate(truncate_bytes); 81 | result = Ok(total_bytes); 82 | } 83 | Ok(BufferAction::Done) => stream_end = true, 84 | Err(e) => { 85 | result = Err(e); 86 | stream_end = true 87 | } 88 | } 89 | } 90 | Err(err) => { 91 | result = Err(err); 92 | stream_end = true 93 | } 94 | } 95 | } 96 | result 97 | } 98 | 99 | fn parse_and_handle_messages( 100 | read_buf: &[u8], 101 | response_handler: &mut F, 102 | ) -> Result 103 | where 104 | F: FnMut(&FastMessage) -> Result<(), Error>, 105 | { 106 | let mut offset = 0; 107 | let mut done = false; 108 | 109 | let mut result = Ok(BufferAction::Keep); 110 | 111 | while !done { 112 | match FastMessage::parse(&read_buf[offset..]) { 113 | Ok(ref fm) if fm.status == FastMessageStatus::End => { 114 | result = Ok(BufferAction::Done); 115 | done = true; 116 | } 117 | Ok(fm) => { 118 | offset += fm.msg_size.unwrap(); 119 | match fm.status { 120 | FastMessageStatus::Data | FastMessageStatus::End => { 121 | if let Err(e) = response_handler(&fm) { 122 | result = Err(e); 123 | done = true; 124 | } else { 125 | result = Ok(BufferAction::Trim(offset)); 126 | } 127 | } 128 | FastMessageStatus::Error => { 129 | result = serde_json::from_value(fm.data.d) 130 | .or_else(|_| Err(unspecified_error().into())) 131 | .and_then( 132 | |e: FastMessageServerError| Err(e.into()), 133 | ); 134 | 135 | done = true; 136 | } 137 | } 138 | } 139 | Err(FastParseError::NotEnoughBytes(_bytes)) => { 140 | done = true; 141 | } 142 | Err(FastParseError::IOError(e)) => { 143 | result = Err(e); 144 | done = true; 145 | } 146 | } 147 | } 148 | 149 | result 150 | } 151 | 152 | fn unspecified_error() -> FastMessageServerError { 153 | FastMessageServerError::new( 154 | "UnspecifiedServerError", 155 | "Server reported unspecified error.", 156 | ) 157 | } 158 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joyent, Inc. 2 | 3 | //! Fast: A simple RPC protcol used by Joyent products 4 | //! 5 | //! Fast is a simple RPC protocol used in 6 | //! Joyent's[Triton](http://github.com/joyent/triton) and 7 | //! [Manta](https://github.com/joyent/manta) systems, particularly in the 8 | //! [Moray](https://github.com/joyent/moray) and 9 | //! [Boray](https://github.com/joyent/boray) components. 10 | //! 11 | //! Protocol overview 12 | //! 13 | //! The Fast protocol is intended for use with TCP. Typically, a Fast server 14 | //! listens for TCP connections on a well-known port, and Fast clients connect 15 | //! to the server to make RPC requests. Clients can make multiple connections 16 | //! to the server, but each connection represents a logically separate 17 | //! client. Communication between client and server consist of discrete 18 | //! _messages_ sent over the TCP connection. 19 | //! 20 | //! Fast protocol messages have the following structure: 21 | //! 22 | //! 23 | //! 24 | //! * VERSION 1-byte integer. The only supported value is "1". 25 | //! 26 | //! * TYPE 1-byte integer. The only supported value is TYPE_JSON (0x1), 27 | //! indicating that the data payload is an encoded JSON object. 28 | //! 29 | //! * STATUS 1-byte integer. The only supported values are: 30 | //! 31 | //! * STATUS_DATA 0x1 indicates a "data" message 32 | //! 33 | //! * STATUS_END 0x2 indicates an "end" message 34 | //! 35 | //! * STATUS_ERROR 0x3 indicates an "error" message 36 | //! 37 | //! * MSGID0...MSGID3 4-byte big-endian unsigned integer, a unique identifier 38 | //! for this message. 39 | //! 40 | //! * CRC0...CRC3 4-byte big-endian unsigned integer representing the CRC16 41 | //! value of the data payload 42 | //! 43 | //! * DLEN0...DLEN4 4-byte big-endian unsigned integer representing the number 44 | //! of bytes of data payload that follow 45 | //! 46 | //! * DATA0...DATAN Data payload. This is a JSON-encoded object (for TYPE = 47 | //! TYPE_JSON). The encoding length in bytes is given by the 48 | //! DLEN0...DLEN4 bytes. 49 | //! 50 | //! ### Status 51 | //! 52 | //! There are three allowed values for `status`: 53 | //! 54 | //! |Status value | Status name | Description | 55 | //! |------------ | ----------- | ----------- | 56 | //! | `0x1` | `DATA` | From clients, indicates an RPC request. From servers, indicates one of many values emitted by an RPC call.| 57 | //! | `0x2` | `END` | Indicates the successful completion of an RPC call. Only sent by servers. | 58 | //! | `0x3` | `ERROR` | Indicates the failed completion of an RPC call. Only sent by servers. | 59 | //! 60 | //! ### Message IDs 61 | //! 62 | //! Each Fast message has a message id, which is scoped to the Fast 63 | //! connection. These are allocated sequentially from a circular 31-bit space. 64 | //! 65 | //! ### Data payload 66 | //! 67 | //! For all messages, the `data` field contains properties: 68 | //! 69 | //! | Field | Type | Purpose | 70 | //! | -------- | ----------------- | ------- | 71 | //! | `m` | object | describes the RPC method being invoked | 72 | //! | `m.name` | string | name of the RPC method being invoked | 73 | //! | `m.uts` | number (optional) | timestamp of message creation, in microseconds since the Unix epoch | 74 | //! | `d` | object or array | varies by message status | 75 | //! 76 | //! ### Messaging Scenarios 77 | //! 78 | //! Essentially, there are only four messaging scenarios with Fast: 79 | //! 80 | //! **Client initiates an RPC request.** The client allocates a new message 81 | //! identifier and sends a `DATA` message with `data.m.name` set to the name of 82 | //! the RPC method it wants to invoke. Arguments are specified by the array 83 | //! `data.d`. Clients may issue concurrent requests over a single TCP 84 | //! connection, provided they do not re-use a message identifier for separate 85 | //! requests. 86 | //! 87 | //! **Server sends data from an RPC call.** RPC calls may emit an arbitrary 88 | //! number of values back to the client. To emit these values, the server sends 89 | //! `DATA` messages with `data.d` set to an array of non-null values to be 90 | //! emitted. All `DATA` messages for the same RPC request have the same message 91 | //! identifier that the client included in its original `DATA` message that 92 | //! initiated the RPC call. 93 | //! 94 | //! **Server completes an RPC call successfully.** When an RPC call completes 95 | //! successfully, the server sends an `END` event having the same message 96 | //! identifier as the one in the client's original `DATA` message that initiated 97 | //! the RPC call. This message can contain data as well, in which case it should 98 | //! be processed the same way as for a DATA message. 99 | //! 100 | //! **Server reports a failed RPC call.** Any time before an `END` message is 101 | //! generated for an RPC call, the server may send an `ERROR` message having the 102 | //! same message identifier as the one in the client's original `DATA` message 103 | //! that initiated the RPC call. 104 | //! 105 | //! By convention, the `m` fields (`m.name` and `m.uts`) are populated for all 106 | //! server messages, even though `m.name` is redundant. 107 | //! 108 | //! The RPC request begins when the client sends the initial `DATA` message. 109 | //! The RPC request is finished when the server sends either an `ERROR` or `END` 110 | //! message for that request. In summary, the client only ever sends one 111 | //! message for each request. The server may send any number of `DATA` messages 112 | //! and exactly one `END` or `ERROR` message. 113 | 114 | #![allow(missing_docs)] 115 | 116 | pub mod client; 117 | pub mod protocol; 118 | pub mod server; 119 | -------------------------------------------------------------------------------- /src/protocol.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joyent, Inc. 2 | 3 | //! This module contains the types and functions used to encode and decode Fast 4 | //! messages. The contents of this module are not needed for normal client or 5 | //! server consumers of this crate, but they are exposed for the special case of 6 | //! someone needing to implement custom client or server code. 7 | 8 | use std::io::{Error, ErrorKind}; 9 | use std::sync::atomic::AtomicUsize; 10 | use std::time::{SystemTime, UNIX_EPOCH}; 11 | use std::{io, str, usize}; 12 | 13 | use byteorder::{BigEndian, ByteOrder}; 14 | use bytes::{BufMut, BytesMut}; 15 | use crc16::*; 16 | use num::{FromPrimitive, ToPrimitive}; 17 | use num_derive::{FromPrimitive, ToPrimitive}; 18 | use serde_derive::{Deserialize, Serialize}; 19 | use serde_json::Value; 20 | use tokio_io::_tokio_codec::{Decoder, Encoder}; 21 | 22 | const FP_OFF_TYPE: usize = 0x1; 23 | const FP_OFF_STATUS: usize = 0x2; 24 | const FP_OFF_MSGID: usize = 0x3; 25 | const FP_OFF_CRC: usize = 0x7; 26 | const FP_OFF_DATALEN: usize = 0xb; 27 | const FP_OFF_DATA: usize = 0xf; 28 | 29 | /// The size of a Fast message header 30 | pub const FP_HEADER_SZ: usize = FP_OFF_DATA; 31 | 32 | const FP_VERSION_2: u8 = 0x2; 33 | const FP_VERSION_CURRENT: u8 = FP_VERSION_2; 34 | 35 | /// A data type representing a Fast message id that can safely be shard between 36 | /// threads. The `next` associated function retrieves the next id value and 37 | /// manages the circular message id space internally. 38 | #[derive(Default)] 39 | pub struct FastMessageId(AtomicUsize); 40 | 41 | impl FastMessageId { 42 | /// Creates a new FastMessageId 43 | pub fn new() -> Self { 44 | FastMessageId(AtomicUsize::new(0x0)) 45 | } 46 | } 47 | 48 | impl Iterator for FastMessageId { 49 | type Item = usize; 50 | 51 | /// Returns the next Fast message id and increments the value modulo the 52 | /// usize MAX_VALUE - 1. 53 | fn next(&mut self) -> Option { 54 | // Increment our count. This is why we started at zero. 55 | let id_value = self.0.get_mut(); 56 | let current = *id_value; 57 | *id_value = (*id_value + 1) % (usize::max_value() - 1); 58 | 59 | Some(current) 60 | } 61 | } 62 | 63 | /// An error type representing a failure to parse a buffer as a Fast message. 64 | #[derive(Debug)] 65 | pub enum FastParseError { 66 | NotEnoughBytes(usize), 67 | IOError(Error), 68 | } 69 | 70 | impl From for FastParseError { 71 | fn from(error: io::Error) -> Self { 72 | FastParseError::IOError(error) 73 | } 74 | } 75 | 76 | impl From for Error { 77 | fn from(pfr: FastParseError) -> Self { 78 | match pfr { 79 | FastParseError::NotEnoughBytes(_) => { 80 | let msg = "Unable to parse message: not enough bytes"; 81 | Error::new(ErrorKind::Other, msg) 82 | } 83 | FastParseError::IOError(e) => e, 84 | } 85 | } 86 | } 87 | 88 | /// An error type representing Fast error messages that may be returned from a 89 | /// Fast server. 90 | #[derive(Debug, Deserialize, Serialize)] 91 | pub struct FastMessageServerError { 92 | pub name: String, 93 | pub message: String, 94 | } 95 | 96 | impl FastMessageServerError { 97 | pub fn new(name: &str, message: &str) -> Self { 98 | FastMessageServerError { 99 | name: String::from(name), 100 | message: String::from(message), 101 | } 102 | } 103 | } 104 | 105 | impl From for Error { 106 | fn from(err: FastMessageServerError) -> Self { 107 | Error::new(ErrorKind::Other, format!("{}: {}", err.name, err.message)) 108 | } 109 | } 110 | 111 | /// Represents the Type field of a Fast message. Currently there is only one 112 | /// valid value, JSON. 113 | #[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, Clone)] 114 | pub enum FastMessageType { 115 | Json = 1, 116 | } 117 | 118 | /// Represents the Status field of a Fast message. 119 | #[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, Clone)] 120 | pub enum FastMessageStatus { 121 | Data = 1, 122 | End = 2, 123 | Error = 3, 124 | } 125 | 126 | /// This type encapsulates the header of a Fast message. 127 | pub struct FastMessageHeader { 128 | /// The Type field of the Fast message 129 | msg_type: FastMessageType, 130 | /// The Status field of the Fast message 131 | status: FastMessageStatus, 132 | /// The Fast message identifier 133 | id: u32, 134 | /// The CRC16 check value of the Fast message data payload 135 | crc: u32, 136 | /// The length in bytes of the Fast message data payload 137 | data_len: usize, 138 | } 139 | 140 | /// Represents the metadata about a `FastMessage` data payload. This includes a 141 | /// timestamp and an RPC method name. 142 | #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] 143 | pub struct FastMessageMetaData { 144 | pub uts: u64, 145 | pub name: String, 146 | } 147 | 148 | impl FastMessageMetaData { 149 | pub fn new(n: String) -> FastMessageMetaData { 150 | let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); 151 | let now_micros = 152 | now.as_secs() * 1_000_000 + u64::from(now.subsec_micros()); 153 | 154 | FastMessageMetaData { 155 | uts: now_micros, 156 | name: n, 157 | } 158 | } 159 | } 160 | 161 | /// Encapsulates the Fast message metadata and the JSON formatted message data. 162 | #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] 163 | pub struct FastMessageData { 164 | pub m: FastMessageMetaData, 165 | pub d: Value, 166 | } 167 | 168 | impl FastMessageData { 169 | pub fn new(n: String, d: Value) -> FastMessageData { 170 | FastMessageData { 171 | m: FastMessageMetaData::new(n), 172 | d, 173 | } 174 | } 175 | } 176 | 177 | /// Represents a Fast message including the header and data payload 178 | #[derive(Debug, Clone)] 179 | pub struct FastMessage { 180 | /// The Type field of the Fast message 181 | pub msg_type: FastMessageType, 182 | /// The Status field of the Fast message 183 | pub status: FastMessageStatus, 184 | /// The Fast message identifier 185 | pub id: u32, 186 | /// The length in bytes of the Fast message data payload 187 | pub msg_size: Option, 188 | /// The data payload of the Fast message 189 | pub data: FastMessageData, 190 | } 191 | 192 | impl PartialEq for FastMessage { 193 | fn eq(&self, other: &FastMessage) -> bool { 194 | self.msg_type == other.msg_type 195 | && self.status == other.status 196 | && self.id == other.id 197 | && self.msg_size == other.msg_size 198 | && self.data == other.data 199 | } 200 | } 201 | 202 | impl FastMessage { 203 | /// Parse a byte buffer into a `FastMessage`. Returns a `FastParseError` if 204 | /// the available bytes cannot be parsed to a `FastMessage`. 205 | pub fn parse(buf: &[u8]) -> Result { 206 | FastMessage::check_buffer_size(buf)?; 207 | let header = FastMessage::parse_header(buf)?; 208 | 209 | FastMessage::validate_data_length(buf, header.data_len)?; 210 | let raw_data = &buf[FP_OFF_DATA..FP_OFF_DATA + header.data_len]; 211 | FastMessage::validate_crc(raw_data, header.crc)?; 212 | let data = FastMessage::parse_data(raw_data)?; 213 | 214 | let msg_size = match header.status { 215 | FastMessageStatus::End => None, 216 | _ => Some(FP_OFF_DATA + header.data_len), 217 | }; 218 | 219 | Ok(FastMessage { 220 | msg_type: header.msg_type, 221 | status: header.status, 222 | id: header.id, 223 | msg_size, 224 | data, 225 | }) 226 | } 227 | 228 | /// Check that the provided byte buffer contains at least `FP_HEADER_SZ` 229 | /// bytes. Returns a `FastParseError` if this is not the case. 230 | pub fn check_buffer_size(buf: &[u8]) -> Result<(), FastParseError> { 231 | if buf.len() < FP_HEADER_SZ { 232 | Err(FastParseError::NotEnoughBytes(buf.len())) 233 | } else { 234 | Ok(()) 235 | } 236 | } 237 | 238 | /// Parse a portion of a byte buffer into a `FastMessageHeader`. Returns a 239 | /// `FastParseError` if the available bytes cannot be parsed to a 240 | /// `FastMessageHeader`. 241 | pub fn parse_header( 242 | buf: &[u8], 243 | ) -> Result { 244 | let msg_type = 245 | FromPrimitive::from_u8(buf[FP_OFF_TYPE]).ok_or_else(|| { 246 | let msg = "Failed to parse message type"; 247 | FastParseError::IOError(Error::new(ErrorKind::Other, msg)) 248 | })?; 249 | let status = 250 | FromPrimitive::from_u8(buf[FP_OFF_STATUS]).ok_or_else(|| { 251 | let msg = "Failed to parse message status"; 252 | FastParseError::IOError(Error::new(ErrorKind::Other, msg)) 253 | })?; 254 | let msg_id = BigEndian::read_u32(&buf[FP_OFF_MSGID..FP_OFF_MSGID + 4]); 255 | let expected_crc = 256 | BigEndian::read_u32(&buf[FP_OFF_CRC..FP_OFF_CRC + 4]); 257 | let data_len = 258 | BigEndian::read_u32(&buf[FP_OFF_DATALEN..FP_OFF_DATALEN + 4]) 259 | as usize; 260 | 261 | Ok(FastMessageHeader { 262 | msg_type, 263 | status, 264 | id: msg_id, 265 | crc: expected_crc, 266 | data_len, 267 | }) 268 | } 269 | 270 | fn validate_data_length( 271 | buf: &[u8], 272 | data_length: usize, 273 | ) -> Result<(), FastParseError> { 274 | if buf.len() < (FP_HEADER_SZ + data_length) { 275 | Err(FastParseError::NotEnoughBytes(buf.len())) 276 | } else { 277 | Ok(()) 278 | } 279 | } 280 | 281 | fn validate_crc(data_buf: &[u8], crc: u32) -> Result<(), FastParseError> { 282 | let calculated_crc = u32::from(State::::calculate(data_buf)); 283 | if crc != calculated_crc { 284 | let msg = "Calculated CRC does not match the provided CRC"; 285 | Err(FastParseError::IOError(Error::new(ErrorKind::Other, msg))) 286 | } else { 287 | Ok(()) 288 | } 289 | } 290 | 291 | fn parse_data(data_buf: &[u8]) -> Result { 292 | match str::from_utf8(data_buf) { 293 | Ok(data_str) => serde_json::from_str(data_str).map_err(|_e| { 294 | let msg = "Failed to parse data payload as JSON"; 295 | FastParseError::IOError(Error::new(ErrorKind::Other, msg)) 296 | }), 297 | Err(_) => { 298 | let msg = "Failed to parse data payload as UTF-8"; 299 | Err(FastParseError::IOError(Error::new(ErrorKind::Other, msg))) 300 | } 301 | } 302 | } 303 | 304 | /// Returns a `FastMessage` that represents a Fast protocol `DATA` message 305 | /// with the provided message identifer and data payload. 306 | pub fn data(msg_id: u32, data: FastMessageData) -> FastMessage { 307 | FastMessage { 308 | msg_type: FastMessageType::Json, 309 | status: FastMessageStatus::Data, 310 | id: msg_id, 311 | msg_size: None, 312 | data, 313 | } 314 | } 315 | 316 | /// Returns a `FastMessage` that represents a Fast protocol `END` message 317 | /// with the provided message identifer. The method parameter is used in the 318 | /// otherwise empty data payload. 319 | pub fn end(msg_id: u32, method: String) -> FastMessage { 320 | FastMessage { 321 | msg_type: FastMessageType::Json, 322 | status: FastMessageStatus::End, 323 | id: msg_id, 324 | msg_size: None, 325 | data: FastMessageData::new(method, Value::Array(vec![])), 326 | } 327 | } 328 | 329 | /// Returns a `FastMessage` that represents a Fast protocol `ERROR` message 330 | /// with the provided message identifer and data payload. 331 | pub fn error(msg_id: u32, data: FastMessageData) -> FastMessage { 332 | FastMessage { 333 | msg_type: FastMessageType::Json, 334 | status: FastMessageStatus::Error, 335 | id: msg_id, 336 | msg_size: None, 337 | data, 338 | } 339 | } 340 | } 341 | 342 | /// This type implements the functions necessary for the Fast protocl framing. 343 | pub struct FastRpc; 344 | 345 | impl Decoder for FastRpc { 346 | type Item = Vec; 347 | type Error = Error; 348 | 349 | fn decode( 350 | &mut self, 351 | buf: &mut BytesMut, 352 | ) -> Result, Error> { 353 | let mut msgs: Self::Item = Vec::new(); 354 | let mut done = false; 355 | 356 | while !done && !buf.is_empty() { 357 | // Make sure there is room in msgs to fit a message 358 | if msgs.len() + 1 > msgs.capacity() { 359 | msgs.reserve(1); 360 | } 361 | 362 | match FastMessage::parse(&buf) { 363 | Ok(parsed_msg) => { 364 | // TODO: Handle the error case here! 365 | let data_str = 366 | serde_json::to_string(&parsed_msg.data).unwrap(); 367 | let data_len = data_str.len(); 368 | buf.advance(FP_HEADER_SZ + data_len); 369 | msgs.push(parsed_msg); 370 | Ok(()) 371 | } 372 | Err(FastParseError::NotEnoughBytes(_)) => { 373 | // Not enough bytes available yet so we need to return 374 | // Ok(None) to let the Framed instance know to read more 375 | // data before calling this function again. 376 | done = true; 377 | Ok(()) 378 | } 379 | Err(err) => { 380 | let msg = format!( 381 | "failed to parse Fast request: {}", 382 | Error::from(err) 383 | ); 384 | Err(Error::new(ErrorKind::Other, msg)) 385 | } 386 | }? 387 | } 388 | 389 | if msgs.is_empty() { 390 | Ok(None) 391 | } else { 392 | Ok(Some(msgs)) 393 | } 394 | } 395 | } 396 | 397 | impl Encoder for FastRpc { 398 | type Item = Vec; 399 | //TODO: Create custom FastMessage error type 400 | type Error = io::Error; 401 | fn encode( 402 | &mut self, 403 | item: Self::Item, 404 | buf: &mut BytesMut, 405 | ) -> Result<(), io::Error> { 406 | let results: Vec> = 407 | item.iter().map(|x| encode_msg(x, buf)).collect(); 408 | let result: Result, String> = results.iter().cloned().collect(); 409 | match result { 410 | Ok(_) => Ok(()), 411 | Err(errs) => Err(Error::new(ErrorKind::Other, errs)), 412 | } 413 | } 414 | } 415 | 416 | /// Encode a `FastMessage` into a byte buffer. The `Result` contains a unit type 417 | /// on success and an error string on failure. 418 | pub(crate) fn encode_msg( 419 | msg: &FastMessage, 420 | buf: &mut BytesMut, 421 | ) -> Result<(), String> { 422 | let m_msg_type_u8 = msg.msg_type.to_u8(); 423 | let m_status_u8 = msg.status.to_u8(); 424 | match (m_msg_type_u8, m_status_u8) { 425 | (Some(msg_type_u8), Some(status_u8)) => { 426 | // TODO: Handle the error case here! 427 | let data_str = serde_json::to_string(&msg.data).unwrap(); 428 | let data_len = data_str.len(); 429 | let buf_capacity = buf.capacity(); 430 | if buf.len() + FP_HEADER_SZ + data_len > buf_capacity { 431 | buf.reserve(FP_HEADER_SZ + data_len as usize); 432 | } 433 | buf.put_u8(FP_VERSION_CURRENT); 434 | buf.put_u8(msg_type_u8); 435 | buf.put_u8(status_u8); 436 | buf.put_u32_be(msg.id); 437 | buf.put_u32_be(u32::from(State::::calculate( 438 | data_str.as_bytes(), 439 | ))); 440 | buf.put_u32_be(data_str.len() as u32); 441 | buf.put(data_str); 442 | Ok(()) 443 | } 444 | (None, Some(_)) => Err(String::from("Invalid message type")), 445 | (Some(_), None) => Err(String::from("Invalid status")), 446 | (None, None) => Err(String::from("Invalid message type and status")), 447 | } 448 | } 449 | 450 | #[cfg(test)] 451 | mod test { 452 | use super::*; 453 | 454 | use std::iter; 455 | 456 | use quickcheck::{quickcheck, Arbitrary, Gen}; 457 | use rand::distributions::Alphanumeric; 458 | use rand::seq::SliceRandom; 459 | use rand::Rng; 460 | use serde_json::Map; 461 | 462 | fn random_string(g: &mut G, len: usize) -> String { 463 | iter::repeat(()) 464 | .map(|()| g.sample(Alphanumeric)) 465 | .take(len) 466 | .collect() 467 | } 468 | 469 | fn nested_object(g: &mut G) -> Value { 470 | let k_len = g.gen::() as usize; 471 | let v_len = g.gen::() as usize; 472 | let k = random_string(g, k_len); 473 | let v = random_string(g, v_len); 474 | let count = g.gen::(); 475 | let mut inner_obj = Map::new(); 476 | let mut outer_obj = Map::new(); 477 | let _ = inner_obj.insert(k, Value::String(v)); 478 | outer_obj 479 | .insert(String::from("value"), Value::Object(inner_obj)) 480 | .and_then(|_| { 481 | outer_obj.insert(String::from("count"), count.into()) 482 | }); 483 | Value::Object(outer_obj) 484 | } 485 | 486 | #[derive(Clone, Debug)] 487 | struct MessageCount(u8); 488 | 489 | impl Arbitrary for MessageCount { 490 | fn arbitrary(g: &mut G) -> MessageCount { 491 | let mut c = 0; 492 | while c == 0 { 493 | c = g.gen::() 494 | } 495 | 496 | MessageCount(c) 497 | } 498 | } 499 | 500 | impl Arbitrary for FastMessageStatus { 501 | fn arbitrary(g: &mut G) -> FastMessageStatus { 502 | let choices = [ 503 | FastMessageStatus::Data, 504 | FastMessageStatus::End, 505 | FastMessageStatus::Error, 506 | ]; 507 | 508 | choices.choose(g).unwrap().clone() 509 | } 510 | } 511 | 512 | impl Arbitrary for FastMessageType { 513 | fn arbitrary(g: &mut G) -> FastMessageType { 514 | let choices = [FastMessageType::Json]; 515 | 516 | choices.choose(g).unwrap().clone() 517 | } 518 | } 519 | 520 | impl Arbitrary for FastMessageMetaData { 521 | fn arbitrary(g: &mut G) -> FastMessageMetaData { 522 | let name = random_string(g, 10); 523 | FastMessageMetaData::new(name) 524 | } 525 | } 526 | 527 | impl Arbitrary for FastMessageData { 528 | fn arbitrary(g: &mut G) -> FastMessageData { 529 | let md = FastMessageMetaData::arbitrary(g); 530 | 531 | let choices = [ 532 | Value::Array(vec![]), 533 | Value::Object(Map::new()), 534 | nested_object(g), 535 | Value::Array(vec![nested_object(g)]), 536 | ]; 537 | 538 | let value = choices.choose(g).unwrap().clone(); 539 | 540 | FastMessageData { m: md, d: value } 541 | } 542 | } 543 | 544 | impl Arbitrary for FastMessage { 545 | fn arbitrary(g: &mut G) -> FastMessage { 546 | let msg_type = FastMessageType::arbitrary(g); 547 | let status = FastMessageStatus::arbitrary(g); 548 | let id = g.gen::(); 549 | 550 | let data = FastMessageData::arbitrary(g); 551 | let data_str = serde_json::to_string(&data).unwrap(); 552 | let msg_sz = match status { 553 | FastMessageStatus::End => None, 554 | _ => Some(FP_OFF_DATA + data_str.len()), 555 | }; 556 | 557 | FastMessage { 558 | msg_type, 559 | status, 560 | id, 561 | msg_size: msg_sz, 562 | data, 563 | } 564 | } 565 | } 566 | 567 | quickcheck! { 568 | fn prop_fast_message_roundtrip(msg: FastMessage) -> bool { 569 | let mut write_buf = BytesMut::new(); 570 | match encode_msg(&msg, &mut write_buf) { 571 | Ok(_) => { 572 | match FastMessage::parse(&write_buf) { 573 | Ok(decoded_msg) => decoded_msg == msg, 574 | Err(_) => false 575 | } 576 | }, 577 | Err(_) => false 578 | } 579 | } 580 | } 581 | 582 | quickcheck! { 583 | fn prop_fast_message_bundling(msg: FastMessage, msg_count: MessageCount) -> bool { 584 | let mut write_buf = BytesMut::new(); 585 | let mut error_occurred = false; 586 | for _ in 0..msg_count.0 { 587 | match encode_msg(&msg, &mut write_buf) { 588 | Ok(_) => (), 589 | Err(_) => { 590 | error_occurred = true; 591 | } 592 | } 593 | } 594 | 595 | if error_occurred { 596 | return false; 597 | } 598 | 599 | let msg_size = write_buf.len() / msg_count.0 as usize; 600 | let mut offset = 0; 601 | for _ in 0..msg_count.0 { 602 | match FastMessage::parse(&write_buf[offset..offset+msg_size]) { 603 | Ok(decoded_msg) => error_occurred = decoded_msg != msg, 604 | Err(_) => error_occurred = true 605 | } 606 | offset += msg_size; 607 | } 608 | 609 | !error_occurred 610 | } 611 | } 612 | 613 | quickcheck! { 614 | fn prop_fast_message_decoding(msg: FastMessage, msg_count: MessageCount) -> bool { 615 | let mut write_buf = BytesMut::new(); 616 | let mut error_occurred = false; 617 | let mut fast_msgs: Vec = 618 | Vec::with_capacity(msg_count.0 as usize); 619 | 620 | (0..msg_count.0).for_each(|_| { 621 | fast_msgs.push(msg.clone()); 622 | }); 623 | 624 | let mut fast_rpc = FastRpc; 625 | let encode_res = fast_rpc.encode(fast_msgs, &mut write_buf); 626 | 627 | if encode_res.is_err() { 628 | return false; 629 | } 630 | 631 | let decode_result = fast_rpc.decode(&mut write_buf); 632 | if decode_result.is_err() { 633 | return false; 634 | } 635 | 636 | let m_decoded_msgs = decode_result.unwrap(); 637 | 638 | 639 | if m_decoded_msgs.is_none() { 640 | return false; 641 | } 642 | 643 | let decoded_msgs = m_decoded_msgs.unwrap(); 644 | if decoded_msgs.len() != msg_count.0 as usize { 645 | return false; 646 | } 647 | 648 | 649 | for decoded_msg in decoded_msgs { 650 | error_occurred = decoded_msg != msg; 651 | } 652 | 653 | !error_occurred 654 | } 655 | } 656 | } 657 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joyent, Inc. 2 | 3 | //! This module provides the interface for creating Fast servers. 4 | 5 | use std::io::Error; 6 | 7 | use serde_json::json; 8 | use slog::{debug, error, o, Drain, Logger}; 9 | use tokio; 10 | use tokio::codec::Decoder; 11 | use tokio::net::TcpStream; 12 | use tokio::prelude::*; 13 | 14 | use crate::protocol::{FastMessage, FastMessageData, FastRpc}; 15 | 16 | /// Create a task to be used by the tokio runtime for handling responses to Fast 17 | /// protocol requests. 18 | pub fn make_task( 19 | socket: TcpStream, 20 | mut response_handler: F, 21 | log: Option<&Logger>, 22 | ) -> impl Future + Send 23 | where 24 | F: FnMut(&FastMessage, &Logger) -> Result, Error> + Send, 25 | { 26 | let (tx, rx) = FastRpc.framed(socket).split(); 27 | 28 | // If no logger was provided use the slog StdLog drain by default 29 | let rx_log = log 30 | .cloned() 31 | .unwrap_or_else(|| Logger::root(slog_stdlog::StdLog.fuse(), o!())); 32 | 33 | let tx_log = rx_log.clone(); 34 | tx.send_all(rx.and_then(move |x| { 35 | debug!(rx_log, "processing fast message"); 36 | respond(x, &mut response_handler, &rx_log) 37 | })) 38 | .then(move |res| { 39 | if let Err(e) = res { 40 | error!(tx_log, "failed to process connection"; "err" => %e); 41 | } 42 | 43 | debug!(tx_log, "transmitted response to client"); 44 | Ok(()) 45 | }) 46 | } 47 | 48 | fn respond( 49 | msgs: Vec, 50 | response_handler: &mut F, 51 | log: &Logger, 52 | ) -> impl Future, Error = Error> + Send 53 | where 54 | F: FnMut(&FastMessage, &Logger) -> Result, Error> + Send, 55 | { 56 | debug!(log, "responding to {} messages", msgs.len()); 57 | 58 | let mut responses: Vec = Vec::new(); 59 | 60 | for msg in msgs { 61 | match response_handler(&msg, &log) { 62 | Ok(mut response) => { 63 | // Make sure there is room in responses to fit another response plus an 64 | // end message 65 | let responses_len = responses.len(); 66 | let response_len = response.len(); 67 | let responses_capacity = responses.capacity(); 68 | if responses_len + response_len > responses_capacity { 69 | let needed_capacity = 70 | responses_len + response_len - responses_capacity; 71 | responses.reserve(needed_capacity); 72 | } 73 | 74 | // Add all response messages for this message to the vector of 75 | // all responses 76 | response.drain(..).for_each(|r| { 77 | responses.push(r); 78 | }); 79 | 80 | debug!(log, "generated response"); 81 | let method = msg.data.m.name.clone(); 82 | responses.push(FastMessage::end(msg.id, method)); 83 | } 84 | Err(err) => { 85 | let method = msg.data.m.name.clone(); 86 | let value = json!({ 87 | "name": "FastError", 88 | "message": err.to_string() 89 | }); 90 | 91 | let err_msg = FastMessage::error( 92 | msg.id, 93 | FastMessageData::new(method, value), 94 | ); 95 | responses.push(err_msg); 96 | } 97 | } 98 | } 99 | 100 | Box::new(future::ok(responses)) 101 | } 102 | -------------------------------------------------------------------------------- /tests/client_server_test.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joyent, Inc. 2 | 3 | use std::io::{Error, ErrorKind}; 4 | use std::net::{Shutdown, SocketAddr, TcpStream}; 5 | use std::process; 6 | use std::sync::{Arc, Barrier, Mutex}; 7 | use std::thread; 8 | 9 | use serde_json::Value; 10 | use slog::{debug, error, info, o, Drain, Logger}; 11 | use tokio::net::TcpListener; 12 | use tokio::prelude::*; 13 | 14 | use fast_rpc::client; 15 | use fast_rpc::protocol::{FastMessage, FastMessageId}; 16 | use fast_rpc::server; 17 | 18 | fn echo_handler( 19 | msg: &FastMessage, 20 | mut response: Vec, 21 | log: &Logger, 22 | ) -> Result, Error> { 23 | debug!(log, "handling echo function request"); 24 | response.push(FastMessage::data(msg.id, msg.data.clone())); 25 | Ok(response) 26 | } 27 | 28 | fn msg_handler( 29 | msg: &FastMessage, 30 | log: &Logger, 31 | ) -> Result, Error> { 32 | let response: Vec = vec![]; 33 | 34 | match msg.data.m.name.as_str() { 35 | "echo" => echo_handler(msg, response, &log), 36 | _ => Err(Error::new( 37 | ErrorKind::Other, 38 | format!("Unsupported function: {}", msg.data.m.name), 39 | )), 40 | } 41 | } 42 | 43 | fn run_server(barrier: Arc) { 44 | let plain = slog_term::PlainSyncDecorator::new(std::io::stdout()); 45 | let root_log = Logger::root( 46 | Mutex::new(slog_term::FullFormat::new(plain).build()).fuse(), 47 | o!("build-id" => "0.1.0"), 48 | ); 49 | 50 | let addr_str = "127.0.0.1:56652".to_string(); 51 | match addr_str.parse::() { 52 | Ok(addr) => { 53 | let listener = TcpListener::bind(&addr).expect("failed to bind"); 54 | info!(root_log, "listening for fast requests"; "address" => addr); 55 | 56 | barrier.wait(); 57 | 58 | tokio::run({ 59 | let process_log = root_log.clone(); 60 | let err_log = root_log.clone(); 61 | listener 62 | .incoming() 63 | .map_err(move |e| error!(&err_log, "failed to accept socket"; "err" => %e)) 64 | .for_each(move |socket| { 65 | let task = server::make_task(socket, msg_handler, Some(&process_log)); 66 | tokio::spawn(task); 67 | Ok(()) 68 | }) 69 | }) 70 | } 71 | Err(e) => { 72 | eprintln!("error parsing address: {}", e); 73 | } 74 | } 75 | } 76 | 77 | fn assert_handler(expected_data_size: usize) -> impl Fn(&FastMessage) { 78 | move |msg| { 79 | let data: Vec = 80 | serde_json::from_value(msg.data.d.clone()).unwrap(); 81 | assert_eq!(data.len(), 1); 82 | assert_eq!(data[0].len(), expected_data_size); 83 | } 84 | } 85 | 86 | fn response_handler( 87 | data_size: usize, 88 | ) -> impl Fn(&FastMessage) -> Result<(), Error> { 89 | let handler = assert_handler(data_size); 90 | move |msg| { 91 | handler(msg); 92 | Ok(()) 93 | } 94 | } 95 | 96 | #[test] 97 | fn client_server_comms() { 98 | let barrier = Arc::new(Barrier::new(2)); 99 | let barrier_clone = barrier.clone(); 100 | let _h_server = thread::spawn(move || run_server(barrier_clone)); 101 | 102 | barrier.clone().wait(); 103 | 104 | let addr_str = "127.0.0.1:56652".to_string(); 105 | let addr = addr_str.parse::().unwrap(); 106 | 107 | let mut stream = TcpStream::connect(&addr).unwrap_or_else(|e| { 108 | eprintln!("Failed to connect to server: {}", e); 109 | process::exit(1) 110 | }); 111 | 112 | (1..100).for_each(|x| { 113 | let data_size = x * 1000; 114 | let method = String::from("echo"); 115 | let args_str = ["[\"", &"a".repeat(data_size), "\"]"].concat(); 116 | let args: Value = serde_json::from_str(&args_str).unwrap(); 117 | let handler = response_handler(data_size); 118 | let mut msg_id = FastMessageId::new(); 119 | let result = client::send(method, args, &mut msg_id, &mut stream) 120 | .and_then(|_bytes_written| client::receive(&mut stream, handler)); 121 | 122 | assert!(result.is_ok()); 123 | }); 124 | 125 | let shutdown_result = stream.shutdown(Shutdown::Both); 126 | 127 | assert!(shutdown_result.is_ok()); 128 | } 129 | --------------------------------------------------------------------------------