New Upstream Release - pyrad

Ready changes

Summary

Merged new upstream version: 2.4 (was: 2.1).

Resulting package

Built on 2023-06-20T19:07 (took 4m41s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-pyrad

Lintian Result

Diff

diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index ac72d1c..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-build/
-dist/
-docs/.build/
-pyrad.egg-info/
-*.pyc
-*__pycache__
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 23d81c1..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-language: python
-python:
-  - "2.7"
-  - "3.5"
-# command to install dependencies
-install: "pip install six nose coverage netaddr"
-# command to run tests
-script: nosetests -v --with-coverage
diff --git a/CHANGES.rst b/CHANGES.rst
deleted file mode 100644
index a98a190..0000000
--- a/CHANGES.rst
+++ /dev/null
@@ -1,231 +0,0 @@
-Changelog
-=========
-
-2.1 - Feb 2, 2017
------------------
-
-* Add CoA support (client and server)
-
-* Add tagged attribute support (send only).
-
-* Add salt encryption support (encrypt 2).
-
-* Add ascend data filter support (human readable format to octets).
-
-* Add ipv6 address and prefix support.
-
-* Add support for octet strings in hex (starting with 0x).
-
-* Add support for types short, signed and byte.
-
-* Add support for VSA's with multiple sub TLV's.
-
-* Use a different random generator to improve the security of generated
-  packet ids and authenticators.
-
-
-2.0 - May 15, 2011
-------------------
-
-* Start moving codebase to PEP8 compatible coding style.
-
-* Add support for Python 3.2.
-
-* Several code cleanups. As a side effect Python versions before 2.6
-  are unfortunatley no longer supported. If you use Python 2.5 or older
-  Pyrad 1.2 will still work for you.
-
-
-1.2 - July 12, 2009
--------------------
-
-* Setup sphinx based documentation.
-
-* Use hashlib instead of md5, if present. This fixes deprecation warnings
-  for python 2.6. Patch from Jeremy Liané.
-
-* Support parsing VENDOR format specifications in dictionary files. Patch by
-  Kristoffer Grönlun.
-
-* Supprt $INCLUDE directores in dictionary files. Patch by
-  Kristoffer Grönlun.
-
-* Standardize on 4 spaces for indents. Patch by Kristoffer Grönlund/
-  Purplescout.
-
-* Make sure all encoding utility methods raise a TypeError if a value of
-  the wrong type is passed in.
-
-
-1.1 - September 30, 2007
-------------------------
-
-* Add the 'octets' datatype from FreeRADIUS. This is treated just like string;
-  the only difference is how FreeRADIUS prints it.
-
-* Check against unimplemented datatypes in EncodeData and DecodeData instead
-  of assuming an identity transform works.
-
-* Make Packet.has_key and __contains__ gracefully handle unknown attributes.
-  Based on a patch from Alexey V Michurun <am@rol.ru>.
-
-* Add a __delitem__ implementation to Packet. Based on a patch from
-  Alexey V Michurun <am@rol.ru>.
-
-
-1.0 - September 16, 2007
-------------------------
-
-* Add unit tests. Pyrad now has 100% test coverage!
-
-* Moved the proxy server has been out of the server module to a new
-  proxy module.
-
-* Fix several errors that prevented the proxy code from working.
-
-* Use the standard logging module instead of printing to stdout.
-
-* The default dictionary for Server instances was shared between all
-  instances, possibly leading to unwanted data pollution. Each Server now
-  gets its own dict instance if none is passed in to the constructor.
-
-* Fixed a timeout handling problem in the client: after receiving an
-  invalid reply the current time was not updated, possibly leading to
-  the client blocking forever.
-
-* Switch to setuptools, allowing pyrad to be distributed as an egg
-  via the python package index.
-
-* Use absolute instead of relative imports.
-
-* Sockets are now opened with SO_REUSEADDR enabled to allow for faster
-  restarts.
-
-
-0.9 - April 25, 2007
-------------------------
-
-* Start using trac to manage the project: http://code.wiggy.net/tracker/pyrad/
-
-* [bug 3] Fix handling of packets with an id of 0
-
-* [bug 2] Fix handling of file descriptor parameters in the server
-  code and example.
-
-* [bug 4] Fix wrong variable name in exception raised when encountering
-  an overly long packet.
-
-* [bug 5] Fix error message in parse error for dictionaries.
-
-* [bug 8] Packet.CreateAuthenticator is now a static method.
-
-
-0.8
----
-
-* Fix time-handling in the client packet sending code: it would loop
-  forever since the now time was updated at the wrong moment. Fix from
-  Michael Mitchell <Michael.Mitchell@team.telstra.com>
-
-* Fix passing of dict parameter when creating reply packets
-
-
-0.7
----
-
-* add HandleAuthPacket and HandleAcctPacket hooks to Server class.
-  Request from Thomas Boettcher.
-
-* Pass on dict attribute when creating a reply packet. Requested by
-  Thomas Boettcher.
-
-* Allow specififying new attributes when using
-  Server.CreateReplyPacket. Requested by Thomas Boettcher.
-
-
-0.6
----
-
-* packet.VerifyReply() had a syntax error when not called with a raw packet.
-
-* Add bind() method to the Client class.
-
-* [SECURITY] Fix handling of timeouts in client module: when a bad
-  packet was received pyrad immediately started the next retry instead of
-  discarding it and waiting for a timeout. This could be exploited by
-  sending a number of bogus responses before a correct reply to make pyrad
-  not see the real response.
-
-* correctly set Acct-Delay-Time when resending accounting requests packets.
-
-* verify account request packages as well (from Farshad Khoshkhui).
-
-* protect against packets with bogus lengths (from Farshad Khoshkhui).
-
-
-0.5
----
-
-* Fix typo in server class which broke handling of accounting packets.
-
-* Create seperate AuthPacket and AcctPacket classes; this resulted in
-  a fair number of API changes.
-
-* Packets now know how to create and verify replies.
-
-* Client now directs authentication and accounting packets to the
-  correct port on the server.
-
-* Add twisted support via the new curved module.
-
-* Fix incorrect exception handling in client code.
-
-* Update example server to handle accounting packets.
-
-* Add example for sending account packets.
-
-
-0.4
----
-
-* Fix last case of bogus exception usage.
-
-* Move RADIUS code constants to packet module.
-
-* Add support for decoding passwords and generating reply packets to Packet
-  class.
-
-* Add basic RADIUS server and proxy implementation.
-
-
-0.3
----
-
-* client.Timeout is now derived from Exception.
-
-* Docstring documentation added.
-
-* Include example dictionaries and authentication script.
-
-
-0.2
----
-
-* Use proper exceptions.
-
-* Encode and decode vendor attributes.
-
-* Dictionary can parse vendor dictionaries.
-
-* Dictionary can handle attribute values.
-
-* Enhance most constructors; they now take extra optional parameters
-  with initialisation info.
-
-* No longer use obsolete python interfaces like whrandom.
-
-
-0.1
----
-
-* First release
diff --git a/LICENSE.txt b/LICENSE.txt
deleted file mode 100644
index e01f563..0000000
--- a/LICENSE.txt
+++ /dev/null
@@ -1,29 +0,0 @@
-Copyright 2002-2008 Wichert Akkerman. All rights reserved.
-Copyright 2007-2008 Simplon. All rights reserved.
-
-All rights reserved.
-
-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 University 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 REGENTS 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 REGENTS 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.
-
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 6cb73b5..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-recursive-include example *
-prune example/.svn
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..155c290
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,98 @@
+Metadata-Version: 1.1
+Name: pyrad
+Version: 2.4
+Summary: RADIUS tools
+Home-page: https://github.com/pyradius/pyrad
+Author: Istvan Ruzman, Christian Giese
+Author-email: istvan@ruzman.eu, developer@gicnet.de
+License: BSD
+Description: .. image:: https://travis-ci.org/pyradius/pyrad.svg?branch=master
+            :target: https://travis-ci.org/pyradius/pyrad
+        .. image:: https://coveralls.io/repos/github/pyradius/pyrad/badge.svg?branch=master
+            :target: https://coveralls.io/github/pyradius/pyrad?branch=master
+        .. image:: https://img.shields.io/pypi/v/pyrad.svg
+            :target: https://pypi.python.org/pypi/pyrad
+        .. image:: https://img.shields.io/pypi/pyversions/pyrad.svg
+            :target: https://pypi.python.org/pypi/pyrad
+        .. image:: https://img.shields.io/pypi/dm/pyrad.svg
+            :target: https://pypi.python.org/pypi/pyrad
+        .. image:: https://readthedocs.org/projects/pyrad/badge/?version=latest
+            :target: http://pyrad.readthedocs.io/en/latest/?badge=latest
+            :alt: Documentation Status
+        .. image:: https://img.shields.io/pypi/l/pyrad.svg
+            :target: https://pypi.python.org/pypi/pyrad
+        
+        Introduction
+        ============
+        
+        pyrad is an implementation of a RADIUS client/server as described in RFC2865.
+        It takes care of all the details like building RADIUS packets, sending
+        them and decoding responses.
+        
+        Here is an example of doing a authentication request::
+        
+            from __future__ import print_function
+            from pyrad.client import Client
+            from pyrad.dictionary import Dictionary
+            import pyrad.packet
+        
+            srv = Client(server="localhost", secret=b"Kah3choteereethiejeimaeziecumi",
+                         dict=Dictionary("dictionary"))
+        
+            # create request
+            req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest,
+                                       User_Name="wichert", NAS_Identifier="localhost")
+            req["User-Password"] = req.PwCrypt("password")
+        
+            # send request
+            reply = srv.SendPacket(req)
+        
+            if reply.code == pyrad.packet.AccessAccept:
+                print("access accepted")
+            else:
+                print("access denied")
+        
+            print("Attributes returned by server:")
+            for i in reply.keys():
+                print("%s: %s" % (i, reply[i]))
+        
+        
+        
+        Requirements & Installation
+        ===========================
+        
+        pyrad requires Python 2.7, or Python 3.6 or later
+        
+        Installing is simple; pyrad uses the standard distutils system for installing
+        Python modules::
+        
+          python setup.py install
+        
+        
+        Author, Copyright, Availability
+        ===============================
+        
+        pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is maintained by 
+        Christian Giese (GIC-de) and Istvan Ruzman (Istvan91). 
+        
+        This project is licensed under a BSD license.
+        
+        Copyright and license information can be found in the LICENSE.txt file.
+        
+        The current version and documentation can be found on pypi:
+        https://pypi.org/project/pyrad/
+        
+        Bugs and wishes can be submitted in the pyrad issue tracker on github:
+        https://github.com/pyradius/pyrad/issues
+        
+Keywords: radius,authentication
+Platform: UNKNOWN
+Classifier: Development Status :: 6 - Mature
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
diff --git a/README.rst b/README.rst
index e530f57..5f85783 100644
--- a/README.rst
+++ b/README.rst
@@ -1,12 +1,18 @@
-.. image:: https://travis-ci.org/wichert/pyrad.svg?branch=master
-    :target: https://travis-ci.org/wichert/pyrad
+.. image:: https://travis-ci.org/pyradius/pyrad.svg?branch=master
+    :target: https://travis-ci.org/pyradius/pyrad
+.. image:: https://coveralls.io/repos/github/pyradius/pyrad/badge.svg?branch=master
+    :target: https://coveralls.io/github/pyradius/pyrad?branch=master
 .. image:: https://img.shields.io/pypi/v/pyrad.svg
     :target: https://pypi.python.org/pypi/pyrad
+.. image:: https://img.shields.io/pypi/pyversions/pyrad.svg
+    :target: https://pypi.python.org/pypi/pyrad
 .. image:: https://img.shields.io/pypi/dm/pyrad.svg
     :target: https://pypi.python.org/pypi/pyrad
 .. image:: https://readthedocs.org/projects/pyrad/badge/?version=latest
     :target: http://pyrad.readthedocs.io/en/latest/?badge=latest
     :alt: Documentation Status
+.. image:: https://img.shields.io/pypi/l/pyrad.svg
+    :target: https://pypi.python.org/pypi/pyrad
 
 Introduction
 ============
@@ -47,7 +53,7 @@ Here is an example of doing a authentication request::
 Requirements & Installation
 ===========================
 
-pyrad requires Python 2.6 or later, or Python 3.2 or later
+pyrad requires Python 2.7, or Python 3.6 or later
 
 Installing is simple; pyrad uses the standard distutils system for installing
 Python modules::
@@ -58,13 +64,15 @@ Python modules::
 Author, Copyright, Availability
 ===============================
 
-pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is licensed
-under a BSD license.
+pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is maintained by 
+Christian Giese (GIC-de) and Istvan Ruzman (Istvan91). 
+
+This project is licensed under a BSD license.
 
 Copyright and license information can be found in the LICENSE.txt file.
 
 The current version and documentation can be found on pypi:
-http://pypi.python.org/pypi/pyrad
+https://pypi.org/project/pyrad/
 
 Bugs and wishes can be submitted in the pyrad issue tracker on github:
