From 5974215c9864ca72945b553f5374dbc8ba9a191d Mon Sep 17 00:00:00 2001 From: Dimitri Staessens Date: Sat, 16 May 2020 17:22:09 +0200 Subject: Initial commit: Basic Ouroboros API Signed-off-by: Dimitri Staessens --- LICENSE | 344 ++++++++++++++++++++++++++++++++++++++++ README.md | 194 +++++++++++++++++++++++ examples/oecho.py | 62 ++++++++ examples/oecho_set.py | 71 +++++++++ ffi/fccntl_wrap.h | 73 +++++++++ ffi/pyouroboros_build.py | 138 ++++++++++++++++ ouroboros/dev.py | 398 +++++++++++++++++++++++++++++++++++++++++++++++ ouroboros/event.py | 147 +++++++++++++++++ ouroboros/qos.py | 110 +++++++++++++ setup.py | 25 +++ 10 files changed, 1562 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100755 examples/oecho.py create mode 100755 examples/oecho_set.py create mode 100644 ffi/fccntl_wrap.h create mode 100644 ffi/pyouroboros_build.py create mode 100644 ouroboros/dev.py create mode 100644 ouroboros/event.py create mode 100644 ouroboros/qos.py create mode 100755 setup.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a618b2b --- /dev/null +++ b/LICENSE @@ -0,0 +1,344 @@ + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..dec2db5 --- /dev/null +++ b/README.md @@ -0,0 +1,194 @@ +# pyOuroboros +> a Python API for the Ouroboros recursive network prototype + +## Dependencies + +pyOuroboros requires Ouroboros +to be installed + +## Installation +To build and install PyOuroboros: + +```shell +./setup.py install +``` + +## Basic Usage + +```Python +from ouroboros.dev import * +``` + +Server side: Accepting a flow: + +```Python +f = flow_accept() +``` + +returns a new allocated flow object. + +Client side: Allocating a flow to a certain _name_: + +```Python +f = flow_alloc("name") +``` + +returns a new allocated Flow object. + +Broadcast: + +```Python +f = flow_join("name") +``` + +returns a new allocated Flow object. + +Deallocation: + +```Python +f.dealloc() +``` +To avoid having to call dealloc(), you can use the with statement: + +```Python +with flow_alloc("dst") as f: + f.writeline("line") + print(f.readline()) +``` + +deallocates the flow. After this call, the Flow object is not readable +or writeable anymore. + +```Python +f.alloc("name") +``` + + will allocate a new flow for an existing Flow object. + +To read / write from a flow: + +```Python +f.read(count) # read up to _count_ bytes and return bytes +f.readline(count) # read up to _count_ characters as a string +f.write(buf, count) # write up to _count_ bytes from buffer +f.writeline(str, count) # write up to _count_ characters from string +``` + +## Quality of Service (QoS) + +The QoS spec details have not been finalized in Ouroboros. It is just +here to give a general idea and to control some basics of the flow. +You can specify a QoSSpec for flow allocation. + +For instance, + +```Python +qos = QoSSpec(loss=0, cypher_s=256) +f = flow_alloc("name", qos) +``` + +will create a new flow with FRCP retransmission enabled and encrypted +using a 256-bit ECDHE-AES-SHA3 cypher. + +## Manipulating flows + +A number of methods are available for how to interact with Flow + +```Python +f.set_snd_timeout(0.5) # set timeout for blocking write +f.set_rcv_timeout(1.0) # set timeout for blocking read +f.get_snd_timeout() # get timeout for blocking write +f.get_rcv_timeout() # get timeout for blocking read +f.get_qos() # get the QoSSpec for this flow +f.get_rx_queue_len() # get the number of packets in the rx buffer +f.get_tx_queue_len() # get the number of packets in the tx buffer +f.set_flags(flags) # set a number of flags for this flow +f.get_flags() # get the flags for this flow +``` + +The flags are specified as an enum FlowProperties: + +```Python +class FlowProperties(IntFlag): + ReadOnly + WriteOnly + ReadWrite + Down + NonBlockingRead + NonBlockingWrite + NonBlocking + NoPartialRead + NoPartialWrite +``` + +See the Ouroboros fccntl documentation for more details. + +```shell +man fccntl +``` + +## Event API + +Multiple flows can be monitored for activity in parallel using a +FlowSet and FEventQueue objects. + +FlowSets allow grouping a bunch of Flow objects together to listen for +activity. It can be constructed with an optional list of Flows, or +flows can be added or removed using the following methods: + +```Python +set = FlowSet() # create a flow set, +set.add(f) # add a Flow 'f' to this set +set.remove(f) # remove a Flow 'f' from this set +set.zero() # remove all Flows in this set +``` + +An FEventQueue stores pending events on flows. + +The event types are defined as follows: +```Python +class FEventType(IntFlag): + FlowPkt + FlowDown + FlowUp + FlowAlloc + FlowDealloc +``` + +and can be obtained by calling the next method: + +```Python + f, t = fq.next() # Return active flow 'f' and type of event 't' +``` + +An FEventQueue is populated from a FlowSet. + +```Python +fq = FEventQueue() # Create an eventqueue +set = FlowSet([f1, f2, f3]) # Create a new set with a couple of Flow objects +set.wait(fq, timeo=1.0) # Wait for 1 second or until event +while f, t = fq.next(): + if t == FEventType.FlowPkt: + msg = f.readline() + ... +set.destroy() +``` + +A flow_set must be destroyed when it goes out of scope. +To avoid having to call destroy, Python's with statement can be used: + +```Python +fq = FEventQueue() +with FlowSet([f]) as fs: + fs.wait(fq) +f2, t = fq.next() +if t == FEventType.FlowPkt: + line = f2.readline() +``` + +## Examples + +Some example code is in the examples folder. + +## License +pyOuorboros is LGPLv2.1. The examples are 3-clause BSD. diff --git a/examples/oecho.py b/examples/oecho.py new file mode 100755 index 0000000..395930c --- /dev/null +++ b/examples/oecho.py @@ -0,0 +1,62 @@ +#!/bin/python + +# Ouroboros - Copyright (C) 2016 - 2020 +# +# A simple echo application +# +# Dimitri Staessens +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. + +from ouroboros.dev import * +import argparse + + +def client(): + with flow_alloc("oecho") as f: + f.writeline("Hello, PyOuroboros!") + print(f.readline()) + + +def server(): + print("Starting the server.") + while True: + with flow_accept() as f: + print("New flow.") + line = f.readline() + print("Message from client is " + line) + f.writeline(line) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='A simple echo client/server') + parser.add_argument('-l', '--listen', help='run as a server', action='store_true') + args = parser.parse_args() + server() if args.listen is True else client() diff --git a/examples/oecho_set.py b/examples/oecho_set.py new file mode 100755 index 0000000..4d9d1f7 --- /dev/null +++ b/examples/oecho_set.py @@ -0,0 +1,71 @@ +#!/bin/python + +# Ouroboros - Copyright (C) 2016 - 2020 +# +# A simple echo application +# +# Dimitri Staessens +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. + +from ouroboros.event import * +import argparse + + +def client(): + with flow_alloc("oecho") as f: + f.writeline("Hello, PyOuroboros!") + print(f.readline()) + + +def server(): + print("Starting the server.") + while True: + with flow_accept() as f: + print("New flow.") + fq = FEventQueue() + with FlowSet([f]) as fs: + fs.wait(fq) + f2, t = fq.next() + if t != FEventType.FlowPkt: + continue + line = f2.readline() + print("Message from client is " + line) + f2.writeline(line) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='A simple echo client/server') + parser.add_argument('-l', '--listen', help='run as a server', action='store_true') + args = parser.parse_args() + if args.listen is True: + server() + else: + client() diff --git a/ffi/fccntl_wrap.h b/ffi/fccntl_wrap.h new file mode 100644 index 0000000..ab227ea --- /dev/null +++ b/ffi/fccntl_wrap.h @@ -0,0 +1,73 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2020 + * + * An fccntl wrapper + * + * Dimitri Staessens + * Sander Vrijders + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include + +int flow_set_snd_timeout(int fd, struct timespec * ts) +{ + return fccntl(fd, FLOWSSNDTIMEO, ts); +} + +int flow_set_rcv_timeout(int fd, struct timespec * ts) +{ + return fccntl(fd, FLOWSRCVTIMEO, ts); +} + +int flow_get_snd_timeout(int fd, struct timespec * ts) +{ + return fccntl(fd, FLOWGSNDTIMEO, ts); +} + +int flow_get_rcv_timeout(int fd, struct timespec * ts) +{ + return fccntl(fd, FLOWGRCVTIMEO, ts); +} + +int flow_get_qos(int fd, qosspec_t * qs) +{ + return fccntl(fd, FLOWGQOSSPEC, qs); +} + +int flow_get_rx_qlen(int fd, size_t * sz) +{ + return fccntl(fd, FLOWGRXQLEN, sz); +} + +int flow_get_tx_qlen(int fd, size_t * sz) +{ + return fccntl(fd, FLOWGTXQLEN, sz); +} + +int flow_set_flags(int fd, uint32_t flags) +{ + return fccntl(fd, FLOWSFLAGS, flags); +} + +int flow_get_flags(int fd) +{ + uint32_t flags; + + if (fccntl(fd, FLOWGFLAGS, &flags)) + return -EPERM; + + return (int) flags; +} diff --git a/ffi/pyouroboros_build.py b/ffi/pyouroboros_build.py new file mode 100644 index 0000000..b4ace8e --- /dev/null +++ b/ffi/pyouroboros_build.py @@ -0,0 +1,138 @@ +# +# Ouroboros - Copyright (C) 2016 - 2020 +# +# Python API for applications +# +# Dimitri Staessens +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# version 2.1 as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# + +from cffi import FFI + +ffibuilder: FFI = FFI() + +ffibuilder.cdef(""" +/* OUROBOROS QOS.H */ +typedef struct qos_spec { + uint32_t delay; /* In ms */ + uint64_t bandwidth; /* In bits/s */ + uint8_t availability; /* Class of 9s */ + uint32_t loss; /* Packet loss */ + uint32_t ber; /* Bit error rate, errors per billion bits */ + uint8_t in_order; /* In-order delivery, enables FRCT */ + uint32_t max_gap; /* In ms */ + uint16_t cypher_s; /* Cypher strength, 0 = no encryption */ +} qosspec_t; + +/* OUROBOROS DEV.H */ +/* Returns flow descriptor, qs updates to supplied QoS. */ +int flow_alloc(const char * dst_name, + qosspec_t * qs, + const struct timespec * timeo); + +/* Returns flow descriptor, qs updates to supplied QoS. */ +int flow_accept(qosspec_t * qs, + const struct timespec * timeo); + +/* Returns flow descriptor, qs updates to supplied QoS. */ +int flow_join(const char * bc, + qosspec_t * qs, + const struct timespec * timeo); + +int flow_dealloc(int fd); + +ssize_t flow_write(int fd, + const void * buf, + size_t count); + +ssize_t flow_read(int fd, + void * buf, + size_t count); + +/*OUROBOROS FCCNTL.H, VIA WRAPPER */ +int flow_set_snd_timeout(int fd, struct timespec * ts); + +int flow_set_rcv_timeout(int fd, struct timespec * ts); + +int flow_get_snd_timeout(int fd, struct timespec * ts); + +int flow_get_rcv_timeout(int fd, struct timespec * ts); + +int flow_get_qos(int fd, qosspec_t * qs); + +int flow_get_rx_qlen(int fd, size_t * sz); + +int flow_get_tx_qlen(int fd, size_t * sz); + +int flow_set_flags(int fd, uint32_t flags); + +int flow_get_flags(int fd); + +/*OUROBOROS FQUEUE.H */ +enum fqtype { + FLOW_PKT = (1 << 0), + FLOW_DOWN = (1 << 1), + FLOW_UP = (1 << 2), + FLOW_ALLOC = (1 << 3), + FLOW_DEALLOC = (1 << 4) +}; + +struct flow_set; + +struct fqueue; + +typedef struct flow_set fset_t; +typedef struct fqueue fqueue_t; + +fset_t * fset_create(void); + +void fset_destroy(fset_t * set); + +fqueue_t * fqueue_create(void); + +void fqueue_destroy(struct fqueue * fq); + +void fset_zero(fset_t * set); + +int fset_add(fset_t * set, + int fd); + +bool fset_has(const fset_t * set, + int fd); + +void fset_del(fset_t * set, + int fd); + +int fqueue_next(fqueue_t * fq); + +int fqueue_type(fqueue_t * fq); + +ssize_t fevent(fset_t * set, + fqueue_t * fq, + const struct timespec * timeo); +""") + +ffibuilder.set_source("_ouroboros_cffi", + """ +#include "ouroboros/qos.h" +#include "ouroboros/dev.h" +#include "fccntl_wrap.h" +#include "ouroboros/fqueue.h" + """, + libraries=['ouroboros-dev'], + extra_compile_args=["-I./ffi/"]) + +if __name__ == "__main__": + ffibuilder.compile(verbose=True) diff --git a/ouroboros/dev.py b/ouroboros/dev.py new file mode 100644 index 0000000..7d29624 --- /dev/null +++ b/ouroboros/dev.py @@ -0,0 +1,398 @@ +# +# Ouroboros - Copyright (C) 2016 - 2020 +# +# Python API for applications +# +# Dimitri Staessens +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# version 2.1 as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# + +from _ouroboros_cffi import ffi, lib +import errno +from enum import IntFlag +from ouroboros.qos import * +from ouroboros.qos import _qos_to_qosspec, _fl_to_timespec, _qosspec_to_qos, _timespec_to_fl + +# Some constants +MILLION = 1000 * 1000 +BILLION = 1000 * 1000 * 1000 + + +# ouroboros exceptions +class FlowAllocatedException(Exception): + pass + + +class FlowNotAllocatedException(Exception): + pass + + +class FlowDownException(Exception): + pass + + +class FlowPermissionException(Exception): + pass + + +class FlowException(Exception): + pass + + +class FlowDeallocWarning(Warning): + pass + + +def _raise(e: int) -> None: + if e >= 0: + return + + print("error: " + str(e)) + if e == -errno.ETIMEDOUT: + raise TimeoutError() + if e == -errno.EINVAL: + raise ValueError() + if e == -errno.ENOMEM: + raise MemoryError() + else: + raise FlowException() + + +class FlowProperties(IntFlag): + ReadOnly = 0o0 + WriteOnly = 0o1 + ReadWrite = 0o2 + Down = 0o4 + NonBlockingRead = 0o1000 + NonBlockingWrite = 0o2000 + NonBlocking = NonBlockingRead | NonBlockingWrite + NoPartialRead = 0o10000 + NoPartialWrite = 0o200000 + + +class Flow: + + def __init__(self): + self.__fd: int = -1 + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + lib.flow_dealloc(self.__fd) + + def alloc(self, + dst: str, + qos: QoSSpec = None, + timeo: float = None) -> Optional[QoSSpec]: + """ + Allocates a flow with a certain QoS to a destination + + :param dst: The destination name (string) + :param qos: The QoS for the requested flow (QoSSpec) + :param timeo: The timeout for the flow allocation (None -> forever, 0->async) + :return: The QoS for the new flow + """ + + if self.__fd >= 0: + raise FlowAllocatedException() + + _qos = _qos_to_qosspec(qos) + + _timeo = _fl_to_timespec(timeo) + + self.__fd = lib.flow_alloc(dst.encode(), _qos, _timeo) + + _raise(self.__fd) + + return _qosspec_to_qos(_qos) + + def accept(self, + timeo: float = None) -> QoSSpec: + """ + Accepts new flows and returns the QoS + + :param timeo: The timeout for the flow allocation (None -> forever, 0->async) + :return: The QoS for the new flow + """ + + if self.__fd >= 0: + raise FlowAllocatedException() + + _qos = ffi.new("qosspec_t *") + + _timeo = _fl_to_timespec(timeo) + + self.__fd = lib.flow_accept(_qos, _timeo) + + _raise(self.__fd) + + return _qosspec_to_qos(_qos) + + def join(self, + dst: str, + qos: QoSSpec = None, + timeo: float = None) -> Optional[QoSSpec]: + """ + Join a broadcast layer + + :param dst: The destination broadcast layer name (string) + :param qos: The QoS for the requested flow (QoSSpec) + :param timeo: The timeout for the flow allocation (None -> forever, 0->async) + :return: The QoS for the flow + """ + + if self.__fd >= 0: + raise FlowAllocatedException() + + _qos = _qos_to_qosspec(qos) + + _timeo = _fl_to_timespec(timeo) + + self.__fd = lib.flow_join(dst.encode(), _qos, _timeo) + + _raise(self.__fd) + + return _qosspec_to_qos(_qos) + + def dealloc(self): + """ + Deallocate a flow + + """ + + self.__fd = lib.flow_dealloc(self.__fd) + + if self.__fd < 0: + raise FlowDeallocWarning + + self.__fd = -1 + + def write(self, + buf: bytes, + count: int = None) -> int: + """ + Attempt to write bytes to a flow + + :param buf: Buffer to write from + :param count: Number of bytes to write from the buffer + :return: Number of bytes written + """ + + if self.__fd < 0: + raise FlowNotAllocatedException() + + if count is None: + return lib.flow_write(self.__fd, ffi.from_buffer(buf), len(buf)) + else: + return lib.flow_write(self.__fd, ffi.from_buffer(buf), count) + + def writeline(self, + ln: str) -> int: + """ + Attempt to write a string to a flow + + :param ln: String to write + :return: Number of bytes written + """ + + if self.__fd < 0: + raise FlowNotAllocatedException() + + return self.write(ln.encode(), len(ln)) + + def read(self, + count: int = None) -> bytes: + """ + Attempt to read bytes from a flow + + :param count: Maximum number of bytes to read + :return: Bytes read + """ + + if self.__fd < 0: + raise FlowNotAllocatedException() + + if count is None: + count = 2048 + + _buf = ffi.new("char []", count) + + result = lib.flow_read(self.__fd, _buf, count) + + return ffi.unpack(_buf, result) + + def readline(self): + """ + + :return: A string + """ + if self.__fd < 0: + raise FlowNotAllocatedException() + + return self.read().decode() + + # flow manipulation + def set_snd_timeout(self, + timeo: float): + """ + Set the timeout for blocking writes + """ + _timeo = _fl_to_timespec(timeo) + + if lib.flow_set_snd_timout(self.__fd, _timeo) != 0: + raise FlowPermissionException() + + def get_snd_timeout(self) -> float: + """ + Get the timeout for blocking writes + + :return: timeout for blocking writes + """ + _timeo = ffi.new("struct timespec *") + + if lib.flow_get_snd_timeout(self.__fd, _timeo) != 0: + raise FlowPermissionException() + + return _timespec_to_fl(_timeo) + + def set_rcv_timeout(self, + timeo: float): + """ + Set the timeout for blocking writes + """ + _timeo = _fl_to_timespec(timeo) + + if lib.flow_set_rcv_timout(self.__fd, _timeo) != 0: + raise FlowPermissionException() + + def get_rcv_timeout(self) -> float: + """ + Get the timeout for blocking reads + + :return: timeout for blocking writes + """ + _timeo = ffi.new("struct timespec *") + + if lib.flow_get_rcv_timeout(self.__fd, _timeo) != 0: + raise FlowPermissionException() + + return _timespec_to_fl(_timeo) + + def get_qos(self) -> QoSSpec: + """ + + :return: Current QoS on the flow + """ + _qos = ffi.new("qosspec_t *") + + if lib.flow_get_qos(self.__fd, _qos) != 0: + raise FlowPermissionException() + + return _qosspec_to_qos(_qos) + + def get_rx_queue_len(self) -> int: + """ + + :return: + """ + + size = ffi.new("size_t *") + + if lib.flow_get_rx_qlen(self.__fd, size) != 0: + raise FlowPermissionException() + + return int(size) + + def get_tx_queue_len(self) -> int: + """ + + :return: + """ + + size = ffi.new("size_t *") + + if lib.flow_get_tx_qlen(self.__fd, size) != 0: + raise FlowPermissionException() + + return int(size) + + def set_flags(self, flags: FlowProperties): + """ + Set flags for this flow. + :param flags: + """ + + _flags = ffi.new("uint32_t *", int(flags)) + + if lib.flow_set_flag(self.__fd, _flags): + raise FlowPermissionException() + + def get_flags(self) -> FlowProperties: + """ + Get the flags for this flow + """ + + flags = lib.flow_get_flag(self.__fd) + if flags < 0: + raise FlowPermissionException() + + return FlowProperties(int(flags)) + + +def flow_alloc(dst: str, + qos: QoSSpec = None, + timeo: float = None) -> Flow: + """ + + :param dst: Destination name + :param qos: Requested QoS + :param timeo: Timeout to wait for the allocation + :return: A new Flow() + """ + + f = Flow() + f.alloc(dst, qos, timeo) + return f + + +def flow_accept(timeo: float = None) -> Flow: + """ + + :param timeo: Timeout to wait for the allocation + :return: A new Flow() + """ + + f = Flow() + f.accept(timeo) + return f + + +def flow_join(dst: str, + qos: QoSSpec = None, + timeo: float = None) -> Flow: + """ + + :param dst: Broadcast layer name + :param qos: Requested QoS + :param timeo: Timeout to wait for the allocation + :return: A new Flow() + """ + + f = Flow() + f.join(dst, qos, timeo) + return f + + diff --git a/ouroboros/event.py b/ouroboros/event.py new file mode 100644 index 0000000..b707c1b --- /dev/null +++ b/ouroboros/event.py @@ -0,0 +1,147 @@ +# +# Ouroboros - Copyright (C) 2016 - 2020 +# +# Python API for applications +# +# Dimitri Staessens +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# version 2.1 as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# + +from ouroboros.dev import * +from ouroboros.qos import _fl_to_timespec + + +# async API +class FlowEventError(Exception): + pass + + +class FEventType(IntFlag): + FlowPkt = lib.FLOW_PKT + FlowDown = lib.FLOW_DOWN + FlowUp = lib.FLOW_UP + FlowAlloc = lib.FLOW_ALLOC + FlowDealloc = lib.FLOW_DEALLOC + + +class FEventQueue: + """ + A queue of events waiting to be handled + """ + + def __init__(self): + self.__fq = lib.fqueue_create() + if self.__fq is ffi.NULL: + raise MemoryError("Failed to create FEventQueue") + + def __del__(self): + lib.fqueue_destroy(self.__fq) + + def next(self): + """ + Get the next event + :return: Flow and eventtype on that flow + """ + f = Flow() + f._Flow__fd = lib.fqueue_next(self.__fq) + if f._Flow__fd < 0: + raise FlowEventError + + _type = lib.fqueue_type(self.__fq) + if _type < 0: + raise FlowEventError + + return f, _type + + +class FlowSet: + """ + A set of flows that can be monitored for events + """ + def __init__(self, + flows: [Flow] = None): + + self.__set = lib.fset_create() + if self.__set is ffi.NULL: + raise MemoryError("Failed to create FlowSet") + + if flows is not None: + for flow in flows: + if lib.fset_add(self.__set, flow._Flow__fd) != 0: + lib.fset_destroy(self.__set) + self.__set = ffi.NULL + raise MemoryError("Failed to add flow " + str(flow._Flow__fd) + ".") + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + lib.fset_destroy(self.__set) + + def add(self, + flow: Flow): + """ + Add a Flow + + :param flow: The flow object to add + """ + + if self.__set is ffi.NULL: + raise ValueError + + if lib.fset_add(self.__set, flow._Flow___fd) != 0: + raise MemoryError("Failed to add flow") + + def zero(self): + """ + Remove all Flows from this set + """ + + if self.__set is ffi.NULL: + raise ValueError + + lib.fset_zero(self.__set) + + def remove(self, + flow: Flow): + """ + Remove a flow from a set + + :param flow: + """ + + if self.__set is ffi.NULL: + raise ValueError + + lib.fset_del(self.__set, flow._Flow__fd) + + def wait(self, + fq: FEventType, + timeo: float = None): + """ + Wait for at least one event on one of the monitored flows + """ + + if self.__set is ffi.NULL: + raise ValueError + + _timeo = _fl_to_timespec(timeo) + + ret = lib.fevent(self.__set, fq._FEventQueue__fq, _timeo) + if ret < 0: + raise FlowEventError + + def destroy(self): + lib.fset_destroy(self.__set) diff --git a/ouroboros/qos.py b/ouroboros/qos.py new file mode 100644 index 0000000..f437ee2 --- /dev/null +++ b/ouroboros/qos.py @@ -0,0 +1,110 @@ +# +# Ouroboros - Copyright (C) 2016 - 2020 +# +# Python API for applications - QoS +# +# Dimitri Staessens +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# version 2.1 as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# + +from _ouroboros_cffi import ffi +from math import modf +from typing import Optional + +# Some constants +MILLION = 1000 * 1000 +BILLION = 1000 * 1000 * 1000 + + +class QoSSpec: + """ + delay: In ms, default 1000s + bandwidth: In bits / s, default 0 + availability: Class of 9s, default 0 + loss: Packet loss in ppm, default MILLION + ber: Bit error rate, errors per billion bits. default BILLION + in_order: In-order delivery, enables FRCT, default 0 + max_gap: Maximum interruption in ms, default MILLION + cypher_s: Requested encryption strength in bits + """ + + def __init__(self, + delay: int = MILLION, + bandwidth: int = 0, + availability: int = 0, + loss: int = 1, + ber: int = MILLION, + in_order: int = 0, + max_gap: int = MILLION, + cypher_s: int = 0): + self.delay = delay + self.bandwidth = bandwidth + self.availability = availability + self.loss = loss + self.ber = ber + self.in_order = in_order + self.max_gap = max_gap + self.cypher_s = cypher_s + + +def _fl_to_timespec(timeo: float): + if timeo is None: + return ffi.NULL + elif timeo <= 0: + return ffi.new("struct timespec *", [0, 0]) + else: + frac, whole = modf(timeo) + _timeo = ffi.new("struct timespec *") + _timeo.tv_sec = whole + _timeo.tv_nsec = frac * BILLION + return _timeo + + +def _timespec_to_fl(_timeo) -> Optional[float]: + if _timeo is ffi.NULL: + return None + elif _timeo.tv_sec <= 0 and _timeo.tv_nsec == 0: + return 0 + else: + return _timeo.tv_sec + _timeo.tv_nsec / BILLION + + +def _qos_to_qosspec(qos: QoSSpec): + if qos is None: + return ffi.NULL + else: + return ffi.new("qosspec_t *", + [qos.delay, + qos.bandwidth, + qos.availability, + qos.loss, + qos.ber, + qos.in_order, + qos.max_gap, + qos.cypher_s]) + + +def _qosspec_to_qos(_qos) -> Optional[QoSSpec]: + if _qos is ffi.NULL: + return None + else: + return QoSSpec(delay=_qos.delay, + bandwidth=_qos.bandwidth, + availability=_qos.availability, + loss=_qos.loss, + ber=_qos.ber, + in_order=_qos.in_order, + max_gap=_qos.max_gap, + cypher_s=_qos.cypher_s) diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..023f3b4 --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python + +import setuptools + +setuptools.setup( + name='PyOuroboros', + version=0.17, + url='https://ouroboros.rocks', + keywords='ouroboros IPC subsystem', + author='Dimitri Staessens', + author_email='dimitri@ouroboros.rocks', + license='LGPLv2.1', + description='Python API for Ouroboros', + packages=[ + 'ouroboros' + ], + setup_requires=[ + "cffi>=1.0.0" + ], + cffi_modules=[ + "ffi/pyouroboros_build.py:ffibuilder" + ], + install_requires=[ + "cffi>=1.0.0" + ]) -- cgit v1.2.3