├── .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 |
--------------------------------------------------------------------------------