-https://github.com/wichert/pyrad/issues
+https://github.com/pyradius/pyrad/issues
diff --git a/TODO.rst b/TODO.rst
deleted file mode 100644
index 165b971..0000000
--- a/TODO.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-ToDo
-====
diff --git a/debian/changelog b/debian/changelog
index fa7ea5c..e855e25 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+pyrad (2.4-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Tue, 20 Jun 2023 19:03:13 -0000
+
 pyrad (2.1-3) unstable; urgency=low
 
   [ Debian Janitor ]
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index 1446ca6..0000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,20 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
-SPHINXPROJ    = pyrad
-SOURCEDIR     = source
-BUILDDIR      = build
-
-# Put it first so that "make" without argument is like "make help".
-help:
-	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/docs/make.bat b/docs/make.bat
deleted file mode 100644
index 9fa49d9..0000000
--- a/docs/make.bat
+++ /dev/null
@@ -1,36 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
-	set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=source
-set BUILDDIR=build
-set SPHINXPROJ=pyrad
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
-	echo.
-	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
-	echo.installed, then set the SPHINXBUILD environment variable to point
-	echo.to the full path of the 'sphinx-build' executable. Alternatively you
-	echo.may add the Sphinx directory to PATH.
-	echo.
-	echo.If you don't have Sphinx installed, grab it from
-	echo.http://sphinx-doc.org/
-	exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-
-:end
-popd
diff --git a/docs/source/_static/logo.png b/docs/source/_static/logo.png
deleted file mode 100644
index a4e16d5..0000000
Binary files a/docs/source/_static/logo.png and /dev/null differ
diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst
deleted file mode 100644
index ae90c0d..0000000
--- a/docs/source/api/client.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-:mod:`pyrad.client` -- basic client
-===================================
-
-.. automodule:: pyrad.client
-
-  .. autoclass:: Timeout
-    :members:
-
-  .. autoclass:: Client
-    :members:
diff --git a/docs/source/api/dictionary.rst b/docs/source/api/dictionary.rst
deleted file mode 100644
index da370e8..0000000
--- a/docs/source/api/dictionary.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-:mod:`pyrad.dictionary` -- RADIUS dictionary
-============================================
-
-.. automodule:: pyrad.dictionary
-
-  .. autoclass:: ParseError
-    :members:
-
-  .. autoclass:: Dictionary
-    :members:
diff --git a/docs/source/api/host.rst b/docs/source/api/host.rst
deleted file mode 100644
index 29e1760..0000000
--- a/docs/source/api/host.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-:mod:`pyrad.host` -- RADIUS host definition
-===========================================
-
-.. automodule:: pyrad.host
-
-  .. autoclass:: Host
-    :members:
diff --git a/docs/source/api/packet.rst b/docs/source/api/packet.rst
deleted file mode 100644
index 05c4a21..0000000
--- a/docs/source/api/packet.rst
+++ /dev/null
@@ -1,48 +0,0 @@
-:mod:`pyrad.packet` -- packet encoding and decoding
-===================================================
-
-.. automodule:: pyrad.packet
-
-  .. autoclass:: Packet
-    :members:
-
-  .. autoclass:: AuthPacket
-    :members:
-
-  .. autoclass:: AcctPacket
-    :members:
-
-  .. autoclass:: CoAPacket
-    :members:
-
-  .. autoclass:: PacketError
-    :members:
-
-
-Constants
----------
-
-The :mod:`pyrad.packet` module defines several common constants
-that are useful when dealing with RADIUS packets.
-
-The following packet codes are defined:
-
-==================    ======
-Constant name         Value
-==================    ======
-AccessRequest         1
-------------------    ------
-AccessAccept          2
-AccessReject          3
-AccountingRequest     4
-AccountingResponse    5
-AccessChallenge       11
-StatusServer          12
-StatusClient          13
-DisconnectRequest     40
-DisconnectACK         41
-DisconnectNAK         42
-CoARequest            43
-CoAACK                44
-CoANAK                45
-==================    ======
diff --git a/docs/source/api/proxy.rst b/docs/source/api/proxy.rst
deleted file mode 100644
index 34017a4..0000000
--- a/docs/source/api/proxy.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-:mod:`pyrad.proxy` -- basic proxy
-=================================
-
-.. automodule:: pyrad.proxy
-
-  .. autoclass:: Proxy
-    :members:
diff --git a/docs/source/api/server.rst b/docs/source/api/server.rst
deleted file mode 100644
index 2cfbd38..0000000
--- a/docs/source/api/server.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-:mod:`pyrad.server` -- basic server
-===================================
-
-.. automodule:: pyrad.server
-
-  .. autoclass:: RemoteHost
-    :members:
-
-  .. autoclass:: ServerPacketError
-    :members:
-
-  .. autoclass:: Server
-    :members:
diff --git a/docs/source/conf.py b/docs/source/conf.py
deleted file mode 100644
index 3cf1b40..0000000
--- a/docs/source/conf.py
+++ /dev/null
@@ -1,162 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# pyrad documentation build configuration file, created by
-# sphinx-quickstart on Thu Feb  2 15:16:16 2017.
-#
-# This file is execfile()d with the current directory set to its
-# containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-
-import os
-import sys
-sys.path.insert(0, os.path.abspath('../../'))
-
-
-# -- General configuration ------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#
-# needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
-# ones.
-extensions = ['sphinx.ext.autodoc',
-    'sphinx.ext.intersphinx',
-    'sphinx.ext.todo',
-    'sphinx.ext.viewcode']
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix(es) of source filenames.
-# You can specify multiple suffix as a list of string:
-#
-# source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'pyrad'
-copyright = u'2017, Wichert Akkerman'
-author = u'Wichert Akkerman'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = u'2.1'
-# The full version, including alpha/beta/rc tags.
-release = u'2.1'
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#
-# This is also used if you do content translation via gettext catalogs.
-# Usually you set "language" from the command line for these cases.
-language = None
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-# This patterns also effect to html_static_path and html_extra_path
-exclude_patterns = []
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = True
-
-
-# -- Options for HTML output ----------------------------------------------
-
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-#
-# html_theme = 'alabaster'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-#
-# html_theme_options = {}
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
-
-html_logo = '_static/logo.png'
-
-# -- Options for HTMLHelp output ------------------------------------------
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'pyraddoc'
-
-# -- Options for LaTeX output ---------------------------------------------
-
-latex_elements = {
-    # The paper size ('letterpaper' or 'a4paper').
-    #
-    # 'papersize': 'letterpaper',
-
-    # The font size ('10pt', '11pt' or '12pt').
-    #
-    # 'pointsize': '10pt',
-
-    # Additional stuff for the LaTeX preamble.
-    #
-    # 'preamble': '',
-
-    # Latex figure (float) alignment
-    #
-    # 'figure_align': 'htbp',
-}
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title,
-#  author, documentclass [howto, manual, or own class]).
-latex_documents = [
-    (master_doc, 'pyrad.tex', u'pyrad Documentation',
-     u'Wichert Akkerman', 'manual'),
-]
-
-
-# -- Options for manual page output ---------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
-    (master_doc, 'pyrad', u'pyrad Documentation',
-     [author], 1)
-]
-
-
-# -- Options for Texinfo output -------------------------------------------
-
-# Grouping the document tree into Texinfo files. List of tuples
-# (source start file, target name, title, author,
-#  dir menu entry, description, category)
-texinfo_documents = [
-    (master_doc, 'pyrad', u'pyrad Documentation',
-     author, 'pyrad', 'One line description of project.',
-     'Miscellaneous'),
-]
-
-
-
-
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'https://docs.python.org/': None}
diff --git a/docs/source/index.rst b/docs/source/index.rst
deleted file mode 100644
index 27998ec..0000000
--- a/docs/source/index.rst
+++ /dev/null
@@ -1,76 +0,0 @@
-
-*********************************
-:mod:`pyrad` -- RADIUS for Python
-*********************************
-
-:Author: Wichert Akkerman
-:Version: |version|
-
-Introduction
-============
-
-pyrad is an implementation of a RADIUS client/server as described in RFC2865.
-It takes care of all the details like building RADIUS packets, sending
-them and decoding responses.
-
-Here is an example of doing a authentication request::
-
-    from __future__ import print_function
-    from pyrad.client import Client
-    from pyrad.dictionary import Dictionary
-    import pyrad.packet
-
-    srv = Client(server="localhost", secret=b"Kah3choteereethiejeimaeziecumi",
-                 dict=Dictionary("dictionary"))
-
-    # create request
-    req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest,
-                               User_Name="wichert", NAS_Identifier="localhost")
-    req["User-Password"] = req.PwCrypt("password")
-
-    # send request
-    reply = srv.SendPacket(req)
-
-    if reply.code == pyrad.packet.AccessAccept:
-        print("access accepted")
-    else:
-        print("access denied")
-
-    print("Attributes returned by server:")
-    for i in reply.keys():
-        print("%s: %s" % (i, reply[i]))
-
-
-Requirements & Installation
-===========================
-
-pyrad requires Python 2.6 or later, or Python 3.2 or later
-
-Installing is simple; pyrad uses the standard distutils system for installing
-Python modules::
-
-  python setup.py install
-
-
-API Documentation
-=================
-
-Per-module :mod:`pyrad` API documentation.
-
-.. toctree::
-  :maxdepth: 2
-
-  api/client
-  api/dictionary
-  api/host
-  api/packet
-  api/proxy
-  api/server
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
diff --git a/example/acct.py b/example/acct.py
deleted file mode 100755
index c5a09fe..0000000
--- a/example/acct.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/python
-from __future__ import print_function
-from pyrad.client import Client
-from pyrad.dictionary import Dictionary
-import random
-import socket
-import sys
-import pyrad.packet
-
-
-def SendPacket(srv, req):
-    try:
-        srv.SendPacket(req)
-    except pyrad.client.Timeout:
-        print("RADIUS server does not reply")
-        sys.exit(1)
-    except socket.error as error:
-        print("Network error: " + error[1])
-        sys.exit(1)
-
-srv = Client(server="localhost", secret=b"Kah3choteereethiejeimaeziecumi", dict=Dictionary("dictionary"))
-
-req = srv.CreateAcctPacket(User_Name="wichert")
-
-req["NAS-IP-Address"] = "192.168.1.10"
-req["NAS-Port"] = 0
-req["NAS-Identifier"] = "trillian"
-req["Called-Station-Id"] = "00-04-5F-00-0F-D1"
-req["Calling-Station-Id"] = "00-01-24-80-B3-9C"
-req["Framed-IP-Address"] = "10.0.0.100"
-
-print("Sending accounting start packet")
-req["Acct-Status-Type"] = "Start"
-SendPacket(srv, req)
-
-print("Sending accounting stop packet")
-req["Acct-Status-Type"] = "Stop"
-req["Acct-Input-Octets"] = random.randrange(2**10, 2**30)
-req["Acct-Output-Octets"] = random.randrange(2**10, 2**30)
-req["Acct-Session-Time"] = random.randrange(120, 3600)
-req["Acct-Terminate-Cause"] = random.choice(["User-Request", "Idle-Timeout"])
-SendPacket(srv, req)
diff --git a/example/auth.py b/example/auth.py
deleted file mode 100755
index 3c64987..0000000
--- a/example/auth.py
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/python
-from __future__ import print_function
-from pyrad.client import Client
-from pyrad.dictionary import Dictionary
-import socket
-import sys
-import pyrad.packet
-
-srv = Client(server="localhost", secret=b"Kah3choteereethiejeimaeziecumi", dict=Dictionary("dictionary"))
-
-req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest, User_Name="wichert")
-
-req["NAS-IP-Address"] = "192.168.1.10"
-req["NAS-Port"] = 0
-req["Service-Type"] = "Login-User"
-req["NAS-Identifier"] = "trillian"
-req["Called-Station-Id"] = "00-04-5F-00-0F-D1"
-req["Calling-Station-Id"] = "00-01-24-80-B3-9C"
-req["Framed-IP-Address"] = "10.0.0.100"
-
-try:
-    print("Sending authentication request")
-    reply = srv.SendPacket(req)
-except pyrad.client.Timeout:
-    print("RADIUS server does not reply")
-    sys.exit(1)
-except socket.error as error:
-    print("Network error: " + error[1])
-    sys.exit(1)
-
-if reply.code == pyrad.packet.AccessAccept:
-    print("Access accepted")
-else:
-    print("Access denied")
-
-print("Attributes returned by server:")
-for i in reply.keys():
-    print("%s: %s" % (i, reply[i]))
diff --git a/example/coa.py b/example/coa.py
deleted file mode 100644
index ec0479c..0000000
--- a/example/coa.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/python
-from __future__ import print_function
-from pyrad.client import Client
-from pyrad import dictionary
-from pyrad import packet
-
-ADDRESS = "127.0.0.1"
-SECRET = b"Kah3choteereethiejeimaeziecumi"
-ATTRIBUTES = {
-    "Acct-Session-Id": "1337"
-}
-
-# create coa client
-client = Client(server=ADDRESS, secret=SECRET, dict=dictionary.Dictionary("dictionary"))
-
-# set coa timeout
-client.timeout = 30
-
-# create coa request packet
-attributes = {k.replace("-", "_"): ATTRIBUTES[k] for k in ATTRIBUTES}
-
-# create coa request
-request = client.CreateCoAPacket(**attributes)
-# create disconnect request
-# request = client.CreateCoAPacket(code=packet.DisconnectRequest, **attributes)
-
-# send request
-result = client.SendPacket(request)
-print(result)
-print(result.code)
diff --git a/example/dictionary b/example/dictionary
deleted file mode 100644
index e6766be..0000000
--- a/example/dictionary
+++ /dev/null
@@ -1,405 +0,0 @@
-#
-# Version $Id: dictionary,v 1.1.1.1 2002/10/11 12:25:39 wichert Exp $
-#
-#	This file contains dictionary translations for parsing
-#	requests and generating responses.  All transactions are
-#	composed of Attribute/Value Pairs.  The value of each attribute
-#	is specified as one of 4 data types.  Valid data types are:
-#
-#	string  - 0-253 octets
-#	ipaddr  - 4 octets in network byte order
-#	integer - 32 bit value in big endian order (high byte first)
-#	date    - 32 bit value in big endian order - seconds since
-#					00:00:00 GMT,  Jan.  1,  1970
-#
-#	FreeRADIUS includes extended data types which are not defined
-#	in RFC 2865 or RFC 2866.  These data types are:
-#
-#	abinary - Ascend's binary filter format.
-#	octets  - raw octets, printed and input as hex strings.
-#		  e.g.: 0x123456789abcdef
-#
-#
-#	Enumerated values are stored in the user file with dictionary
-#	VALUE translations for easy administration.
-#
-#	Example:
-#
-#	ATTRIBUTE	  VALUE
-#	---------------   -----
-#	Framed-Protocol = PPP
-#	7		= 1	(integer encoding)
-#
-
-#
-#	Include compatibility dictionary for older users file. Move this
-#	directive to the end of the file if you want to see the old names
-#	in the logfiles too.
-#
-#$INCLUDE dictionary.compat	# compability issues
-#$INCLUDE dictionary.acc
-#$INCLUDE dictionary.ascend
-#$INCLUDE dictionary.bay
-#$INCLUDE dictionary.cisco
-#$INCLUDE dictionary.livingston
-#$INCLUDE dictionary.microsoft
-#$INCLUDE dictionary.quintum
-#$INCLUDE dictionary.redback
-#$INCLUDE dictionary.shasta
-#$INCLUDE dictionary.shiva
-#$INCLUDE dictionary.tunnel
-#$INCLUDE dictionary.usr
-#$INCLUDE dictionary.versanet
-#$INCLUDE dictionary.erx
-#$INCLUDE dictionary.freeradius
-#$INCLUDE dictionary.alcatel
-
-#
-#	Following are the proper new names. Use these.
-#
-ATTRIBUTE	User-Name		1	string
-ATTRIBUTE	User-Password		2	string
-ATTRIBUTE	CHAP-Password		3	octets
-ATTRIBUTE	NAS-IP-Address		4	ipaddr
-ATTRIBUTE	NAS-Port		5	integer
-ATTRIBUTE	Service-Type		6	integer
-ATTRIBUTE	Framed-Protocol		7	integer
-ATTRIBUTE	Framed-IP-Address	8	ipaddr
-ATTRIBUTE	Framed-IP-Netmask	9	ipaddr
-ATTRIBUTE	Framed-Routing		10	integer
-ATTRIBUTE	Filter-Id		11	string
-ATTRIBUTE	Framed-MTU		12	integer
-ATTRIBUTE	Framed-Compression	13	integer
-ATTRIBUTE	Login-IP-Host		14	ipaddr
-ATTRIBUTE	Login-Service		15	integer
-ATTRIBUTE	Login-TCP-Port		16	integer
-ATTRIBUTE	Reply-Message		18	string
-ATTRIBUTE	Callback-Number		19	string
-ATTRIBUTE	Callback-Id		20	string
-ATTRIBUTE	Framed-Route		22	string
-ATTRIBUTE	Framed-IPX-Network	23	ipaddr
-ATTRIBUTE	State			24	octets
-ATTRIBUTE	Class			25	octets
-ATTRIBUTE	Vendor-Specific		26	octets
-ATTRIBUTE	Session-Timeout		27	integer
-ATTRIBUTE	Idle-Timeout		28	integer
-ATTRIBUTE	Termination-Action	29	integer
-ATTRIBUTE	Called-Station-Id	30	string
-ATTRIBUTE	Calling-Station-Id	31	string
-ATTRIBUTE	NAS-Identifier		32	string
-ATTRIBUTE	Proxy-State		33	octets
-ATTRIBUTE	Login-LAT-Service	34	string
-ATTRIBUTE	Login-LAT-Node		35	string
-ATTRIBUTE	Login-LAT-Group		36	octets
-ATTRIBUTE	Framed-AppleTalk-Link	37	integer
-ATTRIBUTE	Framed-AppleTalk-Network 38	integer
-ATTRIBUTE	Framed-AppleTalk-Zone	39	string
-
-ATTRIBUTE	Acct-Status-Type	40	integer
-ATTRIBUTE	Acct-Delay-Time		41	integer
-ATTRIBUTE	Acct-Input-Octets	42	integer
-ATTRIBUTE	Acct-Output-Octets	43	integer
-ATTRIBUTE	Acct-Session-Id		44	string
-ATTRIBUTE	Acct-Authentic		45	integer
-ATTRIBUTE	Acct-Session-Time	46	integer
-ATTRIBUTE       Acct-Input-Packets	47	integer
-ATTRIBUTE       Acct-Output-Packets	48	integer
-ATTRIBUTE	Acct-Terminate-Cause	49	integer
-ATTRIBUTE	Acct-Multi-Session-Id	50	string
-ATTRIBUTE	Acct-Link-Count		51	integer
-ATTRIBUTE	Acct-Input-Gigawords    52      integer
-ATTRIBUTE	Acct-Output-Gigawords   53      integer
-ATTRIBUTE	Event-Timestamp         55      date
-
-ATTRIBUTE	CHAP-Challenge		60	string
-ATTRIBUTE	NAS-Port-Type		61	integer
-ATTRIBUTE	Port-Limit		62	integer
-ATTRIBUTE	Login-LAT-Port		63	integer
-
-ATTRIBUTE	Acct-Tunnel-Connection	68	string
-
-ATTRIBUTE	ARAP-Password           70      string
-ATTRIBUTE	ARAP-Features           71      string
-ATTRIBUTE	ARAP-Zone-Access        72      integer
-ATTRIBUTE	ARAP-Security           73      integer
-ATTRIBUTE	ARAP-Security-Data      74      string
-ATTRIBUTE	Password-Retry          75      integer
-ATTRIBUTE	Prompt                  76      integer
-ATTRIBUTE	Connect-Info		77	string
-ATTRIBUTE	Configuration-Token	78	string
-ATTRIBUTE	EAP-Message		79	string
-ATTRIBUTE	Message-Authenticator	80	octets
-ATTRIBUTE	ARAP-Challenge-Response	84	string	# 10 octets
-ATTRIBUTE	Acct-Interim-Interval   85      integer
-ATTRIBUTE	NAS-Port-Id		87	string
-ATTRIBUTE	Framed-Pool		88	string
-ATTRIBUTE	NAS-IPv6-Address	95	octets	# really IPv6
-ATTRIBUTE	Framed-Interface-Id	96	octets	# 8 octets
-ATTRIBUTE	Framed-IPv6-Prefix	97	ipv6prefix	# stupid format
-ATTRIBUTE	Login-IPv6-Host		98	octets	# really IPv6
-ATTRIBUTE	Framed-IPv6-Route	99	string
-ATTRIBUTE	Framed-IPv6-Pool	100	string
-ATTRIBUTE   Delegated-IPv6-Prefix   123     ipv6prefix
-
-
-ATTRIBUTE	Digest-Response		206	string
-ATTRIBUTE	Digest-Attributes	207	octets	# stupid format
-
-#
-#	Experimental Non Protocol Attributes used by Cistron-Radiusd
-#
-
-# 	These attributes CAN go in the reply item list.
-ATTRIBUTE	Fall-Through		500	integer
-ATTRIBUTE	Exec-Program		502	string
-ATTRIBUTE	Exec-Program-Wait	503	string
-
-#	These attributes CANNOT go in the reply item list.
-ATTRIBUTE	User-Category		1029	string
-ATTRIBUTE	Group-Name		1030	string
-ATTRIBUTE	Huntgroup-Name		1031	string
-ATTRIBUTE	Simultaneous-Use	1034	integer
-ATTRIBUTE	Strip-User-Name		1035	integer
-ATTRIBUTE	Hint			1040	string
-ATTRIBUTE	Pam-Auth		1041	string
-ATTRIBUTE	Login-Time		1042	string
-ATTRIBUTE	Stripped-User-Name	1043	string
-ATTRIBUTE	Current-Time		1044	string
-ATTRIBUTE	Realm			1045	string
-ATTRIBUTE	No-Such-Attribute	1046	string
-ATTRIBUTE	Packet-Type		1047	integer
-ATTRIBUTE	Proxy-To-Realm		1048	string
-ATTRIBUTE	Replicate-To-Realm	1049	string
-ATTRIBUTE	Acct-Session-Start-Time	1050	date
-ATTRIBUTE	Acct-Unique-Session-Id  1051	string
-ATTRIBUTE	Client-IP-Address	1052	ipaddr
-ATTRIBUTE	Ldap-UserDn		1053	string
-ATTRIBUTE	NS-MTA-MD5-Password	1054	string
-ATTRIBUTE	SQL-User-Name	 	1055	string
-ATTRIBUTE	LM-Password		1057	octets
-ATTRIBUTE	NT-Password		1058	octets
-ATTRIBUTE	SMB-Account-CTRL	1059	integer
-ATTRIBUTE	SMB-Account-CTRL-TEXT	1061	string
-ATTRIBUTE	User-Profile		1062	string
-ATTRIBUTE	Digest-Realm		1063	string
-ATTRIBUTE	Digest-Nonce		1064	string
-ATTRIBUTE	Digest-Method		1065	string
-ATTRIBUTE	Digest-URI		1066	string
-ATTRIBUTE	Digest-QOP		1067	string
-ATTRIBUTE	Digest-Algorithm	1068	string
-ATTRIBUTE	Digest-Body-Digest	1069	string
-ATTRIBUTE	Digest-CNonce		1070	string
-ATTRIBUTE	Digest-Nonce-Count	1071	string
-ATTRIBUTE	Digest-User-Name	1072	string
-ATTRIBUTE	Pool-Name		1073	string
-ATTRIBUTE	Ldap-Group		1074	string
-ATTRIBUTE	Module-Success-Message	1075	string
-ATTRIBUTE	Module-Failure-Message	1076	string
-#		X99-Fast		1077	integer
-
-#
-#	Non-Protocol Attributes
-#	These attributes are used internally by the server
-#
-ATTRIBUTE	Auth-Type		1000	integer
-ATTRIBUTE	Menu			1001	string
-ATTRIBUTE	Termination-Menu	1002	string
-ATTRIBUTE	Prefix			1003	string
-ATTRIBUTE	Suffix			1004	string
-ATTRIBUTE	Group			1005	string
-ATTRIBUTE	Crypt-Password		1006	string
-ATTRIBUTE	Connect-Rate		1007	integer
-ATTRIBUTE	Add-Prefix		1008	string
-ATTRIBUTE	Add-Suffix		1009	string
-ATTRIBUTE	Expiration		1010	date
-ATTRIBUTE	Autz-Type		1011	integer
-
-#
-#	Integer Translations
-#
-
-#	User Types
-
-VALUE		Service-Type		Login-User		1
-VALUE		Service-Type		Framed-User		2
-VALUE		Service-Type		Callback-Login-User	3
-VALUE		Service-Type		Callback-Framed-User	4
-VALUE		Service-Type		Outbound-User		5
-VALUE		Service-Type		Administrative-User	6
-VALUE		Service-Type		NAS-Prompt-User		7
-VALUE		Service-Type		Authenticate-Only	8
-VALUE		Service-Type		Callback-NAS-Prompt	9
-VALUE		Service-Type		Call-Check		10
-VALUE		Service-Type		Callback-Administrative	11
-
-#	Framed Protocols
-
-VALUE		Framed-Protocol		PPP			1
-VALUE		Framed-Protocol		SLIP			2
-VALUE		Framed-Protocol		ARAP			3
-VALUE		Framed-Protocol		Gandalf-SLML		4
-VALUE		Framed-Protocol		Xylogics-IPX-SLIP	5
-VALUE		Framed-Protocol		X.75-Synchronous	6
-
-#	Framed Routing Values
-
-VALUE		Framed-Routing		None			0
-VALUE		Framed-Routing		Broadcast		1
-VALUE		Framed-Routing		Listen			2
-VALUE		Framed-Routing		Broadcast-Listen	3
-
-#	Framed Compression Types
-
-VALUE		Framed-Compression	None			0
-VALUE		Framed-Compression	Van-Jacobson-TCP-IP	1
-VALUE		Framed-Compression	IPX-Header-Compression	2
-VALUE		Framed-Compression	Stac-LZS		3
-
-#	Login Services
-
-VALUE		Login-Service		Telnet			0
-VALUE		Login-Service		Rlogin			1
-VALUE		Login-Service		TCP-Clear		2
-VALUE		Login-Service		PortMaster		3
-VALUE		Login-Service		LAT			4
-VALUE		Login-Service		X25-PAD			5
-VALUE		Login-Service		X25-T3POS		6
-VALUE		Login-Service		TCP-Clear-Quiet		7
-
-#	Login-TCP-Port		(see /etc/services for more examples)
-
-VALUE		Login-TCP-Port		Telnet			23
-VALUE		Login-TCP-Port		Rlogin			513
-VALUE		Login-TCP-Port		Rsh			514
-
-#	Status Types
-
-VALUE		Acct-Status-Type	Start			1
-VALUE		Acct-Status-Type	Stop			2
-VALUE		Acct-Status-Type	Interim-Update		3
-VALUE		Acct-Status-Type	Alive			3
-VALUE		Acct-Status-Type	Accounting-On		7
-VALUE		Acct-Status-Type	Accounting-Off		8
-#	RFC 2867 Additional Status-Type Values
-VALUE		Acct-Status-Type	Tunnel-Start		9
-VALUE		Acct-Status-Type	Tunnel-Stop		10
-VALUE		Acct-Status-Type	Tunnel-Reject		11
-VALUE		Acct-Status-Type	Tunnel-Link-Start	12
-VALUE		Acct-Status-Type	Tunnel-Link-Stop	13
-VALUE		Acct-Status-Type	Tunnel-Link-Reject	14
-
-#	Authentication Types
-
-VALUE		Acct-Authentic		RADIUS			1
-VALUE		Acct-Authentic		Local			2
-
-#	Termination Options
-
-VALUE		Termination-Action	Default			0
-VALUE		Termination-Action	RADIUS-Request		1
-
-#	NAS Port Types
-
-VALUE		NAS-Port-Type		Async			0
-VALUE		NAS-Port-Type		Sync			1
-VALUE		NAS-Port-Type		ISDN			2
-VALUE		NAS-Port-Type		ISDN-V120		3
-VALUE		NAS-Port-Type		ISDN-V110		4
-VALUE		NAS-Port-Type		Virtual			5
-VALUE		NAS-Port-Type		PIAFS			6
-VALUE		NAS-Port-Type		HDLC-Clear-Channel	7
-VALUE		NAS-Port-Type		X.25			8
-VALUE		NAS-Port-Type		X.75			9
-VALUE		NAS-Port-Type		G.3-Fax			10
-VALUE		NAS-Port-Type		SDSL			11
-VALUE		NAS-Port-Type		ADSL-CAP		12
-VALUE		NAS-Port-Type		ADSL-DMT		13
-VALUE		NAS-Port-Type		IDSL			14
-VALUE		NAS-Port-Type		Ethernet		15
-VALUE		NAS-Port-Type		xDSL			16
-VALUE		NAS-Port-Type		Cable			17
-VALUE		NAS-Port-Type		Wireless-Other		18
-VALUE		NAS-Port-Type		Wireless-802.11		19
-
-#	Acct Terminate Causes, available in 3.3.2 and later
-
-VALUE           Acct-Terminate-Cause    User-Request            1
-VALUE           Acct-Terminate-Cause    Lost-Carrier            2
-VALUE           Acct-Terminate-Cause    Lost-Service            3
-VALUE           Acct-Terminate-Cause    Idle-Timeout            4
-VALUE           Acct-Terminate-Cause    Session-Timeout         5
-VALUE           Acct-Terminate-Cause    Admin-Reset             6
-VALUE           Acct-Terminate-Cause    Admin-Reboot            7
-VALUE           Acct-Terminate-Cause    Port-Error              8
-VALUE           Acct-Terminate-Cause    NAS-Error               9
-VALUE           Acct-Terminate-Cause    NAS-Request             10
-VALUE           Acct-Terminate-Cause    NAS-Reboot              11
-VALUE           Acct-Terminate-Cause    Port-Unneeded           12
-VALUE           Acct-Terminate-Cause    Port-Preempted          13
-VALUE           Acct-Terminate-Cause    Port-Suspended          14
-VALUE           Acct-Terminate-Cause    Service-Unavailable     15
-VALUE           Acct-Terminate-Cause    Callback                16
-VALUE           Acct-Terminate-Cause    User-Error              17
-VALUE           Acct-Terminate-Cause    Host-Request            18
-
-#VALUE		Tunnel-Type		L2TP			3
-#VALUE		Tunnel-Medium-Type	IP			1
-
-VALUE		Prompt			No-Echo			0
-VALUE		Prompt			Echo			1
-
-#
-#	Non-Protocol Integer Translations
-#
-
-VALUE		Auth-Type		Local			0
-VALUE		Auth-Type		System			1
-VALUE		Auth-Type		SecurID			2
-VALUE		Auth-Type		Crypt-Local		3
-VALUE		Auth-Type		Reject			4
-VALUE		Auth-Type		ActivCard		5
-VALUE		Auth-Type		EAP			6
-VALUE		Auth-Type		ARAP			7
-
-#
-#	Cistron extensions
-#
-VALUE		Auth-Type		Ldap			252
-VALUE		Auth-Type		Pam			253
-VALUE		Auth-Type		Accept			254
-
-VALUE		Auth-Type		PAP			1024
-VALUE		Auth-Type		CHAP			1025
-VALUE		Auth-Type		LDAP			1026
-VALUE		Auth-Type		PAM			1027
-VALUE		Auth-Type		MS-CHAP			1028
-VALUE		Auth-Type		Kerberos		1029
-VALUE		Auth-Type		CRAM			1030
-VALUE		Auth-Type		NS-MTA-MD5		1031
-VALUE		Auth-Type		CRAM			1032
-VALUE		Auth-Type		SMB			1033
-
-#
-#	Authorization type, too.
-#
-VALUE		Autz-Type		Local			0
-
-#
-#	Experimental Non-Protocol Integer Translations for Cistron-Radiusd
-#
-VALUE		Fall-Through		No			0
-VALUE		Fall-Through		Yes			1
-
-VALUE		Packet-Type	Access-Request			1
-VALUE		Packet-Type	Access-Accept			2
-VALUE		Packet-Type	Access-Reject			3
-VALUE		Packet-Type	Accounting-Request		4
-VALUE		Packet-Type	Accounting-Response		5
-VALUE		Packet-Type	Accounting-Status		6
-VALUE		Packet-Type	Password-Request		7
-VALUE		Packet-Type	Password-Accept			8
-VALUE		Packet-Type	Password-Reject			9
-VALUE		Packet-Type	Accounting-Message		10
-VALUE		Packet-Type	Access-Challenge		11
-VALUE		Packet-Type	Status-Server			12
-VALUE		Packet-Type	Status-Client			13
diff --git a/example/pyrad.log b/example/pyrad.log
deleted file mode 100644
index e69de29..0000000
diff --git a/example/server.py b/example/server.py
deleted file mode 100755
index d07a6a0..0000000
--- a/example/server.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/python
-from __future__ import print_function
-from pyrad import dictionary, packet, server
-import logging
-
-logging.basicConfig(filename="pyrad.log", level="DEBUG",
-                    format="%(asctime)s [%(levelname)-8s] %(message)s")
-
-
-class FakeServer(server.Server):
-
-    def HandleAuthPacket(self, pkt):
-        print("Received an authentication request")
-        print("Attributes: ")
-        for attr in pkt.keys():
-            print("%s: %s" % (attr, pkt[attr]))
-
-        reply = self.CreateReplyPacket(pkt, **{
-            "Service-Type": "Framed-User",
-            "Framed-IP-Address": '192.168.0.1',
-            "Framed-IPv6-Prefix": "fc66::1/64"
-        })
-
-        reply.code = packet.AccessAccept
-        self.SendReplyPacket(pkt.fd, reply)
-
-    def HandleAcctPacket(self, pkt):
-
-        print("Received an accounting request")
-        print("Attributes: ")
-        for attr in pkt.keys():
-            print("%s: %s" % (attr, pkt[attr]))
-
-        reply = self.CreateReplyPacket(pkt)
-        self.SendReplyPacket(pkt.fd, reply)
-
-    def HandleCoaPacket(self, pkt):
-
-        print("Received an coa request")
-        print("Attributes: ")
-        for attr in pkt.keys():
-            print("%s: %s" % (attr, pkt[attr]))
-
-        reply = self.CreateReplyPacket(pkt)
-        self.SendReplyPacket(pkt.fd, reply)
-
-    def HandleDisconnectPacket(self, pkt):
-
-        print("Received an disconnect request")
-        print("Attributes: ")
-        for attr in pkt.keys():
-            print("%s: %s" % (attr, pkt[attr]))
-
-        reply = self.CreateReplyPacket(pkt)
-        # COA NAK
-        reply.code = 45
-        self.SendReplyPacket(pkt.fd, reply)
-
-if __name__ == '__main__':
-
-    # create server and read dictionary
-    srv = FakeServer(dict=dictionary.Dictionary("dictionary"), coa_enabled=True)
-
-    # add clients (address, secret, name)
-    srv.hosts["127.0.0.1"] = server.RemoteHost("127.0.0.1", b"Kah3choteereethiejeimaeziecumi", "localhost")
-    srv.BindToAddress("")
-
-    # start server
-    srv.Run()
diff --git a/index.txt b/index.txt
deleted file mode 100644
index acaf52b..0000000
--- a/index.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-.. Pyrad documentation master file, created by sphinx-quickstart on Sat Nov 15 00:01:13 2008.
-   You can adapt this file completely to your liking, but it should at least
-   contain the root `toctree` directive.
-
-Welcome to Pyrad's documentation!
-=================================
-
-Contents:
-
-.. toctree::
-   :maxdepth: 2
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100755
index 0000000..87b1df3
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,40 @@
+[build-system]
+requires = ["poetry>=1.0"]
+build-backend = "poetry.masonry.api"
+
+[tool.poetry]
+name = "pyrad"
+version= "2.4"
+readme = "README.rst"
+license = "BSD-3-Clause"
+description="RADIUS tools"
+authors = [
+  "Istvan Ruzman <istvan@ruzman.eu>",
+  "Christian Giese <developer@gicnet.de>",
+]
+keywords = ["AAA", "accounting", "authentication", "authorization", "RADIUS"]
+classifiers = [
+    "Development Status :: 6 - Mature",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: BSD License",
+    "Programming Language :: Python :: 2.7",
+    "Programming Language :: Python :: 3.6",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Topic :: Software Development :: Libraries :: Python Modules",
+    "Topic :: System :: Systems Administration :: Authentication/Directory",
+]
+include = [
+  "example/*"
+]
+
+[tool.poetry.urls]
+repository = "https://github.com/pyradius/pyrad"
+
+[tool.poetry.dependencies]
+python = "^2.7 || ^3.6"
+six = "^1.15.0"
+netaddr = "^0.8"
+
+[tool.poetry.dev-dependencies]
+nose = "^0.10.0b1"
diff --git a/pyrad.egg-info/PKG-INFO b/pyrad.egg-info/PKG-INFO
new file mode 100644
index 0000000..155c290
--- /dev/null
+++ b/pyrad.egg-info/PKG-INFO
@@ -0,0 +1,98 @@
+Metadata-Version: 1.1
+Name: pyrad
+Version: 2.4
+Summary: RADIUS tools
+Home-page: https://github.com/pyradius/pyrad
+Author: Istvan Ruzman, Christian Giese
+Author-email: istvan@ruzman.eu, developer@gicnet.de
+License: BSD
+Description: .. image:: https://travis-ci.org/pyradius/pyrad.svg?branch=master
+            :target: https://travis-ci.org/pyradius/pyrad
+        .. image:: https://coveralls.io/repos/github/pyradius/pyrad/badge.svg?branch=master
+            :target: https://coveralls.io/github/pyradius/pyrad?branch=master
+        .. image:: https://img.shields.io/pypi/v/pyrad.svg
+            :target: https://pypi.python.org/pypi/pyrad
+        .. image:: https://img.shields.io/pypi/pyversions/pyrad.svg
+            :target: https://pypi.python.org/pypi/pyrad
+        .. image:: https://img.shields.io/pypi/dm/pyrad.svg
+            :target: https://pypi.python.org/pypi/pyrad
+        .. image:: https://readthedocs.org/projects/pyrad/badge/?version=latest
+            :target: http://pyrad.readthedocs.io/en/latest/?badge=latest
+            :alt: Documentation Status
+        .. image:: https://img.shields.io/pypi/l/pyrad.svg
+            :target: https://pypi.python.org/pypi/pyrad
+        
+        Introduction
+        ============
+        
+        pyrad is an implementation of a RADIUS client/server as described in RFC2865.
+        It takes care of all the details like building RADIUS packets, sending
+        them and decoding responses.
+        
+        Here is an example of doing a authentication request::
+        
+            from __future__ import print_function
+            from pyrad.client import Client
+            from pyrad.dictionary import Dictionary
+            import pyrad.packet
+        
+            srv = Client(server="localhost", secret=b"Kah3choteereethiejeimaeziecumi",
+                         dict=Dictionary("dictionary"))
+        
+            # create request
+            req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest,
+                                       User_Name="wichert", NAS_Identifier="localhost")
+            req["User-Password"] = req.PwCrypt("password")
+        
+            # send request
+            reply = srv.SendPacket(req)
+        
+            if reply.code == pyrad.packet.AccessAccept:
+                print("access accepted")
+            else:
+                print("access denied")
+        
+            print("Attributes returned by server:")
+            for i in reply.keys():
+                print("%s: %s" % (i, reply[i]))
+        
+        
+        
+        Requirements & Installation
+        ===========================
+        
+        pyrad requires Python 2.7, or Python 3.6 or later
+        
+        Installing is simple; pyrad uses the standard distutils system for installing
+        Python modules::
+        
+          python setup.py install
+        
+        
+        Author, Copyright, Availability
+        ===============================
+        
+        pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is maintained by 
+        Christian Giese (GIC-de) and Istvan Ruzman (Istvan91). 
+        
+        This project is licensed under a BSD license.
+        
+        Copyright and license information can be found in the LICENSE.txt file.
+        
+        The current version and documentation can be found on pypi:
+        https://pypi.org/project/pyrad/
+        
+        Bugs and wishes can be submitted in the pyrad issue tracker on github:
+        https://github.com/pyradius/pyrad/issues
+        
+Keywords: radius,authentication
+Platform: UNKNOWN
+Classifier: Development Status :: 6 - Mature
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
diff --git a/pyrad.egg-info/SOURCES.txt b/pyrad.egg-info/SOURCES.txt
new file mode 100644
index 0000000..a2bd7b0
--- /dev/null
+++ b/pyrad.egg-info/SOURCES.txt
@@ -0,0 +1,23 @@
+README.rst
+pyproject.toml
+setup.cfg
+setup.py
+pyrad/__init__.py
+pyrad/bidict.py
+pyrad/client.py
+pyrad/client_async.py
+pyrad/curved.py
+pyrad/dictfile.py
+pyrad/dictionary.py
+pyrad/host.py
+pyrad/packet.py
+pyrad/proxy.py
+pyrad/server.py
+pyrad/server_async.py
+pyrad/tools.py
+pyrad.egg-info/PKG-INFO
+pyrad.egg-info/SOURCES.txt
+pyrad.egg-info/dependency_links.txt
+pyrad.egg-info/requires.txt
+pyrad.egg-info/top_level.txt
+pyrad.egg-info/zip-safe
\ No newline at end of file
diff --git a/pyrad.egg-info/dependency_links.txt b/pyrad.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pyrad.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/requirements.txt b/pyrad.egg-info/requires.txt
similarity index 100%
rename from requirements.txt
rename to pyrad.egg-info/requires.txt
diff --git a/pyrad.egg-info/top_level.txt b/pyrad.egg-info/top_level.txt
new file mode 100644
index 0000000..a39422a
--- /dev/null
+++ b/pyrad.egg-info/top_level.txt
@@ -0,0 +1 @@
+pyrad
diff --git a/pyrad.egg-info/zip-safe b/pyrad.egg-info/zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pyrad.egg-info/zip-safe
@@ -0,0 +1 @@
+
diff --git a/pyrad/__init__.py b/pyrad/__init__.py
index cd3e78a..feb21f8 100644
--- a/pyrad/__init__.py
+++ b/pyrad/__init__.py
@@ -38,8 +38,9 @@ This package contains four modules:
 
 __docformat__ = 'epytext en'
 
