├── .gitignore ├── CHANGES.md ├── COPYING ├── COPYING.LESSER ├── Makefile.in ├── README.md ├── autogen.sh ├── c_src ├── Makefile.in ├── build_czmq.sh ├── czmq_benchmark.c ├── czmq_port.c ├── erl_czmq.c ├── erl_czmq.h ├── vector.c └── vector.h ├── configure ├── configure.ac ├── erlang.mk ├── include └── czmq.hrl ├── priv └── .dummy ├── rebar.config ├── rebar.config.script └── src ├── Makefile ├── czmq.app.src ├── czmq.erl ├── czmq_benchmark.erl ├── czmq_poller.erl ├── czmq_reloader.erl ├── czmq_test.erl ├── erlzmq_benchmark.erl ├── ezmq_benchmark.erl └── zmq_gen_benchmark.erl /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.beam 4 | *.bak 5 | ebin 6 | priv/czmq-port 7 | priv/czmq-benchmark 8 | autom4te.cache 9 | config.log 10 | config.status 11 | Makefile 12 | c_src/Makefile 13 | c_src/.libs 14 | c_src/.dists 15 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Release History 2 | 3 | ## 0.1.0 4 | 5 | First release containining stable features. 6 | 7 | - One CZMQ context per external C port 8 | - Create and destroy sockets 9 | - Bind and connect operations 10 | - zstr (single part, string oriented messages) send and receive operations 11 | - Multipart send and receive operations 12 | - Most auth operations (i.e. black and white lists, BASIC, and CURVE - may 13 | be incomplete for edge cases) 14 | - Polling mechanism for pull messages into Erlang process mailboxes 15 | - Partial support for socket options (edge cases not yet supported) 16 | - Relatively complete test suite 17 | - Benchmarking tools 18 | - Cross platform build support including static or dynamic builds 19 | - Integration with rebar builds 20 | 21 | Thanks to Benoit Chesneau (benoitc) for driving this release forward! 22 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /COPYING.LESSER: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- 168 | 169 | STATIC LINK EXCEPTION 170 | 171 | As a special exception, the Authors give you permission to link this library with 172 | independent modules to produce an executable, regardless of the license terms 173 | of these independent modules, and to copy and distribute the resulting 174 | executable under terms of your choice, provided that you also meet, for each 175 | linked independent module, the terms and conditions of the license of that 176 | module. An independent module is a module which is not derived from or based on 177 | this library. If you modify this library, you must extend this exception to your 178 | version of the library. 179 | 180 | Note: this exception relieves you of any obligations under sections 4 and 5 181 | of this license, and section 6 of the GNU General Public License. 182 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | PROJECT = czmq 2 | COMPILE_FIRST = zmq_gen_benchmark 3 | 4 | include erlang.mk 5 | 6 | app:: 7 | cd c_src; make 8 | 9 | clean:: 10 | cd c_src; make clean 11 | 12 | ERL ?= erl 13 | test: 14 | @$(ERL) -pa ebin -noshell -eval 'czmq_test:test()' -s init stop 15 | 16 | opts= 17 | shell: app 18 | erl -pa ebin -s czmq_reloader ${opts} 19 | 20 | check: app 21 | erl -eval "czmq_test:test()" -s init stop -noshell -pa ebin 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Erlang to CZMQ Bindings 2 | 3 | Goals: 4 | 5 | - Provide a canonical CZMQ interface from Erlang 6 | - Safe: bugs, errors, assertion failures in CZMQ must not crash Erlang 7 | - Reasonably performant 8 | 9 | Non Goals: 10 | 11 | - Value atop CZMQ - this is left to applications 12 | - Performance at the expense of safety 13 | 14 | ## Approach 15 | 16 | The "bindings" (this is a lose term given the approach here) are 17 | implemented as a C Port to ensure that crashes don't effect the Erlang VM. 18 | 19 | The API mirrors that of CZMQ with all functions being available through the 20 | `czmq`module. 21 | 22 | ## Building erlang-czmq 23 | 24 | $ git clone https://github.com/gar1t/erlang-czmq.git 25 | $ cd erlang-czmq 26 | $ ./configure 27 | $ make check 28 | 29 | This will build the library and run the tests. 30 | 31 | Please report and problems by opening an issue here: 32 | 33 | https://github.com/gar1t/erlang-czmq/issues 34 | 35 | ### Port to CZMQ Mapping 36 | 37 | The port manages a single ZMQ context. All context managed state is associated 38 | with a port. 39 | 40 | The port manages its state appropriately: 41 | 42 | - ZMQ context 43 | - Sockets 44 | - Auth object (limited to one per context) 45 | - Certs 46 | 47 | We use dynamic arrays (vectors) to store references to ZMQ and CZMQ 48 | objects. Objects are referenced using their array index. 49 | 50 | When an object is destroyed, it's associated element in the applicable array is 51 | set to NULL. 52 | 53 | ### Sockets - Sending and Receiving 54 | 55 | Erlang C ports use a synchronous request/response protocol over standard input 56 | output. This makes them unsuitable for handling the asynchronous events that 57 | are endemic to ZeroMQ. Received events must be routinely polled using non 58 | blocking operations. 59 | 60 | Messages can be checked explicitly or routinely using `czmq_poller`. Messages 61 | received by `czmq_poller` can be delivered as Erlang messages to another 62 | process, effectively simulating asynchronous message delivery, albiet with some 63 | latency introduced by the polling sleep interval. 64 | 65 | ## Using `erlang-czmq` 66 | 67 | ## Benchmarks 68 | 69 | While safety is the prime consideration for this binding, performance is 70 | important as well. `erlang-czmq` provides a simple framework for measuring 71 | message send/receive throughput using different bindings. 72 | 73 | Benchmarks follow this approach: 74 | 75 | - A receiver binds to a local port and receives messages as quickly as it can, 76 | printing the number of received messages per second. 77 | - A sender connects to the receiver port and sends messages as quickly as it 78 | can for a period of time. 79 | 80 | This scheme can be used to test different combinations of bindings for sending 81 | and receiving. Below are some preliminary results, which are useful as a rough 82 | gage for the relative performance differences of bindings. 83 | 84 | ### C Receiver / C Sender 85 | 86 | To test the native (i.e C) performance of CZMQ, use `czmq-benchmark` located in 87 | `priv` after compiling `erlang-czmq`. First, start the receiver: 88 | 89 | $ cd erlang-czmq/priv 90 | $ ./czmq-benchmark recv 91 | 92 | The receiver will print the total number of messages it receives for each 93 | interval. 94 | 95 | Next, in a separate shell, start the sender: 96 | 97 | $ cd erlang-czmq/priv 98 | $ ./czmq-benchmark send 99 | 100 | You will see the number of messages the receiver received during the time the 101 | sender was sending. Discard the first and last observations as they reflect 102 | partial intervals. 103 | 104 | ### C Receiver / erlang-czmq Sender 105 | 106 | This test measures the throughput of using an `erlang-czmq` sender with a C 107 | receiver. 108 | 109 | Start the receiver as with the C / C test above. 110 | 111 | Next, 112 | 113 | ### Benchark Summary - Lenovo X220 at 2.7 GHz 114 | 115 | +---------------------------+-------------+---+ 116 | | Recv / Send | Average MPS | N | 117 | |---------------------------|-------------|---| 118 | | C / C | 1190500 | 5 | 119 | | C / erlzmq | 128136 | 5 | 120 | | C / erlang-czmq | 35990 | 5 | 121 | | erlzmq / C | 152678 | 5 | 122 | | erlang-czmq / C | 10126 | 5 | 123 | | erlzmq / erlzmq | 134234 | 5 | 124 | | erlang-czmq / erlang-czmq | 9614 | 5 | 125 | +---------------------------+-------------+---+ 126 | 127 | ## Versioning Scheme 128 | 129 | This project uses the traditional versioning scheme: 130 | 131 | MAJOR "." MINOR "." REVISION 132 | 133 | After major version "1", changes to Major represent potential API changes. 134 | 135 | Minor version increments represent major milestones and will be documented. 136 | 137 | Revision increments represent minor milestones including bug fixes. 138 | 139 | Versions will be indicated using tags. 140 | 141 | Changes are documented in CHANGES.md. 142 | 143 | ## Releases 144 | 145 | This project doesn't make formal releases by instead tags git commits using the 146 | versioning scheme above. 147 | 148 | To increment a revision: 149 | 150 | 1. Change the `vsn` in src/czmq.app.src 151 | 2. Add an entry to CHANGES.md with reasonable detail to help users understand 152 | what _changed_ in between the new documented release and the previous 153 | documented release 154 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | autoconf configure.ac > configure 3 | chmod +x configure 4 | -------------------------------------------------------------------------------- /c_src/Makefile.in: -------------------------------------------------------------------------------- 1 | UNAME := $(shell uname) 2 | 3 | CC = @CC@ 4 | CFLAGS = -Wall -g2 5 | INCLUDES = -I@ERLANG_LIB_DIR_erl_interface@/include 6 | LFLAGS = -L@ERLANG_LIB_DIR_erl_interface@/lib -lstdc++ 7 | ifneq ($(UNAME), Darwin) 8 | LFLAGS += -lrt 9 | endif 10 | LIBS = -lerl_interface -lei -lpthread -lczmq -lzmq 11 | STATIC_DIR = $(shell pwd)/.libs 12 | BUILD_CZMQ = 13 | ENABLE_STATIC = @enable_static@ 14 | 15 | ifeq ($(ENABLE_STATIC), yes) 16 | INCLUDES = -I@ERLANG_LIB_DIR_erl_interface@/include -I$(STATIC_DIR)/libsodium/include -I$(STATIC_DIR)/libzmq/include -I$(STATIC_DIR)/czmq/include 17 | LIBS = -lerl_interface -lei -lpthread -lstdc++ $(STATIC_DIR)/czmq/lib/libczmq.a $(STATIC_DIR)/libzmq/lib/libzmq.a $(STATIC_DIR)/libsodium/lib/libsodium.a -lstdc++ 18 | BUILD_CZMQ = buildcmq 19 | endif 20 | 21 | 22 | SRCS = $(wildcard *.c) 23 | 24 | BIN_DIR = ../priv 25 | 26 | CZMQ_PORT = $(BIN_DIR)/czmq-port 27 | CZMQ_PORT_OBJS = czmq_port.o erl_czmq.o vector.o 28 | 29 | CZMQ_BENCHMARK = $(BIN_DIR)/czmq-benchmark 30 | CZMQ_BENCHMARK_OBJS = czmq_benchmark.o erl_czmq.o vector.o 31 | 32 | .PHONY: depend clean 33 | 34 | all: $(BUILD_CZMQ) $(CZMQ_PORT) $(CZMQ_BENCHMARK) 35 | 36 | buildcmq: 37 | ./build_czmq.sh 38 | 39 | $(CZMQ_PORT): $(CZMQ_PORT_OBJS) 40 | $(CC) $(CFLAGS) $(INCLUDES) -o $(CZMQ_PORT) $(CZMQ_PORT_OBJS) \ 41 | $(LFLAGS) $(LIBS) 42 | 43 | $(CZMQ_BENCHMARK): $(CZMQ_BENCHMARK_OBJS) 44 | $(CC) $(CFLAGS) $(INCLUDES) -o $(CZMQ_BENCHMARK) $(CZMQ_BENCHMARK_OBJS) \ 45 | $(LFLAGS) $(LIBS) 46 | 47 | .c.o: 48 | $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ 49 | 50 | clean: 51 | $(RM) *.o *~ $(CZMQ_PORT) $(CZMQ_BENCHMARK) 52 | ./build_czmq.sh clean 53 | 54 | depend: $(SRCS) 55 | makedepend -Y. $^ 2>/dev/null 56 | 57 | # DO NOT DELETE THIS LINE -- make depend needs it 58 | 59 | vector.o: vector.h 60 | -------------------------------------------------------------------------------- /c_src/build_czmq.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | if [ "x$CORE_TOP" = "x" ]; then 4 | CORE_TOP=`pwd` 5 | export CORE_TOP 6 | fi 7 | 8 | CURLBIN=`which curl` 9 | if [ -z "$CURLBIN" ]; then 10 | echo "Error: curl is required. Add it to 'PATH'" 11 | exit 1 12 | fi 13 | 14 | GUNZIP=`which gunzip` 15 | UNZIP=`which unzip` 16 | TAR=`which tar` 17 | GNUMAKE=`which gmake 2>/dev/null || which make` 18 | 19 | STATICLIBS=$CORE_TOP/.libs 20 | DISTDIR=$CORE_TOP/.dists 21 | 22 | LIBSODIUM_VER=1.0.0 23 | LIBSODIUM_DISTNAME=libsodium-${LIBSODIUM_VER}.tar.gz 24 | LIBSODIUM_SITE=https://github.com/jedisct1/libsodium/releases/download/${LIBSODIUM_VER}/ 25 | LIBSODIUM_DIR=$STATICLIBS/libsodium 26 | 27 | LIBZMQ_VER=4.0.4 28 | LIBZMQ_DISTNAME=zeromq-${LIBZMQ_VER}.tar.gz 29 | LIBZMQ_SITE=https://archive.org/download/zeromq_${LIBZMQ_VER} 30 | LIBZMQ_DIR=$STATICLIBS/libzmq 31 | 32 | CZMQ_VER=2.2.0 33 | CZMQ_DISTNAME=czmq-${CZMQ_VER}.tar.gz 34 | CZMQ_SITE=https://archive.org/download/zeromq_czmq_${CZMQ_VER} 35 | CZMQ_DIR=$STATICLIBS/czmq 36 | 37 | [ "$MACHINE" ] || MACHINE=`(uname -m) 2>/dev/null` || MACHINE="unknown" 38 | [ "$RELEASE" ] || RELEASE=`(uname -r) 2>/dev/null` || RELEASE="unknown" 39 | [ "$SYSTEM" ] || SYSTEM=`(uname -s) 2>/dev/null` || SYSTEM="unknown" 40 | [ "$BUILD" ] || VERSION=`(uname -v) 2>/dev/null` || VERSION="unknown" 41 | 42 | # find arch 43 | PATCH=patch 44 | case "$SYSTEM" in 45 | Linux) 46 | ARCH=`arch 2>/dev/null` 47 | ;; 48 | FreeBSD|OpenBSD|NetBSD) 49 | ARCH=`(uname -p) 2>/dev/null` 50 | ;; 51 | Darwin) 52 | ARCH=`(uname -p) 2>/dev/null` 53 | ;; 54 | Solaris) 55 | ARCH=`(uname -p) 2>/dev/null` 56 | PATCH=gpatch 57 | ;; 58 | *) 59 | ARCH="unknown" 60 | ;; 61 | esac 62 | 63 | CFLAGS="-g -O2 -Wall" 64 | LDFLAGS="-lstdc++" 65 | 66 | # TODO: add mirror & signature validation support 67 | fetch() 68 | { 69 | TARGET=$DISTDIR/$1 70 | if ! test -f $TARGET; then 71 | echo "==> Fetch $1 to $TARGET" 72 | $CURLBIN --progress-bar -fL $2/$1 -o $TARGET 73 | fi 74 | } 75 | 76 | build_libsodium() 77 | { 78 | fetch $LIBSODIUM_DISTNAME $LIBSODIUM_SITE 79 | echo "==> build libsodium" 80 | 81 | cd $STATICLIBS 82 | if ! test -f $STATICLIBS/libsodium-${LIBSODIUM_VER}; then 83 | $GUNZIP -c $DISTDIR/$LIBSODIUM_DISTNAME | $TAR xf - 84 | fi 85 | 86 | cd $STATICLIBS/libsodium-${LIBSODIUM_VER} 87 | if ! test -f config.status; then 88 | ./configure --prefix=$LIBSODIUM_DIR \ 89 | --disable-debug \ 90 | --disable-dependency-tracking \ 91 | --disable-silent-rules 92 | fi 93 | make && make install || exit 1 94 | } 95 | 96 | build_libzmq() 97 | { 98 | fetch $LIBZMQ_DISTNAME $LIBZMQ_SITE 99 | echo "==> build libzmq" 100 | 101 | cd $STATICLIBS 102 | if ! test -f $STATICLIBS/zeromq-${LIBZMQ_VER}; then 103 | $GUNZIP -c $DISTDIR/$LIBZMQ_DISTNAME | $TAR xf - 104 | fi 105 | 106 | cd $STATICLIBS/zeromq-${LIBZMQ_VER} 107 | if ! test -f config.status; then 108 | env CFLAGS="$CFLAGS -I$LIBSODIUM_DIR/include" \ 109 | LDFLAGS="-L$LIBSODIUM_DIR/lib -lstdc++ " \ 110 | CPPFLAGS="-Wno-long-long" \ 111 | ./configure --prefix=$LIBZMQ_DIR \ 112 | --disable-dependency-tracking \ 113 | --enable-static \ 114 | --with-libsodium=$LIBSODIUM_DIR \ 115 | --disable-silent-rules 116 | fi 117 | make && make install || exit 1 118 | } 119 | 120 | build_czmq() 121 | { 122 | fetch $CZMQ_DISTNAME $CZMQ_SITE 123 | echo "==> build czmq" 124 | 125 | cd $STATICLIBS 126 | if ! test -f $STATICLIBS/czmq-${CZMQ_VER}; then 127 | $GUNZIP -c $DISTDIR/$CZMQ_DISTNAME | $TAR xf - 128 | fi 129 | 130 | echo $LIBZMQ_DIR 131 | cd $STATICLIBS/czmq-${CZMQ_VER} 132 | 133 | if ! test -f config.status; then 134 | env CFLAGS="-I$LIBSODIUM_DIR/include -I$LIBZMQ_DIR/include" \ 135 | LDFLAGS="-lstdc++ -lpthread -L$LIBSODIUM_DIR/lib -L$LIBZMQ_DIR/lib -lstdc++" \ 136 | ./configure --prefix=$CZMQ_DIR \ 137 | --disable-dependency-tracking \ 138 | --enable-static \ 139 | --with-libsodium=$LIBSODIUM_DIR \ 140 | --with-libzmq=$LIBZMQ_DIR \ 141 | --disable-silent-rules 142 | fi 143 | make && make install || exit 1 144 | } 145 | 146 | do_build() 147 | { 148 | mkdir -p $DISTDIR 149 | mkdir -p $STATICLIBS 150 | 151 | if [ ! -f $LIBSODIUM_DIR/lib/libsodium.a ]; then 152 | build_libsodium 153 | fi 154 | 155 | if [ ! -f $LIBZMQ_DIR/lib/libzmq.a ]; then 156 | build_libzmq 157 | fi 158 | 159 | if [ ! -f $CZMQ_DIR/lib/libczmq.a ]; then 160 | build_czmq 161 | fi 162 | } 163 | 164 | clean() 165 | { 166 | rm -rf $STATICLIBS 167 | rm -rf $DISTDIR 168 | } 169 | 170 | usage() 171 | { 172 | cat << EOF 173 | Usage: $basename [command] [OPTIONS] 174 | 175 | The $basename command compile czmq statically 176 | 177 | Commands: 178 | 179 | all: build static libs 180 | clean: clean static libs 181 | -?: display usage 182 | 183 | Report bugs at . 184 | EOF 185 | } 186 | 187 | if [ "x$1" = "x" ]; then 188 | do_build 189 | exit 0 190 | fi 191 | 192 | case "$1" in 193 | all) 194 | shift 1 195 | do_build 196 | ;; 197 | clean) 198 | shift 1 199 | clean 200 | ;; 201 | help|--help|-h|-?) 202 | usage 203 | exit 0 204 | ;; 205 | *) 206 | echo $basename: ERROR Unknown command $arg 1>&2 207 | echo 1>&2 208 | usage 1>&2 209 | echo "### $basename: Exitting." 1>&2 210 | exit 1; 211 | ;; 212 | esac 213 | 214 | exit 0 215 | -------------------------------------------------------------------------------- /c_src/czmq_benchmark.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012, 2013 Garrett Smith 2 | // 3 | // This program is free software: you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation, either version 3 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program. If not, see . 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #ifdef __MACH__ 23 | #include 24 | #define CLOCK_REALTIME 0 25 | #define CLOCK_MONOTONIC 0 26 | int clock_gettime(int clk_id, struct timespec *t){ 27 | mach_timebase_info_data_t timebase; 28 | mach_timebase_info(&timebase); 29 | uint64_t time; 30 | time = mach_absolute_time(); 31 | double nseconds = ((double)time * (double)timebase.numer)/((double)timebase.denom); 32 | double seconds = ((double)time * (double)timebase.numer)/((double)timebase.denom * 1e9); 33 | t->tv_sec = seconds; 34 | t->tv_nsec = nseconds; 35 | return 0; 36 | } 37 | #else 38 | #include 39 | #endif 40 | 41 | #include "czmq.h" 42 | 43 | #define default_port 5555 44 | #define default_time 5 45 | #define default_msg_size 512 46 | #define default_send_socket_type ZMQ_PUSH; 47 | #define default_recv_socket_type ZMQ_PULL; 48 | 49 | typedef struct { 50 | uint port; 51 | uint time; 52 | int socket_type; 53 | ulong msg_size; 54 | } benchmark_options; 55 | 56 | static void print_usage() { 57 | printf("Usage: czmq-benchmark [OPTION] COMMAND\n"); 58 | printf("\n"); 59 | printf("Commands:\n"); 60 | printf(" send send messages to PORT for TIME seconds\n"); 61 | printf(" recv receive messages on PORT for TIME seconds\n"); 62 | printf("\n"); 63 | printf("Options:\n"); 64 | printf(" -p PORT port to send to / listen on (default is %i)\n", 65 | default_port); 66 | printf(" -t TIME seconds to sendfor (default is %i)\n", 67 | default_time); 68 | printf(" -s MSG_SIZE message size in bytes (default is %i)\n", 69 | default_msg_size); 70 | printf(" -h print this message and exit\n"); 71 | } 72 | 73 | static char rand_char() { 74 | // printable ascii range: 32 - 126 (94 chars) 75 | //int rand94 = 94 * (rand() / (RAND_MAX + 1.0)); 76 | //return (char)(32 + rand94); 77 | return '!'; // temp simplification to work around segfault 78 | // for large strings (related to getting string len) 79 | } 80 | 81 | static char *create_message(ulong size) { 82 | char *msg = malloc(size + 1); 83 | ulong i; 84 | for (i = 0; i < size; i++) { 85 | msg[i] = rand_char(); 86 | } 87 | msg[size] = '\0'; 88 | return msg; 89 | } 90 | 91 | static long now_ms() { 92 | struct timespec spec; 93 | int rc = clock_gettime(CLOCK_REALTIME, &spec); 94 | assert(rc == 0); 95 | return spec.tv_sec * 1000 + (spec.tv_nsec / 1.0e6); 96 | } 97 | 98 | static void send_messages(benchmark_options *options) { 99 | zctx_t *ctx = zctx_new(); 100 | assert (ctx); 101 | 102 | void *socket = zsocket_new(ctx, options->socket_type); 103 | assert(socket); 104 | int rc = zsocket_connect(socket, "tcp://localhost:%i", options->port); 105 | assert(rc == 0); 106 | 107 | char *msg = create_message(options->msg_size); 108 | 109 | long now = now_ms(); 110 | long stop = now + options->time * 1000; 111 | 112 | while (now < stop) { 113 | zstr_send(socket, msg); 114 | now = now_ms(); 115 | } 116 | 117 | free(msg); 118 | 119 | sleep(1); 120 | zctx_destroy(&ctx); 121 | } 122 | 123 | static int recv_loop; 124 | 125 | static void stop_recv(int sig) { 126 | recv_loop = 0; 127 | } 128 | 129 | static void recv_messages(benchmark_options *options) { 130 | zctx_t *ctx = zctx_new(); 131 | assert (ctx); 132 | 133 | void *socket = zsocket_new(ctx, options->socket_type); 134 | assert(socket); 135 | int rc = zsocket_bind(socket, "tcp://*:%i", options->port); 136 | if (rc == -1) { 137 | printf("Error binding to port %i\n", options->port); 138 | exit(1); 139 | } 140 | 141 | long last_log = now_ms(), now; 142 | int msg_count = 0; 143 | char *msg; 144 | 145 | recv_loop = 1; 146 | signal(SIGINT, stop_recv); 147 | 148 | while (recv_loop) { 149 | now = now_ms(); 150 | if (now - last_log >= 1000) { 151 | printf("%li %i\n", now, msg_count); 152 | last_log = now; 153 | msg_count = 0; 154 | } 155 | while (1) { 156 | msg = zstr_recv_nowait(socket); 157 | if (!msg) { 158 | break; 159 | } 160 | msg_count++; 161 | free(msg); 162 | } 163 | usleep(100); 164 | } 165 | 166 | sleep(1); 167 | zctx_destroy(&ctx); 168 | } 169 | 170 | int main(int argc, char *argv[]) { 171 | char *port_arg = NULL; 172 | char *time_arg = NULL; 173 | char *msg_size_arg = NULL; 174 | int c; 175 | 176 | while ((c = getopt (argc, argv, "hp:t:s:")) != -1) 177 | switch (c) 178 | { 179 | case 'h': 180 | print_usage(); 181 | return 0; 182 | case 'p': 183 | port_arg = optarg; 184 | break; 185 | case 't': 186 | time_arg = optarg; 187 | break; 188 | case 's': 189 | msg_size_arg = optarg; 190 | break; 191 | default: 192 | print_usage(); 193 | return 1; 194 | } 195 | 196 | if (optind != (argc - 1)) { 197 | print_usage(); 198 | return 1; 199 | } 200 | 201 | char *cmd_arg = argv[optind]; 202 | 203 | benchmark_options options; 204 | uint int_val; 205 | ulong long_val; 206 | 207 | // port 208 | if (port_arg) { 209 | if (sscanf(port_arg, "%u", &int_val) != 1) { 210 | printf("Invalid port value %s\n", port_arg); 211 | return 1; 212 | } 213 | options.port = int_val; 214 | } else { 215 | options.port = default_port; 216 | } 217 | 218 | // time 219 | if (time_arg) { 220 | if (sscanf(time_arg, "%u", &int_val) != 1) { 221 | printf("Invalid time value %s\n", time_arg); 222 | return 1; 223 | } 224 | options.time = int_val; 225 | } else { 226 | options.time = default_time; 227 | } 228 | 229 | // msg_size 230 | if (msg_size_arg) { 231 | if (sscanf(msg_size_arg, "%lu", &long_val) != 1) { 232 | printf("Invalid msg size value %s\n", msg_size_arg); 233 | return 1; 234 | } 235 | options.msg_size = long_val; 236 | } else { 237 | options.msg_size = default_msg_size; 238 | } 239 | 240 | if (strcmp(cmd_arg, "send") == 0) { 241 | options.socket_type = default_send_socket_type; 242 | send_messages(&options); 243 | return 0; 244 | } else if (strcmp(cmd_arg, "recv") == 0) { 245 | options.socket_type = default_recv_socket_type; 246 | recv_messages(&options); 247 | return 0; 248 | } else { 249 | print_usage(); 250 | return 1; 251 | } 252 | 253 | printf("port_arg = %s, time_arg = %s, cmd = %s\n", 254 | port_arg, time_arg, cmd_arg); 255 | 256 | return 0; 257 | } 258 | -------------------------------------------------------------------------------- /c_src/czmq_port.c: -------------------------------------------------------------------------------- 1 | /* ========================================================================= 2 | czmq_port - An Erlang port wrapper for CZMQ 3 | 4 | ------------------------------------------------------------------------- 5 | Copyright (c) 2013-214 Garrett Smith 6 | Copyright other contributors as noted in the AUTHORS file. 7 | 8 | This file is part of erlang-czmq: https://github.com/gar1t/erlang-czmq 9 | 10 | This is free software; you can redistribute it and/or modify it under 11 | the terms of the GNU Lesser General Public License as published by the 12 | Free Software Foundation; either version 3 of the License, or (at your 13 | option) any later version. 14 | 15 | This software is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABIL- 17 | ITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General 18 | Public License for more details. 19 | 20 | You should have received a copy of the GNU Lesser General Public License 21 | along with this program. If not, see . 22 | ========================================================================= 23 | */ 24 | 25 | #include "erl_czmq.h" 26 | #include "vector.h" 27 | 28 | static int test(erl_czmq_state *state) { 29 | printf("Testing erlang-czmq\n"); 30 | vector_test(); 31 | return 0; 32 | } 33 | 34 | int main(int argc, char *argv[]) { 35 | erl_czmq_state state; 36 | erl_czmq_init(&state); 37 | 38 | int ret; 39 | if (argc > 1 && strcmp(argv[1], "--test") == 0) { 40 | ret = test(&state); 41 | } else { 42 | ret = erl_czmq_loop(&state); 43 | } 44 | 45 | return ret; 46 | } 47 | -------------------------------------------------------------------------------- /c_src/erl_czmq.c: -------------------------------------------------------------------------------- 1 | /* ========================================================================= 2 | erl_czmq - General functions for czmq_port 3 | 4 | ------------------------------------------------------------------------- 5 | Copyright (c) 2013-214 Garrett Smith 6 | Copyright other contributors as noted in the AUTHORS file. 7 | 8 | This file is part of erlang-czmq: https://github.com/gar1t/erlang-czmq 9 | 10 | This is free software; you can redistribute it and/or modify it under 11 | the terms of the GNU Lesser General Public License as published by the 12 | Free Software Foundation; either version 3 of the License, or (at your 13 | option) any later version. 14 | 15 | This software is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABIL- 17 | ITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General 18 | Public License for more details. 19 | 20 | You should have received a copy of the GNU Lesser General Public License 21 | along with this program. If not, see . 22 | ========================================================================= 23 | */ 24 | 25 | #include "czmq.h" 26 | #undef ETERM // collision between zmq.h and erl_interface.h 27 | #include "erl_interface.h" 28 | #include "erl_czmq.h" 29 | 30 | ETERM *ETERM_OK; 31 | ETERM *ETERM_UNDEFINED; 32 | ETERM *ETERM_TRUE; 33 | ETERM *ETERM_FALSE; 34 | ETERM *ETERM_PONG; 35 | ETERM *ETERM_ERROR; 36 | ETERM *ETERM_ERROR_INVALID_SOCKET; 37 | ETERM *ETERM_ERROR_BIND_FAILED; 38 | ETERM *ETERM_ERROR_UNBIND_FAILED; 39 | ETERM *ETERM_ERROR_CONNECT_FAILED; 40 | ETERM *ETERM_ERROR_DISCONNECT_FAILED; 41 | ETERM *ETERM_ERROR_INVALID_AUTH; 42 | ETERM *ETERM_ERROR_INVALID_CERT; 43 | 44 | #define ZCTX_SET_IOTHREADS 0 45 | #define ZCTX_SET_LINGER 1 46 | #define ZCTX_SET_PIPEHWM 2 47 | #define ZCTX_SET_SNDHWM 3 48 | #define ZCTX_SET_RCVHWM 4 49 | 50 | #define ZSOCKOPT_ZAP_DOMAIN 0 51 | #define ZSOCKOPT_PLAIN_SERVER 1 52 | #define ZSOCKOPT_PLAIN_USERNAME 2 53 | #define ZSOCKOPT_PLAIN_PASSWORD 3 54 | #define ZSOCKOPT_CURVE_SERVER 4 55 | #define ZSOCKOPT_CURVE_SERVERKEY 5 56 | #define ZSOCKOPT_BACKLOG 6 57 | #define ZSOCKOPT_SNDHWM 7 58 | #define ZSOCKOPT_RCVHWM 8 59 | #define ZSOCKOPT_SUBSCRIBE 9 60 | #define ZSOCKOPT_UNSUBSCRIBE 10 61 | #define ZSOCKOPT_IDENTITY 11 62 | 63 | #define SUCCESS 0 64 | 65 | #define EXIT_OK 0 66 | #define EXIT_PORT_READ_ERROR 253 67 | #define EXIT_INTERNAL_ERROR 254 68 | 69 | #define CMD_BUF_SIZE 10240 70 | 71 | #define MAX_SOCKETS 999999 72 | #define MAX_CERTS 999999 73 | 74 | #define assert_tuple_size(term, size) \ 75 | assert(ERL_IS_TUPLE(term)); \ 76 | assert(erl_size(term) == size) 77 | 78 | typedef void (*cmd_handler)(ETERM*, erl_czmq_state*); 79 | 80 | static bool prepare_cmd_buffer(int term_len, erl_czmq_state *state) 81 | { 82 | if (term_len > ERL_CZMQ_MAX_BUF_SIZE) { 83 | fprintf(stderr, "term_len %u > max_buf_size %u", term_len, ERL_CZMQ_MAX_BUF_SIZE); 84 | exit(EXIT_INTERNAL_ERROR); 85 | } else if (term_len > state->cmd_buf_size) { 86 | state->cmd_buf_size = term_len; 87 | state->cmd_buf = realloc(state->cmd_buf, term_len); 88 | } 89 | 90 | return state->cmd_buf && term_len <= state->cmd_buf_size; 91 | } 92 | 93 | static bool prepare_reply_buffer(int term_len, erl_czmq_state *state) 94 | { 95 | if (term_len > ERL_CZMQ_MAX_BUF_SIZE) { 96 | fprintf(stderr, "term_len %u > max_buf_size %u", term_len, ERL_CZMQ_MAX_BUF_SIZE); 97 | exit(EXIT_INTERNAL_ERROR); 98 | } else if (term_len > state->reply_buf_size) { 99 | state->reply_buf_size = term_len; 100 | state->reply_buf = realloc(state->reply_buf, term_len); 101 | } 102 | 103 | return state->reply_buf && term_len <= state->reply_buf_size; 104 | } 105 | 106 | static int read_exact(byte *buf, int len) 107 | { 108 | int i, got = 0; 109 | 110 | do { 111 | if ((i = read(0, buf + got, len - got)) <= 0) 112 | return i; 113 | got += i; 114 | } while (got < len); 115 | 116 | return len; 117 | } 118 | 119 | static int read_cmd(erl_czmq_state *state) 120 | { 121 | int len; 122 | 123 | if (read_exact(state->cmd_buf, 4) != 4) { 124 | return -1; 125 | } 126 | 127 | len = (state->cmd_buf[0] << 24) 128 | | (state->cmd_buf[1] << 16) 129 | | (state->cmd_buf[2] << 8) 130 | | state->cmd_buf[3]; 131 | 132 | if (!prepare_cmd_buffer(len, state)) { 133 | return -1; 134 | } 135 | 136 | return read_exact(state->cmd_buf, len); 137 | } 138 | 139 | static int write_exact(byte *buf, int len) 140 | { 141 | int i, wrote = 0; 142 | 143 | do { 144 | if ((i = write(1, buf + wrote, len - wrote)) <= 0) 145 | return (i); 146 | wrote += i; 147 | } while (wrote < len); 148 | 149 | return len; 150 | } 151 | 152 | static int write_cmd(byte *buf, int len) 153 | { 154 | byte li; 155 | 156 | li = (len >> 24) & 0xff; 157 | write_exact(&li, 1); 158 | li = (len >> 16) & 0xff; 159 | write_exact(&li, 1); 160 | li = (len >> 8) & 0xff; 161 | write_exact(&li, 1); 162 | li = len & 0xff; 163 | write_exact(&li, 1); 164 | return write_exact(buf, len); 165 | } 166 | 167 | static int safe_erl_encode(ETERM *term, erl_czmq_state *state) { 168 | int term_len, encoded_len; 169 | term_len = erl_term_len(term); 170 | if (!prepare_reply_buffer(term_len, state)) { 171 | exit(EXIT_INTERNAL_ERROR); 172 | } 173 | 174 | if ((encoded_len = erl_encode(term, state->reply_buf)) != term_len) { 175 | fprintf(stderr, "bad result from erl_encode %u, expected %u", 176 | term_len, encoded_len); 177 | exit(EXIT_INTERNAL_ERROR); 178 | } 179 | 180 | return encoded_len; 181 | } 182 | 183 | static void write_term(ETERM *term, erl_czmq_state *state) { 184 | int len = safe_erl_encode(term, state); 185 | write_cmd(state->reply_buf, len); 186 | } 187 | 188 | static void handle_ping(ETERM *args, erl_czmq_state *state) { 189 | write_term(ETERM_PONG, state); 190 | } 191 | 192 | static int save_socket(void *socket, erl_czmq_state *state) { 193 | int i; 194 | for (i = 0; i < MAX_SOCKETS; i++) { 195 | if (!vector_get(&state->sockets, i)) { 196 | vector_set(&state->sockets, i, socket); 197 | return i; 198 | } 199 | } 200 | assert(0); 201 | } 202 | 203 | static void handle_zctx_set_int(ETERM *args, erl_czmq_state *state) { 204 | assert_tuple_size(args, 2); 205 | 206 | ETERM *opt_arg = erl_element(1, args); 207 | int opt = ERL_INT_VALUE(opt_arg); 208 | 209 | ETERM *val_arg = erl_element(2, args); 210 | int val = ERL_INT_VALUE(val_arg); 211 | 212 | switch(opt) { 213 | case ZCTX_SET_IOTHREADS: 214 | zctx_set_iothreads(state->ctx, val); 215 | break; 216 | case ZCTX_SET_LINGER: 217 | zctx_set_linger(state->ctx, val); 218 | break; 219 | case ZCTX_SET_PIPEHWM: 220 | zctx_set_pipehwm(state->ctx, val); 221 | break; 222 | case ZCTX_SET_SNDHWM: 223 | zctx_set_sndhwm(state->ctx, val); 224 | break; 225 | case ZCTX_SET_RCVHWM: 226 | zctx_set_rcvhwm(state->ctx, val); 227 | break; 228 | default: 229 | assert(0); 230 | } 231 | 232 | write_term(ETERM_OK, state); 233 | } 234 | 235 | static void handle_zsocket_new(ETERM *args, erl_czmq_state *state) { 236 | assert_tuple_size(args, 1); 237 | ETERM *type_arg = erl_element(1, args); 238 | int type = ERL_INT_VALUE(type_arg); 239 | 240 | void *socket = zsocket_new(state->ctx, type); 241 | assert(socket); 242 | 243 | int index = save_socket(socket, state); 244 | ETERM *index_term = erl_mk_int(index); 245 | write_term(index_term, state); 246 | erl_free_term(index_term); 247 | } 248 | 249 | static int int_arg(ETERM *args, int arg_pos) { 250 | return ERL_INT_VALUE(erl_element(arg_pos, args)); 251 | } 252 | 253 | static void *socket_from_arg(ETERM *args, int arg_pos, erl_czmq_state *state) { 254 | return vector_get(&state->sockets, int_arg(args, arg_pos)); 255 | } 256 | 257 | static void handle_zsocket_type_str(ETERM *args, erl_czmq_state *state) { 258 | assert_tuple_size(args, 1); 259 | 260 | void *socket = socket_from_arg(args, 1, state); 261 | if (!socket) { 262 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 263 | return; 264 | } 265 | 266 | const char *type_str = zsocket_type_str(socket); 267 | ETERM *reply = erl_mk_string(type_str); 268 | 269 | write_term(reply, state); 270 | 271 | erl_free_term(reply); 272 | } 273 | 274 | static void handle_zsocket_bind(ETERM *args, erl_czmq_state *state) { 275 | assert_tuple_size(args, 2); 276 | 277 | void *socket = socket_from_arg(args, 1, state); 278 | if (!socket) { 279 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 280 | return; 281 | } 282 | 283 | ETERM *endpoint_arg = erl_element(2, args); 284 | char *endpoint = erl_iolist_to_string(endpoint_arg); 285 | int rc = zsocket_bind(socket, "%s", endpoint); 286 | if (rc == -1) { 287 | write_term(ETERM_ERROR_BIND_FAILED, state); 288 | return; 289 | } 290 | 291 | ETERM *result_parts[2]; 292 | result_parts[0] = ETERM_OK; 293 | ETERM *rc_int = erl_mk_int(rc); 294 | result_parts[1] = rc_int; 295 | ETERM *result = erl_mk_tuple(result_parts, 2); 296 | write_term(result, state); 297 | 298 | erl_free(endpoint); 299 | erl_free_term(rc_int); 300 | erl_free_term(result); 301 | } 302 | 303 | static void handle_zsocket_unbind(ETERM *args, erl_czmq_state *state) { 304 | assert_tuple_size(args, 2); 305 | 306 | void *socket = socket_from_arg(args, 1, state); 307 | if (!socket) { 308 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 309 | return; 310 | } 311 | 312 | ETERM *endpoint_arg = erl_element(2, args); 313 | char *endpoint = erl_iolist_to_string(endpoint_arg); 314 | int rc = zsocket_unbind(socket, "%s", endpoint); 315 | if (rc == -1) { 316 | write_term(ETERM_ERROR_UNBIND_FAILED, state); 317 | return; 318 | } 319 | 320 | write_term(ETERM_OK, state); 321 | 322 | erl_free(endpoint); 323 | } 324 | 325 | static void handle_zsocket_connect(ETERM *args, erl_czmq_state *state) { 326 | assert_tuple_size(args, 2); 327 | 328 | void *socket = socket_from_arg(args, 1, state); 329 | if (!socket) { 330 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 331 | return; 332 | } 333 | 334 | ETERM *endpoint_arg = erl_element(2, args); 335 | char *endpoint = erl_iolist_to_string(endpoint_arg); 336 | int rc = zsocket_connect(socket, "%s", endpoint); 337 | if (rc == -1) { 338 | write_term(ETERM_ERROR_CONNECT_FAILED, state); 339 | return; 340 | } 341 | 342 | write_term(ETERM_OK, state); 343 | 344 | erl_free(endpoint); 345 | } 346 | 347 | static void handle_zsocket_disconnect(ETERM *args, erl_czmq_state *state) { 348 | assert_tuple_size(args, 2); 349 | 350 | void *socket = socket_from_arg(args, 1, state); 351 | if (!socket) { 352 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 353 | return; 354 | } 355 | 356 | ETERM *endpoint_arg = erl_element(2, args); 357 | char *endpoint = erl_iolist_to_string(endpoint_arg); 358 | int rc = zsocket_disconnect(socket, "%s", endpoint); 359 | if (rc == -1) { 360 | write_term(ETERM_ERROR_DISCONNECT_FAILED, state); 361 | return; 362 | } 363 | 364 | write_term(ETERM_OK, state); 365 | 366 | erl_free(endpoint); 367 | } 368 | 369 | static void handle_zsocket_sendmem(ETERM *args, erl_czmq_state *state) { 370 | assert_tuple_size(args, 3); 371 | 372 | void *socket = socket_from_arg(args, 1, state); 373 | if (!socket) { 374 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 375 | return; 376 | } 377 | 378 | ETERM *data_bin_arg = erl_element(2, args); 379 | const void *data_bin = ERL_BIN_PTR(data_bin_arg); 380 | size_t data_bin_size = ERL_BIN_SIZE(data_bin_arg); 381 | 382 | ETERM *flags_arg = erl_element(3, args); 383 | int flags = ERL_INT_VALUE(flags_arg) | ZFRAME_DONTWAIT; 384 | 385 | int rc = zsocket_sendmem(socket, data_bin, data_bin_size, flags); 386 | if (rc == 0) { 387 | write_term(ETERM_OK, state); 388 | } else { 389 | write_term(ETERM_ERROR, state); 390 | } 391 | } 392 | 393 | static void clear_socket(int socket_index, erl_czmq_state *state) { 394 | vector_set(&state->sockets, socket_index, NULL); 395 | } 396 | 397 | static void handle_zsocket_destroy(ETERM *args, erl_czmq_state *state) { 398 | assert_tuple_size(args, 1); 399 | 400 | void *socket = socket_from_arg(args, 1, state); 401 | if (!socket) { 402 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 403 | return; 404 | } 405 | 406 | zsocket_destroy(state->ctx, socket); 407 | clear_socket(int_arg(args, 1), state); 408 | 409 | write_term(ETERM_OK, state); 410 | } 411 | 412 | static void handle_zsockopt_get_str(ETERM *args, erl_czmq_state *state) { 413 | assert_tuple_size(args, 2); 414 | 415 | void *socket = socket_from_arg(args, 1, state); 416 | if (!socket) { 417 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 418 | return; 419 | } 420 | 421 | ETERM *opt_arg = erl_element(2, args); 422 | int opt = ERL_INT_VALUE(opt_arg); 423 | 424 | char* val; 425 | 426 | switch(opt) { 427 | case ZSOCKOPT_ZAP_DOMAIN: 428 | val = zsocket_zap_domain(socket); 429 | break; 430 | case ZSOCKOPT_PLAIN_USERNAME: 431 | val = zsocket_plain_username(socket); 432 | break; 433 | case ZSOCKOPT_PLAIN_PASSWORD: 434 | val = zsocket_plain_password(socket); 435 | break; 436 | case ZSOCKOPT_CURVE_SERVERKEY: 437 | val = zsocket_curve_serverkey(socket); 438 | break; 439 | case ZSOCKOPT_IDENTITY: 440 | val = zsocket_identity(socket); 441 | break; 442 | default: 443 | assert(0); 444 | } 445 | 446 | assert(val); 447 | ETERM *result = erl_mk_string(val); 448 | write_term(result, state); 449 | 450 | erl_free_term(result); 451 | erl_free(val); 452 | } 453 | 454 | static void handle_zsockopt_get_int(ETERM *args, erl_czmq_state *state) { 455 | assert_tuple_size(args, 2); 456 | 457 | void *socket = socket_from_arg(args, 1, state); 458 | if (!socket) { 459 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 460 | return; 461 | } 462 | 463 | ETERM *opt_arg = erl_element(2, args); 464 | int opt = ERL_INT_VALUE(opt_arg); 465 | 466 | int val; 467 | 468 | switch(opt) { 469 | case ZSOCKOPT_PLAIN_SERVER: 470 | val = zsocket_plain_server(socket); 471 | break; 472 | case ZSOCKOPT_CURVE_SERVER: 473 | val = zsocket_curve_server(socket); 474 | break; 475 | case ZSOCKOPT_BACKLOG: 476 | val = zsocket_backlog(socket); 477 | break; 478 | case ZSOCKOPT_SNDHWM: 479 | val = zsocket_sndhwm(socket); 480 | break; 481 | case ZSOCKOPT_RCVHWM: 482 | val = zsocket_rcvhwm(socket); 483 | break; 484 | default: 485 | assert(0); 486 | } 487 | 488 | ETERM *result = erl_mk_int(val); 489 | 490 | write_term(result, state); 491 | 492 | erl_free_term(result); 493 | } 494 | 495 | static void handle_zsockopt_set_str(ETERM *args, erl_czmq_state *state) { 496 | assert_tuple_size(args, 3); 497 | 498 | void *socket = socket_from_arg(args, 1, state); 499 | if (!socket) { 500 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 501 | return; 502 | } 503 | 504 | ETERM *opt_arg = erl_element(2, args); 505 | int opt = ERL_INT_VALUE(opt_arg); 506 | 507 | ETERM *val_arg = erl_element(3, args); 508 | char *val = erl_iolist_to_string(val_arg); 509 | 510 | switch(opt) { 511 | case ZSOCKOPT_ZAP_DOMAIN: 512 | zsocket_set_zap_domain(socket, val); 513 | break; 514 | case ZSOCKOPT_PLAIN_USERNAME: 515 | zsocket_set_plain_username(socket, val); 516 | break; 517 | case ZSOCKOPT_PLAIN_PASSWORD: 518 | zsocket_set_plain_password(socket, val); 519 | break; 520 | case ZSOCKOPT_CURVE_SERVERKEY: 521 | zsocket_set_curve_serverkey(socket, val); 522 | break; 523 | case ZSOCKOPT_SUBSCRIBE: 524 | zsocket_set_subscribe(socket, val); 525 | break; 526 | case ZSOCKOPT_IDENTITY: 527 | zsocket_set_identity(socket, val); 528 | break; 529 | case ZSOCKOPT_UNSUBSCRIBE: 530 | zsocket_set_unsubscribe(socket, val); 531 | break; 532 | default: 533 | assert(0); 534 | } 535 | 536 | write_term(ETERM_OK, state); 537 | 538 | erl_free(val); 539 | } 540 | 541 | static void handle_zsockopt_set_int(ETERM *args, erl_czmq_state *state) { 542 | assert_tuple_size(args, 3); 543 | 544 | void *socket = socket_from_arg(args, 1, state); 545 | if (!socket) { 546 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 547 | return; 548 | } 549 | 550 | ETERM *opt_arg = erl_element(2, args); 551 | int opt = ERL_INT_VALUE(opt_arg); 552 | 553 | ETERM *val_arg = erl_element(3, args); 554 | int val = ERL_INT_VALUE(val_arg); 555 | 556 | switch(opt) { 557 | case ZSOCKOPT_PLAIN_SERVER: 558 | zsocket_set_plain_server(socket, val); 559 | break; 560 | case ZSOCKOPT_CURVE_SERVER: 561 | zsocket_set_curve_server(socket, val); 562 | break; 563 | case ZSOCKOPT_BACKLOG: 564 | zsocket_set_backlog(socket, val); 565 | break; 566 | case ZSOCKOPT_SNDHWM: 567 | zsocket_set_sndhwm(socket, val); 568 | break; 569 | case ZSOCKOPT_RCVHWM: 570 | zsocket_set_rcvhwm(socket, val); 571 | break; 572 | default: 573 | assert(0); 574 | } 575 | 576 | write_term(ETERM_OK, state); 577 | } 578 | 579 | static void handle_zstr_send(ETERM *args, erl_czmq_state *state) { 580 | assert_tuple_size(args, 2); 581 | 582 | void *socket = socket_from_arg(args, 1, state); 583 | if (!socket) { 584 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 585 | return; 586 | } 587 | 588 | ETERM *data_arg = erl_element(2, args); 589 | char *data = erl_iolist_to_string(data_arg); 590 | int data_len = strlen(data); 591 | 592 | // Use zsocket_sendmem to use non-blocking send (zstr_send blocks) 593 | int rc = zsocket_sendmem(socket, data, data_len, ZFRAME_DONTWAIT); 594 | if (rc == 0) { 595 | write_term(ETERM_OK, state); 596 | } else { 597 | write_term(ETERM_ERROR, state); 598 | } 599 | 600 | erl_free(data); 601 | } 602 | 603 | static void handle_zstr_recv_nowait(ETERM *args, erl_czmq_state *state) { 604 | assert_tuple_size(args, 1); 605 | 606 | void *socket = socket_from_arg(args, 1, state); 607 | if (!socket) { 608 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 609 | return; 610 | } 611 | 612 | char *data = zstr_recv_nowait(socket); 613 | 614 | if (!data) { 615 | write_term(ETERM_ERROR, state); 616 | return; 617 | } 618 | 619 | ETERM *result_parts[2]; 620 | result_parts[0] = ETERM_OK; 621 | ETERM *data_string = erl_mk_string(data); 622 | result_parts[1] = data_string; 623 | ETERM *result = erl_mk_tuple(result_parts, 2); 624 | 625 | write_term(result, state); 626 | 627 | erl_free_term(data_string); 628 | erl_free_term(result); 629 | free(data); 630 | } 631 | 632 | static void handle_zframe_recv_nowait(ETERM *args, erl_czmq_state *state) { 633 | assert_tuple_size(args, 1); 634 | 635 | void *socket = socket_from_arg(args, 1, state); 636 | if (!socket) { 637 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 638 | return; 639 | } 640 | 641 | zframe_t *frame = zframe_recv_nowait(socket); 642 | if (!frame) { 643 | write_term(ETERM_ERROR, state); 644 | return; 645 | } 646 | 647 | size_t frame_size = zframe_size(frame); 648 | byte *frame_data = zframe_data(frame); 649 | int more = zframe_more(frame); 650 | 651 | ETERM *result_parts[2]; 652 | result_parts[0] = ETERM_OK; 653 | ETERM *data_more_parts[2]; 654 | ETERM *data_bin = erl_mk_binary((char*)frame_data, frame_size); 655 | data_more_parts[0] = data_bin; 656 | ETERM *more_boolean = more ? ETERM_TRUE : ETERM_FALSE; 657 | data_more_parts[1] = more_boolean; 658 | ETERM *data_more = erl_mk_tuple(data_more_parts, 2); 659 | result_parts[1] = data_more; 660 | ETERM *result = erl_mk_tuple(result_parts, 2); 661 | 662 | write_term(result, state); 663 | 664 | zframe_destroy(&frame); 665 | erl_free_term(data_bin); 666 | erl_free_term(data_more); 667 | erl_free_term(result); 668 | } 669 | 670 | static void set_auth(zauth_t *auth, erl_czmq_state *state) { 671 | assert(state->auth == NULL); 672 | state->auth = auth; 673 | } 674 | 675 | static void handle_zauth_new(ETERM *args, erl_czmq_state *state) { 676 | assert_tuple_size(args, 0); 677 | 678 | void *auth = zauth_new(state->ctx); 679 | assert(auth); 680 | 681 | set_auth(auth, state); 682 | ETERM *mock_index_term = erl_mk_int(0); // only have one auth/ctx 683 | write_term(mock_index_term, state); 684 | erl_free_term(mock_index_term); 685 | } 686 | 687 | static zauth_t *auth_from_arg(ETERM *args, int arg_pos, 688 | erl_czmq_state *state) { 689 | ETERM *auth_arg = erl_element(arg_pos, args); 690 | int auth_id = ERL_INT_VALUE(auth_arg); 691 | // We only have one auth/state which is represented by the mock ID 0 692 | if (auth_id == 0) { 693 | return state->auth; 694 | } else { 695 | return NULL; 696 | } 697 | } 698 | 699 | static void handle_zauth_deny(ETERM *args, erl_czmq_state *state) { 700 | assert_tuple_size(args, 2); 701 | 702 | zauth_t *auth = auth_from_arg(args, 1, state); 703 | if (!auth) { 704 | write_term(ETERM_ERROR_INVALID_AUTH, state); 705 | return; 706 | } 707 | 708 | ETERM *address_arg = erl_element(2, args); 709 | char *address = erl_iolist_to_string(address_arg); 710 | zauth_deny(auth, address); 711 | 712 | write_term(ETERM_OK, state); 713 | 714 | erl_free(address); 715 | } 716 | 717 | static void handle_zauth_allow(ETERM *args, erl_czmq_state *state) { 718 | assert_tuple_size(args, 2); 719 | 720 | zauth_t *auth = auth_from_arg(args, 1, state); 721 | if (!auth) { 722 | write_term(ETERM_ERROR_INVALID_AUTH, state); 723 | return; 724 | } 725 | 726 | ETERM *address_arg = erl_element(2, args); 727 | char *address = erl_iolist_to_string(address_arg); 728 | zauth_allow(auth, address); 729 | 730 | write_term(ETERM_OK, state); 731 | 732 | erl_free(address); 733 | } 734 | 735 | static void handle_zauth_configure_plain(ETERM *args, erl_czmq_state *state) { 736 | assert_tuple_size(args, 3); 737 | 738 | zauth_t *auth = auth_from_arg(args, 1, state); 739 | if (!auth) { 740 | write_term(ETERM_ERROR_INVALID_AUTH, state); 741 | return; 742 | } 743 | 744 | ETERM *domain_arg = erl_element(2, args); 745 | char *domain = erl_iolist_to_string(domain_arg); 746 | 747 | ETERM *pwd_file_arg = erl_element(3, args); 748 | char *pwd_file = erl_iolist_to_string(pwd_file_arg); 749 | 750 | zauth_configure_plain(auth, domain, pwd_file); 751 | 752 | write_term(ETERM_OK, state); 753 | 754 | erl_free(domain); 755 | erl_free(pwd_file); 756 | } 757 | 758 | static void handle_zauth_configure_curve(ETERM *args, erl_czmq_state *state) { 759 | assert_tuple_size(args, 3); 760 | 761 | zauth_t *auth = auth_from_arg(args, 1, state); 762 | if (!auth) { 763 | write_term(ETERM_ERROR_INVALID_AUTH, state); 764 | return; 765 | } 766 | 767 | ETERM *domain_arg = erl_element(2, args); 768 | char *domain = erl_iolist_to_string(domain_arg); 769 | 770 | ETERM *location_arg = erl_element(3, args); 771 | char *location = erl_iolist_to_string(location_arg); 772 | 773 | zauth_configure_curve(auth, domain, location); 774 | 775 | write_term(ETERM_OK, state); 776 | 777 | erl_free(domain); 778 | erl_free(location); 779 | } 780 | 781 | static void clear_auth(erl_czmq_state *state) { 782 | state->auth = NULL; 783 | } 784 | 785 | static void handle_zauth_destroy(ETERM *args, erl_czmq_state *state) { 786 | assert_tuple_size(args, 1); 787 | 788 | zauth_t *auth = auth_from_arg(args, 1, state); 789 | if (!auth) { 790 | write_term(ETERM_ERROR_INVALID_AUTH, state); 791 | return; 792 | } 793 | 794 | zauth_destroy(&auth); 795 | clear_auth(state); 796 | 797 | write_term(ETERM_OK, state); 798 | } 799 | 800 | static int save_cert(void *cert, erl_czmq_state *state) { 801 | int i; 802 | for (i = 0; i < MAX_CERTS; i++) { 803 | if (!vector_get(&state->certs, i)) { 804 | vector_set(&state->certs, i, cert); 805 | return i; 806 | } 807 | } 808 | assert(0); 809 | } 810 | 811 | static void handle_zcert_new(ETERM *args, erl_czmq_state *state) { 812 | assert_tuple_size(args, 0); 813 | 814 | zcert_t *cert = zcert_new(); 815 | assert(cert); 816 | 817 | int index = save_cert(cert, state); 818 | ETERM *index_term = erl_mk_int(index); 819 | write_term(index_term, state); 820 | erl_free_term(index_term); 821 | } 822 | 823 | static zcert_t *cert_from_arg(ETERM *args, int arg_pos, 824 | erl_czmq_state *state) { 825 | return vector_get(&state->certs, int_arg(args, arg_pos)); 826 | } 827 | 828 | static void handle_zcert_apply(ETERM *args, erl_czmq_state *state) { 829 | assert_tuple_size(args, 2); 830 | 831 | zcert_t *cert = cert_from_arg(args, 1, state); 832 | if (!cert) { 833 | write_term(ETERM_ERROR_INVALID_CERT, state); 834 | return; 835 | } 836 | 837 | void *socket = socket_from_arg(args, 2, state); 838 | if (!socket) { 839 | write_term(ETERM_ERROR_INVALID_SOCKET, state); 840 | return; 841 | } 842 | 843 | zcert_apply(cert, socket); 844 | 845 | write_term(ETERM_OK, state); 846 | } 847 | 848 | static void handle_zcert_public_txt(ETERM *args, erl_czmq_state *state) { 849 | assert_tuple_size(args, 1); 850 | 851 | zcert_t *cert = cert_from_arg(args, 1, state); 852 | if (!cert) { 853 | write_term(ETERM_ERROR_INVALID_CERT, state); 854 | return; 855 | } 856 | 857 | char *txt = zcert_public_txt(cert); 858 | assert(txt); 859 | 860 | ETERM *result_parts[2]; 861 | result_parts[0] = ETERM_OK; 862 | ETERM *txt_string = erl_mk_string(txt); 863 | result_parts[1] = txt_string; 864 | ETERM *result = erl_mk_tuple(result_parts, 2); 865 | 866 | write_term(result, state); 867 | 868 | erl_free_term(txt_string); 869 | erl_free_term(result); 870 | } 871 | 872 | static void handle_zcert_save_public(ETERM *args, erl_czmq_state *state) { 873 | assert_tuple_size(args, 2); 874 | 875 | void *cert = cert_from_arg(args, 1, state); 876 | if (!cert) { 877 | write_term(ETERM_ERROR_INVALID_CERT, state); 878 | return; 879 | } 880 | 881 | ETERM *file_arg = erl_element(2, args); 882 | char *file = erl_iolist_to_string(file_arg); 883 | 884 | zcert_save_public(cert, file); 885 | 886 | write_term(ETERM_OK, state); 887 | 888 | erl_free(file); 889 | } 890 | 891 | static void clear_cert(int cert_index, erl_czmq_state *state) { 892 | vector_set(&state->certs, cert_index, NULL); 893 | } 894 | 895 | static void handle_zcert_destroy(ETERM *args, erl_czmq_state *state) { 896 | assert_tuple_size(args, 1); 897 | 898 | zcert_t *cert = cert_from_arg(args, 1, state); 899 | if (!cert) { 900 | write_term(ETERM_ERROR_INVALID_CERT, state); 901 | return; 902 | } 903 | 904 | zcert_destroy(&cert); 905 | clear_cert(int_arg(args, 1), state); 906 | 907 | write_term(ETERM_OK, state); 908 | } 909 | 910 | static void handle_cmd(erl_czmq_state *state, int handler_count, 911 | cmd_handler *handlers) { 912 | ETERM *cmd_term = erl_decode(state->cmd_buf); 913 | if (!ERL_IS_TUPLE(cmd_term) || ERL_TUPLE_SIZE(cmd_term) != 2) { 914 | fprintf(stderr, "invalid cmd format: "); 915 | erl_print_term(stderr, cmd_term); 916 | fprintf(stderr, "\n"); 917 | exit(EXIT_INTERNAL_ERROR); 918 | } 919 | 920 | ETERM *cmd_id_term = erl_element(1, cmd_term); 921 | int cmd_id = ERL_INT_VALUE(cmd_id_term); 922 | if (cmd_id < 0 || cmd_id >= handler_count) { 923 | fprintf(stderr, "cmd_id out of range: %i", cmd_id); 924 | exit(EXIT_INTERNAL_ERROR); 925 | } 926 | 927 | ETERM *cmd_args_term = erl_element(2, cmd_term); 928 | handlers[cmd_id](cmd_args_term, state); 929 | 930 | erl_free_compound(cmd_term); 931 | erl_free_compound(cmd_id_term); 932 | erl_free_compound(cmd_args_term); 933 | } 934 | 935 | static void init_eterms() { 936 | ETERM_OK = erl_mk_atom("ok"); 937 | ETERM_UNDEFINED = erl_mk_atom("undefined"); 938 | ETERM_TRUE = erl_mk_atom("true"); 939 | ETERM_FALSE = erl_mk_atom("false"); 940 | ETERM_PONG = erl_mk_atom("pong"); 941 | ETERM_ERROR = erl_mk_atom("error"); 942 | ETERM_ERROR_INVALID_SOCKET = erl_format("{error,invalid_socket}"); 943 | ETERM_ERROR_BIND_FAILED = erl_format("{error,bind_failed}"); 944 | ETERM_ERROR_UNBIND_FAILED = erl_format("{error,unbind_failed}"); 945 | ETERM_ERROR_CONNECT_FAILED = erl_format("{error,connect_failed}"); 946 | ETERM_ERROR_DISCONNECT_FAILED = erl_format("{error,disconnect_failed}"); 947 | ETERM_ERROR_INVALID_AUTH = erl_format("{error,invalid_auth}"); 948 | ETERM_ERROR_INVALID_CERT = erl_format("{error,invalid_cert}"); 949 | } 950 | 951 | void erl_czmq_init(erl_czmq_state *state) { 952 | erl_init(NULL, 0); 953 | init_eterms(); 954 | state->ctx = zctx_new(); 955 | assert(state->ctx); 956 | vector_init(&state->sockets); 957 | state->auth = NULL; 958 | vector_init(&state->certs); 959 | state->reply_buf = malloc(ERL_CZMQ_REPLY_BUF_SIZE); 960 | state->reply_buf_size = ERL_CZMQ_REPLY_BUF_SIZE; 961 | state->cmd_buf = malloc(CMD_BUF_SIZE); 962 | state->cmd_buf_size = CMD_BUF_SIZE; 963 | } 964 | 965 | int erl_czmq_loop(erl_czmq_state *state) { 966 | int HANDLER_COUNT = 28; 967 | cmd_handler handlers[HANDLER_COUNT]; 968 | handlers[0] = &handle_ping; 969 | handlers[1] = &handle_zsocket_new; 970 | handlers[2] = &handle_zsocket_type_str; 971 | handlers[3] = &handle_zsocket_bind; 972 | handlers[4] = &handle_zsocket_connect; 973 | handlers[5] = &handle_zsocket_sendmem; 974 | handlers[6] = &handle_zsocket_destroy; 975 | handlers[7] = &handle_zsockopt_get_str; 976 | handlers[8] = &handle_zsockopt_get_int; 977 | handlers[9] = &handle_zsockopt_set_str; 978 | handlers[10] = &handle_zsockopt_set_int; 979 | handlers[11] = &handle_zstr_send; 980 | handlers[12] = &handle_zstr_recv_nowait; 981 | handlers[13] = &handle_zframe_recv_nowait; 982 | handlers[14] = &handle_zauth_new; 983 | handlers[15] = &handle_zauth_deny; 984 | handlers[16] = &handle_zauth_allow; 985 | handlers[17] = &handle_zauth_configure_plain; 986 | handlers[18] = &handle_zauth_configure_curve; 987 | handlers[19] = &handle_zauth_destroy; 988 | handlers[20] = &handle_zcert_new; 989 | handlers[21] = &handle_zcert_apply; 990 | handlers[22] = &handle_zcert_public_txt; 991 | handlers[23] = &handle_zcert_save_public; 992 | handlers[24] = &handle_zcert_destroy; 993 | handlers[25] = &handle_zsocket_unbind; 994 | handlers[26] = &handle_zsocket_disconnect; 995 | handlers[27] = &handle_zctx_set_int; 996 | 997 | int cmd_len; 998 | 999 | while (1) { 1000 | cmd_len = read_cmd(state); 1001 | if (cmd_len == 0) { 1002 | exit(EXIT_OK); 1003 | } else if (cmd_len < 0) { 1004 | exit(EXIT_PORT_READ_ERROR); 1005 | } else { 1006 | handle_cmd(state, HANDLER_COUNT, handlers); 1007 | } 1008 | } 1009 | 1010 | return 0; 1011 | } 1012 | -------------------------------------------------------------------------------- /c_src/erl_czmq.h: -------------------------------------------------------------------------------- 1 | #ifndef __ERL_CZMQ_H_INCLUDED__ 2 | #define __ERL_CZMQ_H_INCLUDED__ 3 | 4 | #include "czmq.h" 5 | #include "vector.h" 6 | 7 | #define ERL_CZMQ_REPLY_BUF_SIZE 10240 8 | #define ERL_CZMQ_MAX_BUF_SIZE 10000000 9 | 10 | typedef struct { 11 | byte *reply_buf; 12 | int reply_buf_size; 13 | byte *cmd_buf; 14 | int cmd_buf_size; 15 | zctx_t *ctx; 16 | vector sockets; 17 | zauth_t *auth; 18 | vector certs; 19 | } erl_czmq_state; 20 | 21 | void erl_czmq_init(erl_czmq_state *state); 22 | 23 | int erl_czmq_loop(erl_czmq_state *state); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /c_src/vector.c: -------------------------------------------------------------------------------- 1 | /* ========================================================================= 2 | vector - Dynamic array support for czmq_port 3 | 4 | ------------------------------------------------------------------------- 5 | Copyright (c) 2013-214 Garrett Smith 6 | Copyright other contributors as noted in the AUTHORS file. 7 | 8 | This file is part of erlang-czmq: https://github.com/gar1t/erlang-czmq 9 | 10 | This is free software; you can redistribute it and/or modify it under 11 | the terms of the GNU Lesser General Public License as published by the 12 | Free Software Foundation; either version 3 of the License, or (at your 13 | option) any later version. 14 | 15 | This software is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABIL- 17 | ITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General 18 | Public License for more details. 19 | 20 | You should have received a copy of the GNU Lesser General Public License 21 | along with this program. If not, see . 22 | ========================================================================= 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "vector.h" 30 | 31 | void vector_ensure_capacity(vector *v); 32 | 33 | void vector_init(vector *v) { 34 | v->size = 0; 35 | v->capacity = VECTOR_INITIAL_CAPACITY; 36 | v->data = malloc(sizeof(void*) * v->capacity); 37 | } 38 | 39 | void vector_append(vector *v, void *value) { 40 | vector_ensure_capacity(v); 41 | v->data[v->size++] = value; 42 | } 43 | 44 | void *vector_get(vector *v, int index) { 45 | if (index >= v->size || index < 0) { 46 | return NULL; 47 | } 48 | return v->data[index]; 49 | } 50 | 51 | void vector_set(vector *v, int index, void *value) { 52 | while (index >= v->size) { 53 | vector_append(v, 0); 54 | } 55 | v->data[index] = value; 56 | } 57 | 58 | void vector_ensure_capacity(vector *v) { 59 | if (v->size >= v->capacity) { 60 | v->capacity *= 2; 61 | v->data = realloc(v->data, sizeof(void*) * v->capacity); 62 | } 63 | } 64 | 65 | void vector_free(vector *v) { 66 | free(v->data); 67 | } 68 | 69 | void vector_test() 70 | { 71 | printf (" * vector: "); 72 | 73 | vector v; 74 | vector_init(&v); 75 | 76 | // Write a bunch of heap allocated integers 77 | const int count = 100000; 78 | int i, *j; 79 | for (i = 0; i < count; i++) { 80 | j = malloc(sizeof(int)); 81 | *j = i; 82 | vector_set(&v, i, j); 83 | } 84 | 85 | // Read values back and check 86 | int errors = 0; 87 | for (i = 0; i < count; i++) { 88 | j = (int*)vector_get(&v, i); 89 | if (i != *j) { 90 | printf(" # unexpected vector value at pos %i: %i\n", i, *j); 91 | errors++; 92 | } 93 | } 94 | 95 | // Free memory and set values to NULL 96 | for (i = 0; i < count; i++) { 97 | j = (int*)vector_get(&v, i); 98 | free(j); 99 | vector_set(&v, i, NULL); 100 | j = (int*)vector_get(&v, i); 101 | if (j) { 102 | printf(" # unexpected vector value at pos %i: %i\n", i, *j); 103 | errors++; 104 | } 105 | } 106 | 107 | vector_free(&v); 108 | 109 | assert (!errors); 110 | 111 | printf ("OK\n"); 112 | } 113 | -------------------------------------------------------------------------------- /c_src/vector.h: -------------------------------------------------------------------------------- 1 | // vector.h 2 | 3 | #ifndef __VECTOR_H_INCLUDED__ 4 | #define __VECTOR_H_INCLUDED__ 5 | 6 | #define VECTOR_INITIAL_CAPACITY 100; 7 | 8 | typedef struct { 9 | int size; 10 | int capacity; 11 | void **data; 12 | } vector; 13 | 14 | void vector_init(vector *v); 15 | 16 | void vector_append(vector *v, void *value); 17 | 18 | void *vector_get(vector *v, int index); 19 | 20 | void vector_set(vector *v, int index, void *value); 21 | 22 | void vector_free(vector *v); 23 | 24 | void vector_test(); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT(erlang-czmq, version-0.0) 2 | 3 | AC_PROG_CC 4 | AC_ERLANG_CHECK_LIB([erl_interface]) 5 | 6 | AC_ARG_ENABLE([static], 7 | AC_HELP_STRING([--enable-static=@<:@yes/no@:>@], 8 | [enable static build [default=yes]]), 9 | [], 10 | [enable_static=yes]) 11 | AC_MSG_NOTICE([enabling static build... $enable_static]) 12 | AC_SUBST(enable_static) 13 | 14 | AC_OUTPUT(Makefile) 15 | AC_OUTPUT(c_src/Makefile) 16 | -------------------------------------------------------------------------------- /erlang.mk: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013, Loïc Hoguin 2 | # 3 | # Permission to use, copy, modify, and/or distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | # Project. 16 | 17 | PROJECT ?= $(notdir $(CURDIR)) 18 | 19 | # Packages database file. 20 | 21 | PKG_FILE ?= $(CURDIR)/.erlang.mk.packages.v1 22 | export PKG_FILE 23 | 24 | PKG_FILE_URL ?= https://raw.github.com/extend/erlang.mk/master/packages.v1.tsv 25 | 26 | define get_pkg_file 27 | wget --no-check-certificate -O $(PKG_FILE) $(PKG_FILE_URL) || rm $(PKG_FILE) 28 | endef 29 | 30 | # Verbosity and tweaks. 31 | 32 | V ?= 0 33 | 34 | appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src; 35 | appsrc_verbose = $(appsrc_verbose_$(V)) 36 | 37 | erlc_verbose_0 = @echo " ERLC " $(filter %.erl %.core,$(?F)); 38 | erlc_verbose = $(erlc_verbose_$(V)) 39 | 40 | xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F)); 41 | xyrl_verbose = $(xyrl_verbose_$(V)) 42 | 43 | dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F)); 44 | dtl_verbose = $(dtl_verbose_$(V)) 45 | 46 | gen_verbose_0 = @echo " GEN " $@; 47 | gen_verbose = $(gen_verbose_$(V)) 48 | 49 | .PHONY: rel clean-rel all clean-all app clean deps clean-deps \ 50 | docs clean-docs build-tests tests build-plt dialyze 51 | 52 | # Release. 53 | 54 | RELX_CONFIG ?= $(CURDIR)/relx.config 55 | 56 | ifneq ($(wildcard $(RELX_CONFIG)),) 57 | 58 | RELX ?= $(CURDIR)/relx 59 | export RELX 60 | 61 | RELX_URL ?= https://github.com/erlware/relx/releases/download/v0.5.2/relx 62 | RELX_OPTS ?= 63 | 64 | define get_relx 65 | wget -O $(RELX) $(RELX_URL) || rm $(RELX) 66 | chmod +x $(RELX) 67 | endef 68 | 69 | rel: clean-rel all $(RELX) 70 | @$(RELX) -c $(RELX_CONFIG) $(RELX_OPTS) 71 | 72 | $(RELX): 73 | @$(call get_relx) 74 | 75 | clean-rel: 76 | @rm -rf _rel 77 | 78 | endif 79 | 80 | # Deps directory. 81 | 82 | DEPS_DIR ?= $(CURDIR)/deps 83 | export DEPS_DIR 84 | 85 | REBAR_DEPS_DIR = $(DEPS_DIR) 86 | export REBAR_DEPS_DIR 87 | 88 | ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DEPS)) 89 | ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS)) 90 | 91 | # Application. 92 | 93 | ifeq ($(filter $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),) 94 | ifeq ($(ERL_LIBS),) 95 | ERL_LIBS = $(DEPS_DIR) 96 | else 97 | ERL_LIBS := $(ERL_LIBS):$(DEPS_DIR) 98 | endif 99 | endif 100 | export ERL_LIBS 101 | 102 | ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \ 103 | +warn_shadow_vars +warn_obsolete_guard # +bin_opt_info +warn_missing_spec 104 | COMPILE_FIRST ?= 105 | COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST))) 106 | 107 | all: deps app 108 | 109 | clean-all: clean clean-deps clean-docs 110 | $(gen_verbose) rm -rf .$(PROJECT).plt $(DEPS_DIR) logs 111 | 112 | app:: ebin/$(PROJECT).app 113 | $(eval MODULES := $(shell find ebin -type f -name \*.beam \ 114 | | sed 's/ebin\///;s/\.beam/,/' | sed '$$s/.$$//')) 115 | $(appsrc_verbose) cat src/$(PROJECT).app.src \ 116 | | sed 's/{modules,[[:space:]]*\[\]}/{modules, \[$(MODULES)\]}/' \ 117 | > ebin/$(PROJECT).app 118 | 119 | define compile_erl 120 | $(erlc_verbose) erlc -v $(ERLC_OPTS) -o ebin/ \ 121 | -pa ebin/ -I include/ $(COMPILE_FIRST_PATHS) $(1) 122 | endef 123 | 124 | define compile_xyrl 125 | $(xyrl_verbose) erlc -v -o ebin/ $(1) 126 | $(xyrl_verbose) erlc $(ERLC_OPTS) -o ebin/ ebin/*.erl 127 | @rm ebin/*.erl 128 | endef 129 | 130 | define compile_dtl 131 | $(dtl_verbose) erl -noshell -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/ -eval ' \ 132 | Compile = fun(F) -> \ 133 | Module = list_to_atom( \ 134 | string:to_lower(filename:basename(F, ".dtl")) ++ "_dtl"), \ 135 | erlydtl_compiler:compile(F, Module, [{out_dir, "ebin/"}]) \ 136 | end, \ 137 | _ = [Compile(F) || F <- string:tokens("$(1)", " ")], \ 138 | init:stop()' 139 | endef 140 | 141 | ebin/$(PROJECT).app: $(shell find src -type f -name \*.erl) \ 142 | $(shell find src -type f -name \*.core) \ 143 | $(shell find src -type f -name \*.xrl) \ 144 | $(shell find src -type f -name \*.yrl) \ 145 | $(shell find templates -type f -name \*.dtl 2>/dev/null) 146 | @mkdir -p ebin/ 147 | $(if $(strip $(filter %.erl %.core,$?)), \ 148 | $(call compile_erl,$(filter %.erl %.core,$?))) 149 | $(if $(strip $(filter %.xrl %.yrl,$?)), \ 150 | $(call compile_xyrl,$(filter %.xrl %.yrl,$?))) 151 | $(if $(strip $(filter %.dtl,$?)), \ 152 | $(call compile_dtl,$(filter %.dtl,$?))) 153 | 154 | clean:: 155 | $(gen_verbose) rm -rf ebin/ test/*.beam erl_crash.dump 156 | 157 | # Dependencies. 158 | 159 | define get_dep 160 | @mkdir -p $(DEPS_DIR) 161 | ifeq (,$(findstring pkg://,$(word 1,$(dep_$(1))))) 162 | git clone -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1) 163 | else 164 | @if [ ! -f $(PKG_FILE) ]; then $(call get_pkg_file); fi 165 | git clone -n -- `awk 'BEGIN { FS = "\t" }; \ 166 | $$$$1 == "$(subst pkg://,,$(word 1,$(dep_$(1))))" { print $$$$2 }' \ 167 | $(PKG_FILE)` $(DEPS_DIR)/$(1) 168 | endif 169 | cd $(DEPS_DIR)/$(1) ; git checkout -q $(word 2,$(dep_$(1))) 170 | endef 171 | 172 | define dep_target 173 | $(DEPS_DIR)/$(1): 174 | $(call get_dep,$(1)) 175 | endef 176 | 177 | $(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep)))) 178 | 179 | deps: $(ALL_DEPS_DIRS) 180 | @for dep in $(ALL_DEPS_DIRS) ; do \ 181 | if [ -f $$dep/Makefile ] ; then \ 182 | $(MAKE) -C $$dep ; \ 183 | else \ 184 | echo "include $(CURDIR)/erlang.mk" | $(MAKE) -f - -C $$dep ; \ 185 | fi ; \ 186 | done 187 | 188 | clean-deps: 189 | @for dep in $(ALL_DEPS_DIRS) ; do \ 190 | if [ -f $$dep/Makefile ] ; then \ 191 | $(MAKE) -C $$dep clean ; \ 192 | else \ 193 | echo "include $(CURDIR)/erlang.mk" | $(MAKE) -f - -C $$dep clean ; \ 194 | fi ; \ 195 | done 196 | 197 | # Documentation. 198 | 199 | EDOC_OPTS ?= 200 | 201 | docs: clean-docs 202 | $(gen_verbose) erl -noshell \ 203 | -eval 'edoc:application($(PROJECT), ".", [$(EDOC_OPTS)]), init:stop().' 204 | 205 | clean-docs: 206 | $(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info 207 | 208 | # Tests. 209 | 210 | $(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep)))) 211 | 212 | build-test-deps: $(ALL_TEST_DEPS_DIRS) 213 | @for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done 214 | 215 | build-tests: build-test-deps 216 | $(gen_verbose) erlc -v $(ERLC_OPTS) -o test/ \ 217 | $(wildcard test/*.erl test/*/*.erl) -pa ebin/ 218 | 219 | CT_RUN = ct_run \ 220 | -no_auto_compile \ 221 | -noshell \ 222 | -pa $(realpath ebin) $(DEPS_DIR)/*/ebin \ 223 | -dir test \ 224 | -logdir logs 225 | # -cover test/cover.spec 226 | 227 | CT_SUITES ?= 228 | 229 | define test_target 230 | test_$(1): ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}' 231 | test_$(1): clean deps app build-tests 232 | @if [ -d "test" ] ; \ 233 | then \ 234 | mkdir -p logs/ ; \ 235 | $(CT_RUN) -suite $(addsuffix _SUITE,$(1)) ; \ 236 | fi 237 | $(gen_verbose) rm -f test/*.beam 238 | endef 239 | 240 | $(foreach test,$(CT_SUITES),$(eval $(call test_target,$(test)))) 241 | 242 | tests: ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}' 243 | tests: clean deps app build-tests 244 | @if [ -d "test" ] ; \ 245 | then \ 246 | mkdir -p logs/ ; \ 247 | $(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) ; \ 248 | fi 249 | $(gen_verbose) rm -f test/*.beam 250 | 251 | # Dialyzer. 252 | 253 | PLT_APPS ?= 254 | DIALYZER_OPTS ?= -Werror_handling -Wrace_conditions \ 255 | -Wunmatched_returns # -Wunderspecs 256 | 257 | build-plt: deps app 258 | @dialyzer --build_plt --output_plt .$(PROJECT).plt \ 259 | --apps erts kernel stdlib $(PLT_APPS) $(ALL_DEPS_DIRS) 260 | 261 | dialyze: 262 | @dialyzer --src src --plt .$(PROJECT).plt --no_native $(DIALYZER_OPTS) 263 | 264 | # Packages. 265 | 266 | $(PKG_FILE): 267 | @$(call get_pkg_file) 268 | 269 | pkg-list: $(PKG_FILE) 270 | @cat $(PKG_FILE) | awk 'BEGIN { FS = "\t" }; { print \ 271 | "Name:\t\t" $$1 "\n" \ 272 | "Repository:\t" $$2 "\n" \ 273 | "Website:\t" $$3 "\n" \ 274 | "Description:\t" $$4 "\n" }' 275 | 276 | ifdef q 277 | pkg-search: $(PKG_FILE) 278 | @cat $(PKG_FILE) | grep -i ${q} | awk 'BEGIN { FS = "\t" }; { print \ 279 | "Name:\t\t" $$1 "\n" \ 280 | "Repository:\t" $$2 "\n" \ 281 | "Website:\t" $$3 "\n" \ 282 | "Description:\t" $$4 "\n" }' 283 | else 284 | pkg-search: 285 | @echo "Usage: make pkg-search q=STRING" 286 | endif 287 | -------------------------------------------------------------------------------- /include/czmq.hrl: -------------------------------------------------------------------------------- 1 | -define(ZMQ_PAIR, 0). 2 | -define(ZMQ_PUB, 1). 3 | -define(ZMQ_SUB, 2). 4 | -define(ZMQ_REQ, 3). 5 | -define(ZMQ_REP, 4). 6 | -define(ZMQ_DEALER, 5). 7 | -define(ZMQ_ROUTER, 6). 8 | -define(ZMQ_PULL, 7). 9 | -define(ZMQ_PUSH, 8). 10 | -define(ZMQ_XPUB, 9). 11 | -define(ZMQ_XSUB, 10). 12 | -define(ZMQ_STREAM, 11). 13 | 14 | -define(ZFRAME_MORE, 1). 15 | -define(ZFRAME_REUSE, 2). 16 | -define(ZFRAME_DONTWAIT, 4). 17 | 18 | -define(CURVE_ALLOW_ANY, "*"). 19 | -------------------------------------------------------------------------------- /priv/.dummy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gar1t/erlang-czmq/530c976ee4d0892294ac0468e366ad65c70e6a50/priv/.dummy -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ft=erlang ts=4 sw=4 et 3 | 4 | {erl_opts, [ 5 | warnings_as_errors, 6 | warn_export_all 7 | ]}. 8 | {erl_first_files, ["src/zmq_gen_benchmark.erl"]}. 9 | {pre_hooks, [{clean, "rm -fr ebin priv erl_crash.dump"}]}. 10 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | %% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ft=erlang ts=4 sw=4 et 3 | %% 4 | 5 | %% check if we build it statically or not 6 | IsStatic = case os:getenv("ENABLE_STATIC") of 7 | "no" -> false; 8 | _ -> true 9 | end, 10 | 11 | %% set common dor 12 | {ok, Cwd} = file:get_cwd(), 13 | StaticDir = filename:join([Cwd, "c_src", ".libs"]), 14 | 15 | %% helpers 16 | Include = fun(Name) -> 17 | filename:join([StaticDir, Name, "include"]) 18 | end, 19 | 20 | Lib = fun(Name, FName) -> 21 | filename:join([StaticDir, Name, "lib", FName]) 22 | end, 23 | 24 | %% set top directory environment used by build_czmq.sh 25 | os:putenv("CORE_TOP", filename:join([Cwd, "c_src"])), 26 | 27 | %% set the flags to build czmq_port and czmq_benchmark 28 | PortEnv = case IsStatic of 29 | true -> 30 | [{"CFLAGS", "-Wall -c -g -O2" ++ 31 | " -I" ++ Include("libsodium") ++ 32 | " -I" ++ Include("libzmq") ++ 33 | " -I" ++ Include("czmq")}, 34 | {"LDFLAGS", "-lerl_interface -lei -lstdc++ -lpthread -lrt" ++ 35 | " " ++ Lib("czmq", "libczmq.a") ++ 36 | " " ++ Lib("libzmq", "libzmq.a") ++ 37 | " " ++ Lib("libsodium", "libsodium.a") ++ 38 | " -lstdc++"}]; 39 | false -> 40 | [{"CFLAGS", "-Wall -c -g -O2"}, 41 | {"LDFLAGS", "-lerl_interface -lei -lstdc++ -lpthread -lczmq -lzmq"}] 42 | end, 43 | 44 | %% config to build czmq_port and czmq_benchmark 45 | PortInfo0 = [{port_env, PortEnv}, 46 | {port_specs, [ 47 | {filename:join(["priv", "czmq-port"]), 48 | ["c_src/czmq_port.c", 49 | "c_src/erl_czmq.c", 50 | "c_src/vector.c"]}, 51 | {filename:join(["priv", "czmq-benchmark"]), 52 | ["c_src/czmq_benchmark.c", 53 | "c_src/erl_czmq.c", 54 | "c_src/vector.c"]} 55 | ]}], 56 | 57 | PortInfo = case IsStatic of 58 | true -> 59 | PortInfo0 ++ 60 | [{pre_hooks, [{compile, "./c_src/build_czmq.sh"}]}, 61 | {post_hooks, [{clean, "./c_src/build_czmq.sh clean"}]}]; 62 | false -> 63 | PortInfo0 64 | end, 65 | 66 | %% update the rebar config 67 | lists:keymerge(1,lists:keysort(1, PortInfo), lists:keysort(1, CONFIG)). 68 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | cd ..; make 3 | 4 | %: 5 | cd ..; make $@ 6 | -------------------------------------------------------------------------------- /src/czmq.app.src: -------------------------------------------------------------------------------- 1 | %%% -*-erlang-*- 2 | {application, czmq, 3 | [{description, "Erlang bindings for CZMQ"}, 4 | {vsn, "0.1.0"}, 5 | {registered, []}, 6 | {applications, [kernel, stdlib]}, 7 | {env, []}, 8 | {modules, []} 9 | ]}. 10 | -------------------------------------------------------------------------------- /src/czmq.erl: -------------------------------------------------------------------------------- 1 | %% =================================================================== 2 | %% @author Garrett Smith 3 | %% @copyright 2014 Garrett Smith 4 | %% 5 | %% @doc czmq interface (facade). 6 | %% 7 | %% All czmq operations are accessed via this module. Refer to docs, 8 | %% tests, and sample code for more information. 9 | %% 10 | %% @end 11 | %% =================================================================== 12 | 13 | -module(czmq). 14 | 15 | -behavior(gen_server). 16 | 17 | -export([start/0, start_link/0, 18 | ping/1, ping/2, 19 | zctx_set_iothreads/2, 20 | zctx_set_linger/2, 21 | zctx_set_pipehwm/2, 22 | zctx_set_sndhwm/2, 23 | zctx_set_rcvhwm/2, 24 | zsocket_new/2, 25 | zsocket_type_str/1, 26 | zsocket_bind/2, 27 | zsocket_unbind/2, 28 | zsocket_connect/2, 29 | zsocket_disconnect/2, 30 | zsocket_sendmem/2, 31 | zsocket_sendmem/3, 32 | zsocket_send_all/2, 33 | zsocket_destroy/1, 34 | zsocket_sndhwm/1, 35 | zsocket_rcvhwm/1, 36 | zsocket_backlog/1, 37 | zsocket_identity/1, 38 | zsocket_set_zap_domain/2, 39 | zsocket_set_plain_server/2, 40 | zsocket_set_plain_username/2, 41 | zsocket_set_plain_password/2, 42 | zsocket_set_curve_server/2, 43 | zsocket_set_curve_serverkey/2, 44 | zsocket_set_sndhwm/2, 45 | zsocket_set_rcvhwm/2, 46 | zsocket_set_backlog/2, 47 | zsocket_set_identity/2, 48 | zsocket_set_subscribe/2, 49 | zsocket_set_unsubscribe/2, 50 | zstr_send/2, 51 | zstr_recv_nowait/1, 52 | zstr_recv/1, 53 | zstr_recv/2, 54 | zframe_recv_nowait/1, 55 | zframe_recv_all/1, 56 | zframe_data/1, 57 | zframe_more/1, 58 | zauth_new/1, 59 | zauth_deny/2, 60 | zauth_allow/2, 61 | zauth_configure_plain/3, 62 | zauth_configure_curve/3, 63 | zauth_destroy/1, 64 | zcert_new/1, 65 | zcert_apply/2, 66 | zcert_public_txt/1, 67 | zcert_save_public/2, 68 | zcert_destroy/1, 69 | subscribe/1, subscribe/2, 70 | subscribe_link/1, subscribe_link/2, 71 | unsubscribe/1, 72 | terminate/1]). 73 | 74 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 75 | terminate/2, code_change/3]). 76 | 77 | -include("czmq.hrl"). 78 | 79 | -record(state, {port}). 80 | 81 | -define(DEFAULT_PING_TIMEOUT, 1000). 82 | -define(MSG_TIMEOUT, 1000). 83 | 84 | %% These *must* correspond to the handlers in czmq_port.c 85 | -define(CMD_PING, 0). 86 | -define(CMD_ZSOCKET_NEW, 1). 87 | -define(CMD_ZSOCKET_TYPE_STR, 2). 88 | -define(CMD_ZSOCKET_BIND, 3). 89 | -define(CMD_ZSOCKET_CONNECT, 4). 90 | -define(CMD_ZSOCKET_SENDMEM, 5). 91 | -define(CMD_ZSOCKET_DESTROY, 6). 92 | -define(CMD_ZSOCKOPT_GET_STR, 7). 93 | -define(CMD_ZSOCKOPT_GET_INT, 8). 94 | -define(CMD_ZSOCKOPT_SET_STR, 9). 95 | -define(CMD_ZSOCKOPT_SET_INT, 10). 96 | -define(CMD_ZSTR_SEND, 11). 97 | -define(CMD_ZSTR_RECV_NOWAIT, 12). 98 | -define(CMD_ZFRAME_RECV_NOWAIT, 13). 99 | -define(CMD_ZAUTH_NEW, 14). 100 | -define(CMD_ZAUTH_DENY, 15). 101 | -define(CMD_ZAUTH_ALLOW, 16). 102 | -define(CMD_ZAUTH_CONFIGURE_PLAIN, 17). 103 | -define(CMD_ZAUTH_CONFIGURE_CURVE, 18). 104 | -define(CMD_ZAUTH_DESTROY, 19). 105 | -define(CMD_ZCERT_NEW, 20). 106 | -define(CMD_ZCERT_APPLY, 21). 107 | -define(CMD_ZCERT_PUBLIC_TXT, 22). 108 | -define(CMD_ZCERT_SAVE_PUBLIC, 23). 109 | -define(CMD_ZCERT_DESTROY, 24). 110 | -define(CMD_ZSOCKET_UNBIND, 25). 111 | -define(CMD_ZSOCKET_DISCONNECT, 26). 112 | -define(CMD_ZCTX_SET, 27). 113 | 114 | 115 | %% These *must* correspond to the ZCTX_SET_XXX definitions in czmq_port.c 116 | -define(ZCTX_SET_IOTHREADS, 0). 117 | -define(ZCTX_SET_LINGER, 1). 118 | -define(ZCTX_SET_PIPEHWM, 2). 119 | -define(ZCTX_SET_SNDHWM, 3). 120 | -define(ZCTX_SET_RCVHWM, 4). 121 | 122 | %% These *must* correspond to the ZSOCKOPT_XXX definitions in czmq_port.c 123 | -define(ZSOCKOPT_ZAP_DOMAIN, 0). 124 | -define(ZSOCKOPT_PLAIN_SERVER, 1). 125 | -define(ZSOCKOPT_PLAIN_USERNAME, 2). 126 | -define(ZSOCKOPT_PLAIN_PASSWORD, 3). 127 | -define(ZSOCKOPT_CURVE_SERVER, 4). 128 | -define(ZSOCKOPT_CURVE_SERVERKEY, 5). 129 | -define(ZSOCKOPT_BACKLOG, 6). 130 | -define(ZSOCKOPT_SNDHWM, 7). 131 | -define(ZSOCKOPT_RCVHWM, 8). 132 | -define(ZSOCKOPT_SUBSCRIBE, 9). 133 | -define(ZSOCKOPT_UNSUBSCRIBE, 10). 134 | -define(ZSOCKOPT_IDENTITY, 11). 135 | 136 | 137 | 138 | 139 | %%%=================================================================== 140 | %%% Start / init 141 | %%%=================================================================== 142 | 143 | start() -> 144 | gen_server:start(?MODULE, [], []). 145 | 146 | start_link() -> 147 | gen_server:start_link(?MODULE, [], []). 148 | 149 | init([]) -> 150 | process_flag(trap_exit, true), 151 | Port = start_port(), 152 | {ok, #state{port=Port}}. 153 | 154 | start_port() -> 155 | open_port({spawn, port_exe()}, [{packet, 4}, binary, exit_status]). 156 | 157 | port_exe() -> 158 | EbinDir = filename:dirname(code:which(?MODULE)), 159 | filename:join([EbinDir, "..", "priv", "czmq-port"]). 160 | 161 | %%%=================================================================== 162 | %%% API 163 | %%%=================================================================== 164 | 165 | ping(Ctx) -> 166 | ping(Ctx, ?DEFAULT_PING_TIMEOUT). 167 | 168 | ping(Ctx, Timeout) -> 169 | gen_server:call(Ctx, {?CMD_PING, {}}, Timeout). 170 | 171 | 172 | zctx_set_iothreads(Ctx, Val) when is_integer(Val) -> 173 | zctx_set_int(Ctx, ?ZCTX_SET_IOTHREADS, Val). 174 | 175 | zctx_set_linger(Ctx, Val) when is_integer(Val) -> 176 | zctx_set_int(Ctx, ?ZCTX_SET_LINGER, Val). 177 | 178 | zctx_set_pipehwm(Ctx, Val) when is_integer(Val) -> 179 | zctx_set_int(Ctx, ?ZCTX_SET_PIPEHWM, Val). 180 | 181 | zctx_set_sndhwm(Ctx, Val) when is_integer(Val) -> 182 | zctx_set_int(Ctx, ?ZCTX_SET_SNDHWM, Val). 183 | 184 | zctx_set_rcvhwm(Ctx, Val) when is_integer(Val) -> 185 | zctx_set_int(Ctx, ?ZCTX_SET_RCVHWM, Val). 186 | 187 | 188 | zctx_set_int(Ctx, Opt, Val) when is_integer(Val) -> 189 | Args = {Opt, Val}, 190 | gen_server:call(Ctx, {?CMD_ZCTX_SET, Args}, infinity). 191 | 192 | 193 | 194 | zsocket_new(Ctx, Type) when is_atom(Type) -> 195 | zsocket_new(Ctx, atom_to_socket_type(Type)); 196 | zsocket_new(Ctx, Type) -> 197 | Socket = gen_server:call(Ctx, {?CMD_ZSOCKET_NEW, {Type}}, infinity), 198 | bound_socket(Socket, Ctx). 199 | 200 | atom_to_socket_type(pair) -> ?ZMQ_PAIR; 201 | atom_to_socket_type(pub) -> ?ZMQ_PUB; 202 | atom_to_socket_type(sub) -> ?ZMQ_SUB; 203 | atom_to_socket_type(req) -> ?ZMQ_REQ; 204 | atom_to_socket_type(rep) -> ?ZMQ_REP; 205 | atom_to_socket_type(dealer) -> ?ZMQ_DEALER; 206 | atom_to_socket_type(router) -> ?ZMQ_ROUTER; 207 | atom_to_socket_type(pull) -> ?ZMQ_PULL; 208 | atom_to_socket_type(push) -> ?ZMQ_PUSH; 209 | atom_to_socket_type(xpub) -> ?ZMQ_XPUB; 210 | atom_to_socket_type(xsub) -> ?ZMQ_XSUB; 211 | atom_to_socket_type(stream) -> ?ZMQ_STREAM. 212 | 213 | bound_socket(Socket, Ctx) -> {Ctx, Socket}. 214 | 215 | zsocket_type_str({Ctx, Socket}) -> 216 | gen_server:call(Ctx, {?CMD_ZSOCKET_TYPE_STR, {Socket}}, infinity). 217 | 218 | zsocket_bind({Ctx, Socket}, Endpoint) -> 219 | gen_server:call(Ctx, {?CMD_ZSOCKET_BIND, {Socket, Endpoint}}, infinity). 220 | 221 | zsocket_unbind({Ctx, Socket}, Endpoint) -> 222 | gen_server:call(Ctx, {?CMD_ZSOCKET_UNBIND, {Socket, Endpoint}}, infinity). 223 | 224 | zsocket_connect({Ctx, Socket}, Endpoint) -> 225 | gen_server:call(Ctx, {?CMD_ZSOCKET_CONNECT, {Socket, Endpoint}}, infinity). 226 | 227 | zsocket_disconnect({Ctx, Socket}, Endpoint) -> 228 | gen_server:call(Ctx, {?CMD_ZSOCKET_DISCONNECT, {Socket, Endpoint}}, infinity). 229 | 230 | zsocket_sendmem(BoundSocket, Data) -> 231 | zsocket_sendmem(BoundSocket, Data, 0). 232 | 233 | zsocket_sendmem(BoundSocket, Data, Flag) when is_atom(Flag) -> 234 | zsocket_sendmem(BoundSocket, Data, atom_to_zframe_flag(Flag)); 235 | zsocket_sendmem({Ctx, Socket}, Data, Flags) -> 236 | DataBin = iolist_to_binary(Data), 237 | gen_server:call( 238 | Ctx, {?CMD_ZSOCKET_SENDMEM, {Socket, DataBin, Flags}}, infinity). 239 | 240 | atom_to_zframe_flag(more) -> ?ZFRAME_MORE; 241 | atom_to_zframe_flag(reuse) -> ?ZFRAME_REUSE; 242 | atom_to_zframe_flag(dontwait) -> ?ZFRAME_DONTWAIT. 243 | 244 | zsocket_send_all(BoundSocket, [Last]) -> 245 | zsocket_sendmem(BoundSocket, Last, 0); 246 | zsocket_send_all(BoundSocket, [Frame|Rest]) -> 247 | handle_sendmem_all( 248 | zsocket_sendmem(BoundSocket, Frame, ?ZFRAME_MORE), 249 | BoundSocket, Rest). 250 | 251 | handle_sendmem_all(ok, BoundSocket, Rest) -> 252 | zsocket_send_all(BoundSocket, Rest); 253 | handle_sendmem_all(Err, _BoundSocket, _Rest) -> 254 | Err. 255 | 256 | zsocket_destroy({Ctx, Socket}) -> 257 | gen_server:call(Ctx, {?CMD_ZSOCKET_DESTROY, {Socket}}, infinity). 258 | 259 | sockopt_int({Ctx, Socket}, Opt) -> 260 | gen_server:call(Ctx, {?CMD_ZSOCKOPT_GET_INT, {Socket, Opt}}, infinity). 261 | 262 | sockopt_str({Ctx, Socket}, Opt) -> 263 | gen_server:call(Ctx, {?CMD_ZSOCKOPT_GET_STR, {Socket, Opt}}, infinity). 264 | 265 | zsocket_sndhwm(Sock) -> 266 | sockopt_int(Sock, ?ZSOCKOPT_SNDHWM). 267 | 268 | zsocket_rcvhwm(Sock) -> 269 | sockopt_int(Sock, ?ZSOCKOPT_RCVHWM). 270 | 271 | zsocket_backlog(Sock) -> 272 | sockopt_int(Sock, ?ZSOCKOPT_BACKLOG). 273 | 274 | zsocket_identity(Sock) -> 275 | sockopt_str(Sock, ?ZSOCKOPT_IDENTITY). 276 | 277 | sockopt_set_str({Ctx, Socket}, Opt, Str) when is_list(Str) -> 278 | Args = {Socket, Opt, Str}, 279 | gen_server:call(Ctx, {?CMD_ZSOCKOPT_SET_STR, Args}, infinity). 280 | 281 | sockopt_set_int({Ctx, Socket}, Opt, Int) when is_integer(Int) -> 282 | Args = {Socket, Opt, Int}, 283 | gen_server:call(Ctx, {?CMD_ZSOCKOPT_SET_INT, Args}, infinity); 284 | sockopt_set_int(Sock, Opt, true) -> 285 | sockopt_set_int(Sock, Opt, 1); 286 | sockopt_set_int(Sock, Opt, false) -> 287 | sockopt_set_int(Sock, Opt, 0). 288 | 289 | zsocket_set_zap_domain(Sock, Domain) -> 290 | sockopt_set_str(Sock, ?ZSOCKOPT_ZAP_DOMAIN, Domain). 291 | 292 | zsocket_set_plain_server(Sock, Flag) -> 293 | sockopt_set_int(Sock, ?ZSOCKOPT_PLAIN_SERVER, Flag). 294 | 295 | zsocket_set_plain_username(Sock, Username) -> 296 | sockopt_set_str(Sock, ?ZSOCKOPT_PLAIN_USERNAME, Username). 297 | 298 | zsocket_set_plain_password(Sock, Password) -> 299 | sockopt_set_str(Sock, ?ZSOCKOPT_PLAIN_PASSWORD, Password). 300 | 301 | zsocket_set_curve_server(Sock, Flag) -> 302 | sockopt_set_int(Sock, ?ZSOCKOPT_CURVE_SERVER, Flag). 303 | 304 | zsocket_set_curve_serverkey(Sock, Key) -> 305 | sockopt_set_str(Sock, ?ZSOCKOPT_CURVE_SERVERKEY, Key). 306 | 307 | zsocket_set_sndhwm(Sock, Hwm) -> 308 | sockopt_set_int(Sock, ?ZSOCKOPT_SNDHWM, Hwm). 309 | 310 | zsocket_set_rcvhwm(Sock, Hwm) -> 311 | sockopt_set_int(Sock, ?ZSOCKOPT_RCVHWM, Hwm). 312 | 313 | zsocket_set_backlog(Sock, Backlog) -> 314 | sockopt_set_int(Sock, ?ZSOCKOPT_BACKLOG, Backlog). 315 | 316 | zsocket_set_identity(Sock, Identity) -> 317 | sockopt_set_str(Sock, ?ZSOCKOPT_IDENTITY, Identity). 318 | 319 | zsocket_set_subscribe(Sock, Subscribe) -> 320 | sockopt_set_str(Sock, ?ZSOCKOPT_SUBSCRIBE, Subscribe). 321 | 322 | zsocket_set_unsubscribe(Sock, Unsubscribe) -> 323 | sockopt_set_str(Sock, ?ZSOCKOPT_UNSUBSCRIBE, Unsubscribe). 324 | 325 | zstr_send({Ctx, Socket}, Data) -> 326 | gen_server:call(Ctx, {?CMD_ZSTR_SEND, {Socket, Data}}, infinity). 327 | 328 | zstr_recv_nowait({Ctx, Socket}) -> 329 | gen_server:call(Ctx, {?CMD_ZSTR_RECV_NOWAIT, {Socket}}, infinity). 330 | 331 | zstr_recv(BoundSocket) -> 332 | zstr_recv(BoundSocket, []). 333 | 334 | zstr_recv(BoundSocket, Options) -> 335 | Poller = start_poller(BoundSocket, Options), 336 | Reply = zstr_recv_reply(poller_recv(Poller, poll_timeout(Options))), 337 | stop_poller(Poller), 338 | Reply. 339 | 340 | start_poller(BoundSocket, Options) -> 341 | {ok, Poller} = czmq_poller:start_link(BoundSocket, Options), 342 | Poller. 343 | 344 | poll_timeout(Options) -> 345 | proplists:get_value(timeout, Options, infinity). 346 | 347 | poller_recv(Poller, Timeout) -> 348 | receive 349 | {Poller, Msg} -> {ok, Msg} 350 | after 351 | Timeout -> {error, timeout} 352 | end. 353 | 354 | zstr_recv_reply({ok, Parts}) -> {ok, parts_to_list(Parts)}; 355 | zstr_recv_reply({error, Err}) -> {error, Err}. 356 | 357 | parts_to_list(Parts) -> 358 | binary_to_list(iolist_to_binary(Parts)). 359 | 360 | stop_poller(Poller) -> 361 | ok = czmq_poller:stop(Poller). 362 | 363 | zframe_recv_nowait({Ctx, Socket}) -> 364 | gen_server:call(Ctx, {?CMD_ZFRAME_RECV_NOWAIT, {Socket}}, infinity). 365 | 366 | zframe_recv_all(BoundSocket) -> 367 | handle_frame_recv( 368 | zframe_recv_nowait(BoundSocket), 369 | BoundSocket, []). 370 | 371 | handle_frame_recv({ok, {Frame, true}}, BoundSocket, Acc) -> 372 | handle_frame_recv( 373 | zframe_recv_nowait(BoundSocket), 374 | BoundSocket, [Frame|Acc]); 375 | handle_frame_recv({ok, {Frame, false}}, _BoundSocket, Acc) -> 376 | {ok, lists:reverse([Frame|Acc])}; 377 | handle_frame_recv(error, _BoundSocket, []) -> 378 | error. 379 | 380 | zframe_data({Data, _More}) -> Data. 381 | 382 | zframe_more({_Data, More}) -> More. 383 | 384 | zauth_new(Ctx) -> 385 | Auth = gen_server:call(Ctx, {?CMD_ZAUTH_NEW, {}}, infinity), 386 | bound_auth(Auth, Ctx). 387 | 388 | bound_auth(Auth, Ctx) -> {Ctx, Auth}. 389 | 390 | zauth_deny({Ctx, Auth}, Addr) -> 391 | gen_server:call(Ctx, {?CMD_ZAUTH_DENY, {Auth, Addr}}, infinity). 392 | 393 | zauth_allow({Ctx, Auth}, Addr) -> 394 | gen_server:call(Ctx, {?CMD_ZAUTH_ALLOW, {Auth, Addr}}, infinity). 395 | 396 | zauth_configure_plain({Ctx, Auth}, Domain, PwdFile) -> 397 | gen_server:call( 398 | Ctx, {?CMD_ZAUTH_CONFIGURE_PLAIN, {Auth, Domain, PwdFile}}). 399 | 400 | zauth_configure_curve(BoundAuth, Domain, allow_any) -> 401 | zauth_configure_curve(BoundAuth, Domain, ?CURVE_ALLOW_ANY); 402 | zauth_configure_curve({Ctx, Auth}, Domain, Location) -> 403 | gen_server:call( 404 | Ctx, {?CMD_ZAUTH_CONFIGURE_CURVE, {Auth, Domain, Location}}). 405 | 406 | zauth_destroy({Ctx, Auth}) -> 407 | gen_server:call(Ctx, {?CMD_ZAUTH_DESTROY, {Auth}}, infinity). 408 | 409 | zcert_new(Ctx) -> 410 | Cert = gen_server:call(Ctx, {?CMD_ZCERT_NEW, {}}, infinity), 411 | bound_cert(Cert, Ctx). 412 | 413 | zcert_apply({Ctx, Cert}, {Ctx, Socket}) -> 414 | gen_server:call(Ctx, {?CMD_ZCERT_APPLY, {Cert, Socket}}, infinity). 415 | 416 | zcert_public_txt({Ctx, Cert}) -> 417 | gen_server:call(Ctx, {?CMD_ZCERT_PUBLIC_TXT, {Cert}}, infinity). 418 | 419 | zcert_save_public({Ctx, Cert}, File) -> 420 | gen_server:call(Ctx, {?CMD_ZCERT_SAVE_PUBLIC, {Cert, File}}, infinity). 421 | 422 | zcert_destroy({Ctx, Cert}) -> 423 | gen_server:call(Ctx, {?CMD_ZCERT_DESTROY, {Cert}}, infinity). 424 | 425 | bound_cert(Cert, Ctx) -> {Ctx, Cert}. 426 | 427 | subscribe(Socket) -> subscribe(Socket, []). 428 | 429 | subscribe(Socket, Options) -> 430 | czmq_poller:start(Socket, Options). 431 | 432 | subscribe_link(Socket) -> subscribe_link(Socket, []). 433 | 434 | subscribe_link(Socket, Options) -> 435 | czmq_poller:start_link(Socket, Options). 436 | 437 | unsubscribe(Poller) -> 438 | czmq_poller:stop(Poller). 439 | 440 | terminate(Ctx) -> 441 | gen_server:call(Ctx, terminate, infinity). 442 | 443 | %%%=================================================================== 444 | %%% Callbacks 445 | %%%=================================================================== 446 | 447 | handle_call(terminate, _From, State) -> 448 | {stop, normal, ok, State}; 449 | handle_call(Msg, _From, State) -> 450 | Reply = send_to_port(Msg, State), 451 | NextState = handle_msg_reply(Msg, Reply, State), 452 | {reply, Reply, NextState}. 453 | 454 | send_to_port(Msg, #state{port=Port}) -> 455 | erlang:send(Port, {self(), {command, term_to_binary(Msg)}}), 456 | receive 457 | {Port, {data, Data}} -> 458 | binary_to_term(Data); 459 | {Port, {exit_status, Status}} -> 460 | exit({port_exit, Status}); 461 | {'EXIT', Port, Reason} -> 462 | exit({port_exit, Reason}) 463 | end. 464 | 465 | handle_msg_reply(_Msg, _Reply, State) -> 466 | %% TODO: For creating sockets, we'll need to maintain an association 467 | %% between the socket ID and the process that should receive messages from 468 | %% that socket. 469 | State. 470 | 471 | handle_cast(_Msg, State) -> 472 | {noreply, State}. 473 | 474 | handle_info({Port, {exit_status, Exit}}, #state{port=Port}=State) -> 475 | {stop, {port_process_exit, Exit}, State}; 476 | handle_info({'EXIT', Port, Reason}, #state{port=Port}=State) -> 477 | {stop, {port_exit, Reason}, State}; 478 | handle_info(Msg, State) -> 479 | {stop, {unhandled_msg, Msg}, State}. 480 | 481 | terminate(_Reason, _State) -> 482 | ok. 483 | 484 | code_change(_OldVsn, State, _Extra) -> 485 | {ok, State}. 486 | -------------------------------------------------------------------------------- /src/czmq_benchmark.erl: -------------------------------------------------------------------------------- 1 | %% =================================================================== 2 | %% @author Garrett Smith 3 | %% @copyright 2014 Garrett Smith 4 | %% 5 | %% @doc Benchmarker for czmq (external port bindings). 6 | %% 7 | %% @end 8 | %% =================================================================== 9 | 10 | -module(czmq_benchmark). 11 | 12 | -behavior(zmq_gen_benchmark). 13 | 14 | -export([start_recv/0, start_recv/1, start_send/0, start_send/1, stop/1]). 15 | 16 | -export([init_recv/1, init_send/1, recv/1, send/2, terminate/1]). 17 | 18 | -include("czmq.hrl"). 19 | 20 | -define(DEFAULT_PORT, 5555). 21 | -define(DEFAULT_HOST, "localhost"). 22 | -define(DEFAULT_RECV_SOCKET_TYPE, ?ZMQ_PULL). 23 | -define(DEFAULT_SEND_SOCKET_TYPE, ?ZMQ_PUSH). 24 | -define(DEFAULT_POLL_INTERVAL, 100). 25 | 26 | -record(state, {ctx, socket, poll_interval}). 27 | 28 | %%%=================================================================== 29 | %%% API 30 | %%%=================================================================== 31 | 32 | start_recv() -> start_recv([]). 33 | 34 | start_recv(Options) -> 35 | zmq_gen_benchmark:start_recv(?MODULE, Options). 36 | 37 | start_send() -> start_send([]). 38 | 39 | start_send(Options) -> 40 | zmq_gen_benchmark:start_send(?MODULE, Options). 41 | 42 | stop(Benchmark) -> 43 | zmq_gen_benchmark:stop(Benchmark). 44 | 45 | %%%=================================================================== 46 | %%% Recv 47 | %%%=================================================================== 48 | 49 | init_recv(Options) -> 50 | PollInterval = poll_interval_option(Options), 51 | {ok, Ctx} = czmq:start_link(), 52 | Socket = czmq:zsocket_new(Ctx, recv_socket_type(Options)), 53 | czmq:zsocket_bind(Socket, bind_endpoint(Options)), 54 | {ok, #state{ctx=Ctx, socket=Socket, poll_interval=PollInterval}}. 55 | 56 | poll_interval_option(Options) -> 57 | proplists:get_value(poll_interval, Options, ?DEFAULT_POLL_INTERVAL). 58 | 59 | recv_socket_type(Options) -> 60 | proplists:get_value(socket_type, Options, ?DEFAULT_RECV_SOCKET_TYPE). 61 | 62 | bind_endpoint(Options) -> 63 | Port = proplists:get_value(port, Options, ?DEFAULT_PORT), 64 | "tcp://*:" ++ integer_to_list(Port). 65 | 66 | recv(#state{socket=Socket}=State) -> 67 | handle_czmq_recv(czmq:zstr_recv_nowait(Socket), State). 68 | 69 | handle_czmq_recv({ok, Msg}, State) -> 70 | {ok, Msg, State}; 71 | handle_czmq_recv(error, State) -> 72 | sleep_poll_interval(State), 73 | {error, State}. 74 | 75 | sleep_poll_interval(#state{poll_interval=I}) -> 76 | timer:sleep(I). 77 | 78 | %%%=================================================================== 79 | %%% Send 80 | %%%=================================================================== 81 | 82 | init_send(Options) -> 83 | {ok, Ctx} = czmq:start_link(), 84 | Socket = czmq:zsocket_new(Ctx, send_socket_type(Options)), 85 | ok = czmq:zsocket_connect(Socket, connect_endpoint(Options)), 86 | {ok, #state{ctx=Ctx, socket=Socket}}. 87 | 88 | send_socket_type(Options) -> 89 | proplists:get_value(socket_type, Options, ?DEFAULT_SEND_SOCKET_TYPE). 90 | 91 | connect_endpoint(Options) -> 92 | Port = proplists:get_value(port, Options, ?DEFAULT_PORT), 93 | Host = proplists:get_value(host, Options, ?DEFAULT_HOST), 94 | "tcp://" ++ Host ++ ":" ++ integer_to_list(Port). 95 | 96 | send(Msg, #state{socket=Socket}=State) -> 97 | handle_czmq_send(czmq:zstr_send(Socket, Msg), State). 98 | 99 | handle_czmq_send(ok, State) -> {ok, State}; 100 | handle_czmq_send(error, State) -> {error, State}. 101 | 102 | %%%=================================================================== 103 | %%% Terminate 104 | %%%=================================================================== 105 | 106 | terminate(#state{ctx=Ctx}) -> 107 | czmq:terminate(Ctx). 108 | -------------------------------------------------------------------------------- /src/czmq_poller.erl: -------------------------------------------------------------------------------- 1 | %% =================================================================== 2 | %% @author Garrett Smith 3 | %% @copyright 2014 Garrett Smith 4 | %% 5 | %% @doc Polling process for socket messages. 6 | %% 7 | %% As erlang-czmq is implemented as an external port, it uses all. 8 | %% non blocking operations. Message devliery must be performed by 9 | %% routinely polling a socket. 10 | %% 11 | %% @end 12 | %% =================================================================== 13 | 14 | -module(czmq_poller). 15 | 16 | -behavior(gen_server). 17 | 18 | -export([start/2, start_link/2, stop/1]). 19 | 20 | -export([init/1, handle_info/2, handle_cast/2, handle_call/3, 21 | terminate/2, code_change/3]). 22 | 23 | -record(state, {socket, dispatch, interval, start}). 24 | 25 | -define(DEFAULT_POLL_INTERVAL, 1000). 26 | 27 | %%%=================================================================== 28 | %%% Start / init 29 | %%%=================================================================== 30 | 31 | start(Socket, Options) -> 32 | gen_server:start(?MODULE, [Socket, Options, self()], []). 33 | 34 | start_link(Socket, Options) -> 35 | gen_server:start_link(?MODULE, [Socket, Options, self()], []). 36 | 37 | init([Socket, Options, Parent]) -> 38 | DispatchOption = dispatch_option(Options), 39 | Target = maybe_target(DispatchOption, Options, Parent), 40 | maybe_monitor(Target), 41 | DispatchFun = dispatch_fun(DispatchOption, Target), 42 | Interval = poll_interval_option(Options), 43 | Start = timestamp(), 44 | State = #state{ 45 | socket=Socket, 46 | dispatch=DispatchFun, 47 | interval=Interval, 48 | start=Start}, 49 | {ok, State, 0}. 50 | 51 | dispatch_option(Options) -> 52 | proplists:get_value(dispatch, Options). 53 | 54 | maybe_target(undefined, Options, Parent) -> 55 | proplists:get_value(target, Options, Parent); 56 | maybe_target(_Dispatch, _Options, _Parent) -> 57 | undefined. 58 | 59 | maybe_monitor(undefined) -> ok; 60 | maybe_monitor(Pid) -> erlang:monitor(process, Pid). 61 | 62 | dispatch_fun(undefined, Target) -> 63 | fun(Msg) -> erlang:send(Target, {self(), Msg}) end; 64 | dispatch_fun(Dispatch, _Target) -> 65 | Dispatch. 66 | 67 | poll_interval_option(Options) -> 68 | proplists:get_value(poll_interval, Options, ?DEFAULT_POLL_INTERVAL). 69 | 70 | timestamp() -> 71 | {M, S, U} = erlang:timestamp(), 72 | M * 1000000000 + S * 1000 + U div 1000. 73 | 74 | %%%=================================================================== 75 | %%% API 76 | %%%=================================================================== 77 | 78 | stop(Poller) -> 79 | gen_server:call(Poller, stop). 80 | 81 | %%%=================================================================== 82 | %%% Message dispatch 83 | %%%=================================================================== 84 | 85 | handle_info(timeout, State) -> 86 | handle_poll(State); 87 | handle_info({'DOWN', _Ref, process, _Proc, _Reason}, State) -> 88 | {stop, normal, State}. 89 | 90 | %%%=================================================================== 91 | %%% Poll for / dispatch messages 92 | %%%=================================================================== 93 | 94 | handle_poll(State) -> 95 | dispatch_messages(State), 96 | schedule_next(State), 97 | {noreply, State}. 98 | 99 | dispatch_messages(State) -> 100 | handle_recv_msg(recv_msg(State, []), State). 101 | 102 | recv_msg(State, FramesAcc) -> 103 | handle_recv_frame(recv_frame(State), State, FramesAcc). 104 | 105 | recv_frame(#state{socket=Socket}) -> 106 | czmq:zframe_recv_nowait(Socket). 107 | 108 | handle_recv_frame({ok, {Data, More}}, State, FramesAcc) -> 109 | handle_frame_more(More, State, [Data|FramesAcc]); 110 | handle_recv_frame(error, _State, _FramesAcc) -> 111 | error. 112 | 113 | handle_frame_more(true, State, FramesAcc) -> 114 | handle_recv_frame(recv_frame(State), State, FramesAcc); 115 | handle_frame_more(false, _State, FramesAcc) -> 116 | {ok, lists:reverse(FramesAcc)}. 117 | 118 | handle_recv_msg({ok, Msg}, State) -> 119 | dispatch_msg(Msg, State), 120 | dispatch_messages(State); 121 | handle_recv_msg(error, _State) -> 122 | ok. 123 | 124 | dispatch_msg(Msg, #state{dispatch=Dispatch}) -> 125 | Dispatch(Msg). 126 | 127 | schedule_next(State) -> 128 | erlang:send_after(next_delay(State), self(), timeout). 129 | 130 | next_delay(#state{start=Start, interval=Interval}) -> 131 | Now = timestamp(), 132 | ((Now - Start) div Interval + 1) * Interval + Start - Now. 133 | 134 | %%%=================================================================== 135 | %%% Handle stop 136 | %%%=================================================================== 137 | 138 | handle_call(stop, _From, State) -> 139 | {stop, normal, ok, State}. 140 | 141 | %%%=================================================================== 142 | %%% gen_server boilderplate 143 | %%%=================================================================== 144 | 145 | handle_cast(_Msg, State) -> 146 | {noreply, State}. 147 | 148 | terminate(_Reason, _State) -> 149 | ok. 150 | 151 | code_change(_OldVsn, State, _Extra) -> 152 | {ok, State}. 153 | -------------------------------------------------------------------------------- /src/czmq_reloader.erl: -------------------------------------------------------------------------------- 1 | %% =================================================================== 2 | %% @author Matthew Dempsky 3 | %% @copyright 2007 Mochi Media, Inc. 4 | %% 5 | %% @doc Auto module reloader copied from Mochiweb's reloader - used for 6 | %% development and be safely omitted from packaged production software. 7 | %% 8 | %% @end 9 | %% =================================================================== 10 | 11 | -module(czmq_reloader). 12 | -author("Matthew Dempsky "). 13 | 14 | -include_lib("kernel/include/file.hrl"). 15 | 16 | -behaviour(gen_server). 17 | -export([start/0, start_link/0]). 18 | -export([stop/0]). 19 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 20 | -export([all_changed/0]). 21 | -export([is_changed/1]). 22 | -export([reload_modules/1]). 23 | -record(state, {last, tref}). 24 | 25 | %% External API 26 | 27 | %% @spec start() -> ServerRet 28 | %% @doc Start the reloader. 29 | start() -> 30 | gen_server:start({local, ?MODULE}, ?MODULE, [], []). 31 | 32 | %% @spec start_link() -> ServerRet 33 | %% @doc Start the reloader. 34 | start_link() -> 35 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 36 | 37 | %% @spec stop() -> ok 38 | %% @doc Stop the reloader. 39 | stop() -> 40 | gen_server:call(?MODULE, stop). 41 | 42 | %% gen_server callbacks 43 | 44 | %% @spec init([]) -> {ok, State} 45 | %% @doc gen_server init, opens the server in an initial state. 46 | init([]) -> 47 | {ok, TRef} = timer:send_interval(timer:seconds(1), doit), 48 | {ok, #state{last = stamp(), tref = TRef}}. 49 | 50 | %% @spec handle_call(Args, From, State) -> tuple() 51 | %% @doc gen_server callback. 52 | handle_call(stop, _From, State) -> 53 | {stop, shutdown, stopped, State}; 54 | handle_call(_Req, _From, State) -> 55 | {reply, {error, badrequest}, State}. 56 | 57 | %% @spec handle_cast(Cast, State) -> tuple() 58 | %% @doc gen_server callback. 59 | handle_cast(_Req, State) -> 60 | {noreply, State}. 61 | 62 | %% @spec handle_info(Info, State) -> tuple() 63 | %% @doc gen_server callback. 64 | handle_info(doit, State) -> 65 | Now = stamp(), 66 | _ = doit(State#state.last, Now), 67 | {noreply, State#state{last = Now}}; 68 | handle_info(_Info, State) -> 69 | {noreply, State}. 70 | 71 | %% @spec terminate(Reason, State) -> ok 72 | %% @doc gen_server termination callback. 73 | terminate(_Reason, State) -> 74 | {ok, cancel} = timer:cancel(State#state.tref), 75 | ok. 76 | 77 | 78 | %% @spec code_change(_OldVsn, State, _Extra) -> State 79 | %% @doc gen_server code_change callback (trivial). 80 | code_change(_Vsn, State, _Extra) -> 81 | {ok, State}. 82 | 83 | %% @spec reload_modules([atom()]) -> [{module, atom()} | {error, term()}] 84 | %% @doc code:purge/1 and code:load_file/1 the given list of modules in order, 85 | %% return the results of code:load_file/1. 86 | reload_modules(Modules) -> 87 | [begin code:purge(M), code:load_file(M) end || M <- Modules]. 88 | 89 | %% @spec all_changed() -> [atom()] 90 | %% @doc Return a list of beam modules that have changed. 91 | all_changed() -> 92 | [M || {M, Fn} <- code:all_loaded(), is_list(Fn), is_changed(M)]. 93 | 94 | %% @spec is_changed(atom()) -> boolean() 95 | %% @doc true if the loaded module is a beam with a vsn attribute 96 | %% and does not match the on-disk beam file, returns false otherwise. 97 | is_changed(M) -> 98 | try 99 | module_vsn(M:module_info()) =/= module_vsn(code:get_object_code(M)) 100 | catch _:_ -> 101 | false 102 | end. 103 | 104 | %% Internal API 105 | 106 | module_vsn({M, Beam, _Fn}) -> 107 | {ok, {M, Vsn}} = beam_lib:version(Beam), 108 | Vsn; 109 | module_vsn(L) when is_list(L) -> 110 | {_, Attrs} = lists:keyfind(attributes, 1, L), 111 | {_, Vsn} = lists:keyfind(vsn, 1, Attrs), 112 | Vsn. 113 | 114 | doit(From, To) -> 115 | [case file:read_file_info(Filename) of 116 | {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To -> 117 | reload(Module); 118 | {ok, _} -> 119 | unmodified; 120 | {error, enoent} -> 121 | %% The Erlang compiler deletes existing .beam files if 122 | %% recompiling fails. Maybe it's worth spitting out a 123 | %% warning here, but I'd want to limit it to just once. 124 | gone; 125 | {error, Reason} -> 126 | io:format("Error reading ~s's file info: ~p~n", 127 | [Filename, Reason]), 128 | error 129 | end || {Module, Filename} <- code:all_loaded(), is_list(Filename)]. 130 | 131 | reload(Module) -> 132 | io:format("Reloading ~p ...", [Module]), 133 | code:purge(Module), 134 | case code:load_file(Module) of 135 | {module, Module} -> 136 | io:format(" ok.~n"), 137 | case erlang:function_exported(Module, test, 0) of 138 | true -> 139 | io:format(" - Calling ~p:test() ...", [Module]), 140 | case catch Module:test() of 141 | ok -> 142 | io:format(" ok.~n"), 143 | reload; 144 | Reason -> 145 | io:format(" fail: ~p.~n", [Reason]), 146 | reload_but_test_failed 147 | end; 148 | false -> 149 | reload 150 | end; 151 | {error, Reason} -> 152 | io:format(" fail: ~p.~n", [Reason]), 153 | error 154 | end. 155 | 156 | 157 | stamp() -> 158 | erlang:localtime(). 159 | 160 | %% 161 | %% Tests 162 | %% 163 | -ifdef(TEST). 164 | -include_lib("eunit/include/eunit.hrl"). 165 | -endif. 166 | -------------------------------------------------------------------------------- /src/czmq_test.erl: -------------------------------------------------------------------------------- 1 | %% =================================================================== 2 | %% @author Garrett Smith 3 | %% @copyright 2014 Garrett Smith 4 | %% 5 | %% @doc Tests for erlang-czmq. 6 | %% 7 | %% @end 8 | %% =================================================================== 9 | 10 | -module(czmq_test). 11 | 12 | -export([test/0, 13 | zstr_send_recv/1, 14 | sendmem_framerecv/1, 15 | zauth/1, 16 | poller/1, 17 | router_dealer/1, 18 | push_pull/1, 19 | req_rep/1, 20 | pub_sub/1, 21 | sockopts/1, 22 | large_message/1]). 23 | 24 | -include("czmq.hrl"). 25 | 26 | %%-------------------------------------------------------------------- 27 | %% @doc The list of tests to execute. 28 | %% 29 | %% Add new tests here to run them as a part of the test suite. 30 | %% 31 | %% Tests must handle a single Ctx argument and signify a failure by 32 | %% generating an exception. This is usually done by letting a pattern 33 | %% match fail - but any exception will be counted as a failed test. 34 | %% 35 | %% If a test does not generate an exception, it is considered to have 36 | %% passed. 37 | %% 38 | %% @spec tests() -> [fun((Ctx) -> any())] 39 | %% @end 40 | %%-------------------------------------------------------------------- 41 | 42 | tests() -> 43 | [fun zstr_send_recv/1, 44 | fun sendmem_framerecv/1, 45 | fun zauth/1, 46 | fun poller/1, 47 | fun router_dealer/1, 48 | fun push_pull/1, 49 | fun req_rep/1, 50 | fun pub_sub/1, 51 | fun sockopts/1, 52 | fun large_message/1]. 53 | 54 | %%-------------------------------------------------------------------- 55 | %% @doc Run all tests. 56 | %% @end 57 | %%-------------------------------------------------------------------- 58 | 59 | test() -> 60 | io:format("Testing erlang-czmq...~n"), 61 | {ok, Ctx} = czmq:start_link(), 62 | TestResults = run_tests(tests(), Ctx), 63 | report_test_results(TestResults), 64 | czmq:terminate(Ctx). 65 | 66 | run_tests(Tests, Ctx) -> 67 | run_tests(Tests, Ctx, {0, 0}). 68 | 69 | run_tests([Test|Rest], Ctx, ResultsAcc) -> 70 | Result = (catch Test(Ctx)), 71 | run_tests(Rest, Ctx, handle_test_result(Result, ResultsAcc)); 72 | run_tests([], _Ctx, ResultsAcc) -> 73 | ResultsAcc. 74 | 75 | handle_test_result({'EXIT', Err}, {Passed, Failed}) -> 76 | io:format("FAILED~n ~p~n", [Err]), 77 | {Passed, Failed + 1}; 78 | handle_test_result(_, {Passed, Failed}) -> 79 | {Passed + 1, Failed}. 80 | 81 | report_test_results({Passed, Failed}) -> 82 | io:format("PASSED: ~b~n", [Passed]), 83 | io:format("FAILED: ~b~n", [Failed]). 84 | 85 | %%-------------------------------------------------------------------- 86 | %% @doc Tests basic zstr send and receive. 87 | %% @end 88 | %%-------------------------------------------------------------------- 89 | 90 | zstr_send_recv(Ctx) -> 91 | io:format(" * zstr_send_recv: "), 92 | 93 | Writer = czmq:zsocket_new(Ctx, push), 94 | {ok, Port} = czmq:zsocket_bind(Writer, "tcp://127.0.0.1:*"), 95 | 96 | ok = czmq:zsocket_unbind(Writer, connect_endpoint(Port)), 97 | {ok, Port} = czmq:zsocket_bind(Writer, connect_endpoint(Port)), 98 | 99 | Reader = czmq:zsocket_new(Ctx, pull), 100 | ok = czmq:zsocket_connect(Reader, connect_endpoint(Port)), 101 | 102 | 103 | timer:sleep(10), 104 | 105 | Msg = "Watson, I found your shirt", 106 | ok = czmq:zstr_send(Writer, Msg), 107 | 108 | timer:sleep(10), 109 | 110 | {ok, Msg} = czmq:zstr_recv_nowait(Reader), 111 | 112 | error = czmq:zstr_recv_nowait(Reader), 113 | 114 | czmq:zsocket_destroy(Writer), 115 | czmq:zsocket_destroy(Reader), 116 | 117 | io:format("ok~n"). 118 | 119 | %%-------------------------------------------------------------------- 120 | %% @doc Tests sendmem and frame receieves as per 121 | %% http://czmq.zeromq.org/manual:zsocket. 122 | %% @end 123 | %%-------------------------------------------------------------------- 124 | 125 | sendmem_framerecv(Ctx) -> 126 | io:format(" * sendmem_framerecv: "), 127 | 128 | Writer = czmq:zsocket_new(Ctx, push), 129 | "PUSH" = czmq:zsocket_type_str(Writer), 130 | {ok, Port} = czmq:zsocket_bind(Writer, "tcp://*:*"), 131 | 132 | Reader = czmq:zsocket_new(Ctx, pull), 133 | "PULL" = czmq:zsocket_type_str(Reader), 134 | ok = czmq:zsocket_connect(Reader, connect_endpoint(Port)), 135 | 136 | timer:sleep(10), 137 | 138 | ok = czmq:zsocket_sendmem(Writer, "ABC", more), 139 | ok = czmq:zsocket_sendmem(Writer, "DEFG"), 140 | 141 | timer:sleep(10), 142 | 143 | {ok, Frame1} = czmq:zframe_recv_nowait(Reader), 144 | 145 | <<"ABC">> = czmq:zframe_data(Frame1), 146 | true = czmq:zframe_more(Frame1), 147 | 148 | {ok, Frame2} = czmq:zframe_recv_nowait(Reader), 149 | <<"DEFG">> = czmq:zframe_data(Frame2), 150 | false = czmq:zframe_more(Frame2), 151 | 152 | error = czmq:zframe_recv_nowait(Reader), 153 | 154 | czmq:zsocket_destroy(Writer), 155 | czmq:zsocket_destroy(Reader), 156 | 157 | io:format("ok~n"). 158 | 159 | %%-------------------------------------------------------------------- 160 | %% @doc Tests zauth as per http://czmq.zeromq.org/manual:zauth. 161 | %% @end 162 | %%-------------------------------------------------------------------- 163 | 164 | zauth(Ctx) -> 165 | io:format(" * zauth: "), 166 | 167 | Auth = czmq:zauth_new(Ctx), 168 | 169 | %% Default is to accept all clients. 170 | NullConfig = fun(_Client, _Server) -> ok end, 171 | true = client_server_can_connect(Ctx, NullConfig), 172 | 173 | %% Setting a domain turns on auth but without policies, clients 174 | %% are allowed. 175 | DomainConfig = 176 | fun(_Client, Server) -> 177 | czmq:zsocket_set_zap_domain(Server, "global") 178 | end, 179 | true = client_server_can_connect(Ctx, DomainConfig), 180 | 181 | %% Blacklist 127.0.0.1, connection should fail. 182 | czmq:zauth_deny(Auth, "127.0.0.1"), 183 | false = client_server_can_connect(Ctx, DomainConfig), 184 | 185 | %% Whitelist our address, which overrides the blacklist. 186 | czmq:zauth_allow(Auth, "127.0.0.1"), 187 | true = client_server_can_connect(Ctx, DomainConfig), 188 | 189 | %% PLAIN auth 190 | PlainConfig = 191 | fun(Username, Password) -> 192 | fun(Client, Server) -> 193 | czmq:zsocket_set_plain_server(Server, true), 194 | czmq:zsocket_set_plain_username(Client, Username), 195 | czmq:zsocket_set_plain_password(Client, Password) 196 | end 197 | end, 198 | 199 | %% Without authentication configured, all clients are denied. 200 | false = client_server_can_connect(Ctx, PlainConfig("admin", "Password")), 201 | 202 | %% Write a password file. 203 | TmpDir = create_tmp_dir(), 204 | PwdFile = filename:join(TmpDir, "password-file"), 205 | write_password_file(PwdFile, [{"admin", "Password"}]), 206 | 207 | %% With server config only matching credentials are allowed. 208 | czmq:zauth_configure_plain(Auth, "*", PwdFile), 209 | true = client_server_can_connect(Ctx, PlainConfig("admin", "Password")), 210 | false = client_server_can_connect(Ctx, PlainConfig("admin", "Bogus")), 211 | 212 | %% CURVE authentication 213 | ServerCert = czmq:zcert_new(Ctx), 214 | ClientCert = czmq:zcert_new(Ctx), 215 | 216 | CurveConfig = 217 | fun(Client, Server) -> 218 | %% Server config 219 | czmq:zcert_apply(ServerCert, Server), 220 | czmq:zsocket_set_curve_server(Server, true), 221 | 222 | %% Client config 223 | czmq:zcert_apply(ClientCert, Client), 224 | {ok, ServerKey} = czmq:zcert_public_txt(ServerCert), 225 | czmq:zsocket_set_curve_serverkey(Client, ServerKey) 226 | end, 227 | 228 | %% Without authentication configured, all clients are denied. 229 | false = client_server_can_connect(Ctx, CurveConfig), 230 | 231 | %% Configure curve to allow any 232 | czmq:zauth_configure_curve(Auth, "*", allow_any), 233 | true = client_server_can_connect(Ctx, CurveConfig), 234 | 235 | %% Specifying a location with no valid certs, clients are defined. 236 | czmq:zauth_configure_curve(Auth, "*", TmpDir), 237 | false = client_server_can_connect(Ctx, CurveConfig), 238 | 239 | %% Location with a valid cert, client is allowed. 240 | ClientCertFile = filename:join(TmpDir, "mycert.txt"), 241 | czmq:zcert_save_public(ClientCert, ClientCertFile), 242 | true = client_server_can_connect(Ctx, CurveConfig), 243 | 244 | %% Remove valid cert, client is once again denied. 245 | delete_file(ClientCertFile), 246 | false = client_server_can_connect(Ctx, CurveConfig), 247 | 248 | czmq:zcert_destroy(ServerCert), 249 | czmq:zcert_destroy(ClientCert), 250 | 251 | %% Remove authentication - clients are allowed. 252 | czmq:zauth_destroy(Auth), 253 | true = client_server_can_connect(Ctx, NullConfig), 254 | 255 | %% Cleanup 256 | delete_dir(TmpDir), 257 | czmq:zauth_destroy(Auth), 258 | 259 | io:format("ok~n"). 260 | 261 | create_tmp_dir() -> 262 | Dir = "/tmp/czmq_test", 263 | handle_make_dir(file:make_dir(Dir), Dir). 264 | 265 | handle_make_dir(ok, Dir) -> Dir; 266 | handle_make_dir({error, eexist}, Dir) -> Dir. 267 | 268 | delete_dir(Dir) when Dir /= "/" -> 269 | "" = os:cmd("rm -rf " ++ Dir). 270 | 271 | delete_file(File) -> 272 | "" = os:cmd("rm " ++ File). 273 | 274 | write_password_file(File, Creds) -> 275 | Bytes = [[User, "=", Pwd, "\n"] || {User, Pwd} <- Creds], 276 | ok = file:write_file(File, Bytes). 277 | 278 | client_server_can_connect(Ctx, Config) -> 279 | Server = czmq:zsocket_new(Ctx, push), 280 | Client = czmq:zsocket_new(Ctx, pull), 281 | 282 | ok = Config(Client, Server), 283 | 284 | {ok, Port} = czmq:zsocket_bind(Server, "tcp://127.0.0.1:*"), 285 | ok = czmq:zsocket_connect(Client, connect_endpoint(Port)), 286 | 287 | timer:sleep(10), 288 | 289 | Sent = czmq:zstr_send(Server, "Watson, sorry about the other night"), 290 | 291 | timer:sleep(10), 292 | 293 | Received = czmq:zstr_recv_nowait(Client), 294 | 295 | czmq:zsocket_destroy(Server), 296 | czmq:zsocket_destroy(Client), 297 | 298 | msg_sent_and_received(Sent, Received). 299 | 300 | msg_sent_and_received(ok, {ok, _}) -> true; 301 | msg_sent_and_received(_Sent, _Received) -> false. 302 | 303 | connect_endpoint(Port) -> 304 | "tcp://127.0.0.1:" ++ integer_to_list(Port). 305 | 306 | %%-------------------------------------------------------------------- 307 | %% @doc Tests using a poller process to recv and dispatch messages. 308 | %% @end 309 | %%-------------------------------------------------------------------- 310 | 311 | poller(Ctx) -> 312 | io:format(" * poller: "), 313 | 314 | Reader = czmq:zsocket_new(Ctx, pull), 315 | {ok, _} = czmq:zsocket_bind(Reader, "inproc://zpoller_test"), 316 | 317 | Writer = czmq:zsocket_new(Ctx, push), 318 | czmq:zsocket_set_sndhwm(Writer, 5000), 319 | ok = czmq:zsocket_connect(Writer, "inproc://zpoller_test"), 320 | 321 | {ok, Poller} = czmq:subscribe_link(Reader, [{poll_interval, 100}]), 322 | 323 | MsgFmt = "Watson, I want you in ~b second(s)", 324 | send_messages(Writer, MsgFmt, 5000), 325 | receive_messages(MsgFmt, 5000), 326 | 327 | czmq:unsubscribe(Poller), 328 | czmq:zsocket_destroy(Reader), 329 | czmq:zsocket_destroy(Writer), 330 | 331 | io:format("ok~n"). 332 | 333 | send_messages(_Socket, _MsgFmt, 0) -> ok; 334 | send_messages(Socket, MsgFmt, N) when N > 0 -> 335 | Msg = io_lib:format(MsgFmt, [N]), 336 | ok = czmq:zstr_send(Socket, Msg), 337 | send_messages(Socket, MsgFmt, N - 1). 338 | 339 | receive_messages(_MsgFmt, 0) -> ok; 340 | receive_messages(MsgFmt, N) when N > 0 -> 341 | Expected = [iolist_to_binary(io_lib:format(MsgFmt, [N]))], 342 | receive 343 | {_Poller, Expected} -> ok 344 | after 345 | 1000 -> error({timeout, N}) 346 | end, 347 | receive_messages(MsgFmt, N - 1). 348 | 349 | %%-------------------------------------------------------------------- 350 | %% @doc Tests router/dealer interactions. 351 | %% @end 352 | %%-------------------------------------------------------------------- 353 | 354 | router_dealer(Ctx) -> 355 | io:format(" * router_dealer: "), 356 | 357 | Router = czmq:zsocket_new(Ctx, router), 358 | {ok, 0} = czmq:zsocket_bind(Router, "inproc://router_dealer"), 359 | 360 | Dealer1 = czmq:zsocket_new(Ctx, dealer), 361 | ok = czmq:zsocket_set_identity(Dealer1, "dealer-1"), 362 | ok = czmq:zsocket_connect(Dealer1, "inproc://router_dealer"), 363 | 364 | Dealer2 = czmq:zsocket_new(Ctx, dealer), 365 | ok = czmq:zsocket_set_identity(Dealer2, "dealer-2"), 366 | ok = czmq:zsocket_connect(Dealer2, "inproc://router_dealer"), 367 | 368 | ok = czmq:zstr_send(Dealer1, "dealer-1 says hello"), 369 | ok = czmq:zstr_send(Dealer2, "dealer-2 says hi"), 370 | 371 | timer:sleep(100), 372 | 373 | %% Routers recv messages preceded by dealer ID frame. 374 | 375 | {ok, [Dealer1Ref, <<"dealer-1 says hello">>]} = 376 | czmq:zframe_recv_all(Router), 377 | 378 | {ok, [Dealer2Ref, <<"dealer-2 says hi">>]} = 379 | czmq:zframe_recv_all(Router), 380 | 381 | error = czmq:zframe_recv_all(Router), 382 | 383 | %% Use dealer IDs to router messages to specific dealers. 384 | 385 | ok = czmq:zsocket_send_all(Router, [Dealer1Ref, "hello dealer-1"]), 386 | ok = czmq:zsocket_send_all(Router, [Dealer2Ref, "hi dealer-2"]), 387 | 388 | timer:sleep(100), 389 | 390 | {ok, [<<"hello dealer-1">>]} = czmq:zframe_recv_all(Dealer1), 391 | {ok, [<<"hi dealer-2">>]} = czmq:zframe_recv_all(Dealer2), 392 | 393 | czmq:zsocket_destroy(Router), 394 | czmq:zsocket_destroy(Dealer1), 395 | czmq:zsocket_destroy(Dealer2), 396 | 397 | io:format("ok~n"). 398 | 399 | %%-------------------------------------------------------------------- 400 | %% @doc Tests push/pull interactions. 401 | %% @end 402 | %%-------------------------------------------------------------------- 403 | 404 | push_pull(Ctx) -> 405 | io:format(" * push_pull: "), 406 | 407 | Pull = czmq:zsocket_new(Ctx, pull), 408 | {ok, 0} = czmq:zsocket_bind(Pull, "inproc://push_pull"), 409 | 410 | Push = czmq:zsocket_new(Ctx, push), 411 | ok = czmq:zsocket_connect(Push, "inproc://push_pull"), 412 | 413 | ok = czmq:zstr_send(Push, "Watson, pour me a scotch"), 414 | 415 | timer:sleep(100), 416 | 417 | {ok, "Watson, pour me a scotch"} = czmq:zstr_recv_nowait(Pull), 418 | error = czmq:zstr_recv_nowait(Pull), 419 | 420 | %% Pull sockets don't support send. 421 | 422 | error = czmq:zstr_send(Pull, "Going the wrong way!"), 423 | 424 | czmq:zsocket_destroy(Push), 425 | czmq:zsocket_destroy(Pull), 426 | 427 | io:format("ok~n"). 428 | 429 | %%-------------------------------------------------------------------- 430 | %% @doc Tests req/rep interactions. 431 | %% @end 432 | %%-------------------------------------------------------------------- 433 | 434 | req_rep(Ctx) -> 435 | io:format(" * req_rep: "), 436 | 437 | Rep = czmq:zsocket_new(Ctx, rep), 438 | {ok, 0} = czmq:zsocket_bind(Rep, "inproc://req_rep"), 439 | 440 | Req1 = czmq:zsocket_new(Ctx, req), 441 | ok = czmq:zsocket_connect(Req1, "inproc://req_rep"), 442 | 443 | Req2 = czmq:zsocket_new(Ctx, req), 444 | ok = czmq:zsocket_connect(Req2, "inproc://req_rep"), 445 | 446 | %% Requests implicitly convey the client ID. 447 | 448 | ok = czmq:zstr_send(Req1, "Watson, what's on TV?"), 449 | ok = czmq:zstr_send(Req2, "Watson, what's for dinner?"), 450 | 451 | timer:sleep(100), 452 | 453 | %% We don't know the order the questions will arrive in, so we'll handle 454 | %% both with a function that we can call twice.. 455 | 456 | HandleMsg = 457 | fun({ok, "Watson, what's on TV?"}) -> 458 | ok = czmq:zstr_send(Rep, "The final episode of Breaking Bad!"); 459 | ({ok, "Watson, what's for dinner?"}) -> 460 | ok = czmq:zstr_send(Rep, "Your favorite, as always!") 461 | end, 462 | 463 | %% Handle two messages using our function - order doesn't matter. 464 | 465 | HandleMsg(czmq:zstr_recv_nowait(Rep)), 466 | HandleMsg(czmq:zstr_recv_nowait(Rep)), 467 | error = czmq:zstr_recv_nowait(Rep), 468 | 469 | %% Each req socket gets only its reply. 470 | 471 | {ok, "The final episode of Breaking Bad!"} = czmq:zstr_recv_nowait(Req1), 472 | error = czmq:zstr_recv_nowait(Req1), 473 | 474 | {ok, "Your favorite, as always!"} = czmq:zstr_recv_nowait(Req2), 475 | error = czmq:zstr_recv_nowait(Req2), 476 | 477 | czmq:zsocket_destroy(Req1), 478 | czmq:zsocket_destroy(Req2), 479 | czmq:zsocket_destroy(Rep), 480 | 481 | io:format("ok~n"). 482 | 483 | %%-------------------------------------------------------------------- 484 | %% @doc Tests pub/sub interactions. 485 | %% @end 486 | %%-------------------------------------------------------------------- 487 | 488 | pub_sub(Ctx) -> 489 | io:format(" * pub_sub: "), 490 | 491 | Pub = czmq:zsocket_new(Ctx, pub), 492 | {ok, 0} = czmq:zsocket_bind(Pub, "inproc://pub_sub"), 493 | 494 | Sub1 = czmq:zsocket_new(Ctx, sub), 495 | ok = czmq:zsocket_connect(Sub1, "inproc://pub_sub"), 496 | ok = czmq:zsocket_set_subscribe(Sub1, "fun:"), 497 | 498 | Sub2 = czmq:zsocket_new(Ctx, sub), 499 | ok = czmq:zsocket_connect(Sub2, "inproc://pub_sub"), 500 | ok = czmq:zsocket_set_subscribe(Sub2, "games:"), 501 | 502 | %% Publish by sending to the pub socket 503 | 504 | ok = czmq:zstr_send(Pub, "fun:Let's frolic!"), 505 | ok = czmq:zstr_send(Pub, "games:Let's play scrabble!"), 506 | ok = czmq:zstr_send(Pub, "other:Let's read by the fire!"), 507 | 508 | timer:sleep(100), 509 | 510 | %% Subscribes receive messages based on their subscription prefix. 511 | 512 | {ok, "fun:Let's frolic!"} = czmq:zstr_recv_nowait(Sub1), 513 | error = czmq:zstr_recv_nowait(Sub1), 514 | 515 | {ok, "games:Let's play scrabble!"} = czmq:zstr_recv_nowait(Sub2), 516 | error = czmq:zstr_recv_nowait(Sub2), 517 | 518 | czmq:zsocket_destroy(Sub1), 519 | czmq:zsocket_destroy(Sub2), 520 | czmq:zsocket_destroy(Pub), 521 | 522 | io:format("ok~n"). 523 | 524 | %%-------------------------------------------------------------------- 525 | %% @doc Tests sockopt API. 526 | %% @end 527 | %%-------------------------------------------------------------------- 528 | 529 | sockopts(Ctx) -> 530 | io:format(" * sockopts: "), 531 | 532 | %% TODO: This is an incomplete test. 533 | 534 | Sock = czmq:zsocket_new(Ctx, router), 535 | 536 | %% Backlog 537 | 100 = czmq:zsocket_backlog(Sock), 538 | czmq:zsocket_set_backlog(Sock, 200), 539 | 200 = czmq:zsocket_backlog(Sock), 540 | 541 | %% HWMs 542 | 1000 = czmq:zsocket_sndhwm(Sock), 543 | czmq:zsocket_set_sndhwm(Sock, 2000), 544 | 2000 = czmq:zsocket_sndhwm(Sock), 545 | 546 | 1000 = czmq:zsocket_rcvhwm(Sock), 547 | czmq:zsocket_set_rcvhwm(Sock, 3000), 548 | 3000 = czmq:zsocket_rcvhwm(Sock), 549 | 550 | %% Identity 551 | "" = czmq:zsocket_identity(Sock), 552 | czmq:zsocket_set_identity(Sock, "Watson"), 553 | "Watson" = czmq:zsocket_identity(Sock), 554 | 555 | czmq:zsocket_destroy(Sock), 556 | 557 | io:format("ok~n"). 558 | 559 | 560 | %%-------------------------------------------------------------------- 561 | %% @doc Tests growing buffer. 562 | %% @end 563 | %%-------------------------------------------------------------------- 564 | 565 | large_message(Ctx) -> 566 | io:format(" * large_messages: ", []), 567 | [large_message(Ctx, _N) || _N <- lists:seq(1, 10)], 568 | io:format("ok~n"). 569 | 570 | large_message(Ctx, N) -> 571 | Pl1 = ["!" || _ <- lists:seq(1, N * 10000)], 572 | 573 | Pub = czmq:zsocket_new(Ctx, pub), 574 | {ok, 0} = czmq:zsocket_bind(Pub, "inproc://pub_sub"), 575 | 576 | Sub1 = czmq:zsocket_new(Ctx, sub), 577 | ok = czmq:zsocket_connect(Sub1, "inproc://pub_sub"), 578 | ok = czmq:zsocket_set_subscribe(Sub1, ""), 579 | 580 | ok = czmq:zstr_send(Pub, Pl1), 581 | 582 | timer:sleep(100), 583 | 584 | {ok, Pl2} = czmq:zstr_recv_nowait(Sub1), 585 | Pl3 = iolist_to_binary(Pl1), 586 | Pl3 = iolist_to_binary(Pl2), 587 | 588 | czmq:zsocket_destroy(Sub1), 589 | czmq:zsocket_destroy(Pub). 590 | 591 | -------------------------------------------------------------------------------- /src/erlzmq_benchmark.erl: -------------------------------------------------------------------------------- 1 | %% =================================================================== 2 | %% @author Garrett Smith 3 | %% @copyright 2014 Garrett Smith 4 | %% 5 | %% @doc Benchmarker for erlzmq (NIF bindings). 6 | %% 7 | %% @end 8 | %% =================================================================== 9 | 10 | -module(erlzmq_benchmark). 11 | 12 | -behavior(zmq_gen_benchmark). 13 | 14 | -export([start_recv/0, start_recv/1, start_send/0, start_send/1, stop/1]). 15 | 16 | -export([init_recv/1, init_send/1, recv/1, send/2, terminate/1]). 17 | 18 | -define(DEFAULT_PORT, 5555). 19 | -define(DEFAULT_HOST, "localhost"). 20 | -define(DEFAULT_RECV_SOCKET_TYPE, pull). 21 | -define(DEFAULT_SEND_SOCKET_TYPE, push). 22 | -define(RECV_TIMEOUT, 100). 23 | 24 | -record(state, {ctx, socket}). 25 | 26 | %%%=================================================================== 27 | %%% API 28 | %%%=================================================================== 29 | 30 | start_recv() -> start_recv([]). 31 | 32 | start_recv(Options) -> 33 | maybe_configure_code_path(Options), 34 | zmq_gen_benchmark:start_recv(?MODULE, Options). 35 | 36 | maybe_configure_code_path(Options) -> 37 | handle_erlzmq_home(proplists:get_value(erlzmq_home, Options)). 38 | 39 | handle_erlzmq_home(undefined) -> ok; 40 | handle_erlzmq_home(Home) -> 41 | true = code:add_path(ebin_dir(Home)). 42 | 43 | ebin_dir(Home) -> 44 | filename:join(Home, "ebin"). 45 | 46 | start_send() -> start_send([]). 47 | 48 | start_send(Options) -> 49 | maybe_configure_code_path(Options), 50 | zmq_gen_benchmark:start_send(?MODULE, Options). 51 | 52 | stop(Benchmark) -> 53 | zmq_gen_benchmark:stop(Benchmark). 54 | 55 | %%%=================================================================== 56 | %%% Recv 57 | %%%=================================================================== 58 | 59 | init_recv(Options) -> 60 | {ok, Ctx} = erlzmq:context(), 61 | {ok, Socket} = erlzmq:socket(Ctx, recv_socket_type(Options)), 62 | ok = erlzmq:setsockopt(Socket, rcvtimeo, ?RECV_TIMEOUT), 63 | ok = erlzmq:bind(Socket, bind_endpoint(Options)), 64 | {ok, #state{ctx=Ctx, socket=Socket}}. 65 | 66 | recv_socket_type(Options) -> 67 | proplists:get_value(socket_type, Options, ?DEFAULT_RECV_SOCKET_TYPE). 68 | 69 | bind_endpoint(Options) -> 70 | Port = proplists:get_value(port, Options, ?DEFAULT_PORT), 71 | "tcp://*:" ++ integer_to_list(Port). 72 | 73 | recv(#state{socket=Socket}=State) -> 74 | handle_erlzmq_recv(erlzmq:recv(Socket), State). 75 | 76 | handle_erlzmq_recv({ok, Msg}, State) -> {ok, Msg, State}; 77 | handle_erlzmq_recv({error, eagain}, State) -> {error, State}; 78 | handle_erlzmq_recv({error, Err}, State) -> 79 | terminate(State), 80 | exit({recv_error, Err}). 81 | 82 | %%%=================================================================== 83 | %%% Send 84 | %%%=================================================================== 85 | 86 | init_send(Options) -> 87 | {ok, Ctx} = erlzmq:context(), 88 | {ok, Socket} = erlzmq:socket(Ctx, send_socket_type(Options)), 89 | ok = erlzmq:connect(Socket, connect_endpoint(Options)), 90 | {ok, #state{ctx=Ctx, socket=Socket}}. 91 | 92 | send_socket_type(Options) -> 93 | proplists:get_value(socket_type, Options, ?DEFAULT_SEND_SOCKET_TYPE). 94 | 95 | connect_endpoint(Options) -> 96 | Port = proplists:get_value(port, Options, ?DEFAULT_PORT), 97 | Host = proplists:get_value(host, Options, ?DEFAULT_HOST), 98 | "tcp://" ++ Host ++ ":" ++ integer_to_list(Port). 99 | 100 | send(Msg, #state{socket=Socket}=State) -> 101 | handle_erlzmq_send(erlzmq:send(Socket, Msg), State). 102 | 103 | handle_erlzmq_send(ok, State) -> {ok, State}. 104 | 105 | %%%=================================================================== 106 | %%% Terminate 107 | %%%=================================================================== 108 | 109 | terminate(#state{socket=Socket, ctx=Ctx}) -> 110 | ok = erlzmq:close(Socket, 1000), 111 | ok = erlzmq:term(Ctx, 1000). 112 | -------------------------------------------------------------------------------- /src/ezmq_benchmark.erl: -------------------------------------------------------------------------------- 1 | %% =================================================================== 2 | %% @author Garrett Smith 3 | %% @copyright 2014 Garrett Smith 4 | %% 5 | %% @doc Benchmarker for ezmq (pure Erlang ZeroMQ support). 6 | %% 7 | %% @end 8 | %% =================================================================== 9 | 10 | -module(ezmq_benchmark). 11 | 12 | -behavior(zmq_gen_benchmark). 13 | 14 | -export([start_recv/0, start_recv/1, start_send/0, start_send/1, stop/1]). 15 | 16 | -export([init_recv/1, init_send/1, recv/1, send/2, terminate/1]). 17 | 18 | -define(DEFAULT_PORT, 5555). 19 | -define(DEFAULT_HOST, "localhost"). 20 | -define(DEFAULT_RECV_SOCKET_TYPE, router). 21 | -define(DEFAULT_SEND_SOCKET_TYPE, dealer). 22 | -define(RECV_TIMEOUT, 100). 23 | 24 | -record(state, {socket}). 25 | 26 | %%%=================================================================== 27 | %%% API 28 | %%%=================================================================== 29 | 30 | start_recv() -> start_recv([]). 31 | 32 | start_recv(Options) -> 33 | maybe_configure_code_path(Options), 34 | ensure_ezmq_started(), 35 | zmq_gen_benchmark:start_recv(?MODULE, Options). 36 | 37 | maybe_configure_code_path(Options) -> 38 | handle_ezmq_home(proplists:get_value(ezmq_home, Options)). 39 | 40 | handle_ezmq_home(undefined) -> ok; 41 | handle_ezmq_home(Home) -> 42 | true = code:add_path(ebin_dir(Home)), 43 | true = code:add_path(ebin_dir(deps_dir(Home, "gen_listener_tcp"))). 44 | 45 | ebin_dir(Home) -> 46 | filename:join(Home, "ebin"). 47 | 48 | deps_dir(Root, Dep) -> 49 | filename:join([Root, "deps", Dep]). 50 | 51 | ensure_ezmq_started() -> 52 | application:start(sasl), 53 | application:start(gen_listener_tcp), 54 | application:start(ezmq). 55 | 56 | start_send() -> start_send([]). 57 | 58 | start_send(Options) -> 59 | maybe_configure_code_path(Options), 60 | ensure_ezmq_started(), 61 | zmq_gen_benchmark:start_send(?MODULE, Options). 62 | 63 | stop(Benchmark) -> 64 | zmq_gen_benchmark:stop(Benchmark). 65 | 66 | %%%=================================================================== 67 | %%% Recv 68 | %%%=================================================================== 69 | 70 | init_recv(Options) -> 71 | SocketOpts = [{type, recv_socket_type(Options)}, {active, false}], 72 | {ok, Socket} = ezmq:socket(SocketOpts), 73 | ok = ezmq:bind(Socket, tcp, bind_port(Options), []), 74 | {ok, #state{socket=Socket}}. 75 | 76 | recv_socket_type(Options) -> 77 | proplists:get_value(socket_type, Options, ?DEFAULT_RECV_SOCKET_TYPE). 78 | 79 | bind_port(Options) -> 80 | proplists:get_value(port, Options, ?DEFAULT_PORT). 81 | 82 | recv(#state{socket=Socket}=State) -> 83 | %% Would like to have a timeout, but something isn't working here. 84 | %% --> handle_erlzmq_recv(ezmq:recv(Socket, ?RECV_TIMEOUT), State). 85 | handle_erlzmq_recv(ezmq:recv(Socket), State). 86 | 87 | handle_erlzmq_recv({ok, Msg}, State) -> 88 | {ok, Msg, State}; 89 | handle_erlzmq_recv({error, Err}, State) -> 90 | terminate(State), 91 | exit({recv_error, Err}). 92 | 93 | %%%=================================================================== 94 | %%% Send 95 | %%%=================================================================== 96 | 97 | init_send(Options) -> 98 | SocketOpts = [{type, send_socket_type(Options)}, {active, false}], 99 | {ok, Socket} = ezmq:socket(SocketOpts), 100 | ok = ezmq:connect( 101 | Socket, tcp, connect_host(Options), connect_port(Options), []), 102 | {ok, #state{socket=Socket}}. 103 | 104 | send_socket_type(Options) -> 105 | proplists:get_value(socket_type, Options, ?DEFAULT_SEND_SOCKET_TYPE). 106 | 107 | connect_host(Options) -> 108 | proplists:get_value(host, Options, ?DEFAULT_HOST). 109 | 110 | connect_port(Options) -> 111 | proplists:get_value(port, Options, ?DEFAULT_PORT). 112 | 113 | send(Msg, #state{socket=Socket}=State) -> 114 | handle_ezmq_send(ezmq:send(Socket, [Msg]), State). 115 | 116 | handle_ezmq_send(ok, State) -> {ok, State}. 117 | 118 | %%%=================================================================== 119 | %%% Terminate 120 | %%%=================================================================== 121 | 122 | terminate(#state{socket=Socket}) -> 123 | ok = ezmq:close(Socket). 124 | -------------------------------------------------------------------------------- /src/zmq_gen_benchmark.erl: -------------------------------------------------------------------------------- 1 | %% =================================================================== 2 | %% @author Garrett Smith 3 | %% @copyright 2014 Garrett Smith 4 | %% 5 | %% @doc Benchmarking behavior. 6 | %% 7 | %% @end 8 | %% =================================================================== 9 | 10 | -module(zmq_gen_benchmark). 11 | 12 | -export([start_recv/1, start_recv/2, start_send/1, start_send/2, stop/1]). 13 | 14 | -export([behaviour_info/1]). 15 | 16 | behaviour_info(callbacks) -> 17 | [{init_recv, 1}, 18 | {recv, 1}, 19 | {init_send, 1}, 20 | {send, 2}, 21 | {terminate, 1}]. 22 | 23 | -define(DEFAULT_RECV_SOCKET_TYPE, pull). 24 | -define(DEFAULT_SEND_TIME, 5). 25 | -define(DEFAULT_MSG_SIZE, 512). 26 | 27 | -record(state, {mod, mod_state, stop_time, msg, msg_count, last_log}). 28 | 29 | %%%=================================================================== 30 | %%% API 31 | %%%=================================================================== 32 | 33 | start_recv(Module) -> start_recv(Module, []). 34 | 35 | start_recv(Module, Options) -> 36 | spawn(fun() -> recv(Module, Options) end). 37 | 38 | start_send(Module) -> start_send(Module, []). 39 | 40 | start_send(Module, Options) -> 41 | spawn(fun() -> send(Module, Options) end). 42 | 43 | stop(Bench) -> 44 | erlang:send(Bench, stop), 45 | ok. 46 | 47 | %%%=================================================================== 48 | %%% Recv 49 | %%%=================================================================== 50 | 51 | recv(Mod, Options) -> 52 | recv_loop(init_recv_state(Mod, Options)). 53 | 54 | init_recv_state(Mod, Options) -> 55 | handle_mod_init_recv(mod_init_recv(Mod, Options), Mod). 56 | 57 | mod_init_recv(Mod, Options) -> Mod:init_recv(Options). 58 | 59 | handle_mod_init_recv({ok, ModState}, Mod) -> 60 | #state{ 61 | mod=Mod, 62 | mod_state=ModState, 63 | msg_count=0, 64 | last_log=0}; 65 | handle_mod_init_recv({error, Err}, _Mod) -> 66 | error({init_recv_error, Err}). 67 | 68 | option(Name, Options, Default) -> 69 | proplists:get_value(Name, Options, Default). 70 | 71 | now_millis() -> 72 | {M, S, U} = erlang:timestamp(), 73 | M * 1000000000 + S * 1000 + U div 1000. 74 | 75 | recv_loop(State) -> 76 | recv_loop(recv_status(), State). 77 | 78 | recv_status() -> 79 | receive 80 | stop -> stop 81 | after 82 | 0 -> continue 83 | end. 84 | 85 | recv_loop(continue, #state{mod=Mod, mod_state=ModState}=State) -> 86 | handle_recv(Mod:recv(ModState), maybe_log(State)); 87 | recv_loop(stop, State) -> 88 | terminate(State). 89 | 90 | maybe_log(State) -> 91 | Now = now_millis(), 92 | maybe_log(time_to_log(Now, State), Now, State). 93 | 94 | time_to_log(Now, #state{last_log=LastLog}) -> 95 | Now - LastLog >= 1000. 96 | 97 | maybe_log(true, Now, #state{msg_count=MsgCount}=State) -> 98 | io:format("~p ~p~n", [Now, MsgCount]), 99 | reset_msg_count(State); 100 | maybe_log(false, _Now, State) -> 101 | State. 102 | 103 | reset_msg_count(State) -> 104 | State#state{msg_count=0, last_log=now_millis()}. 105 | 106 | handle_recv({ok, _Msg, ModState}, State) -> 107 | recv_loop(set_mod_state(ModState, increment_msg_count(State))); 108 | handle_recv({error, ModState}, State) -> 109 | recv_loop(set_mod_state(ModState, State)). 110 | 111 | increment_msg_count(#state{msg_count=Count}=S) -> 112 | S#state{msg_count=Count + 1}. 113 | 114 | set_mod_state(ModState, State) -> 115 | State#state{mod_state=ModState}. 116 | 117 | %%%=================================================================== 118 | %%% Send 119 | %%%=================================================================== 120 | 121 | send(Mod, Options) -> 122 | send_loop(init_send_state(Mod, Options)). 123 | 124 | init_send_state(Mod, Options) -> 125 | handle_mod_init_send(mod_init_send(Mod, Options), Mod, Options). 126 | 127 | mod_init_send(Mod, Options) -> Mod:init_send(Options). 128 | 129 | handle_mod_init_send({ok, ModState}, Mod, Options) -> 130 | SendTime = option(time, Options, ?DEFAULT_SEND_TIME), 131 | StopTime = SendTime * 1000 + now_millis(), 132 | MsgSize = option(msg_size, Options, ?DEFAULT_MSG_SIZE), 133 | Msg = new_msg(MsgSize), 134 | #state{ 135 | mod=Mod, 136 | mod_state=ModState, 137 | stop_time=StopTime, 138 | msg=Msg}; 139 | handle_mod_init_send({error, Err}, _Mod, _Options) -> 140 | error({init_send_error, Err}). 141 | 142 | new_msg(Size) -> 143 | list_to_binary(lists:duplicate(Size, $!)). 144 | 145 | send_loop(State) -> 146 | send_loop(send_status(State), State). 147 | 148 | send_status(#state{stop_time=StopTime}) -> 149 | case now_millis() >= StopTime of 150 | true -> stop; 151 | false -> continue 152 | end. 153 | 154 | send_loop(continue, #state{mod=Mod, mod_state=ModState, msg=Msg}=State) -> 155 | handle_send(Mod:send(Msg, ModState), State); 156 | send_loop(stop, State) -> 157 | terminate(State). 158 | 159 | handle_send({ok, ModState}, State) -> 160 | send_loop(set_mod_state(ModState, State)); 161 | handle_send({error, ModState}, State) -> 162 | send_loop(set_mod_state(ModState, State)). 163 | 164 | %%%=================================================================== 165 | %%% Terminate 166 | %%%=================================================================== 167 | 168 | terminate(#state{mod=Mod, mod_state=ModState}) -> 169 | Mod:terminate(ModState), 170 | exit(normal). 171 | --------------------------------------------------------------------------------