-__author__ = 'Wichert Akkerman <wichert@wiggy.net>'
-__url__ = 'http://www.wiggy.net/code/pyrad.xhtml'
-__copyright__ = 'Copyright 2002-2007 Wichert Akkerman'
+__author__ = 'Christian Giese <developer@gicnet.de>'
+__url__ = 'http://pyrad.readthedocs.io/en/latest/?badge=latest'
+__copyright__ = 'Copyright 2002-2020 Wichert Akkerman and Christian Giese. All rights reserved.'
+__version__ = '2.4'
 
 __all__ = ['client', 'dictionary', 'packet', 'server', 'tools', 'dictfile']
diff --git a/pyrad/client.py b/pyrad/client.py
index 9369010..6964963 100644
--- a/pyrad/client.py
+++ b/pyrad/client.py
@@ -4,13 +4,18 @@
 
 __docformat__ = "epytext en"
 
+import hashlib
 import select
 import socket
 import time
 import six
+import struct
 from pyrad import host
 from pyrad import packet
 
+EAP_CODE_REQUEST = 1
+EAP_CODE_RESPONSE = 2
+EAP_TYPE_IDENTITY = 1
 
 class Timeout(Exception):
     """Simple exception class which is raised when a timeout occurs
@@ -26,10 +31,10 @@ class Client(host.Host):
     :ivar retries: number of times to retry sending a RADIUS request
     :type retries: integer
     :ivar timeout: number of seconds to wait for an answer
-    :type timeout: integer
+    :type timeout: float
     """
     def __init__(self, server, authport=1812, acctport=1813,
-            coaport=3799, secret=six.b(''), dict=None):
+            coaport=3799, secret=six.b(''), dict=None, retries=3, timeout=5):
 
         """Constructor.
 
@@ -51,8 +56,9 @@ class Client(host.Host):
         self.server = server
         self.secret = secret
         self._socket = None
-        self.retries = 3
-        self.timeout = 5
+        self.retries = retries
+        self.timeout = timeout
+        self._poll = select.poll()
 
     def bind(self, addr):
         """Bind socket to an address.
@@ -67,14 +73,20 @@ class Client(host.Host):
         self._socket.bind(addr)
 
     def _SocketOpen(self):
+        try:
+            family = socket.getaddrinfo(self.server, 'www')[0][0]
+        except:
+            family = socket.AF_INET
         if not self._socket:
-            self._socket = socket.socket(socket.AF_INET,
+            self._socket = socket.socket(family,
                                        socket.SOCK_DGRAM)
             self._socket.setsockopt(socket.SOL_SOCKET,
                                     socket.SO_REUSEADDR, 1)
+            self._poll.register(self._socket, select.POLLIN)
 
     def _CloseSocket(self):
         if self._socket:
+            self._poll.unregister(self._socket)
             self._socket.close()
             self._socket = None
 
@@ -86,7 +98,7 @@ class Client(host.Host):
         dictionary and secret used for the client.
 
         :return: a new empty packet instance
-        :rtype:  pyrad.packet.Packet
+        :rtype:  pyrad.packet.AuthPacket
         """
         return host.Host.CreateAuthPacket(self, secret=self.secret, **args)
 
@@ -101,7 +113,7 @@ class Client(host.Host):
         :rtype:  pyrad.packet.Packet
         """
         return host.Host.CreateAcctPacket(self, secret=self.secret, **args)
-        
+
     def CreateCoAPacket(self, **args):
         """Create a new RADIUS packet.
         This utility function creates a new RADIUS packet which can
@@ -134,16 +146,16 @@ class Client(host.Host):
                             pkt["Acct-Delay-Time"][0] + self.timeout
                 else:
                     pkt["Acct-Delay-Time"] = self.timeout
-            self._socket.sendto(pkt.RequestPacket(), (self.server, port))
 
             now = time.time()
             waitto = now + self.timeout
 
+            self._socket.sendto(pkt.RequestPacket(), (self.server, port))
+
             while now < waitto:
-                ready = select.select([self._socket], [], [],
-                                    (waitto - now))
+                ready = self._poll.poll((waitto - now) * 1000)
 
-                if ready[0]:
+                if ready:
                     rawreply = self._socket.recv(4096)
                 else:
                     now = time.time()
@@ -170,7 +182,39 @@ class Client(host.Host):
         :raise Timeout: RADIUS server does not reply
         """
         if isinstance(pkt, packet.AuthPacket):
-            return self._SendPacket(pkt, self.authport)
+            if pkt.auth_type == 'eap-md5':
+                # Creating EAP-Identity
+                password = pkt[2][0] if 2 in pkt else pkt[1][0]
+                pkt[79] = [struct.pack('!BBHB%ds' % len(password),
+                                       EAP_CODE_RESPONSE,
+                                       packet.CurrentID,
+                                       len(password) + 5,
+                                       EAP_TYPE_IDENTITY,
+                                       password)]
+            reply = self._SendPacket(pkt, self.authport)
+            if (
+                reply
+                and reply.code == packet.AccessChallenge
+                and pkt.auth_type == 'eap-md5'
+            ):
+                # Got an Access-Challenge
+                eap_code, eap_id, eap_size, eap_type, eap_md5 = struct.unpack(
+                    '!BBHB%ds' % (len(reply[79][0]) - 5), reply[79][0]
+                )
+                # Sending back an EAP-Type-MD5-Challenge
+                # Thank god for http://www.secdev.org/python/eapy.py
+                client_pw = pkt[2][0] if 2 in pkt else pkt[1][0]
+                md5_challenge = hashlib.md5(
+                    struct.pack('!B', eap_id) + client_pw + eap_md5[1:]
+                ).digest()
+                pkt[79] = [
+                    struct.pack('!BBHBB', 2, eap_id, len(md5_challenge) + 6,
+                                4, len(md5_challenge)) + md5_challenge
+                ]
+                # Copy over Challenge-State
+                pkt[24] = reply[24]
+                reply = self._SendPacket(pkt, self.authport)
+            return reply
         elif isinstance(pkt, packet.CoAPacket):
             return self._SendPacket(pkt, self.coaport)
         else:
diff --git a/pyrad/client_async.py b/pyrad/client_async.py
new file mode 100644
index 0000000..f2cedc8
--- /dev/null
+++ b/pyrad/client_async.py
@@ -0,0 +1,412 @@
+# client_async.py
+#
+# Copyright 2018-2020 Geaaru <geaaru<@>gmail.com>
+
+__docformat__ = "epytext en"
+
+from datetime import datetime
+import asyncio
+import six
+import logging
+import random
+
+from pyrad.packet import Packet, AuthPacket, AcctPacket, CoAPacket
+
+
+class DatagramProtocolClient(asyncio.Protocol):
+
+    def __init__(self, server, port, logger,
+                 client, retries=3, timeout=30):
+        self.transport = None
+        self.port = port
+        self.server = server
+        self.logger = logger
+        self.retries = retries
+        self.timeout = timeout
+        self.client = client
+
+        # Map of pending requests
+        self.pending_requests = {}
+
+        # Use cryptographic-safe random generator as provided by the OS.
+        random_generator = random.SystemRandom()
+        self.packet_id = random_generator.randrange(0, 256)
+
+        self.timeout_future = None
+
+    async def __timeout_handler__(self):
+
+        try:
+
+            while True:
+
+                req2delete = []
+                now = datetime.now()
+                next_weak_up = self.timeout
+                # noinspection PyShadowingBuiltins
+                for id, req in self.pending_requests.items():
+
+                    secs = (req['send_date'] - now).seconds
+                    if secs > self.timeout:
+                        if req['retries'] == self.retries:
+                            self.logger.debug('[%s:%d] For request %d execute all retries', self.server, self.port, id)
+                            req['future'].set_exception(
+                                TimeoutError('Timeout on Reply')
+                            )
+                            req2delete.append(id)
+                        else:
+                            # Send again packet
+                            req['send_date'] = now
+                            req['retries'] += 1
+                            self.logger.debug('[%s:%d] For request %d execute retry %d', self.server, self.port, id, req['retries'])
+                            self.transport.sendto(req['packet'].RequestPacket())
+                    elif next_weak_up > secs:
+                        next_weak_up = secs
+
+                # noinspection PyShadowingBuiltins
+                for id in req2delete:
+                    # Remove request for map
+                    del self.pending_requests[id]
+
+                await asyncio.sleep(next_weak_up)
+
+        except asyncio.CancelledError:
+            pass
+
+    def send_packet(self, packet, future):
+        if packet.id in self.pending_requests:
+            raise Exception('Packet with id %d already present' % packet.id)
+
+        # Store packet on pending requests map
+        self.pending_requests[packet.id] = {
+            'packet': packet,
+            'creation_date': datetime.now(),
+            'retries': 0,
+            'future': future,
+            'send_date': datetime.now()
+        }
+
+        # In queue packet raw on socket buffer
+        self.transport.sendto(packet.RequestPacket())
+
+    def connection_made(self, transport):
+        self.transport = transport
+        socket = transport.get_extra_info('socket')
+        self.logger.info(
+            '[%s:%d] Transport created with binding in %s:%d',
+                self.server, self.port,
+                socket.getsockname()[0],
+                socket.getsockname()[1]
+        )
+
+        pre_loop = asyncio.get_event_loop()
+        asyncio.set_event_loop(loop=self.client.loop)
+        # Start asynchronous timer handler
+        self.timeout_future = asyncio.ensure_future(
+            self.__timeout_handler__()
+        )
+        asyncio.set_event_loop(loop=pre_loop)
+
+    def error_received(self, exc):
+        self.logger.error('[%s:%d] Error received: %s', self.server, self.port, exc)
+
+    def connection_lost(self, exc):
+        if exc:
+            self.logger.warn('[%s:%d] Connection lost: %s', self.server, self.port, str(exc))
+        else:
+            self.logger.info('[%s:%d] Transport closed', self.server, self.port)
+
+    # noinspection PyUnusedLocal
+    def datagram_received(self, data, addr):
+        try:
+            reply = Packet(packet=data, dict=self.client.dict)
+
+            if reply and reply.id in self.pending_requests:
+                req = self.pending_requests[reply.id]
+                packet = req['packet']
+
+                reply.dict = packet.dict
+                reply.secret = packet.secret
+
+                if packet.VerifyReply(reply, data):
+                    req['future'].set_result(reply)
+                    # Remove request for map
+                    del self.pending_requests[reply.id]
+                else:
+                    self.logger.warn('[%s:%d] Ignore invalid reply for id %d. %s', self.server, self.port, reply.id)
+            else:
+                self.logger.warn('[%s:%d] Ignore invalid reply: %s', self.server, self.port, data)
+
+        except Exception as exc:
+            self.logger.error('[%s:%d] Error on decode packet: %s', self.server, self.port, exc)
+
+    async def close_transport(self):
+        if self.transport:
+            self.logger.debug('[%s:%d] Closing transport...', self.server, self.port)
+            self.transport.close()
+            self.transport = None
+        if self.timeout_future:
+            self.timeout_future.cancel()
+            await self.timeout_future
+            self.timeout_future = None
+
+    def create_id(self):
+        self.packet_id = (self.packet_id + 1) % 256
+        return self.packet_id
+
+    def __str__(self):
+        return 'DatagramProtocolClient(server?=%s, port=%d)' % (self.server, self.port)
+
+    # Used as protocol_factory
+    def __call__(self):
+        return self
+
+
+class ClientAsync:
+    """Basic RADIUS client.
+    This class implements a basic RADIUS client. It can send requests
+    to a RADIUS server, taking care of timeouts and retries, and
+    validate its replies.
+
+    :ivar retries: number of times to retry sending a RADIUS request
+    :type retries: integer
+    :ivar timeout: number of seconds to wait for an answer
+    :type timeout: integer
+    """
+    # noinspection PyShadowingBuiltins
+    def __init__(self, server, auth_port=1812, acct_port=1813,
+                 coa_port=3799, secret=six.b(''), dict=None,
+                 loop=None, retries=3, timeout=30,
+                 logger_name='pyrad'):
+
+        """Constructor.
+
+        :param    server: hostname or IP address of RADIUS server
+        :type     server: string
+        :param auth_port: port to use for authentication packets
+        :type  auth_port: integer
+        :param acct_port: port to use for accounting packets
+        :type  acct_port: integer
+        :param  coa_port: port to use for CoA packets
+        :type   coa_port: integer
+        :param    secret: RADIUS secret
+        :type     secret: string
+        :param      dict: RADIUS dictionary
+        :type       dict: pyrad.dictionary.Dictionary
+        :param      loop: Python loop handler
+        :type       loop:  asyncio event loop
+        """
+        if not loop:
+            self.loop = asyncio.get_event_loop()
+        else:
+            self.loop = loop
+        self.logger = logging.getLogger(logger_name)
+
+        self.server = server
+        self.secret = secret
+        self.retries = retries
+        self.timeout = timeout
+        self.dict = dict
+
+        self.auth_port = auth_port
+        self.protocol_auth = None
+
+        self.acct_port = acct_port
+        self.protocol_acct = None
+
+        self.protocol_coa = None
+        self.coa_port = coa_port
+
+    async def initialize_transports(self, enable_acct=False,
+                                    enable_auth=False, enable_coa=False,
+                                    local_addr=None, local_auth_port=None,
+                                    local_acct_port=None, local_coa_port=None):
+
+        task_list = []
+
+        if not enable_acct and not enable_auth and not enable_coa:
+            raise Exception('No transports selected')
+
+        if enable_acct and not self.protocol_acct:
+            self.protocol_acct = DatagramProtocolClient(
+                self.server,
+                self.acct_port,
+                self.logger, self,
+                retries=self.retries,
+                timeout=self.timeout
+            )
+            bind_addr = None
+            if local_addr and local_acct_port:
+                bind_addr = (local_addr, local_acct_port)
+
+            acct_connect = self.loop.create_datagram_endpoint(
+                self.protocol_acct,
+                reuse_port=True,
+                remote_addr=(self.server, self.acct_port),
+                local_addr=bind_addr
+            )
+            task_list.append(acct_connect)
+
+        if enable_auth and not self.protocol_auth:
+            self.protocol_auth = DatagramProtocolClient(
+                self.server,
+                self.auth_port,
+                self.logger, self,
+                retries=self.retries,
+                timeout=self.timeout
+            )
+            bind_addr = None
+            if local_addr and local_auth_port:
+                bind_addr = (local_addr, local_auth_port)
+
+            auth_connect = self.loop.create_datagram_endpoint(
+                self.protocol_auth,
+                reuse_port=True,
+                remote_addr=(self.server, self.auth_port),
+                local_addr=bind_addr
+            )
+            task_list.append(auth_connect)
+
+        if enable_coa and not self.protocol_coa:
+            self.protocol_coa = DatagramProtocolClient(
+                self.server,
+                self.coa_port,
+                self.logger, self,
+                retries=self.retries,
+                timeout=self.timeout
+            )
+            bind_addr = None
+            if local_addr and local_coa_port:
+                bind_addr = (local_addr, local_coa_port)
+
+            coa_connect = self.loop.create_datagram_endpoint(
+                self.protocol_coa,
+                reuse_port=True,
+                remote_addr=(self.server, self.coa_port),
+                local_addr=bind_addr
+            )
+            task_list.append(coa_connect)
+
+        await asyncio.ensure_future(
+            asyncio.gather(
+                *task_list,
+                return_exceptions=False,
+            ),
+            loop=self.loop
+        )
+
+    # noinspection SpellCheckingInspection
+    async def deinitialize_transports(self, deinit_coa=True,
+                                      deinit_auth=True,
+                                      deinit_acct=True):
+        if self.protocol_coa and deinit_coa:
+            await self.protocol_coa.close_transport()
+            del self.protocol_coa
+            self.protocol_coa = None
+        if self.protocol_auth and deinit_auth:
+            await self.protocol_auth.close_transport()
+            del self.protocol_auth
+            self.protocol_auth = None
+        if self.protocol_acct and deinit_acct:
+            await self.protocol_acct.close_transport()
+            del self.protocol_acct
+            self.protocol_acct = None
+
+    # noinspection PyPep8Naming
+    def CreateAuthPacket(self, **args):
+        """Create a new RADIUS packet.
+        This utility function creates a new RADIUS packet which can
+        be used to communicate with the RADIUS server this client
+        talks to. This is initializing the new packet with the
+        dictionary and secret used for the client.
+
+        :return: a new empty packet instance
+        :rtype:  pyrad.packet.Packet
+        """
+        if not self.protocol_auth:
+            raise Exception('Transport not initialized')
+
+        return AuthPacket(dict=self.dict,
+                          id=self.protocol_auth.create_id(),
+                          secret=self.secret, **args)
+
+    # noinspection PyPep8Naming
+    def CreateAcctPacket(self, **args):
+        """Create a new RADIUS packet.
+        This utility function creates a new RADIUS packet which can
+        be used to communicate with the RADIUS server this client
+        talks to. This is initializing the new packet with the
+        dictionary and secret used for the client.
+
+        :return: a new empty packet instance
+        :rtype:  pyrad.packet.Packet
+        """
+        if not self.protocol_acct:
+            raise Exception('Transport not initialized')
+
+        return AcctPacket(id=self.protocol_acct.create_id(),
+                          dict=self.dict,
+                          secret=self.secret, **args)
+
+    # noinspection PyPep8Naming
+    def CreateCoAPacket(self, **args):
+        """Create a new RADIUS packet.
+        This utility function creates a new RADIUS packet which can
+        be used to communicate with the RADIUS server this client
+        talks to. This is initializing the new packet with the
+        dictionary and secret used for the client.
+
+        :return: a new empty packet instance
+        :rtype:  pyrad.packet.Packet
+        """
+
+        if not self.protocol_acct:
+            raise Exception('Transport not initialized')
+
+        return CoAPacket(id=self.protocol_coa.create_id(),
+                         dict=self.dict,
+                         secret=self.secret, **args)
+
+    # noinspection PyPep8Naming
+    # noinspection PyShadowingBuiltins
+    def CreatePacket(self, id, **args):
+        if not id:
+            raise Exception('Missing mandatory packet id')
+
+        return Packet(id=id, dict=self.dict,
+                      secret=self.secret, **args)
+
+    # noinspection PyPep8Naming
+    def SendPacket(self, pkt):
+        """Send a packet to a RADIUS server.
+
+        :param pkt: the packet to send
+        :type  pkt: pyrad.packet.Packet
+        :return:    Future related with packet to send
+        :rtype:     asyncio.Future
+        """
+
+        ans = asyncio.Future(loop=self.loop)
+
+        if isinstance(pkt, AuthPacket):
+            if not self.protocol_auth:
+                raise Exception('Transport not initialized')
+
+            self.protocol_auth.send_packet(pkt, ans)
+
+        elif isinstance(pkt, AcctPacket):
+            if not self.protocol_acct:
+                raise Exception('Transport not initialized')
+
+            self.protocol_acct.send_packet(pkt, ans)
+
+        elif isinstance(pkt, CoAPacket):
+            if not self.protocol_coa:
+                raise Exception('Transport not initialized')
+
+            self.protocol_coa.send_packet(pkt, ans)
+
+        else:
+            raise Exception('Unsupported packet')
+
+        return ans
diff --git a/pyrad/dictionary.py b/pyrad/dictionary.py
index 5cc42db..abe5263 100644
--- a/pyrad/dictionary.py
+++ b/pyrad/dictionary.py
@@ -55,6 +55,10 @@ The datatypes currently supported are:
 +---------------+----------------------------------------------+
 | byte          | 8 bits unsigned number                       |
 +---------------+----------------------------------------------+
+| tlv           | Nested tag-length-value                      |
++---------------+----------------------------------------------+
+| integer64     | 64 bits unsigned number                      |
++---------------+----------------------------------------------+
 
 These datatypes are parsed but not supported:
 
@@ -78,7 +82,7 @@ __docformat__ = 'epytext en'
 
 DATATYPES = frozenset(['string', 'ipaddr', 'integer', 'date', 'octets',
                        'abinary', 'ipv6addr', 'ipv6prefix', 'short', 'byte',
-                       'signed', 'ifid', 'ether'])
+                       'signed', 'ifid', 'ether', 'tlv', 'integer64'])
 
 
 class ParseError(Exception):
@@ -86,7 +90,7 @@ class ParseError(Exception):
 
     :ivar msg:        Error message
     :type msg:        string
-    :ivar linenumber: Line number on which the error occured
+    :ivar linenumber: Line number on which the error occurred
     :type linenumber: integer
     """
 
@@ -111,7 +115,7 @@ class ParseError(Exception):
 
 
 class Attribute(object):
-    def __init__(self, name, code, datatype, vendor='', values={},
+    def __init__(self, name, code, datatype, is_sub_attribute=False, vendor='', values=None,
                  encrypt=0, has_tag=False):
         if datatype not in DATATYPES:
             raise ValueError('Invalid data type')
@@ -122,8 +126,12 @@ class Attribute(object):
         self.encrypt = encrypt
         self.has_tag = has_tag
         self.values = bidict.BiDict()
-        for (key, value) in values.items():
-            self.values.Add(key, value)
+        self.sub_attributes = {}
+        self.parent = None
+        self.is_sub_attribute = is_sub_attribute
+        if values:
+            for (key, value) in values.items():
+                self.values.Add(key, value)
 
 
 class Dictionary(object):
@@ -211,11 +219,28 @@ class Dictionary(object):
 
         (attribute, code, datatype) = tokens[1:4]
 
-        try:
-            # todo: check if float like for extended attributes
-            code = int(code, 0)
-        except:
-            return None
+        codes = code.split('.')
+
+        # Codes can be sent as hex, or octal or decimal string representations.
+        tmp = []
+        for c in codes:
+          if c.startswith('0x'):
+            tmp.append(int(c, 16))
+          elif c.startswith('0o'):
+            tmp.append(int(c, 8))
+          else:
+            tmp.append(int(c, 10))
+        codes = tmp
+
+        is_sub_attribute = (len(codes) > 1)
+        if len(codes) == 2:
+            code = int(codes[1])
+            parent_code = int(codes[0])
+        elif len(codes) == 1:
+            code = int(codes[0])
+            parent_code = None
+        else:
+            raise ParseError('nested tlvs are not supported')
 
         datatype = datatype.split("[")[0]
 
@@ -224,12 +249,25 @@ class Dictionary(object):
                              file=state['file'],
                              line=state['line'])
         if vendor:
-            key = (self.vendors.GetForward(vendor), code)
+            if is_sub_attribute:
+                key = (self.vendors.GetForward(vendor), parent_code, code)
+            else:
+                key = (self.vendors.GetForward(vendor), code)
         else:
-            key = code
+            if is_sub_attribute:
+                key = (parent_code, code)
+            else:
+                key = code
 
         self.attrindex.Add(attribute, key)
-        self.attributes[attribute] = Attribute(attribute, code, datatype, vendor, encrypt=encrypt, has_tag=has_tag)
+        self.attributes[attribute] = Attribute(attribute, code, datatype, is_sub_attribute, vendor, encrypt=encrypt, has_tag=has_tag)
+        if datatype == 'tlv':
+            # save attribute in tlvs
+            state['tlvs'][code] = self.attributes[attribute]
+        if is_sub_attribute:
+            # save sub attribute in parent tlv and update their parent field
+            state['tlvs'][parent_code].sub_attributes[code] = attribute
+            self.attributes[attribute].parent = state['tlvs'][parent_code]
 
     def __ParseValue(self, state, tokens, defer):
         if len(tokens) != 4:
@@ -249,7 +287,7 @@ class Dictionary(object):
                              file=state['file'],
                              line=state['line'])
 
-        if adef.type in ['integer','signed','short','byte']:
+        if adef.type in ['integer', 'signed', 'short', 'byte', 'integer64']:
             value = int(value, 0)
         value = tools.EncodeAttr(adef.type, value)
         self.attributes[attr].values.Add(key, value)
@@ -332,7 +370,7 @@ class Dictionary(object):
 
         state = {}
         state['vendor'] = ''
-
+        state['tlvs'] = {}
         self.defer_parse = []
         for line in fil:
             state['file'] = fil.File()
diff --git a/pyrad/packet.py b/pyrad/packet.py
index dfb9b58..995577a 100644
--- a/pyrad/packet.py
+++ b/pyrad/packet.py
@@ -4,9 +4,22 @@
 #
 # A RADIUS packet as defined in RFC 2138
 
-
+from collections import OrderedDict
 import struct
-import random
+try:
+    import secrets
+    random_generator = secrets.SystemRandom()
+except ImportError:
+    import random
+    random_generator = random.SystemRandom()
+import hmac
+
+import sys
+if sys.version_info >= (3, 0):
+    hmac_new = lambda *x, **y: hmac.new(*x, digestmod='MD5', **y)
+else:
+    hmac_new = hmac.new
+
 try:
     import hashlib
     md5_constructor = hashlib.md5
@@ -33,9 +46,6 @@ CoARequest = 43
 CoAACK = 44
 CoANAK = 45
 
-# Use cryptographic-safe random generator as provided by the OS.
-random_generator = random.SystemRandom()
-
 # Current ID
 CurrentID = random_generator.randrange(1, 255)
 
@@ -44,7 +54,7 @@ class PacketError(Exception):
     pass
 
 
-class Packet(dict):
+class Packet(OrderedDict):
     """Packet acts like a standard python map to provide simple access
     to the RADIUS attributes. Since RADIUS allows for repeated
     attributes the value will always be a sequence. pyrad makes sure
@@ -60,21 +70,22 @@ class Packet(dict):
     :obj:`AuthPacket` or :obj:`AcctPacket` classes.
     """
 
-    def __init__(self, code=0, id=None, secret=six.b(''), authenticator=None, **attributes):
+    def __init__(self, code=0, id=None, secret=six.b(''), authenticator=None,
+                 **attributes):
         """Constructor
 
         :param dict:   RADIUS dictionary
         :type dict:    pyrad.dictionary.Dictionary class
         :param secret: secret needed to communicate with a RADIUS server
         :type secret:  string
-        :param id:     packet identifaction number
+        :param id:     packet identification number
         :type id:      integer (8 bits)
         :param code:   packet type code
         :type code:    integer (8bits)
         :param packet: raw packet to decode
         :type packet:  string
         """
-        dict.__init__(self)
+        OrderedDict.__init__(self)
         self.code = code
         if id is not None:
             self.id = id
@@ -85,21 +96,141 @@ class Packet(dict):
         self.secret = secret
         if authenticator is not None and \
                 not isinstance(authenticator, six.binary_type):
-                    raise TypeError('authenticator must be a binary string')
+            raise TypeError('authenticator must be a binary string')
         self.authenticator = authenticator
+        self.message_authenticator = None
+        self.raw_packet = None
 
         if 'dict' in attributes:
             self.dict = attributes['dict']
 
         if 'packet' in attributes:
-            self.DecodePacket(attributes['packet'])
+            self.raw_packet = attributes['packet']
+            self.DecodePacket(self.raw_packet)
+
+        if 'message_authenticator' in attributes:
+            self.message_authenticator = attributes['message_authenticator']
 
         for (key, value) in attributes.items():
-            if key in ['dict', 'fd', 'packet']:
+            if key in [
+                'dict', 'fd', 'packet',
+                'message_authenticator',
+            ]:
                 continue
             key = key.replace('_', '-')
             self.AddAttribute(key, value)
 
+    def add_message_authenticator(self):
+
+        self.message_authenticator = True
+        # Maintain a zero octets content for md5 and hmac calculation.
+        self['Message-Authenticator'] = 16 * six.b('\00')
+
+        if self.id is None:
+            self.id = self.CreateID()
+
+        if self.authenticator is None and self.code == AccessRequest:
+            self.authenticator = self.CreateAuthenticator()
+            self._refresh_message_authenticator()
+
+    def get_message_authenticator(self):
+        self._refresh_message_authenticator()
+        return self.message_authenticator
+
+    def _refresh_message_authenticator(self):
+        hmac_constructor = hmac_new(self.secret)
+
+        # Maintain a zero octets content for md5 and hmac calculation.
+        self['Message-Authenticator'] = 16 * six.b('\00')
+        attr = self._PktEncodeAttributes()
+
+        header = struct.pack('!BBH', self.code, self.id,
+                             (20 + len(attr)))
+
+        hmac_constructor.update(header[0:4])
+        if self.code in (AccountingRequest, DisconnectRequest,
+                         CoARequest, AccountingResponse):
+            hmac_constructor.update(16 * six.b('\00'))
+        else:
+            # NOTE: self.authenticator on reply packet is initialized
+            #       with request authenticator by design.
+            #       For AccessAccept, AccessReject and AccessChallenge
+            #       it is needed use original Authenticator.
+            #       For AccessAccept, AccessReject and AccessChallenge
+            #       it is needed use original Authenticator.
+            if self.authenticator is None:
+                raise Exception('No authenticator found')
+            hmac_constructor.update(self.authenticator)
+
+        hmac_constructor.update(attr)
+        self['Message-Authenticator'] = hmac_constructor.digest()
+
+    def verify_message_authenticator(self, secret=None,
+                                     original_authenticator=None,
+                                     original_code=None):
+        """Verify packet Message-Authenticator.
+
+        :return: False if verification failed else True
+        :rtype: boolean
+        """
+        if self.message_authenticator is None:
+            raise Exception('No Message-Authenticator AVP present')
+
+        prev_ma = self['Message-Authenticator']
+        # Set zero bytes for Message-Authenticator for md5 calculation
+        if secret is None and self.secret is None:
+            raise Exception('Missing secret for HMAC/MD5 verification')
+
+        if secret:
+            key = secret
+        else:
+            key = self.secret
+
+        # If there's a raw packet, use that to calculate the expected
+        # Message-Authenticator. While the Packet class keeps multiple
+        # instances of an attribute grouped together in the attribute list,
+        # other applications may not. Using _PktEncodeAttributes to get
+        # the attributes could therefore end up changing the attribute order
+        # because of the grouping Packet does, which would cause
+        # Message-Authenticator verification to fail. Using the raw packet
+        # instead, if present, ensures the verification is done using the
+        # attributes exactly as sent.
+        if self.raw_packet:
+            attr = self.raw_packet[20:]
+            attr = attr.replace(prev_ma[0], 16 * six.b('\00'))
+        else:
+            self['Message-Authenticator'] = 16 * six.b('\00')
+            attr = self._PktEncodeAttributes()
+
+        header = struct.pack('!BBH', self.code, self.id,
+                             (20 + len(attr)))
+
+        hmac_constructor = hmac_new(key)
+        hmac_constructor.update(header)
+        if self.code in (AccountingRequest, DisconnectRequest,
+                         CoARequest, AccountingResponse):
+            if original_code is None or original_code != StatusServer:
+                # TODO: Handle Status-Server response correctly.
+                hmac_constructor.update(16 * six.b('\00'))
+        elif self.code in (AccessAccept, AccessChallenge,
+                           AccessReject):
+            if original_authenticator is None:
+                if self.authenticator:
+                    # NOTE: self.authenticator on reply packet is initialized
+                    #       with request authenticator by design.
+                    original_authenticator = self.authenticator
+                else:
+                    raise Exception('Missing original authenticator')
+
+            hmac_constructor.update(original_authenticator)
+        else:
+            # On Access-Request and Status-Server use dynamic authenticator
+            hmac_constructor.update(self.authenticator)
+
+        hmac_constructor.update(attr)
+        self['Message-Authenticator'] = prev_ma[0]
+        return prev_ma[0] == hmac_constructor.digest()
+
     def CreateReply(self, **attributes):
         """Create a new packet as a reply to this one. This method
         makes sure the authenticator and secret are copied over
@@ -132,14 +263,12 @@ class Packet(dict):
         if not isinstance(key, str):
             return (key, values)
 
-        key, _, tag = key.partition(":")
+        if not isinstance(values, (list, tuple)):
+            values = [values]
 
+        key, _, tag = key.partition(":")
         attr = self.dict.attributes[key]
-        if attr.vendor:
-            key = (self.dict.vendors.GetForward(attr.vendor), attr.code)
-        else:
-            key = attr.code
-
+        key = self._EncodeKey(key)
         if tag:
             tag = struct.pack('B', int(tag))
             if attr.type == "integer":
@@ -154,7 +283,7 @@ class Packet(dict):
             return key
 
         attr = self.dict.attributes[key]
-        if attr.vendor:
+        if attr.vendor and not attr.is_sub_attribute:  #sub attribute keys don't need vendor
             return (self.dict.vendors.GetForward(attr.vendor), attr.code)
         else:
             return attr.code
@@ -175,50 +304,69 @@ class Packet(dict):
         :param value: value
         :type value:  depends on type of attribute
         """
-        if isinstance(value, list):
-            (key, value) = self._EncodeKeyValues(key, value)
-            self.setdefault(key, []).extend(value)
+        attr = self.dict.attributes[key.partition(':')[0]]
+
+        (key, value) = self._EncodeKeyValues(key, value)
+
+        if attr.is_sub_attribute:
+            tlv = self.setdefault(self._EncodeKey(attr.parent.name), {})
+            encoded = tlv.setdefault(key, [])
         else:
-            (key, value) = self._EncodeKeyValues(key, [value])
-            value = value[0]
-            self.setdefault(key, []).append(value)
+            encoded = self.setdefault(key, [])
+
+        encoded.extend(value)
+
+    def get(self, key, failobj=None):
+        try:
+            res = self.__getitem__(key)
+        except KeyError:
+            res = failobj
+        return res
 
     def __getitem__(self, key):
         if not isinstance(key, six.string_types):
-            return dict.__getitem__(self, key)
+            return OrderedDict.__getitem__(self, key)
 
-        values = dict.__getitem__(self, self._EncodeKey(key))
+        values = OrderedDict.__getitem__(self, self._EncodeKey(key))
         attr = self.dict.attributes[key]
-        res = []
-        for v in values:
-            res.append(self._DecodeValue(attr, v))
-        return res
+        if attr.type == 'tlv':  # return map from sub attribute code to its values
+            res = {}
+            for (sub_attr_key, sub_attr_val) in values.items():
+                sub_attr_name = attr.sub_attributes[sub_attr_key]
+                sub_attr = self.dict.attributes[sub_attr_name]
+                for v in sub_attr_val:
+                    res.setdefault(sub_attr_name, []).append(self._DecodeValue(sub_attr, v))
+            return res
+        else:
+            res = []
+            for v in values:
+                res.append(self._DecodeValue(attr, v))
+            return res
 
     def __contains__(self, key):
         try:
-            return dict.__contains__(self, self._EncodeKey(key))
+            return OrderedDict.__contains__(self, self._EncodeKey(key))
         except KeyError:
             return False
 
     has_key = __contains__
 
     def __delitem__(self, key):
-        dict.__delitem__(self, self._EncodeKey(key))
+        OrderedDict.__delitem__(self, self._EncodeKey(key))
 
     def __setitem__(self, key, item):
         if isinstance(key, six.string_types):
-            (key, item) = self._EncodeKeyValues(key, [item])
-            dict.__setitem__(self, key, item)
+            (key, item) = self._EncodeKeyValues(key, item)
+            OrderedDict.__setitem__(self, key, item)
         else:
-            assert isinstance(item, list)
-            dict.__setitem__(self, key, item)
+            OrderedDict.__setitem__(self, key, item)
 
     def keys(self):
-        return [self._DecodeKey(key) for key in dict.keys(self)]
+        return [self._DecodeKey(key) for key in OrderedDict.keys(self)]
 
     @staticmethod
     def CreateAuthenticator():
-        """Create a packet autenticator. All RADIUS packets contain a sixteen
+        """Create a packet authenticator. All RADIUS packets contain a sixteen
         byte authenticator which is used to authenticate replies from the
         RADIUS server and in the password hiding algorithm. This function
         returns a suitable random string that can be used as an authenticator.
@@ -228,7 +376,7 @@ class Packet(dict):
         """
 
         data = []
-        for i in range(16):
+        for _ in range(16):
             data.append(random_generator.randrange(0, 256))
         if six.PY3:
             return bytes(data)
@@ -258,11 +406,15 @@ class Packet(dict):
         assert(self.authenticator)
         assert(self.secret is not None)
 
+        if self.message_authenticator:
+            self._refresh_message_authenticator()
+
         attr = self._PktEncodeAttributes()
         header = struct.pack('!BBH', self.code, self.id, (20 + len(attr)))
 
         authenticator = md5_constructor(header[0:4] + self.authenticator
-                              + attr + self.secret).digest()
+                                        + attr + self.secret).digest()
+
         return header + authenticator + attr
 
     def VerifyReply(self, reply, rawreply=None):
@@ -272,8 +424,17 @@ class Packet(dict):
         if rawreply is None:
             rawreply = reply.ReplyPacket()
 
+        attr = reply._PktEncodeAttributes()
+        # The Authenticator field in an Accounting-Response packet is called
+        # the Response Authenticator, and contains a one-way MD5 hash
+        # calculated over a stream of octets consisting of the Accounting
+        # Response Code, Identifier, Length, the Request Authenticator field
+        # from the Accounting-Request packet being replied to, and the
+        # response attributes if any, followed by the shared secret.  The
+        # resulting 16 octet MD5 hash value is stored in the Authenticator
+        # field of the Accounting-Response packet.
         hash = md5_constructor(rawreply[0:4] + self.authenticator +
-                     rawreply[20:] + self.secret).digest()
+                               rawreply[20:] + self.secret).digest()
 
         if hash != rawreply[4:20]:
             return False
@@ -287,12 +448,47 @@ class Packet(dict):
 
         return struct.pack('!BB', key, (len(value) + 2)) + value
 
+    def _PktEncodeTlv(self, tlv_key, tlv_value):
+        tlv_attr = self.dict.attributes[self._DecodeKey(tlv_key)]
+        curr_avp = six.b('')
+        avps = []
+        max_sub_attribute_len = max(map(lambda item: len(item[1]), tlv_value.items()))
+        for i in range(max_sub_attribute_len):
+            sub_attr_encoding = six.b('')
+            for (code, datalst) in tlv_value.items():
+                if i < len(datalst):
+                    sub_attr_encoding += self._PktEncodeAttribute(code, datalst[i])
+            # split above 255. assuming len of one instance of all sub tlvs is lower than 255
+            if (len(sub_attr_encoding) + len(curr_avp)) < 245:
+                curr_avp += sub_attr_encoding
+            else:
+                avps.append(curr_avp)
+                curr_avp = sub_attr_encoding
+        avps.append(curr_avp)
+        tlv_avps = []
+        for avp in avps:
+            value = struct.pack('!BB', tlv_attr.code, (len(avp) + 2)) + avp
+            tlv_avps.append(value)
+        if tlv_attr.vendor:
+            vendor_avps = six.b('')
+            for avp in tlv_avps:
+                vendor_avps += struct.pack(
+                    '!BBL', 26, (len(avp) + 6),
+                    self.dict.vendors.GetForward(tlv_attr.vendor)
+                ) + avp
+            return vendor_avps
+        else:
+            return b''.join(tlv_avps)
+
     def _PktEncodeAttributes(self):
         result = six.b('')
         for (code, datalst) in self.items():
-            for data in datalst:
-                result += self._PktEncodeAttribute(code, data)
-
+            attribute = self.dict.attributes.get(self._DecodeKey(code))
+            if attribute and attribute.type == 'tlv':
+                result += self._PktEncodeTlv(code, datalst)
+            else:
+                for data in datalst:
+                    result += self._PktEncodeAttribute(code, data)
         return result
 
     def _PktDecodeVendorAttribute(self, data):
@@ -301,20 +497,36 @@ class Packet(dict):
         if len(data) < 6:
             return [(26, data)]
 
-        (vendor, type, length) = struct.unpack('!LBB', data[:6])[0:3]
-
-        tlvs = [((vendor, type), data[6:length+4])]
+        (vendor, atype, length) = struct.unpack('!LBB', data[:6])[0:3]
+        attribute = self.dict.attributes.get(self._DecodeKey((vendor, atype)))
+        try:
+            if attribute and attribute.type == 'tlv':
+                self._PktDecodeTlvAttribute((vendor, atype), data[6:length + 4])
+                tlvs = []  # tlv is added to the packet inside _PktDecodeTlvAttribute
+            else:
+                tlvs = [((vendor, atype), data[6:length + 4])]
+        except:
+            return [(26, data)]
 
         sumlength = 4 + length
         while len(data) > sumlength:
             try:
-                type, length = struct.unpack('!BB', data[sumlength:sumlength+2])[0:2]
+                atype, length = struct.unpack('!BB', data[sumlength:sumlength+2])[0:2]
             except:
                 return [(26, data)]
-            tlvs.append(((vendor, type), data[sumlength+2:sumlength+length]))
+            tlvs.append(((vendor, atype), data[sumlength+2:sumlength+length]))
             sumlength += length
         return tlvs
 
+    def _PktDecodeTlvAttribute(self, code, data):
+        sub_attributes = self.setdefault(code, {})
+        loc = 0
+
+        while loc < len(data):
+            atype, length = struct.unpack('!BB', data[loc:loc+2])[0:2]
+            sub_attributes.setdefault(atype, []).append(data[loc+2:loc+length])
+            loc += length
+
     def DecodePacket(self, packet):
         """Initialize the object from raw packet data.  Decode a packet as
         received from the network and decode it.
@@ -325,6 +537,7 @@ class Packet(dict):
         try:
             (self.code, self.id, length, self.authenticator) = \
                     struct.unpack('!BBH16s', packet[0:20])
+
         except struct.error:
             raise PacketError('Packet header is corrupt')
         if len(packet) != length:
@@ -346,9 +559,16 @@ class Packet(dict):
                         'Attribute length is too small (%d)' % attrlen)
 
             value = packet[2:attrlen]
+            attribute = self.dict.attributes.get(self._DecodeKey(key))
             if key == 26:
                 for (key, value) in self._PktDecodeVendorAttribute(value):
                     self.setdefault(key, []).append(value)
+            elif key == 80:
+                # POST: Message Authenticator AVP is present.
+                self.message_authenticator = True
+                self.setdefault(key, []).append(value)
+            elif attribute and attribute.type == 'tlv':
+                self._PktDecodeTlvAttribute(key,value)
             else:
                 self.setdefault(key, []).append(value)
 
@@ -370,17 +590,22 @@ class Packet(dict):
             # self.authenticator = self.CreateAuthenticator()
             self.authenticator = 16 * six.b('\x00')
 
-        salt = struct.pack('!H', random_generator.randrange(0, 65535))
-        salt = chr(ord(salt[0]) | 1 << 7)+salt[1]
+        random_value = 32768 + random_generator.randrange(0, 32767)
+        if six.PY3:
+            salt_raw = struct.pack('!H', random_value )
+            salt = chr(salt_raw[0]) + chr(salt_raw[1])
+        else:
+            salt = struct.pack('!H', random_value )
+            salt = chr(ord(salt[0]) | 1 << 7)+salt[1]
+
+        result = six.b(salt)
 
         length = struct.pack("B", len(value))
         buf = length + value
         if len(buf) % 16 != 0:
             buf += six.b('\x00') * (16 - (len(buf) % 16))
 
-        result = six.b(salt)
-
-        last = self.authenticator + salt
+        last = self.authenticator + six.b(salt)
         while buf:
             hash = md5_constructor(self.secret + last).digest()
             if six.PY3:
@@ -398,12 +623,12 @@ class Packet(dict):
 
 class AuthPacket(Packet):
     def __init__(self, code=AccessRequest, id=None, secret=six.b(''),
-            authenticator=None, **attributes):
+            authenticator=None, auth_type='pap', **attributes):
         """Constructor
 
         :param code:   packet type code
         :type code:    integer (8bits)
-        :param id:     packet identifaction number
+        :param id:     packet identification number
         :type id:      integer (8 bits)
         :param secret: secret needed to communicate with a RADIUS server
         :type secret:  string
@@ -414,7 +639,9 @@ class AuthPacket(Packet):
         :param packet: raw packet to decode
         :type packet:  string
         """
+
         Packet.__init__(self, code, id, secret, authenticator, **attributes)
+        self.auth_type = auth_type
 
     def CreateReply(self, **attributes):
         """Create a new packet as a reply to this one. This method
@@ -422,8 +649,8 @@ class AuthPacket(Packet):
         to the new instance.
         """
         return AuthPacket(AccessAccept, self.id,
-            self.secret, self.authenticator, dict=self.dict,
-            **attributes)
+                          self.secret, self.authenticator, dict=self.dict,
+                          auth_type=self.auth_type, **attributes)
 
     def RequestPacket(self):
         """Create a ready-to-transmit authentication request packet.
@@ -433,21 +660,39 @@ class AuthPacket(Packet):
         :return: raw packet
         :rtype:  string
         """
-        attr = self._PktEncodeAttributes()
-
         if self.authenticator is None:
             self.authenticator = self.CreateAuthenticator()
 
         if self.id is None:
             self.id = self.CreateID()
 
+        if self.message_authenticator:
+            self._refresh_message_authenticator()
+
+        attr = self._PktEncodeAttributes()
+        if self.auth_type == 'eap-md5':
+            header = struct.pack(
+                '!BBH16s', self.code, self.id, (20 + 18 + len(attr)), self.authenticator
+            )
+            digest = hmac_new(
+                self.secret,
+                header
+                + attr
+                + struct.pack('!BB16s', 80, struct.calcsize('!BB16s'), b''),
+            ).digest()
+            return (
+                header
+                + attr
+                + struct.pack('!BB16s', 80, struct.calcsize('!BB16s'), digest)
+            )
+
         header = struct.pack('!BBH16s', self.code, self.id,
-            (20 + len(attr)), self.authenticator)
+                             (20 + len(attr)), self.authenticator)
 
         return header + attr
 
     def PwDecrypt(self, password):
-        """Unobfuscate a RADIUS password. RADIUS hides passwords in packets by
+        """Obfuscate a RADIUS password. RADIUS hides passwords in packets by
         using an algorithm based on the MD5 hash of the packet authenticator
         and RADIUS secret. This function reverses the obfuscation process.
 
@@ -486,7 +731,7 @@ class AuthPacket(Packet):
         will not work.
 
         :param password: plaintext password
-        :type password:  unicode stringn
+        :type password:  unicode string
         :return:         obfuscated version of the password
         :rtype:          binary string
         """
@@ -500,7 +745,6 @@ class AuthPacket(Packet):
         if len(password) % 16 != 0:
             buf += six.b('\x00') * (16 - (len(password) % 16))
 
-        hash = md5_constructor(self.secret + self.authenticator).digest()
         result = six.b('')
 
         last = self.authenticator
@@ -538,13 +782,25 @@ class AuthPacket(Packet):
             return False
 
         chapid = chap_password[0]
+        if six.PY3:
+            chapid = chr(chapid).encode('utf-8')
         password = chap_password[1:]
 
         challenge = self.authenticator
         if 'CHAP-Challenge' in self:
             challenge = self['CHAP-Challenge'][0]
+        return password == md5_constructor(chapid + userpwd + challenge).digest()
 
-        return password == md5_constructor("%s%s%s" % (chapid, userpwd, challenge)).digest()
+    def VerifyAuthRequest(self):
+        """Verify request authenticator.
+
+        :return: True if verification failed else False
+        :rtype: boolean
+        """
+        assert(self.raw_packet)
+        hash = md5_constructor(self.raw_packet[0:4] + 16 * six.b('\x00') +
+                               self.raw_packet[20:] + self.secret).digest()
+        return hash == self.authenticator
 
 
 class AcctPacket(Packet):
@@ -553,14 +809,14 @@ class AcctPacket(Packet):
     """
 
     def __init__(self, code=AccountingRequest, id=None, secret=six.b(''),
-            authenticator=None, **attributes):
+                 authenticator=None, **attributes):
         """Constructor
 
         :param dict:   RADIUS dictionary
         :type dict:    pyrad.dictionary.Dictionary class
         :param secret: secret needed to communicate with a RADIUS server
         :type secret:  string
-        :param id:     packet identifaction number
+        :param id:     packet identification number
         :type id:      integer (8 bits)
         :param code:   packet type code
         :type code:    integer (8bits)
@@ -568,8 +824,6 @@ class AcctPacket(Packet):
         :type packet:  string
         """
         Packet.__init__(self, code, id, secret, authenticator, **attributes)
-        if 'packet' in attributes:
-            self.raw_packet = attributes['packet']
 
     def CreateReply(self, **attributes):
         """Create a new packet as a reply to this one. This method
@@ -577,18 +831,20 @@ class AcctPacket(Packet):
         to the new instance.
         """
         return AcctPacket(AccountingResponse, self.id,
-            self.secret, self.authenticator, dict=self.dict,
-            **attributes)
+                          self.secret, self.authenticator, dict=self.dict,
+                          **attributes)
 
     def VerifyAcctRequest(self):
         """Verify request authenticator.
 
-        :return: True if verification failed else False
+        :return: False if verification failed else True
         :rtype: boolean
         """
         assert(self.raw_packet)
+
         hash = md5_constructor(self.raw_packet[0:4] + 16 * six.b('\x00') +
-                self.raw_packet[20:] + self.secret).digest()
+                               self.raw_packet[20:] + self.secret).digest()
+
         return hash == self.authenticator
 
     def RequestPacket(self):
@@ -600,15 +856,21 @@ class AcctPacket(Packet):
         :rtype:  string
         """
 
-        attr = self._PktEncodeAttributes()
-
         if self.id is None:
             self.id = self.CreateID()
 
+        if self.message_authenticator:
+            self._refresh_message_authenticator()
+
+        attr = self._PktEncodeAttributes()
         header = struct.pack('!BBH', self.code, self.id, (20 + len(attr)))
-        self.authenticator = md5_constructor(header[0:4] + 16 * six.b('\x00') + attr
-            + self.secret).digest()
-        return header + self.authenticator + attr
+        self.authenticator = md5_constructor(header[0:4] + 16 * six.b('\x00') +
+                                             attr + self.secret).digest()
+
+        ans = header + self.authenticator + attr
+
+        return ans
+
 
 class CoAPacket(Packet):
     """RADIUS CoA packets. This class is a specialization
@@ -623,7 +885,7 @@ class CoAPacket(Packet):
         :type dict:    pyrad.dictionary.Dictionary class
         :param secret: secret needed to communicate with a RADIUS server
         :type secret:  string
-        :param id:     packet identifaction number
+        :param id:     packet identification number
         :type id:      integer (8 bits)
         :param code:   packet type code
         :type code:    integer (8bits)
@@ -631,8 +893,6 @@ class CoAPacket(Packet):
         :type packet:  string
         """
         Packet.__init__(self, code, id, secret, authenticator, **attributes)
-        if 'packet' in attributes:
-            self.raw_packet = attributes['packet']
 
     def CreateReply(self, **attributes):
         """Create a new packet as a reply to this one. This method
@@ -640,18 +900,18 @@ class CoAPacket(Packet):
         to the new instance.
         """
         return CoAPacket(CoAACK, self.id,
-            self.secret, self.authenticator, dict=self.dict,
-            **attributes)
+                         self.secret, self.authenticator, dict=self.dict,
+                         **attributes)
 
     def VerifyCoARequest(self):
         """Verify request authenticator.
 
-        :return: True if verification failed else False
+        :return: False if verification failed else True
         :rtype: boolean
         """
         assert(self.raw_packet)
         hash = md5_constructor(self.raw_packet[0:4] + 16 * six.b('\x00') +
-                self.raw_packet[20:] + self.secret).digest()
+                               self.raw_packet[20:] + self.secret).digest()
         return hash == self.authenticator
 
     def RequestPacket(self):
@@ -669,10 +929,18 @@ class CoAPacket(Packet):
             self.id = self.CreateID()
 
         header = struct.pack('!BBH', self.code, self.id, (20 + len(attr)))
-        self.authenticator = md5_constructor(header[0:4] + 16 * six.b('\x00') + attr
-            + self.secret).digest()
+        self.authenticator = md5_constructor(header[0:4] + 16 * six.b('\x00') +
+                                             attr + self.secret).digest()
+
+        if self.message_authenticator:
+            self._refresh_message_authenticator()
+            attr = self._PktEncodeAttributes()
+            self.authenticator = md5_constructor(header[0:4] + 16 * six.b('\x00') +
+                                                 attr + self.secret).digest()
+
         return header + self.authenticator + attr
 
+
 def CreateID():
     """Generate a packet ID.
 
diff --git a/pyrad/server.py b/pyrad/server.py
index 92434a5..d732798 100644
--- a/pyrad/server.py
+++ b/pyrad/server.py
@@ -103,6 +103,27 @@ class Server(host.Host):
         for addr in addresses:
             self.BindToAddress(addr)
 
+    def _GetAddrInfo(self, addr):
+        """Use getaddrinfo to lookup all addresses for each address.
+
+        Returns a list of tuples or an empty list:
+          [(family, address)]
+
+        :param addr: IP address to lookup
+        :type  addr: string
+        """
+        results = set()
+        try:
+            tmp = socket.getaddrinfo(addr, 'www')
+        except socket.gaierror:
+            return []
+
+        for el in tmp:
+            results.add((el[0], el[4][0]))
+
+        return results
+
+
     def BindToAddress(self, addr):
         """Add an address to listen to.
         An empty string indicated you want to listen on all addresses.
@@ -110,23 +131,25 @@ class Server(host.Host):
         :param addr: IP address to listen on
         :type  addr: string
         """
-        if self.auth_enabled:
-            authfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-            authfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-            authfd.bind((addr, self.authport))
-            self.authfds.append(authfd)
-
-        if self.acct_enabled:
-            acctfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-            acctfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-            acctfd.bind((addr, self.acctport))
-            self.acctfds.append(acctfd)
-
-        if self.coa_enabled:
-            coafd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-            coafd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-            coafd.bind((addr, self.coaport))
-            self.coafds.append(coafd)
+        addrFamily = self._GetAddrInfo(addr)
+        for (family, address) in addrFamily:
+            if self.auth_enabled:
+                authfd = socket.socket(family, socket.SOCK_DGRAM)
+                authfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+                authfd.bind((address, self.authport))
+                self.authfds.append(authfd)
+
+            if self.acct_enabled:
+                acctfd = socket.socket(family, socket.SOCK_DGRAM)
+                acctfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+                acctfd.bind((address, self.acctport))
+                self.acctfds.append(acctfd)
+
+            if self.coa_enabled:
+                coafd = socket.socket(family, socket.SOCK_DGRAM)
+                coafd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+                coafd.bind((address, self.coaport))
+                self.coafds.append(coafd)
 
 
     def HandleAuthPacket(self, pkt):
@@ -169,6 +192,19 @@ class Server(host.Host):
         :type  pkt: Packet class instance
         """
 
+    def _AddSecret(self, pkt):
+        """Add secret to packets received and raise ServerPacketError
+        for unknown hosts.
+
+        :param pkt: packet to process
+        :type  pkt: Packet class instance
+        """
+        if pkt.source[0] in self.hosts:
+            pkt.secret = self.hosts[pkt.source[0]].secret
+        elif '0.0.0.0' in self.hosts:
+            pkt.secret = self.hosts['0.0.0.0'].secret
+        else:
+            raise ServerPacketError('Received packet from unknown host')
 
     def _HandleAuthPacket(self, pkt):
         """Process a packet received on the authentication port.
@@ -179,10 +215,7 @@ class Server(host.Host):
         :param pkt: packet to process
         :type  pkt: Packet class instance
         """
-        if pkt.source[0] not in self.hosts:
-            raise ServerPacketError('Received packet from unknown host')
-
-        pkt.secret = self.hosts[pkt.source[0]].secret
+        self._AddSecret(pkt)
         if pkt.code != packet.AccessRequest:
             raise ServerPacketError(
                 'Received non-authentication packet on authentication port')
@@ -197,10 +230,7 @@ class Server(host.Host):
         :param pkt: packet to process
         :type  pkt: Packet class instance
         """
-        if pkt.source[0] not in self.hosts:
-            raise ServerPacketError('Received packet from unknown host')
-
-        pkt.secret = self.hosts[pkt.source[0]].secret
+        self._AddSecret(pkt)
         if pkt.code not in [packet.AccountingRequest,
                             packet.AccountingResponse]:
             raise ServerPacketError(
@@ -216,9 +246,7 @@ class Server(host.Host):
         :param pkt: packet to process
         :type  pkt: Packet class instance
         """
-        if pkt.source[0] not in self.hosts:
-            raise ServerPacketError('Received packet from unknown host')
-
+        self._AddSecret(pkt)
         pkt.secret = self.hosts[pkt.source[0]].secret
         if pkt.code == packet.CoARequest:
             self.HandleCoaPacket(pkt)
@@ -281,15 +309,17 @@ class Server(host.Host):
         :param  fd: socket to read packet from
         :type   fd: socket class instance
         """
-        if fd.fileno() in self._realauthfds:
+        if self.auth_enabled and fd.fileno() in self._realauthfds:
             pkt = self._GrabPacket(lambda data, s=self: s.CreateAuthPacket(packet=data), fd)
             self._HandleAuthPacket(pkt)
-        elif fd.fileno() in self._realacctfds:
+        elif self.acct_enabled and fd.fileno() in self._realacctfds:
             pkt = self._GrabPacket(lambda data, s=self: s.CreateAcctPacket(packet=data), fd)
             self._HandleAcctPacket(pkt)
-        else:
+        elif self.coa_enabled:
             pkt = self._GrabPacket(lambda data, s=self: s.CreateCoAPacket(packet=data), fd)
             self._HandleCoaPacket(pkt)
+        else:
+            raise ServerPacketError('Received packet for unknown handler')
 
     def Run(self):
         """Main loop.
diff --git a/pyrad/server_async.py b/pyrad/server_async.py
new file mode 100644
index 0000000..8e36e2c
--- /dev/null
+++ b/pyrad/server_async.py
@@ -0,0 +1,336 @@
+# server_async.py
+#
+# Copyright 2018-2019 Geaaru <geaaru@gmail.com>
+
+import asyncio
+import logging
+import traceback
+
+from abc import abstractmethod, ABCMeta
+from enum import Enum
+from datetime import datetime
+from pyrad.packet import Packet, AccessAccept, AccessReject, \
+    AccountingRequest, AccountingResponse, \
+    DisconnectACK, DisconnectNAK, DisconnectRequest, CoARequest, \
+    CoAACK, CoANAK, AccessRequest, AuthPacket, AcctPacket, CoAPacket, \
+    PacketError
+
+from pyrad.server import ServerPacketError
+
+
+class ServerType(Enum):
+    Auth = 'Authentication'
+    Acct = 'Accounting'
+    Coa = 'Coa'
+
+
+class DatagramProtocolServer(asyncio.Protocol):
+
+    def __init__(self, ip, port, logger, server, server_type, hosts,
+                 request_callback):
+        self.transport = None
+        self.ip = ip
+        self.port = port
+        self.logger = logger
+        self.server = server
+        self.hosts = hosts
+        self.server_type = server_type
+        self.request_callback = request_callback
+
+    def connection_made(self, transport):
+        self.transport = transport
+        self.logger.info('[%s:%d] Transport created', self.ip, self.port)
+
+    def connection_lost(self, exc):
+        if exc:
+            self.logger.warn('[%s:%d] Connection lost: %s', self.ip, self.port, str(exc))
+        else:
+            self.logger.info('[%s:%d] Transport closed', self.ip, self.port)
+
+    def send_response(self, reply, addr):
+        self.transport.sendto(reply.ReplyPacket(), addr)
+
+    def datagram_received(self, data, addr):
+        self.logger.debug('[%s:%d] Received %d bytes from %s', self.ip, self.port, len(data), addr)
+
+        receive_date = datetime.utcnow()
+
+        if addr[0] in self.hosts:
+            remote_host = self.hosts[addr[0]]
+        elif '0.0.0.0' in self.hosts:
+            remote_host = self.hosts['0.0.0.0']
+        else:
+            self.logger.warn('[%s:%d] Drop package from unknown source %s', self.ip, self.port, addr)
+            return
+
+        try:
+            self.logger.debug('[%s:%d] Received from %s packet: %s', self.ip, self.port, addr, data.hex())
+            req = Packet(packet=data, dict=self.server.dict)
+        except Exception as exc:
+            self.logger.error('[%s:%d] Error on decode packet: %s', self.ip, self.port, exc)
+            return
+
+        try:
+            if req.code in (AccountingResponse, AccessAccept, AccessReject, CoANAK, CoAACK, DisconnectNAK, DisconnectACK):
+                raise ServerPacketError('Invalid response packet %d' % req.code)
+
+            elif self.server_type == ServerType.Auth:
+                if req.code != AccessRequest:
+                    raise ServerPacketError('Received non-auth packet on auth port')
+                req = AuthPacket(secret=remote_host.secret,
+                                 dict=self.server.dict,
+                                 packet=data)
+                if self.server.enable_pkt_verify:
+                    if req.VerifyAuthRequest():
+                        raise PacketError('Packet verification failed')
+
+            elif self.server_type == ServerType.Coa:
+                if req.code != DisconnectRequest and req.code != CoARequest:
+                    raise ServerPacketError('Received non-coa packet on coa port')
+                req = CoAPacket(secret=remote_host.secret,
+                                dict=self.server.dict,
+                                packet=data)
+                if self.server.enable_pkt_verify:
+                    if req.VerifyCoARequest():
+                        raise PacketError('Packet verification failed')
+
+            elif self.server_type == ServerType.Acct:
+
+                if req.code != AccountingRequest:
+                    raise ServerPacketError('Received non-acct packet on acct port')
+                req = AcctPacket(secret=remote_host.secret,
+                                 dict=self.server.dict,
+                                 packet=data)
+                if self.server.enable_pkt_verify:
+                    if req.VerifyAcctRequest():
+                        raise PacketError('Packet verification failed')
+
+            # Call request callback
+            self.request_callback(self, req, addr)
+        except Exception as exc:
+            if self.server.debug:
+                self.logger.exception('[%s:%d] Error for packet from %s', self.ip, self.port, addr)
+            else:
+                self.logger.error('[%s:%d] Error for packet from %s: %s', self.ip, self.port, addr, exc)
+
+        process_date = datetime.utcnow()
+        self.logger.debug('[%s:%d] Request from %s processed in %d ms', self.ip, self.port, addr, (process_date-receive_date).microseconds/1000)
+
+    def error_received(self, exc):
+        self.logger.error('[%s:%d] Error received: %s', self.ip, self.port, exc)
+
+    async def close_transport(self):
+        if self.transport:
+            self.logger.debug('[%s:%d] Close transport...', self.ip, self.port)
+            self.transport.close()
+            self.transport = None
+
+    def __str__(self):
+        return 'DatagramProtocolServer(ip=%s, port=%d)' % (self.ip, self.port)
+
+    # Used as protocol_factory
+    def __call__(self):
+        return self
+
+
+class ServerAsync(metaclass=ABCMeta):
+
+    def __init__(self, auth_port=1812, acct_port=1813,
+                 coa_port=3799, hosts=None, dictionary=None,
+                 loop=None, logger_name='pyrad',
+                 enable_pkt_verify=False,
+                 debug=False):
+
+        if not loop:
+            self.loop = asyncio.get_event_loop()
+        else:
+            self.loop = loop
+        self.logger = logging.getLogger(logger_name)
+
+        if hosts is None:
+            self.hosts = {}
+        else:
+            self.hosts = hosts
+
+        self.auth_port = auth_port
+        self.auth_protocols = []
+
+        self.acct_port = acct_port
+        self.acct_protocols = []
+
+        self.coa_port = coa_port
+        self.coa_protocols = []
+
+        self.dict = dictionary
+        self.enable_pkt_verify = enable_pkt_verify
+
+        self.debug = debug
+
+    def __request_handler__(self, protocol, req, addr):
+
+        try:
+            if protocol.server_type == ServerType.Acct:
+                self.handle_acct_packet(protocol, req, addr)
+            elif protocol.server_type == ServerType.Auth:
+                self.handle_auth_packet(protocol, req, addr)
+            elif protocol.server_type == ServerType.Coa and \
+                    req.code == CoARequest:
+                self.handle_coa_packet(protocol, req, addr)
+            elif protocol.server_type == ServerType.Coa and \
+                    req.code == DisconnectRequest:
+                self.handle_disconnect_packet(protocol, req, addr)
+            else:
+                self.logger.error('[%s:%s] Unexpected request found', protocol.ip, protocol.port)
+        except Exception as exc:
+            if self.debug:
+                self.logger.exception('[%s:%s] Unexpected error', protocol.ip, protocol.port)
+
+            else:
+                self.logger.error('[%s:%s] Unexpected error: %s', protocol.ip, protocol.port, exc)
+
+    def __is_present_proto__(self, ip, port):
+        if port == self.auth_port:
+            for proto in self.auth_protocols:
+                if proto.ip == ip:
+                    return True
+        elif port == self.acct_port:
+            for proto in self.acct_protocols:
+                if proto.ip == ip:
+                    return True
+        elif port == self.coa_port:
+            for proto in self.coa_protocols:
+                if proto.ip == ip:
+                    return True
+        return False
+
+    # noinspection PyPep8Naming
+    @staticmethod
+    def CreateReplyPacket(pkt, **attributes):
+        """Create a reply packet.
+        Create a new packet which can be returned as a reply to a received
+        packet.
+
+        :param pkt:   original packet
+        :type pkt:    Packet instance
+        """
+        reply = pkt.CreateReply(**attributes)
+        return reply
+
+    async def initialize_transports(self, enable_acct=False,
+                                    enable_auth=False, enable_coa=False,
+                                    addresses=None):
+
+        task_list = []
+
+        if not enable_acct and not enable_auth and not enable_coa:
+            raise Exception('No transports selected')
+        if not addresses or len(addresses) == 0:
+            addresses = ['127.0.0.1']
+
+        # noinspection SpellCheckingInspection
+        for addr in addresses:
+
+            if enable_acct and not self.__is_present_proto__(addr, self.acct_port):
+                protocol_acct = DatagramProtocolServer(
+                    addr,
+                    self.acct_port,
+                    self.logger, self,
+                    ServerType.Acct,
+                    self.hosts,
+                    self.__request_handler__
+                )
+
+                bind_addr = (addr, self.acct_port)
+                acct_connect = self.loop.create_datagram_endpoint(
+                    protocol_acct,
+                    reuse_port=True,
+                    local_addr=bind_addr
+                )
+                self.acct_protocols.append(protocol_acct)
+                task_list.append(acct_connect)
+
+            if enable_auth and not self.__is_present_proto__(addr, self.auth_port):
+                protocol_auth = DatagramProtocolServer(
+                    addr,
+                    self.auth_port,
+                    self.logger, self,
+                    ServerType.Auth,
+                    self.hosts,
+                    self.__request_handler__
+                )
+                bind_addr = (addr, self.auth_port)
+
+                auth_connect = self.loop.create_datagram_endpoint(
+                    protocol_auth,
+                    reuse_port=True,
+                    local_addr=bind_addr
+                )
+                self.auth_protocols.append(protocol_auth)
+                task_list.append(auth_connect)
+
+            if enable_coa and not self.__is_present_proto__(addr, self.coa_port):
+                protocol_coa = DatagramProtocolServer(
+                    addr,
+                    self.coa_port,
+                    self.logger, self,
+                    ServerType.Coa,
+                    self.hosts,
+                    self.__request_handler__
+                )
+                bind_addr = (addr, self.coa_port)
+
+                coa_connect = self.loop.create_datagram_endpoint(
+                    protocol_coa,
+                    reuse_port=True,
+                    local_addr=bind_addr
+                )
+                self.coa_protocols.append(protocol_coa)
+                task_list.append(coa_connect)
+
+        await asyncio.ensure_future(
+            asyncio.gather(
+                *task_list,
+                return_exceptions=False,
+            ),
+            loop=self.loop
+        )
+
+    # noinspection SpellCheckingInspection
+    async def deinitialize_transports(self, deinit_coa=True, deinit_auth=True, deinit_acct=True):
+
+        if deinit_coa:
+            for proto in self.coa_protocols:
+                await proto.close_transport()
+                del proto
+
+            self.coa_protocols = []
+
+        if deinit_auth:
+            for proto in self.auth_protocols:
+                await proto.close_transport()
+                del proto
+
+            self.auth_protocols = []
+
+        if deinit_acct:
+            for proto in self.acct_protocols:
+                await proto.close_transport()
+                del proto
+
+            self.acct_protocols = []
+
+    @abstractmethod
+    def handle_auth_packet(self, protocol, pkt, addr):
+        pass
+
+    @abstractmethod
+    def handle_acct_packet(self, protocol, pkt, addr):
+        pass
+
+    @abstractmethod
+    def handle_coa_packet(self, protocol, pkt, addr):
+        pass
+
+    @abstractmethod
+    def handle_disconnect_packet(self, protocol, pkt, addr):
+        pass
diff --git a/pyrad/tests/__init__.py b/pyrad/tests/__init__.py
deleted file mode 100644
index 0a99242..0000000
--- a/pyrad/tests/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-import pyrad
-import sys
-
-pyrad  # keep pyflakes happy
-home = sys.modules["pyrad"].__path__[0]
diff --git a/pyrad/tests/data/dictfiletest b/pyrad/tests/data/dictfiletest
deleted file mode 100644
index 6d908e9..0000000
--- a/pyrad/tests/data/dictfiletest
+++ /dev/null
@@ -1,5 +0,0 @@
-# A failing dictionary
-
-VALUE Not-Defined Undefined-Value 1
-
-
diff --git a/pyrad/tests/data/full b/pyrad/tests/data/full
deleted file mode 100644
index 1bdc870..0000000
--- a/pyrad/tests/data/full
+++ /dev/null
@@ -1,24 +0,0 @@
-# A simple dictionary
-
-ATTRIBUTE  Test-String       1       string
-ATTRIBUTE  Test-Octets       2       octets
-ATTRIBUTE  Test-Integer      3       integer
-
-VALUE      Test-Integer      Zero    0
-VALUE      Test-Integer      One     1
-VALUE      Test-Integer      Two     2
-VALUE      Test-Integer      Three   3
-VALUE      Test-Integer      Four    4
-
-VENDOR Simplon 16
-
-
-BEGIN-VENDOR Simplon
-ATTRIBUTE  Simplon-Number    1     integer
-
-VALUE      Simplon-Number     Zero    0
-VALUE      Simplon-Number     One     1
-VALUE      Simplon-Number     Two     2
-VALUE      Simplon-Number     Three   3
-VALUE      Simplon-Number     Four    4
-END-VENDOR Simplon
diff --git a/pyrad/tests/data/simple b/pyrad/tests/data/simple
deleted file mode 100644
index f9694c6..0000000
--- a/pyrad/tests/data/simple
+++ /dev/null
@@ -1,11 +0,0 @@
-# A simple dictionary
-
-ATTRIBUTE  Test-String       1       string
-ATTRIBUTE  Test-Octets       2       octets
-ATTRIBUTE  Test-Integer      3       integer
-ATTRIBUTE  Test-Ip-Address   4       ipaddr
-ATTRIBUTE  Test-Ipv6-Address 5       ipv6addr
-ATTRIBUTE  Test-If-Id        6       ifid
-ATTRIBUTE  Test-Date         7       date
-ATTRIBUTE  Test-Abinary      8       abinary
-
diff --git a/pyrad/tests/mock.py b/pyrad/tests/mock.py
deleted file mode 100644
index 47c6746..0000000
--- a/pyrad/tests/mock.py
+++ /dev/null
@@ -1,135 +0,0 @@
-import fcntl
-import os
-from pyrad.packet import PacketError
-
-
-class MockPacket:
-    reply = object()
-
-    def __init__(self, code, verify=False, error=False):
-        self.code = code
-        self.data = {}
-        self.verify = verify
-        self.error = error
-
-    def CreateReply(self, packet=None):
-        if self.error:
-            raise PacketError
-        return self.reply
-
-    def VerifyReply(self, reply, rawreply):
-        return self.verify
-
-    def RequestPacket(self):
-        return "request packet"
-
-    def __contains__(self, key):
-        return key in self.data
-    has_key = __contains__
-
-    def __setitem__(self, key, value):
-        self.data[key] = [value]
-
-    def __getitem__(self, key):
-        return self.data[key]
-
-
-class MockSocket:
-    def __init__(self, domain, type, data=None):
-        self.domain = domain
-        self.type = type
-        self.closed = False
-        self.options = []
-        self.address = None
-        self.output = []
-
-        if data is not None:
-            (self.read_end, self.write_end) = os.pipe()
-            fcntl.fcntl(self.write_end, fcntl.F_SETFL, os.O_NONBLOCK)
-            os.write(self.write_end, data)
-            self.data = data
-        else:
-            self.read_end = 1
-            self.write_end = None
-
-    def fileno(self):
-        return self.read_end
-
-    def bind(self, address):
-        self.address = address
-
-    def recv(self, buffer):
-        return self.data[:buffer]
-
-    def sendto(self, data, target):
-        self.output.append((data, target))
-
-    def setsockopt(self, level, opt, value):
-        self.options.append((level, opt, value))
-
-    def close(self):
-        self.closed = True
-
-
-class MockFinished(Exception):
-    pass
-
-
-class MockPoll:
-    results = []
-
-    def __init__(self):
-        self.registry = []
-
-    def register(self, fd, options):
-        self.registry.append((fd, options))
-
-    def poll(self):
-        for result in self.results:
-            yield result
-        raise MockFinished
-
-
-def origkey(klass):
-    return "_originals_" + klass.__name__
-
-
-def MockClassMethod(klass, name, myfunc=None):
-    def func(self, *args, **kwargs):
-        if not hasattr(self, "called"):
-            self.called = []
-        self.called.append((name, args, kwargs))
-
-    key = origkey(klass)
-    if not hasattr(klass, key):
-        setattr(klass, key, {})
-    getattr(klass, key)[name] = getattr(klass, name)
-    if myfunc is None:
-        setattr(klass, name, func)
-    else:
-        setattr(klass, name, myfunc)
-
-
-def UnmockClassMethods(klass):
-    key = origkey(klass)
-    if not hasattr(klass, key):
-        return
-    for (name, func) in getattr(klass, key).items():
-        setattr(klass, name, func)
-
-    delattr(klass, key)
-
-
-class MockFd:
-    data = object()
-    source = object()
-
-    def __init__(self, fd=0):
-        self.fd = fd
-
-    def fileno(self):
-        return self.fd
-
-    def recvfrom(self, size):
-        self.size = size
-        return (self.data, self.source)
diff --git a/pyrad/tests/testBidict.py b/pyrad/tests/testBidict.py
deleted file mode 100644
index ad2f134..0000000
--- a/pyrad/tests/testBidict.py
+++ /dev/null
@@ -1,56 +0,0 @@
-import operator
-import unittest
-from pyrad.bidict import BiDict
-
-
-class BiDictTests(unittest.TestCase):
-    def setUp(self):
-        self.bidict = BiDict()
-
-    def testStartEmpty(self):
-        self.assertEqual(len(self.bidict), 0)
-        self.assertEqual(len(self.bidict.forward), 0)
-        self.assertEqual(len(self.bidict.backward), 0)
-
-    def testLength(self):
-        self.assertEqual(len(self.bidict), 0)
-        self.bidict.Add("from", "to")
-        self.assertEqual(len(self.bidict), 1)
-        del self.bidict["from"]
-        self.assertEqual(len(self.bidict), 0)
-
-    def testDeletion(self):
-        self.assertRaises(KeyError, operator.delitem, self.bidict, "missing")
-        self.bidict.Add("missing", "present")
-        del self.bidict["missing"]
-
-    def testBackwardDeletion(self):
-        self.assertRaises(KeyError, operator.delitem, self.bidict, "missing")
-        self.bidict.Add("missing", "present")
-        del self.bidict["present"]
-        self.assertEqual(self.bidict.HasForward("missing"), False)
-
-    def testForwardAccess(self):
-        self.bidict.Add("shake", "vanilla")
-        self.bidict.Add("pie", "custard")
-        self.assertEqual(self.bidict.HasForward("shake"), True)
-        self.assertEqual(self.bidict.GetForward("shake"), "vanilla")
-        self.assertEqual(self.bidict.HasForward("pie"), True)
-        self.assertEqual(self.bidict.GetForward("pie"), "custard")
-        self.assertEqual(self.bidict.HasForward("missing"), False)
-        self.assertRaises(KeyError, self.bidict.GetForward, "missing")
-
-    def testBackwardAccess(self):
-        self.bidict.Add("shake", "vanilla")
-        self.bidict.Add("pie", "custard")
-        self.assertEqual(self.bidict.HasBackward("vanilla"), True)
-        self.assertEqual(self.bidict.GetBackward("vanilla"), "shake")
-        self.assertEqual(self.bidict.HasBackward("missing"), False)
-        self.assertRaises(KeyError, self.bidict.GetBackward, "missing")
-
-    def testItemAccessor(self):
-        self.bidict.Add("shake", "vanilla")
-        self.bidict.Add("pie", "custard")
-        self.assertRaises(KeyError, operator.getitem, self.bidict, "missing")
-        self.assertEquals(self.bidict["shake"], "vanilla")
-        self.assertEquals(self.bidict["pie"], "custard")
diff --git a/pyrad/tests/testClient.py b/pyrad/tests/testClient.py
deleted file mode 100644
index 708525f..0000000
--- a/pyrad/tests/testClient.py
+++ /dev/null
@@ -1,178 +0,0 @@
-import socket
-import unittest
-import six
-from pyrad.client import Client
-from pyrad.client import Timeout
-from pyrad.packet import AuthPacket
-from pyrad.packet import AcctPacket
-from pyrad.packet import AccessRequest
-from pyrad.packet import AccountingRequest
-from pyrad.tests.mock import MockPacket
-from pyrad.tests.mock import MockSocket
-
-BIND_IP = "127.0.0.1"
-BIND_PORT = 53535
-
-
-class ConstructionTests(unittest.TestCase):
-    def setUp(self):
-        self.server = object()
-
-    def testSimpleConstruction(self):
-        client = Client(self.server)
-        self.failUnless(client.server is self.server)
-        self.assertEqual(client.authport, 1812)
-        self.assertEqual(client.acctport, 1813)
-        self.assertEqual(client.secret, six.b(''))
-        self.assertEqual(client.retries, 3)
-        self.assertEqual(client.timeout, 5)
-        self.failUnless(client.dict is None)
-
-    def testParameterOrder(self):
-        marker = object()
-        client = Client(self.server, 123, 456, 789, "secret", marker)
-        self.failUnless(client.server is self.server)
-        self.assertEqual(client.authport, 123)
-        self.assertEqual(client.acctport, 456)
-        self.assertEqual(client.coaport, 789)
-        self.assertEqual(client.secret, "secret")
-        self.failUnless(client.dict is marker)
-
-    def testNamedParameters(self):
-        marker = object()
-        client = Client(server=self.server, authport=123, acctport=456,
-                      secret="secret", dict=marker)
-        self.failUnless(client.server is self.server)
-        self.assertEqual(client.authport, 123)
-        self.assertEqual(client.acctport, 456)
-        self.assertEqual(client.secret, "secret")
-        self.failUnless(client.dict is marker)
-
-
-class SocketTests(unittest.TestCase):
-    def setUp(self):
-        self.server = object()
-        self.client = Client(self.server)
-        self.orgsocket = socket.socket
-        socket.socket = MockSocket
-
-    def tearDown(self):
-        socket.socket = self.orgsocket
-
-    def testReopen(self):
-        self.client._SocketOpen()
-        sock = self.client._socket
-        self.client._SocketOpen()
-        self.failUnless(sock is self.client._socket)
-
-    def testBind(self):
-        self.client.bind((BIND_IP, BIND_PORT))
-        self.assertEqual(self.client._socket.address, (BIND_IP, BIND_PORT))
-        self.assertEqual(self.client._socket.options,
-                [(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)])
-
-    def testBindClosesSocket(self):
-        s = MockSocket(socket.AF_INET, socket.SOCK_DGRAM)
-        self.client._socket = s
-        self.client.bind((BIND_IP, BIND_PORT))
-        self.assertEqual(s.closed, True)
-
-    def testSendPacket(self):
-        def MockSend(self, pkt, port):
-            self._mock_pkt = pkt
-            self._mock_port = port
-
-        _SendPacket = Client._SendPacket
-        Client._SendPacket = MockSend
-
-        self.client.SendPacket(AuthPacket())
-        self.assertEqual(self.client._mock_port, self.client.authport)
-
-        self.client.SendPacket(AcctPacket())
-        self.assertEqual(self.client._mock_port, self.client.acctport)
-
-        Client._SendPacket = _SendPacket
-
-    def testNoRetries(self):
-        self.client.retries = 0
-        self.assertRaises(Timeout, self.client._SendPacket, None, None)
-
-    def testSingleRetry(self):
-        self.client.retries = 1
-        self.client.timeout = 0
-        packet = MockPacket(AccessRequest)
-        self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
-        self.assertEqual(self.client._socket.output,
-                [("request packet", (self.server, 432))])
-
-    def testDoubleRetry(self):
-        self.client.retries = 2
-        self.client.timeout = 0
-        packet = MockPacket(AccessRequest)
-        self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
-        self.assertEqual(self.client._socket.output,
-                [("request packet", (self.server, 432)),
-                 ("request packet", (self.server, 432))])
-
-    def testAuthDelay(self):
-        self.client.retries = 2
-        self.client.timeout = 1
-        packet = MockPacket(AccessRequest)
-        self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
-        self.failIf("Acct-Delay-Time" in packet)
-
-    def testSingleAccountDelay(self):
-        self.client.retries = 2
-        self.client.timeout = 1
-        packet = MockPacket(AccountingRequest)
-        self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
-        self.assertEqual(packet["Acct-Delay-Time"], [1])
-
-    def testDoubleAccountDelay(self):
-        self.client.retries = 3
-        self.client.timeout = 1
-        packet = MockPacket(AccountingRequest)
-        self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
-        self.assertEqual(packet["Acct-Delay-Time"], [2])
-
-    def testIgnorePacketError(self):
-        self.client.retries = 1
-        self.client.timeout = 1
-        self.client._socket = MockSocket(1, 2, six.b("valid reply"))
-        packet = MockPacket(AccountingRequest, verify=True, error=True)
-        self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
-
-    def testValidReply(self):
-        self.client.retries = 1
-        self.client.timeout = 1
-        self.client._socket = MockSocket(1, 2, six.b("valid reply"))
-        packet = MockPacket(AccountingRequest, verify=True)
-        reply = self.client._SendPacket(packet, 432)
-        self.failUnless(reply is packet.reply)
-
-    def testInvalidReply(self):
-        self.client.retries = 1
-        self.client.timeout = 1
-        self.client._socket = MockSocket(1, 2, six.b("invalid reply"))
-        packet = MockPacket(AccountingRequest, verify=False)
-        self.assertRaises(Timeout, self.client._SendPacket, packet, 432)
-
-
-class OtherTests(unittest.TestCase):
-    def setUp(self):
-        self.server = object()
-        self.client = Client(self.server, secret=six.b('zeer geheim'))
-
-    def testCreateAuthPacket(self):
-        packet = self.client.CreateAuthPacket(id=15)
-        self.failUnless(isinstance(packet, AuthPacket))
-        self.failUnless(packet.dict is self.client.dict)
-        self.assertEqual(packet.id, 15)
-        self.assertEqual(packet.secret, six.b('zeer geheim'))
-
-    def testCreateAcctPacket(self):
-        packet = self.client.CreateAcctPacket(id=15)
-        self.failUnless(isinstance(packet, AcctPacket))
-        self.failUnless(packet.dict is self.client.dict)
-        self.assertEqual(packet.id, 15)
-        self.assertEqual(packet.secret, six.b('zeer geheim'))
diff --git a/pyrad/tests/testDictionary.py b/pyrad/tests/testDictionary.py
deleted file mode 100644
index dd6a45e..0000000
--- a/pyrad/tests/testDictionary.py
+++ /dev/null
@@ -1,303 +0,0 @@
-import unittest
-import operator
-import os
-from six import StringIO
-from pyrad.tests import home
-from pyrad.dictionary import Attribute
-from pyrad.dictionary import Dictionary
-from pyrad.dictionary import ParseError
-from pyrad.tools import DecodeAttr
-from pyrad.dictfile import DictFile
-
-
-class AttributeTests(unittest.TestCase):
-    def testInvalidDataType(self):
-        self.assertRaises(ValueError, Attribute, 'name', 'code', 'datatype')
-
-    def testConstructionParameters(self):
-        attr = Attribute('name', 'code', 'integer', 'vendor')
-        self.assertEqual(attr.name, 'name')
-        self.assertEqual(attr.code, 'code')
-        self.assertEqual(attr.type, 'integer')
-        self.assertEqual(attr.vendor, 'vendor')
-        self.assertEqual(len(attr.values), 0)
-
-    def testNamedConstructionParameters(self):
-        attr = Attribute(name='name', code='code', datatype='integer',
-                vendor='vendor')
-        self.assertEqual(attr.name, 'name')
-        self.assertEqual(attr.code, 'code')
-        self.assertEqual(attr.type, 'integer')
-        self.assertEqual(attr.vendor, 'vendor')
-        self.assertEqual(len(attr.values), 0)
-
-    def testValues(self):
-        attr = Attribute('name', 'code', 'integer', 'vendor',
-                dict(pie='custard', shake='vanilla'))
-        self.assertEqual(len(attr.values), 2)
-        self.assertEqual(attr.values['shake'], 'vanilla')
-
-
-class DictionaryInterfaceTests(unittest.TestCase):
-    def testEmptyDictionary(self):
-        dict = Dictionary()
-        self.assertEqual(len(dict), 0)
-
-    def testContainment(self):
-        dict = Dictionary()
-        self.assertEqual('test' in dict, False)
-        self.assertEqual(dict.has_key('test'), False)
-        dict.attributes['test'] = 'dummy'
-        self.assertEqual('test' in dict, True)
-        self.assertEqual(dict.has_key('test'), True)
-
-    def testReadonlyContainer(self):
-        import six
-        dict = Dictionary()
-        self.assertRaises(TypeError,
-                operator.setitem, dict, 'test', 'dummy')
-        self.assertRaises(AttributeError,
-                operator.attrgetter('clear'), dict)
-        self.assertRaises(AttributeError,
-                operator.attrgetter('update'), dict)
-
-
-class DictionaryParsingTests(unittest.TestCase):
-    def setUp(self):
-        self.path = os.path.join(home, 'tests', 'data')
-        self.dict = Dictionary(os.path.join(self.path, 'simple'))
-
-    def testParseEmptyDictionary(self):
-        dict = Dictionary(StringIO(''))
-        self.assertEqual(len(dict), 0)
-
-    def testParseMultipleDictionaries(self):
-        dict = Dictionary(StringIO(''))
-        self.assertEqual(len(dict), 0)
-        one = StringIO('ATTRIBUTE Test-First 1 string')
-        two = StringIO('ATTRIBUTE Test-Second 2 string')
-        dict = Dictionary(StringIO(''), one, two)
-        self.assertEqual(len(dict), 2)
-
-    def testParseSimpleDictionary(self):
-        self.assertEqual(len(self.dict), 8)
-        values = [
-                ('Test-String', 1, 'string'),
-                ('Test-Octets', 2, 'octets'),
-                ('Test-Integer', 3, 'integer'),
-                ('Test-Ip-Address', 4, 'ipaddr'),
-                ('Test-Ipv6-Address', 5, 'ipv6addr'),
-                ('Test-If-Id', 6, 'ifid'),
-                ('Test-Date', 7, 'date'),
-                ('Test-Abinary', 8, 'abinary'),
-                ]
-
-        for (attr, code, type) in values:
-            attr = self.dict[attr]
-            self.assertEqual(attr.code, code)
-            self.assertEqual(attr.type, type)
-
-    def testAttributeTooFewColumnsError(self):
-        try:
-            self.dict.ReadDictionary(
-                    StringIO('ATTRIBUTE Oops-Too-Few-Columns'))
-        except ParseError as e:
-            self.assertEqual('attribute' in str(e), True)
-        else:
-            self.fail()
-
-    def testAttributeUnknownTypeError(self):
-        try:
-            self.dict.ReadDictionary(StringIO('ATTRIBUTE Test-Type 1 dummy'))
-        except ParseError as e:
-            self.assertEqual('dummy' in str(e), True)
-        else:
-            self.fail()
-
-    def testAttributeUnknownVendorError(self):
-        try:
-            self.dict.ReadDictionary(StringIO('ATTRIBUTE Test-Type 1 Simplon'))
-        except ParseError as e:
-            self.assertEqual('Simplon' in str(e), True)
-        else:
-            self.fail()
-
-    def testAttributeOptions(self):
-        self.dict.ReadDictionary(StringIO(
-            'ATTRIBUTE Option-Type 1 string has_tag,encrypt=1'))
-        self.assertEqual(self.dict['Option-Type'].has_tag, True)
-        self.assertEqual(self.dict['Option-Type'].encrypt, 1)
-
-    def testAttributeEncryptionError(self):
-        try:
-            self.dict.ReadDictionary(StringIO(
-                'ATTRIBUTE Test-Type 1 string encrypt=4'))
-        except ParseError as e:
-            self.assertEqual('encrypt' in str(e), True)
-        else:
-            self.fail()
-
-    def testValueTooFewColumnsError(self):
-        try:
-            self.dict.ReadDictionary(StringIO('VALUE Oops-Too-Few-Columns'))
-        except ParseError as e:
-            self.assertEqual('value' in str(e), True)
-        else:
-            self.fail()
-
-    def testValueForUnknownAttributeError(self):
-        try:
-            self.dict.ReadDictionary(StringIO(
-                'VALUE Test-Attribute Test-Text 1'))
-        except ParseError as e:
-            self.assertEqual('unknown attribute' in str(e), True)
-        else:
-            self.fail()
-
-    def testIntegerValueParsing(self):
-        self.assertEqual(len(self.dict['Test-Integer'].values), 0)
-        self.dict.ReadDictionary(StringIO('VALUE Test-Integer Value-Six 5'))
-        self.assertEqual(len(self.dict['Test-Integer'].values), 1)
-        self.assertEqual(
-                DecodeAttr('integer',
-                    self.dict['Test-Integer'].values['Value-Six']),
-                5)
-
-    def testStringValueParsing(self):
-        self.assertEqual(len(self.dict['Test-String'].values), 0)
-        self.dict.ReadDictionary(StringIO(
-            'VALUE Test-String Value-Custard custardpie'))
-        self.assertEqual(len(self.dict['Test-String'].values), 1)
-        self.assertEqual(
-                DecodeAttr('string',
-                    self.dict['Test-String'].values['Value-Custard']),
-                'custardpie')
-
-    def testVenderTooFewColumnsError(self):
-        try:
-            self.dict.ReadDictionary(StringIO('VENDOR Simplon'))
-        except ParseError as e:
-            self.assertEqual('vendor' in str(e), True)
-        else:
-            self.fail()
-
-    def testVendorParsing(self):
-        self.assertRaises(ParseError, self.dict.ReadDictionary,
-                StringIO('ATTRIBUTE Test-Type 1 integer Simplon'))
-        self.dict.ReadDictionary(StringIO('VENDOR Simplon 42'))
-        self.assertEqual(self.dict.vendors['Simplon'], 42)
-        self.dict.ReadDictionary(StringIO(
-                        'ATTRIBUTE Test-Type 1 integer Simplon'))
-        self.assertEquals(self.dict.attrindex['Test-Type'], (42, 1))
-
-    def testVendorOptionError(self):
-        self.assertRaises(ParseError, self.dict.ReadDictionary,
-                StringIO('ATTRIBUTE Test-Type 1 integer Simplon'))
-        try:
-            self.dict.ReadDictionary(StringIO('VENDOR Simplon 42 badoption'))
-        except ParseError as e:
-            self.assertEqual('option' in str(e), True)
-        else:
-            self.fail()
-
-    def testVendorFormatError(self):
-        self.assertRaises(ParseError, self.dict.ReadDictionary,
-                StringIO('ATTRIBUTE Test-Type 1 integer Simplon'))
-        try:
-            self.dict.ReadDictionary(StringIO(
-                'VENDOR Simplon 42 format=5,4'))
-        except ParseError as e:
-            self.assertEqual('format' in str(e), True)
-        else:
-            self.fail()
-
-    def testVendorFormatSyntaxError(self):
-        self.assertRaises(ParseError, self.dict.ReadDictionary,
-                StringIO('ATTRIBUTE Test-Type 1 integer Simplon'))
-        try:
-            self.dict.ReadDictionary(StringIO(
-                'VENDOR Simplon 42 format=a,1'))
-        except ParseError as e:
-            self.assertEqual('Syntax' in str(e), True)
-        else:
-            self.fail()
-
-    def testBeginVendorTooFewColumns(self):
-        try:
-            self.dict.ReadDictionary(StringIO('BEGIN-VENDOR'))
-        except ParseError as e:
-            self.assertEqual('begin-vendor' in str(e), True)
-        else:
-            self.fail()
-
-    def testBeginVendorUnknownVendor(self):
-        try:
-            self.dict.ReadDictionary(StringIO('BEGIN-VENDOR Simplon'))
-        except ParseError as e:
-            self.assertEqual('Simplon' in str(e), True)
-        else:
-            self.fail()
-
-    def testBeginVendorParsing(self):
-        self.dict.ReadDictionary(StringIO(
-                        'VENDOR Simplon 42\n'
-                        'BEGIN-VENDOR Simplon\n'
-                        'ATTRIBUTE Test-Type 1 integer'))
-        self.assertEquals(self.dict.attrindex['Test-Type'], (42, 1))
-
-    def testEndVendorUnknownVendor(self):
-        try:
-            self.dict.ReadDictionary(StringIO('END-VENDOR'))
-        except ParseError as e:
-            self.assertEqual('end-vendor' in str(e), True)
-        else:
-            self.fail()
-
-    def testEndVendorUnbalanced(self):
-        try:
-            self.dict.ReadDictionary(StringIO(
-                            'VENDOR Simplon 42\n'
-                            'BEGIN-VENDOR Simplon\n'
-                            'END-VENDOR Oops\n'))
-        except ParseError as e:
-            self.assertEqual('Oops' in str(e), True)
-        else:
-            self.fail()
-
-    def testEndVendorParsing(self):
-        self.dict.ReadDictionary(StringIO(
-                        'VENDOR Simplon 42\n'
-                        'BEGIN-VENDOR Simplon\n'
-                        'END-VENDOR Simplon\n'
-                        'ATTRIBUTE Test-Type 1 integer'))
-        self.assertEquals(self.dict.attrindex['Test-Type'], 1)
-
-    def testInclude(self):
-        try:
-            self.dict.ReadDictionary(StringIO(
-                    '$INCLUDE this_file_does_not_exist\n'
-                    'VENDOR Simplon 42\n'
-                    'BEGIN-VENDOR Simplon\n'
-                    'END-VENDOR Simplon\n'
-                    'ATTRIBUTE Test-Type 1 integer'))
-        except IOError as e:
-            self.assertEqual('this_file_does_not_exist' in str(e), True)
-        else:
-            self.fail()
-
-    def testDictFilePostParse(self):
-        f = DictFile(StringIO(
-                'VENDOR Simplon 42\n'))
-        for _ in f:
-            pass
-        self.assertEquals(f.File(), '')
-        self.assertEquals(f.Line(), -1)
-
-    def testDictFileParseError(self):
-        tmpdict = Dictionary()
-        try:
-            tmpdict.ReadDictionary(os.path.join(self.path, 'dictfiletest'))
-        except ParseError as e:
-            self.assertEquals('dictfiletest' in str(e), True)
-        else:
-            self.fail()
diff --git a/pyrad/tests/testHost.py b/pyrad/tests/testHost.py
deleted file mode 100644
index ec51deb..0000000
--- a/pyrad/tests/testHost.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import unittest
-from pyrad.host import Host
-from pyrad.packet import Packet
-from pyrad.packet import AuthPacket
-from pyrad.packet import AcctPacket
-
-
-class ConstructionTests(unittest.TestCase):
-    def testSimpleConstruction(self):
-        host = Host()
-        self.assertEqual(host.authport, 1812)
-        self.assertEqual(host.acctport, 1813)
-
-    def testParameterOrder(self):
-        host = Host(123, 456, 789, 101)
-        self.assertEqual(host.authport, 123)
-        self.assertEqual(host.acctport, 456)
-        self.assertEqual(host.coaport, 789)
-        self.assertEqual(host.dict, 101)
-
-    def testNamedParameters(self):
-        host = Host(authport=123, acctport=456, coaport=789, dict=101)
-        self.assertEqual(host.authport, 123)
-        self.assertEqual(host.acctport, 456)
-        self.assertEqual(host.coaport, 789)
-        self.assertEqual(host.dict, 101)
-
-
-class PacketCreationTests(unittest.TestCase):
-    def setUp(self):
-        self.host = Host()
-
-    def testCreatePacket(self):
-        packet = self.host.CreatePacket(id=15)
-        self.failUnless(isinstance(packet, Packet))
-        self.failUnless(packet.dict is self.host.dict)
-        self.assertEqual(packet.id, 15)
-
-    def testCreateAuthPacket(self):
-        packet = self.host.CreateAuthPacket(id=15)
-        self.failUnless(isinstance(packet, AuthPacket))
-        self.failUnless(packet.dict is self.host.dict)
-        self.assertEqual(packet.id, 15)
-
-    def testCreateAcctPacket(self):
-        packet = self.host.CreateAcctPacket(id=15)
-        self.failUnless(isinstance(packet, AcctPacket))
-        self.failUnless(packet.dict is self.host.dict)
-        self.assertEqual(packet.id, 15)
-
-
-class MockPacket:
-    packet = object()
-    replypacket = object()
-    source = object()
-
-    def Packet(self):
-        return self.packet
-
-    def ReplyPacket(self):
-        return self.replypacket
-
-
-class MockFd:
-    data = None
-    target = None
-
-    def sendto(self, data, target):
-        self.data = data
-        self.target = target
-
-
-class PacketSendTest(unittest.TestCase):
-    def setUp(self):
-        self.host = Host()
-        self.fd = MockFd()
-        self.packet = MockPacket()
-
-    def testSendPacket(self):
-        self.host.SendPacket(self.fd, self.packet)
-        self.failUnless(self.fd.data is self.packet.packet)
-        self.failUnless(self.fd.target is self.packet.source)
-
-    def testSendReplyPacket(self):
-        self.host.SendReplyPacket(self.fd, self.packet)
-        self.failUnless(self.fd.data is self.packet.replypacket)
-        self.failUnless(self.fd.target is self.packet.source)
diff --git a/pyrad/tests/testPacket.py b/pyrad/tests/testPacket.py
deleted file mode 100644
index d5705a0..0000000
--- a/pyrad/tests/testPacket.py
+++ /dev/null
@@ -1,413 +0,0 @@
-import os
-import unittest
-import six
-from pyrad import packet
-from pyrad.tests import home
-from pyrad.dictionary import Dictionary
-
-
-class UtilityTests(unittest.TestCase):
-    def testGenerateID(self):
-        id = packet.CreateID()
-        self.failUnless(isinstance(id, int))
-        newid = packet.CreateID()
-        self.assertNotEqual(id, newid)
-
-
-class PacketConstructionTests(unittest.TestCase):
-    klass = packet.Packet
-
-    def setUp(self):
-        self.path = os.path.join(home, 'tests', 'data')
-        self.dict = Dictionary(os.path.join(self.path, 'simple'))
-
-    def testBasicConstructor(self):
-        pkt = self.klass()
-        self.failUnless(isinstance(pkt.code, int))
-        self.failUnless(isinstance(pkt.id, int))
-        self.failUnless(isinstance(pkt.secret, six.binary_type))
-
-    def testNamedConstructor(self):
-        pkt = self.klass(code=26, id=38, secret=six.b('secret'),
-                authenticator=six.b('authenticator'),
-                dict='fakedict')
-        self.assertEqual(pkt.code, 26)
-        self.assertEqual(pkt.id, 38)
-        self.assertEqual(pkt.secret, six.b('secret'))
-        self.assertEqual(pkt.authenticator, six.b('authenticator'))
-        self.assertEqual(pkt.dict, 'fakedict')
-
-    def testConstructWithDictionary(self):
-        pkt = self.klass(dict=self.dict)
-        self.failUnless(pkt.dict is self.dict)
-
-    def testConstructorIgnoredParameters(self):
-        marker = []
-        pkt = self.klass(fd=marker)
-        self.failIf(getattr(pkt, 'fd', None) is marker)
-
-    def testSecretMustBeBytestring(self):
-        self.assertRaises(TypeError, self.klass, secret=six.u('secret'))
-
-    def testConstructorWithAttributes(self):
-        pkt = self.klass(dict=self.dict, Test_String='this works')
-        self.assertEqual(pkt['Test-String'], ['this works'])
-
-
-class PacketTests(unittest.TestCase):
-    def setUp(self):
-        self.path = os.path.join(home, 'tests', 'data')
-        self.dict = Dictionary(os.path.join(self.path, 'full'))
-        self.packet = packet.Packet(id=0, secret=six.b('secret'),
-                authenticator=six.b('01234567890ABCDEF'), dict=self.dict)
-
-    def testCreateReply(self):
-        reply = self.packet.CreateReply(Test_Integer=10)
-        self.assertEqual(reply.id, self.packet.id)
-        self.assertEqual(reply.secret, self.packet.secret)
-        self.assertEqual(reply.authenticator, self.packet.authenticator)
-        self.assertEqual(reply['Test-Integer'], [10])
-
-    def testAttributeAccess(self):
-        self.packet['Test-Integer'] = 10
-        self.assertEqual(self.packet['Test-Integer'], [10])
-        self.assertEqual(self.packet[3], [six.b('\x00\x00\x00\x0a')])
-
-        self.packet['Test-String'] = 'dummy'
-        self.assertEqual(self.packet['Test-String'], ['dummy'])
-        self.assertEqual(self.packet[1], [six.b('dummy')])
-
-    def testAttributeValueAccess(self):
-        self.packet['Test-Integer'] = 'Three'
-        self.assertEqual(self.packet['Test-Integer'], ['Three'])
-        self.assertEqual(self.packet[3], [six.b('\x00\x00\x00\x03')])
-
-    def testVendorAttributeAccess(self):
-        self.packet['Simplon-Number'] = 10
-        self.assertEqual(self.packet['Simplon-Number'], [10])
-        self.assertEqual(self.packet[(16, 1)], [six.b('\x00\x00\x00\x0a')])
-
-        self.packet['Simplon-Number'] = 'Four'
-        self.assertEqual(self.packet['Simplon-Number'], ['Four'])
-        self.assertEqual(self.packet[(16, 1)], [six.b('\x00\x00\x00\x04')])
-
-    def testRawAttributeAccess(self):
-        marker = [six.b('')]
-        self.packet[1] = marker
-        self.failUnless(self.packet[1] is marker)
-        self.packet[(16, 1)] = marker
-        self.failUnless(self.packet[(16, 1)] is marker)
-
-    def testHasKey(self):
-        self.assertEqual(self.packet.has_key('Test-String'), False)
-        self.assertEqual('Test-String' in self.packet, False)
-        self.packet['Test-String'] = 'dummy'
-        self.assertEqual(self.packet.has_key('Test-String'), True)
-        self.assertEqual(self.packet.has_key(1), True)
-        self.assertEqual(1 in self.packet, True)
-
-    def testHasKeyWithUnknownKey(self):
-        self.assertEqual(self.packet.has_key('Unknown-Attribute'), False)
-        self.assertEqual('Unknown-Attribute' in self.packet, False)
-
-    def testDelItem(self):
-        self.packet['Test-String'] = 'dummy'
-        del self.packet['Test-String']
-        self.assertEqual(self.packet.has_key('Test-String'), False)
-        self.packet['Test-String'] = 'dummy'
-        del self.packet[1]
-        self.assertEqual(self.packet.has_key('Test-String'), False)
-
-    def testKeys(self):
-        self.assertEqual(self.packet.keys(), [])
-        self.packet['Test-String'] = 'dummy'
-        self.assertEqual(self.packet.keys(), ['Test-String'])
-        self.packet['Test-Integer'] = 10
-        self.assertEqual(self.packet.keys(), ['Test-String', 'Test-Integer'])
-        dict.__setitem__(self.packet, 12345, None)
-        self.assertEqual(self.packet.keys(),
-                        ['Test-String', 'Test-Integer', 12345])
-
-    def testCreateAuthenticator(self):
-        a = packet.Packet.CreateAuthenticator()
-        self.failUnless(isinstance(a, six.binary_type))
-        self.assertEqual(len(a), 16)
-
-        b = packet.Packet.CreateAuthenticator()
-        self.assertNotEqual(a, b)
-
-    def testGenerateID(self):
-        id = self.packet.CreateID()
-        self.failUnless(isinstance(id, int))
-        newid = self.packet.CreateID()
-        self.assertNotEqual(id, newid)
-
-    def testReplyPacket(self):
-        reply = self.packet.ReplyPacket()
-        self.assertEqual(reply,
-                six.b('\x00\x00\x00\x14\xb0\x5e\x4b\xfb\xcc\x1c'
-                      '\x8c\x8e\xc4\x72\xac\xea\x87\x45\x63\xa7'))
-
-    def testVerifyReply(self):
-        reply = self.packet.CreateReply()
-        self.assertEqual(self.packet.VerifyReply(reply), True)
-
-        reply.id += 1
-        self.assertEqual(self.packet.VerifyReply(reply), False)
-        reply.id = self.packet.id
-
-        reply.secret = six.b('different')
-        self.assertEqual(self.packet.VerifyReply(reply), False)
-        reply.secret = self.packet.secret
-
-        reply.authenticator = six.b('X') * 16
-        self.assertEqual(self.packet.VerifyReply(reply), False)
-        reply.authenticator = self.packet.authenticator
-
-    def testPktEncodeAttribute(self):
-        encode = self.packet._PktEncodeAttribute
-
-        # Encode a normal attribute
-        self.assertEqual(
-                encode(1, six.b('value')),
-                six.b('\x01\x07value'))
-        # Encode a vendor attribute
-        self.assertEqual(
-                encode((1, 2), six.b('value')),
-                six.b('\x1a\x0d\x00\x00\x00\x01\x02\x07value'))
-
-    def testPktEncodeAttributes(self):
-        self.packet[1] = [six.b('value')]
-        self.assertEqual(self.packet._PktEncodeAttributes(),
-                six.b('\x01\x07value'))
-
-        self.packet.clear()
-        self.packet[(1, 2)] = [six.b('value')]
-        self.assertEqual(self.packet._PktEncodeAttributes(),
-                six.b('\x1a\x0d\x00\x00\x00\x01\x02\x07value'))
-
-        self.packet.clear()
-        self.packet[1] = [six.b('one'), six.b('two'), six.b('three')]
-        self.assertEqual(self.packet._PktEncodeAttributes(),
-                six.b('\x01\x05one\x01\x05two\x01\x07three'))
-
-        self.packet.clear()
-        self.packet[1] = [six.b('value')]
-        self.packet[(1, 2)] = [six.b('value')]
-        self.assertEqual(
-                self.packet._PktEncodeAttributes(),
-                six.b('\x1a\x0d\x00\x00\x00\x01\x02\x07value\x01\x07value'))
-
-    def testPktDecodeVendorAttribute(self):
-        decode = self.packet._PktDecodeVendorAttribute
-
-        # Non-RFC2865 recommended form
-        self.assertEqual(decode(six.b('')), [(26, six.b(''))])
-        self.assertEqual(decode(six.b('12345')), [(26, six.b('12345'))])
-
-        # Almost RFC2865 recommended form: bad length value
-        self.assertEqual(
-                decode(six.b('\x00\x00\x00\x01\x02\x06value')),
-                [(26, six.b('\x00\x00\x00\x01\x02\x06value'))])
-
-        # Proper RFC2865 recommended form
-        self.assertEqual(
-                decode(six.b('\x00\x00\x00\x01\x02\x07value')),
-                [((1, 2), six.b('value'))])
-
-    def testDecodePacketWithEmptyPacket(self):
-        try:
-            self.packet.DecodePacket(six.b(''))
-        except packet.PacketError as e:
-            self.failUnless('header is corrupt' in str(e))
-        else:
-            self.fail()
-
-    def testDecodePacketWithInvalidLength(self):
-        try:
-            self.packet.DecodePacket(six.b('\x00\x00\x00\x001234567890123456'))
-        except packet.PacketError as e:
-            self.failUnless('invalid length' in str(e))
-        else:
-            self.fail()
-
-    def testDecodePacketWithTooBigPacket(self):
-        try:
-            self.packet.DecodePacket(six.b('\x00\x00\x24\x00') + (0x2400 - 4) * six.b('X'))
-        except packet.PacketError as e:
-            self.failUnless('too long' in str(e))
-        else:
-            self.fail()
-
-    def testDecodePacketWithPartialAttributes(self):
-        try:
-            self.packet.DecodePacket(
-                    six.b('\x01\x02\x00\x151234567890123456\x00'))
-        except packet.PacketError as e:
-            self.failUnless('header is corrupt' in str(e))
-        else:
-            self.fail()
-
-    def testDecodePacketWithoutAttributes(self):
-        self.packet.DecodePacket(six.b('\x01\x02\x00\x141234567890123456'))
-        self.assertEqual(self.packet.code, 1)
-        self.assertEqual(self.packet.id, 2)
-        self.assertEqual(self.packet.authenticator, six.b('1234567890123456'))
-        self.assertEqual(self.packet.keys(), [])
-
-    def testDecodePacketWithBadAttribute(self):
-        try:
-            self.packet.DecodePacket(
-                    six.b('\x01\x02\x00\x161234567890123456\x00\x01'))
-        except packet.PacketError as e:
-            self.failUnless('too small' in str(e))
-        else:
-            self.fail()
-
-    def testDecodePacketWithEmptyAttribute(self):
-        self.packet.DecodePacket(
-                six.b('\x01\x02\x00\x161234567890123456\x00\x02'))
-        self.assertEqual(self.packet[0], [six.b('')])
-
-    def testDecodePacketWithAttribute(self):
-        self.packet.DecodePacket(
-            six.b('\x01\x02\x00\x1b1234567890123456\x00\x07value'))
-        self.assertEqual(self.packet[0], [six.b('value')])
-
-    def testDecodePacketWithMultiValuedAttribute(self):
-        self.packet.DecodePacket(
-            six.b('\x01\x02\x00\x1e1234567890123456\x00\x05one\x00\x05two'))
-        self.assertEqual(self.packet[0], [six.b('one'), six.b('two')])
-
-    def testDecodePacketWithTwoAttributes(self):
-        self.packet.DecodePacket(
-            six.b('\x01\x02\x00\x1e1234567890123456\x00\x05one\x01\x05two'))
-        self.assertEqual(self.packet[0], [six.b('one')])
-        self.assertEqual(self.packet[1], [six.b('two')])
-
-    def testDecodePacketWithVendorAttribute(self):
-        self.packet.DecodePacket(
-                six.b('\x01\x02\x00\x1b1234567890123456\x1a\x07value'))
-        self.assertEqual(self.packet[26], [six.b('value')])
-
-    def testEncodeKeyValues(self):
-        self.assertEqual(self.packet._EncodeKeyValues(1, '1234'), (1, '1234'))
-
-    def testEncodeKey(self):
-        self.assertEqual(self.packet._EncodeKey(1), 1)
-
-    def testAddAttribute(self):
-        self.packet.AddAttribute(1, 1)
-        self.assertEqual(dict.__getitem__(self.packet, 1), [1])
-        self.packet.AddAttribute(1, 1)
-        self.assertEqual(dict.__getitem__(self.packet, 1), [1, 1])
-        self.packet.AddAttribute(1, [2, 3])
-        self.assertEqual(dict.__getitem__(self.packet, 1), [1, 1, 2, 3])
-
-class AuthPacketConstructionTests(PacketConstructionTests):
-    klass = packet.AuthPacket
-
-    def testConstructorDefaults(self):
-        pkt = self.klass()
-        self.assertEqual(pkt.code, packet.AccessRequest)
-
-
-class AuthPacketTests(unittest.TestCase):
-    def setUp(self):
-        self.path = os.path.join(home, 'tests', 'data')
-        self.dict = Dictionary(os.path.join(self.path, 'full'))
-        self.packet = packet.AuthPacket(id=0, secret=six.b('secret'),
-                authenticator=six.b('01234567890ABCDEF'), dict=self.dict)
-
-    def testCreateReply(self):
-        reply = self.packet.CreateReply(Test_Integer=10)
-        self.assertEqual(reply.code, packet.AccessAccept)
-        self.assertEqual(reply.id, self.packet.id)
-        self.assertEqual(reply.secret, self.packet.secret)
-        self.assertEqual(reply.authenticator, self.packet.authenticator)
-        self.assertEqual(reply['Test-Integer'], [10])
-
-    def testRequestPacket(self):
-        self.assertEqual(self.packet.RequestPacket(),
-                six.b('\x01\x00\x00\x1401234567890ABCDE'))
-
-    def testRequestPacketCreatesAuthenticator(self):
-        self.packet.authenticator = None
-        self.packet.RequestPacket()
-        self.failUnless(self.packet.authenticator is not None)
-
-    def testRequestPacketCreatesID(self):
-        self.packet.id = None
-        self.packet.RequestPacket()
-        self.failUnless(self.packet.id is not None)
-
-    def testPwCryptEmptyPassword(self):
-        self.assertEqual(self.packet.PwCrypt(''), six.b(''))
-
-    def testPwCryptPassword(self):
-        self.assertEqual(self.packet.PwCrypt('Simplon'),
-                six.b('\xd3U;\xb23\r\x11\xba\x07\xe3\xa8*\xa8x\x14\x01'))
-
-    def testPwCryptSetsAuthenticator(self):
-        self.packet.authenticator = None
-        self.packet.PwCrypt(six.u(''))
-        self.failUnless(self.packet.authenticator is not None)
-
-    def testPwDecryptEmptyPassword(self):
-        self.assertEqual(self.packet.PwDecrypt(six.b('')), six.u(''))
-
-    def testPwDecryptPassword(self):
-        self.assertEqual(self.packet.PwDecrypt(
-                six.b('\xd3U;\xb23\r\x11\xba\x07\xe3\xa8*\xa8x\x14\x01')),
-                six.u('Simplon'))
-
-
-class AcctPacketConstructionTests(PacketConstructionTests):
-    klass = packet.AcctPacket
-
-    def testConstructorDefaults(self):
-        pkt = self.klass()
-        self.assertEqual(pkt.code, packet.AccountingRequest)
-
-    def testConstructorRawPacket(self):
-        raw = six.b('\x00\x00\x00\x14\xb0\x5e\x4b\xfb\xcc\x1c' \
-                    '\x8c\x8e\xc4\x72\xac\xea\x87\x45\x63\xa7')
-        pkt = self.klass(packet=raw)
-        self.assertEqual(pkt.raw_packet, raw)
-
-
-class AcctPacketTests(unittest.TestCase):
-    def setUp(self):
-        self.path = os.path.join(home, 'tests', 'data')
-        self.dict = Dictionary(os.path.join(self.path, 'full'))
-        self.packet = packet.AcctPacket(id=0, secret=six.b('secret'),
-                authenticator=six.b('01234567890ABCDEF'), dict=self.dict)
-
-    def testCreateReply(self):
-        reply = self.packet.CreateReply(Test_Integer=10)
-        self.assertEqual(reply.code, packet.AccountingResponse)
-        self.assertEqual(reply.id, self.packet.id)
-        self.assertEqual(reply.secret, self.packet.secret)
-        self.assertEqual(reply.authenticator, self.packet.authenticator)
-        self.assertEqual(reply['Test-Integer'], [10])
-
-    def testVerifyAcctRequest(self):
-        rawpacket = self.packet.RequestPacket()
-        pkt = packet.AcctPacket(secret=six.b('secret'), packet=rawpacket)
-        self.assertEqual(pkt.VerifyAcctRequest(), True)
-
-        pkt.secret = six.b('different')
-        self.assertEqual(pkt.VerifyAcctRequest(), False)
-        pkt.secret = six.b('secret')
-
-        pkt.raw_packet = six.b('X') + pkt.raw_packet[1:]
-        self.assertEqual(pkt.VerifyAcctRequest(), False)
-
-    def testRequestPacket(self):
-        self.assertEqual(self.packet.RequestPacket(),
-            six.b('\x04\x00\x00\x14\x95\xdf\x90\xccbn\xfb\x15G!\x13\xea\xfa>6\x0f'))
-
-    def testRequestPacketSetsId(self):
-        self.packet.id = None
-        self.packet.RequestPacket()
-        self.failUnless(self.packet.id is not None)
diff --git a/pyrad/tests/testProxy.py b/pyrad/tests/testProxy.py
deleted file mode 100644
index 946cd4f..0000000
--- a/pyrad/tests/testProxy.py
+++ /dev/null
@@ -1,96 +0,0 @@
-import select
-import socket
-import unittest
-from pyrad.proxy import Proxy
-from pyrad.packet import AccessAccept
-from pyrad.packet import AccessRequest
-from pyrad.server import ServerPacketError
-from pyrad.server import Server
-from pyrad.tests.mock import MockFd
-from pyrad.tests.mock import MockPoll
-from pyrad.tests.mock import MockSocket
-from pyrad.tests.mock import MockClassMethod
-from pyrad.tests.mock import UnmockClassMethods
-
-
-class TrivialObject:
-    """dummy object"""
-
-
-class SocketTests(unittest.TestCase):
-    def setUp(self):
-        self.orgsocket = socket.socket
-        socket.socket = MockSocket
-        self.proxy = Proxy()
-        self.proxy._fdmap = {}
-
-    def tearDown(self):
-        socket.socket = self.orgsocket
-
-    def testProxyFd(self):
-        self.proxy._poll = MockPoll()
-        self.proxy._PrepareSockets()
-        self.failUnless(isinstance(self.proxy._proxyfd, MockSocket))
-        self.assertEqual(list(self.proxy._fdmap.keys()), [1])
-        self.assertEqual(self.proxy._poll.registry,
-                [(1, select.POLLIN | select.POLLPRI | select.POLLERR)])
-
-
-class ProxyPacketHandlingTests(unittest.TestCase):
-    def setUp(self):
-        self.proxy = Proxy()
-        self.proxy.hosts['host'] = TrivialObject()
-        self.proxy.hosts['host'].secret = 'supersecret'
-        self.packet = TrivialObject()
-        self.packet.code = AccessAccept
-        self.packet.source = ('host', 'port')
-
-    def testHandleProxyPacketUnknownHost(self):
-        self.packet.source = ('stranger', 'port')
-        try:
-            self.proxy._HandleProxyPacket(self.packet)
-        except ServerPacketError as e:
-            self.failUnless('unknown host' in str(e))
-        else:
-            self.fail()
-
-    def testHandleProxyPacketSetsSecret(self):
-        self.proxy._HandleProxyPacket(self.packet)
-        self.assertEqual(self.packet.secret, 'supersecret')
-
-    def testHandleProxyPacketHandlesWrongPacket(self):
-        self.packet.code = AccessRequest
-        try:
-            self.proxy._HandleProxyPacket(self.packet)
-        except ServerPacketError as e:
-            self.failUnless('non-response' in str(e))
-        else:
-            self.fail()
-
-
-class OtherTests(unittest.TestCase):
-    def setUp(self):
-        self.proxy = Proxy()
-        self.proxy._proxyfd = MockFd()
-
-    def tearDown(self):
-        UnmockClassMethods(Proxy)
-        UnmockClassMethods(Server)
-
-    def testProcessInputNonProxyPort(self):
-        fd = MockFd(fd=111)
-        MockClassMethod(Server, '_ProcessInput')
-        self.proxy._ProcessInput(fd)
-        self.assertEqual(self.proxy.called,
-                [('_ProcessInput', (fd,), {})])
-
-    def testProcessInput(self):
-        MockClassMethod(Proxy, '_GrabPacket')
-        MockClassMethod(Proxy, '_HandleProxyPacket')
-        self.proxy._ProcessInput(self.proxy._proxyfd)
-        self.assertEqual([x[0] for x in self.proxy.called],
-                ['_GrabPacket', '_HandleProxyPacket'])
-
-
-if not hasattr(select, 'poll'):
-    del SocketTests
diff --git a/pyrad/tests/testServer.py b/pyrad/tests/testServer.py
deleted file mode 100644
index e1c3c68..0000000
--- a/pyrad/tests/testServer.py
+++ /dev/null
@@ -1,311 +0,0 @@
-import select
-import socket
-import unittest
-from pyrad.packet import PacketError
-from pyrad.server import RemoteHost
-from pyrad.server import Server
-from pyrad.server import ServerPacketError
-from pyrad.tests.mock import MockFinished
-from pyrad.tests.mock import MockFd
-from pyrad.tests.mock import MockPoll
-from pyrad.tests.mock import MockSocket
-from pyrad.tests.mock import MockClassMethod
-from pyrad.tests.mock import UnmockClassMethods
-from pyrad.packet import AccessRequest
-from pyrad.packet import AccountingRequest
-
-
-class TrivialObject:
-    """dummy objec"""
-
-
-class RemoteHostTests(unittest.TestCase):
-    def testSimpleConstruction(self):
-        host = RemoteHost('address', 'secret', 'name', 'authport', 'acctport', 'coaport')
-        self.assertEqual(host.address, 'address')
-        self.assertEqual(host.secret, 'secret')
-        self.assertEqual(host.name, 'name')
-        self.assertEqual(host.authport, 'authport')
-        self.assertEqual(host.acctport, 'acctport')
-        self.assertEqual(host.coaport, 'coaport')
-
-    def testNamedConstruction(self):
-        host = RemoteHost(address='address', secret='secret', name='name',
-               authport='authport', acctport='acctport', coaport='coaport')
-        self.assertEqual(host.address, 'address')
-        self.assertEqual(host.secret, 'secret')
-        self.assertEqual(host.name, 'name')
-        self.assertEqual(host.authport, 'authport')
-        self.assertEqual(host.acctport, 'acctport')
-        self.assertEqual(host.coaport, 'coaport')
-
-class ServerConstructiontests(unittest.TestCase):
-    def testSimpleConstruction(self):
-        server = Server()
-        self.assertEqual(server.authfds, [])
-        self.assertEqual(server.acctfds, [])
-        self.assertEqual(server.authport, 1812)
-        self.assertEqual(server.acctport, 1813)
-        self.assertEqual(server.coaport, 3799)
-        self.assertEqual(server.hosts, {})
-
-    def testParameterOrder(self):
-        server = Server([], 'authport', 'acctport', 'coaport','hosts', 'dict')
-        self.assertEqual(server.authfds, [])
-        self.assertEqual(server.acctfds, [])
-        self.assertEqual(server.authport, 'authport')
-        self.assertEqual(server.acctport, 'acctport')
-        self.assertEqual(server.coaport, 'coaport')
-        self.assertEqual(server.dict, 'dict')
-
-    def testBindDuringConstruction(self):
-        def BindToAddress(self, addr):
-            self.bound.append(addr)
-        bta = Server.BindToAddress
-        Server.BindToAddress = BindToAddress
-
-        Server.bound = []
-        server = Server(['one', 'two', 'three'])
-        self.assertEqual(server.bound, ['one', 'two', 'three'])
-        del Server.bound
-
-        Server.BindToAddress = bta
-
-
-class SocketTests(unittest.TestCase):
-    def setUp(self):
-        self.orgsocket = socket.socket
-        socket.socket = MockSocket
-        self.server = Server()
-
-    def tearDown(self):
-        socket.socket = self.orgsocket
-
-    def testBind(self):
-        self.server.BindToAddress('192.168.13.13')
-        self.assertEqual(len(self.server.authfds), 1)
-        self.assertEqual(self.server.authfds[0].address,
-                ('192.168.13.13', 1812))
-
-        self.assertEqual(len(self.server.acctfds), 1)
-        self.assertEqual(self.server.acctfds[0].address,
-                ('192.168.13.13', 1813))
-
-    def testGrabPacket(self):
-        def gen(data):
-            res = TrivialObject()
-            res.data = data
-            return res
-
-        fd = MockFd()
-        fd.source = object()
-        pkt = self.server._GrabPacket(gen, fd)
-        self.failUnless(isinstance(pkt, TrivialObject))
-        self.failUnless(pkt.fd is fd)
-        self.failUnless(pkt.source is fd.source)
-        self.failUnless(pkt.data is fd.data)
-
-    def testPrepareSocketNoFds(self):
-        self.server._poll = MockPoll()
-        self.server._PrepareSockets()
-
-        self.assertEqual(self.server._poll.registry, [])
-        self.assertEqual(self.server._realauthfds, [])
-        self.assertEqual(self.server._realacctfds, [])
-
-    def testPrepareSocketAuthFds(self):
-        self.server._poll = MockPoll()
-        self.server._fdmap = {}
-        self.server.authfds = [MockFd(12), MockFd(14)]
-        self.server._PrepareSockets()
-
-        self.assertEqual(list(self.server._fdmap.keys()), [12, 14])
-        self.assertEqual(self.server._poll.registry,
-                [(12, select.POLLIN | select.POLLPRI | select.POLLERR),
-                 (14, select.POLLIN | select.POLLPRI | select.POLLERR)])
-
-    def testPrepareSocketAcctFds(self):
-        self.server._poll = MockPoll()
-        self.server._fdmap = {}
-        self.server.acctfds = [MockFd(12), MockFd(14)]
-        self.server._PrepareSockets()
-
-        self.assertEqual(list(self.server._fdmap.keys()), [12, 14])
-        self.assertEqual(self.server._poll.registry,
-                [(12, select.POLLIN | select.POLLPRI | select.POLLERR),
-                 (14, select.POLLIN | select.POLLPRI | select.POLLERR)])
-
-
-class AuthPacketHandlingTests(unittest.TestCase):
-    def setUp(self):
-        self.server = Server()
-        self.server.hosts['host'] = TrivialObject()
-        self.server.hosts['host'].secret = 'supersecret'
-        self.packet = TrivialObject()
-        self.packet.code = AccessRequest
-        self.packet.source = ('host', 'port')
-
-    def testHandleAuthPacketUnknownHost(self):
-        self.packet.source = ('stranger', 'port')
-        try:
-            self.server._HandleAuthPacket(self.packet)
-        except ServerPacketError as e:
-            self.failUnless('unknown host' in str(e))
-        else:
-            self.fail()
-
-    def testHandleAuthPacketWrongPort(self):
-        self.packet.code = AccountingRequest
-        try:
-            self.server._HandleAuthPacket(self.packet)
-        except ServerPacketError as e:
-            self.failUnless('port' in str(e))
-        else:
-            self.fail()
-
-    def testHandleAuthPacket(self):
-        def HandleAuthPacket(self, pkt):
-            self.handled = pkt
-        hap = Server.HandleAuthPacket
-        Server.HandleAuthPacket = HandleAuthPacket
-
-        self.server._HandleAuthPacket(self.packet)
-        self.failUnless(self.server.handled is self.packet)
-
-        Server.HandleAuthPacket = hap
-
-
-class AcctPacketHandlingTests(unittest.TestCase):
-    def setUp(self):
-        self.server = Server()
-        self.server.hosts['host'] = TrivialObject()
-        self.server.hosts['host'].secret = 'supersecret'
-        self.packet = TrivialObject()
-        self.packet.code = AccountingRequest
-        self.packet.source = ('host', 'port')
-
-    def testHandleAcctPacketUnknownHost(self):
-        self.packet.source = ('stranger', 'port')
-        try:
-            self.server._HandleAcctPacket(self.packet)
-        except ServerPacketError as e:
-            self.failUnless('unknown host' in str(e))
-        else:
-            self.fail()
-
-    def testHandleAcctPacketWrongPort(self):
-        self.packet.code = AccessRequest
-        try:
-            self.server._HandleAcctPacket(self.packet)
-        except ServerPacketError as e:
-            self.failUnless('port' in str(e))
-        else:
-            self.fail()
-
-    def testHandleAcctPacket(self):
-        def HandleAcctPacket(self, pkt):
-            self.handled = pkt
-        hap = Server.HandleAcctPacket
-        Server.HandleAcctPacket = HandleAcctPacket
-
-        self.server._HandleAcctPacket(self.packet)
-        self.failUnless(self.server.handled is self.packet)
-
-        Server.HandleAcctPacket = hap
-
-
-class OtherTests(unittest.TestCase):
-    def setUp(self):
-        self.server = Server()
-
-    def tearDown(self):
-        UnmockClassMethods(Server)
-
-    def testCreateReplyPacket(self):
-        class TrivialPacket:
-            source = object()
-
-            def CreateReply(self, **kw):
-                reply = TrivialObject()
-                reply.kw = kw
-                return reply
-
-        reply = self.server.CreateReplyPacket(TrivialPacket(),
-                one='one', two='two')
-        self.failUnless(isinstance(reply, TrivialObject))
-        self.failUnless(reply.source is TrivialPacket.source)
-        self.assertEqual(reply.kw, dict(one='one', two='two'))
-
-    def testAuthProcessInput(self):
-        fd = MockFd(1)
-        self.server._realauthfds = [1]
-        MockClassMethod(Server, '_GrabPacket')
-        MockClassMethod(Server, '_HandleAuthPacket')
-
-        self.server._ProcessInput(fd)
-        self.assertEqual([x[0] for x in self.server.called],
-                ['_GrabPacket', '_HandleAuthPacket'])
-        self.assertEqual(self.server.called[0][1][1], fd)
-
-    def testAcctProcessInput(self):
-        fd = MockFd(1)
-        self.server._realauthfds = []
-        self.server._realacctfds = [1]
-        MockClassMethod(Server, '_GrabPacket')
-        MockClassMethod(Server, '_HandleAcctPacket')
-
-        self.server._ProcessInput(fd)
-        self.assertEqual([x[0] for x in self.server.called],
-                ['_GrabPacket', '_HandleAcctPacket'])
-        self.assertEqual(self.server.called[0][1][1], fd)
-
-
-class ServerRunTests(unittest.TestCase):
-    def setUp(self):
-        self.server = Server()
-        self.origpoll = select.poll
-        select.poll = MockPoll
-
-    def tearDown(self):
-        MockPoll.results = []
-        select.poll = self.origpoll
-        UnmockClassMethods(Server)
-
-    def testRunInitializes(self):
-        MockClassMethod(Server, '_PrepareSockets')
-        self.assertRaises(MockFinished, self.server.Run)
-        self.assertEqual(self.server.called, [('_PrepareSockets', (), {})])
-        self.failUnless(isinstance(self.server._fdmap, dict))
-        self.failUnless(isinstance(self.server._poll, MockPoll))
-
-    def testRunIgnoresPollErrors(self):
-        self.server.authfds = [MockFd()]
-        MockPoll.results = [(0, select.POLLERR)]
-        self.assertRaises(MockFinished, self.server.Run)
-
-    def testRunIgnoresServerPacketErrors(self):
-        def RaisePacketError(self, fd):
-            raise ServerPacketError
-        MockClassMethod(Server, '_ProcessInput', RaisePacketError)
-        self.server.authfds = [MockFd()]
-        MockPoll.results = [(0, select.POLLIN)]
-        self.assertRaises(MockFinished, self.server.Run)
-
-    def testRunIgnoresPacketErrors(self):
-        def RaisePacketError(self, fd):
-            raise PacketError
-        MockClassMethod(Server, '_ProcessInput', RaisePacketError)
-        self.server.authfds = [MockFd()]
-        MockPoll.results = [(0, select.POLLIN)]
-        self.assertRaises(MockFinished, self.server.Run)
-
-    def testRunRunsProcessInput(self):
-        MockClassMethod(Server, '_ProcessInput')
-        self.server.authfds = fd = [MockFd()]
-        MockPoll.results = [(0, select.POLLIN)]
-        self.assertRaises(MockFinished, self.server.Run)
-        self.assertEqual(self.server.called, [('_ProcessInput', (fd[0],), {})])
-
-if not hasattr(select, 'poll'):
-    del SocketTests
-    del ServerRunTests
diff --git a/pyrad/tests/testTools.py b/pyrad/tests/testTools.py
deleted file mode 100644
index 581ef53..0000000
--- a/pyrad/tests/testTools.py
+++ /dev/null
@@ -1,106 +0,0 @@
-from netaddr import AddrFormatError
-from pyrad import tools
-import unittest
-import six
-import sys
-
-
-
-class EncodingTests(unittest.TestCase):
-    def testStringEncoding(self):
-        self.assertRaises(ValueError, tools.EncodeString, 'x' * 254)
-        self.assertEqual(
-                tools.EncodeString('1234567890'),
-                six.b('1234567890'))
-
-    def testInvalidStringEncodingRaisesTypeError(self):
-        self.assertRaises(TypeError, tools.EncodeString, 1)
-
-    def testAddressEncoding(self):
-        self.assertRaises(AddrFormatError, tools.EncodeAddress, 'TEST123')
-        self.assertEqual(
-                tools.EncodeAddress('192.168.0.255'),
-                six.b('\xc0\xa8\x00\xff'))
-
-    def testInvalidAddressEncodingRaisesTypeError(self):
-        self.assertRaises(TypeError, tools.EncodeAddress, 1)
-
-    def testIntegerEncoding(self):
-        self.assertEqual(tools.EncodeInteger(0x01020304), six.b('\x01\x02\x03\x04'))
-
-    def testUnsignedIntegerEncoding(self):
-        self.assertEqual(tools.EncodeInteger(0xFFFFFFFF), six.b('\xff\xff\xff\xff'))
-
-    def testInvalidIntegerEncodingRaisesTypeError(self):
-        self.assertRaises(TypeError, tools.EncodeInteger, 'ONE')
-
-    def testDateEncoding(self):
-        self.assertEqual(tools.EncodeDate(0x01020304), six.b('\x01\x02\x03\x04'))
-
-    def testInvalidDataEncodingRaisesTypeError(self):
-        self.assertRaises(TypeError, tools.EncodeDate, '1')
-
-    def testEncodeAscendBinary(self):
-        self.assertEqual(
-            tools.EncodeAscendBinary('family=ipv4 action=discard direction=in dst=10.10.255.254/32'),
-            six.b('\x01\x00\x01\x00\x00\x00\x00\x00\n\n\xff\xfe\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'))
-
-    def testStringDecoding(self):
-        self.assertEqual(
-                tools.DecodeString(six.b('1234567890')),
-                '1234567890')
-
-    def testAddressDecoding(self):
-        self.assertEqual(
-                tools.DecodeAddress(six.b('\xc0\xa8\x00\xff')),
-                '192.168.0.255')
-
-    def testIntegerDecoding(self):
-        self.assertEqual(
-                tools.DecodeInteger(six.b('\x01\x02\x03\x04')),
-                0x01020304)
-
-    def testDateDecoding(self):
-        self.assertEqual(
-                tools.DecodeDate(six.b('\x01\x02\x03\x04')),
-                0x01020304)
-
-    def testUnknownTypeEncoding(self):
-        self.assertRaises(ValueError, tools.EncodeAttr, 'unknown', None)
-
-    def testUnknownTypeDecoding(self):
-        self.assertRaises(ValueError, tools.DecodeAttr, 'unknown', None)
-
-    def testEncodeFunction(self):
-        self.assertEqual(
-                tools.EncodeAttr('string', six.u('string')),
-                six.b('string'))
-        self.assertEqual(
-                tools.EncodeAttr('octets', six.b('string')),
-                six.b('string'))
-        self.assertEqual(
-                tools.EncodeAttr('ipaddr', '192.168.0.255'),
-                six.b('\xc0\xa8\x00\xff'))
-        self.assertEqual(
-                tools.EncodeAttr('integer', 0x01020304),
-                six.b('\x01\x02\x03\x04'))
-        self.assertEqual(
-                tools.EncodeAttr('date', 0x01020304),
-                six.b('\x01\x02\x03\x04'))
-
-    def testDecodeFunction(self):
-        self.assertEqual(
-                tools.DecodeAttr('string', six.b('string')),
-                six.u('string'))
-        self.assertEqual(
-                tools.EncodeAttr('octets', six.b('string')),
-                six.b('string'))
-        self.assertEqual(
-                tools.DecodeAttr('ipaddr', six.b('\xc0\xa8\x00\xff')),
-                '192.168.0.255')
-        self.assertEqual(
-                tools.DecodeAttr('integer', six.b('\x01\x02\x03\x04')),
-                0x01020304)
-        self.assertEqual(
-                tools.DecodeAttr('date', six.b('\x01\x02\x03\x04')),
-                0x01020304)
diff --git a/pyrad/tools.py b/pyrad/tools.py
index e3ec5a8..3ddb33e 100644
--- a/pyrad/tools.py
+++ b/pyrad/tools.py
@@ -110,11 +110,10 @@ def EncodeAscendBinary(str):
             terms[key] = struct.pack('B', int(value))
 
     trailer = 8 * b'\x00'
-    result = b'%s%s%s\x00%s%s%s%s%s\x00%s%s%s%s\x00\x00%s' % (
-        terms['family'], terms['action'], terms['direction'], terms['src'],
-        terms['dst'], terms['srcl'], terms['dstl'], terms['proto'],
-        terms['sport'], terms['dport'], terms['sportq'], terms['dportq'],
-        trailer)
+
+    result = b''.join((terms['family'], terms['action'], terms['direction'], b'\x00', 
+        terms['src'], terms['dst'], terms['srcl'], terms['dstl'], terms['proto'], b'\x00',
+        terms['sport'], terms['dport'], terms['sportq'], terms['dportq'], b'\x00\x00', trailer))
     return result
 
 
@@ -125,6 +124,12 @@ def EncodeInteger(num, format='!I'):
         raise TypeError('Can not encode non-integer as integer')
     return struct.pack(format, num)
 
+def EncodeInteger64(num, format='!Q'):
+    try:
+        num = int(num)
+    except:
+        raise TypeError('Can not encode non-integer as integer64')
+    return struct.pack(format, num)
 
 def EncodeDate(num):
     if not isinstance(num, int):
@@ -149,13 +154,13 @@ def DecodeAddress(addr):
 
 def DecodeIPv6Prefix(addr):
     addr = addr + b'\x00' * (18-len(addr))
-    _, length, prefix = ':'.join(map('{:x}'.format, struct.unpack('!BB'+'H'*8, addr))).split(":", 2)
+    _, length, prefix = ':'.join(map('{0:x}'.format, struct.unpack('!BB'+'H'*8, addr))).split(":", 2)
     return str(IPNetwork("%s/%s" % (prefix, int(length, 16))))
 
 
 def DecodeIPv6Address(addr):
     addr = addr + b'\x00' * (16-len(addr))
-    prefix = ':'.join(map('{:x}'.format, struct.unpack('!'+'H'*8, addr)))
+    prefix = ':'.join(map('{0:x}'.format, struct.unpack('!'+'H'*8, addr)))
     return str(IPAddress(prefix))
 
 
@@ -166,6 +171,8 @@ def DecodeAscendBinary(str):
 def DecodeInteger(num, format='!I'):
     return (struct.unpack(format, num))[0]
 
+def DecodeInteger64(num, format='!Q'):
+    return (struct.unpack(format, num))[0]
 
 def DecodeDate(num):
     return (struct.unpack('!I', num))[0]
@@ -194,6 +201,8 @@ def EncodeAttr(datatype, value):
         return EncodeInteger(value, '!B')
     elif datatype == 'date':
         return EncodeDate(value)
+    elif datatype == 'integer64':
+        return EncodeInteger64(value)
     else:
         raise ValueError('Unknown attribute type %s' % datatype)
 
@@ -221,5 +230,7 @@ def DecodeAttr(datatype, value):
         return DecodeInteger(value, '!B')
     elif datatype == 'date':
         return DecodeDate(value)
+    elif datatype == 'integer64':
+        return DecodeInteger64(value)
     else:
         raise ValueError('Unknown attribute type %s' % datatype)
diff --git a/setup.cfg b/setup.cfg
index 5cf9b0c..45e88ab 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,8 @@
-[nosetests]
-nocapture=1
-where=pyrad/tests
+[flake8]
+max-line-length = 100
+doctests = True
+
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff --git a/setup.py b/setup.py
old mode 100755
new mode 100644
index 2de6266..ba93ffb
--- a/setup.py
+++ b/setup.py
@@ -1,29 +1,28 @@
-#!/usr/bin/python
+#!/usr/bin/env python
 
 from setuptools import setup, find_packages
 
-
-version = '2.1'
-
+import pyrad
 
 setup(name='pyrad',
-      version=version,
-      author='Wichert Akkerman',
-      author_email='wichert@wiggy.net',
-      url='https://github.com/wichert/pyrad',
+      version=pyrad.__version__,
+      author='Istvan Ruzman, Christian Giese',
+      author_email='istvan@ruzman.eu, developer@gicnet.de',
+      url='https://github.com/pyradius/pyrad',
       license='BSD',
       description='RADIUS tools',
       long_description=open('README.rst').read(),
       classifiers=[
-       'Development Status :: 6 - Mature',
-       'Intended Audience :: Developers',
-       'License :: OSI Approved :: BSD License',
-       'Programming Language :: Python :: 2.6',
-       'Programming Language :: Python :: 2.7',
-       'Programming Language :: Python :: 3.2',
-       'Topic :: Software Development :: Libraries :: Python Modules',
-       'Topic :: System :: Systems Administration :: Authentication/Directory',
-       ],
+          'Development Status :: 6 - Mature',
+          'Intended Audience :: Developers',
+          'License :: OSI Approved :: BSD License',
+          'Programming Language :: Python :: 2.7',
+          'Programming Language :: Python :: 3.6',
+          'Programming Language :: Python :: 3.7',
+          'Programming Language :: Python :: 3.8',
+          'Topic :: Software Development :: Libraries :: Python Modules',
+          'Topic :: System :: Systems Administration :: Authentication/Directory',
+      ],
       packages=find_packages(exclude=['tests']),
       keywords=['radius', 'authentication'],
       zip_safe=True,
@@ -31,4 +30,4 @@ setup(name='pyrad',
       install_requires=['six', 'netaddr'],
       tests_require='nose>=0.10.0b1',
       test_suite='nose.collector',
-      )
+      )
\ No newline at end of file

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad-2.4.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad-2.4.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad-2.4.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad-2.4.egg-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad-2.4.egg-info/zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/client_async.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/server_async.py

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad-2.1.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad-2.1.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad-2.1.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad-2.1.egg-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad-2.1.egg-info/zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/tests/__init__.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/tests/mock.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/tests/testBidict.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/tests/testClient.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/tests/testDictionary.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/tests/testHost.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/tests/testPacket.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/tests/testProxy.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/tests/testServer.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/pyrad/tests/testTools.py
-rw-r--r--  root/root   /usr/share/doc/python3-pyrad/changelog.gz

No differences were encountered in the control files

More details

Full run details