Merge branch 'master' into pcap_binary_mode
diff --git a/.coveralls.yml b/.coveralls.yml
deleted file mode 100644
index 6e71f53..0000000
--- a/.coveralls.yml
+++ /dev/null
@@ -1 +0,0 @@
-repo_token: D8kiOhp6QNkHx6D0A54t89ER98XWTTgT8
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..5bf96b6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,30 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Details(please complete the following information):**
+ - OS: [e.g. Windows10, Debian, etc]
+ - Python Version [e.g. 3.8.6 ]
+ - Please upload any pcap files that are needed to reproduce the issue. 
+   (Please try to have just the offending packets in the pcap to reduce upload size)
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..8f8f3a8
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,67 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [ master ]
+  schedule:
+    - cron: '17 3 * * 5'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ 'python' ]
+        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+        # Learn more:
+        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v2
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v1
+      with:
+        languages: ${{ matrix.language }}
+        # If you wish to specify custom queries, you can do so here or in a config file.
+        # By default, queries listed here will override any specified in a config file.
+        # Prefix the list here with "+" to use these queries and those in the config file.
+        # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v1
+
+    # ℹī¸ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
new file mode 100644
index 0000000..931805f
--- /dev/null
+++ b/.github/workflows/python-package.yml
@@ -0,0 +1,57 @@
+# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: Python package
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ['2.7', '3.5', '3.6', '3.7', '3.8', '3.9', 'pypy3']
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python-version }}
+
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        python -m pip install flake8 pytest coverage pytest-cov
+
+    - name: pytest and generate coverage report
+      run: |
+        coverage run --source dpkt -m pytest dpkt
+
+    - name: Lint with flake8
+      run: |
+        # stop the build if there are Python syntax errors or undefined names
+        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+
+    - name: Coveralls Python
+      uses: AndreMiras/coveralls-python-action@v20201129
+      with:
+        parallel: true
+        flag-name: Unit Test - Python ${{ matrix.python-version }}
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+
+  coveralls_finish:
+    needs: build
+    runs-on: ubuntu-latest
+    steps:
+    - name: Push to Coveralls
+      uses: AndreMiras/coveralls-python-action@develop
+      with:
+        parallel-finished: true
diff --git a/.gitignore b/.gitignore
index e84fb50..a84e5c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,15 @@
 
 # ReadTheDocs build directory
 _build
+
+# IntelliJ project files 
+*.iml
+*.iws
+*.ipr
+.idea/
+
+# eclipse project file
+.settings/
+.classpath 
+.project
+.pydevproject
\ No newline at end of file
diff --git a/.landscape.yml b/.landscape.yml
deleted file mode 100644
index f87c2f8..0000000
--- a/.landscape.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-doc-warnings: yes
-test-warnings: yes
-strictness: low
-max-line-length: 140
-autodetect: yes
-ignore-paths:
-    - docs
-pep8:
-    disable:
-        - E704
diff --git a/.prospector.yaml b/.prospector.yaml
deleted file mode 100644
index a32a146..0000000
--- a/.prospector.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-pep8:
-  disable:
-    - E1101
-    - E1103
-pylint:
-  disable:
-     - E1101
-     - E1103
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 12ebce7..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-language: python
-matrix:
-  global:
-    LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so
-  include:
-    - python: "2.6"
-      env: TOXENV=py26
-    - python: "2.7"
-      env: TOXENV=py27
-    - python: "3.4"
-      env: TOXENV=py34
-    - python: "3.5"
-      env: TOXENV=py35,coveralls, docs
-    - python: "pypy"
-      env: TOXENV=pypy
-before_install:
-  - python --version
-  - virtualenv --version
-  - pip --version
-  - uname -a
-  - lsb_release -a
-install:
-  - pip install tox
-script:
-  - tox -e $TOXENV
-notifications:
-  email:
-    on_success: never
-    on_failure: always
-
diff --git a/AUTHORS b/AUTHORS
index 9ce79a9..23a498c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -59,11 +59,17 @@
 Tim Yardley <yardley@gmail.com>
 	DHCP definitions
 
-obormot <oscar.ibatullin@gmail.com>
-	pcapng module, Packet repr improvements
-	
+Oscar Ibatullin <oscar.ibatullin@gmail.com>
+	pcapng module, core improvements, bit fields, pretty print, creating parsers doc
+
 Kyle Keppler <kyle.keppler@gmail.com>
 	Python 3 port
 
 Hao Sun <sunhao2013@gmail.com>
 	Python 3 port
+
+Brian Wylie <briford.wylie@gmail.com>
+    Examples, Docs, Tests, CI, Python 3 port
+
+crocogorical <crocogorical@gmail.com>
+	Extend test coverage
diff --git a/CHANGES b/CHANGES
deleted file mode 100644
index 371b46f..0000000
--- a/CHANGES
+++ /dev/null
@@ -1,74 +0,0 @@
-dpkt-1.9.0:
-	- add support for Python 3.4, 3.5. Python 2.6 and 2.7 are still supported.
-
-dpkt-1.8:
-	- fix a typo in vrrp.py
-	- fix IPv4 and IPv6 packet to correctly handle zero payload length
-	- store cipher_suite as int in TLSServerHello to allow app-specific messages
-	- improve SSL parsing
- 
-dpkt-1.7:
-	- handle dynamic imports from py2exe/freeze.py/zipped egg
-	  packages, from plotnikoff
-	- decode Ethernet MPLS labels, Cisco ISL VLAN tags, 802.2 LLC fields
-	- handle multiply-defined HTTP headers from simdream
-	- add IPv6 extension header support (minus ESP) from Owen Stephens
-	- add radiotap module from Timur Alperovich
-	- add IEEE80211 module from Jon Oberheide
-	- add RFB module from Jon Oberheide
-	- fix IP6 checksum to include options
-	- rename 'as' to 'asn' field in BGP header
-	- fix transport-layer checksum in IP6
-	- handle improper TCP header offset
-	- fix SSL typo
-	- handle malformed ICMP headers
-	- added RX module from Jon Oberheide
-	- fixed loopback module IP/IP6 decoding
-	- set transport-layer (TCP, UDP) checksum in IP
-	- MRT module fixes
-	- fix pcap.Writer timestamp calculation
-
-dpkt-1.6:
-	- DNS RR packing fixed
-	- added STUN, H.225, TPKT, NTP, RIP, Diameter, SCTP,
-	  BGP, and MRT modules from Jon Oberheide
-	- new dpkt.NeedData exception
-
-dpkt-1.5:
-	- IP6 checksum fix
-	- __getitem__() interface to Packet (e.g. ip['src'] == ip.src)
-	- faster Ethernet, IP, PPP module loading
-	- support any endianness capture file in pcap module,
-	  and export a pypcap-compatible Reader
-	- additional CDP definitions
-	- replaced rtp module with the grugq's version
-	- added QQ module from Robert Stone
-	- added gzip module
-	- added PPPoE module
-	- added RADIUS module
-
-dpkt-1.4:
-	- fix IP checksum bug on i386, caught by Thomas Taranowski
-
-dpkt-1.3:
-	- autoload IP, Ethernet dispatch tables
-	- IP6 bugfixes from Tim Newsham
-	- additional DHCP definitions from Tim Yardley
-	- HTTP bugfixes and abstraction (see SIP)
-	- RPC bugfixes
-	- added pypcap-compatible PcapReader
-	- added Linux libpcap "cooked" capture module
-	- added preliminary SSL module
-	- added SIP module
-	- added SCCP module
-	- added RTP module
-	- added Portmap module
- 
-dpkt-1.2:
-	- changed license from GPL to BSD
-	- added DTP module
-	- added HTTP module
-	- added DNS RR decodes
-	- added enough PPP to decode PPTP GRE encapsulation
-
-# $Id: CHANGES 379 2006-07-27 05:23:19Z dugsong $
diff --git a/Makefile b/Makefile
index 039ee42..5d64da0 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,9 @@
 	rm -fr *.egg-info
 	rm -fr *.tar.gz
 	rm -fr .tox
+	rm -fr .coverage
+	rm -fr .cache
+	rm -fr .pytest_cache
 	find . -name '__pycache__' -exec rm -fr {} +
 
 clean-pyc:
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..888c52c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+# dpkt
+
+![Python package](https://github.com/kbandla/dpkt/workflows/Python%20package/badge.svg)
+[![Coverage Status](https://coveralls.io/repos/github/kbandla/dpkt/badge.svg?branch=master)](https://coveralls.io/github/kbandla/dpkt?branch=master)
+[![supported-versions](https://img.shields.io/pypi/pyversions/dpkt.svg)](https://pypi.python.org/pypi/dpkt)
+[![supported-versions](https://img.shields.io/pypi/implementation/dpkt.svg)](https://pypi.python.org/pypi/dpkt)
+
+The dpkt project is a python module for fast, simple packet parsing, with definitions for the basic TCP/IP protocols.
+
+### Installation
+```
+pip install dpkt
+```
+
+### Examples and Documentation
+- [Main Docs for DPKT](https://kbandla.github.io/dpkt)
+
+
+
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 48a93a9..0000000
--- a/README.rst
+++ /dev/null
@@ -1,69 +0,0 @@
-
-====
-dpkt
-====
-
-| |travis| |coveralls| |landscape| |version|
-| |wheel| |supported-versions| |supported-implementations|
-
-.. |travis| image:: http://img.shields.io/travis/kbandla/dpkt.svg
-    :alt: Travis-CI Build Status
-    :target: https://travis-ci.org/kbandla/dpkt
-
-.. |coveralls| image:: http://img.shields.io/coveralls/kbandla/dpkt.svg
-    :alt: Coverage Status
-    :target: https://coveralls.io/r/kbandla/dpkt
-
-.. |landscape| image:: https://landscape.io/github/kbandla/dpkt/master/landscape.svg
-    :target: https://landscape.io/github/kbandla/dpkt/master
-    :alt: Code Quality Status
-
-.. |version| image:: http://img.shields.io/pypi/v/dpkt.svg
-    :alt: PyPI Package latest release
-    :target: https://pypi.python.org/pypi/dpkt
-
-.. |wheel| image:: https://img.shields.io/pypi/wheel/dpkt.svg 
-    :alt: PyPI Wheel
-    :target: https://pypi.python.org/pypi/dpkt
-
-.. |supported-versions| image:: https://img.shields.io/pypi/pyversions/dpkt.svg 
-    :alt: Supported versions
-    :target: https://pypi.python.org/pypi/dpkt
-
-.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/dpkt.svg
-    :alt: Supported implementations
-    :target: https://pypi.python.org/pypi/dpkt
-
-Installation
-============
-
-::
-
-    pip install dpkt
-
-Documentation
-=============
-
-https://dpkt.readthedocs.org/
-
-Recent News
-===========
-The DPKT code base now supports both Python2 and Python3 thanks to the efforts of @kylekeppler @jonathanslenders @sunhao2014 and many `more <https://github.com/kbandla/dpkt/graphs/contributors>`__.
-
-Given the large amount of work that went into the Python3 support there's bound to be a few wrinkles that crop up.
-
-- Please submit an Issue if you find a problem
-- The legacy/stable release is dpkt==1.8.8 (https://github.com/kbandla/dpkt/releases), please use this release if you have any troubles.
-
-About
-=====
-
-This code is based on `dpkt code <https://code.google.com/p/dpkt/>`__ lead by Dug Song and 
-is now being maintained and improved by an extended set of 
-`contributors <https://dpkt.readthedocs.org/en/latest/authors.html>`__
-and `developers <https://github.com/kbandla/dpkt/graphs/contributors>`__.
-
-LICENSE
--------
-
-BSD 3-Clause
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index 2687c75..0000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,153 +0,0 @@
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS    =
-SPHINXBUILD   = sphinx-build
-PAPER         =
-BUILDDIR      = _build
-
-# Internal variables.
-PAPEROPT_a4     = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-# the i18n builder cannot share the environment and doctrees with the others
-I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
-
-help:
-	@echo "Please use \`make <target>' where <target> is one of"
-	@echo "  html       to make standalone HTML files"
-	@echo "  dirhtml    to make HTML files named index.html in directories"
-	@echo "  singlehtml to make a single large HTML file"
-	@echo "  pickle     to make pickle files"
-	@echo "  json       to make JSON files"
-	@echo "  htmlhelp   to make HTML files and a HTML help project"
-	@echo "  qthelp     to make HTML files and a qthelp project"
-	@echo "  devhelp    to make HTML files and a Devhelp project"
-	@echo "  epub       to make an epub"
-	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
-	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
-	@echo "  text       to make text files"
-	@echo "  man        to make manual pages"
-	@echo "  texinfo    to make Texinfo files"
-	@echo "  info       to make Texinfo files and run them through makeinfo"
-	@echo "  gettext    to make PO message catalogs"
-	@echo "  changes    to make an overview of all changed/added/deprecated items"
-	@echo "  linkcheck  to check all external links for integrity"
-	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
-
-clean:
-	-rm -rf $(BUILDDIR)/*
-
-html:
-	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
-	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
-	@echo
-	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
-	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
-	@echo
-	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
-	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
-	@echo
-	@echo "Build finished; now you can process the pickle files."
-
-json:
-	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
-	@echo
-	@echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
-	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
-	@echo
-	@echo "Build finished; now you can run HTML Help Workshop with the" \
-	      ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
-	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
-	@echo
-	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
-	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
-	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyspotify.qhcp"
-	@echo "To view the help file:"
-	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyspotify.qhc"
-
-devhelp:
-	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
-	@echo
-	@echo "Build finished."
-	@echo "To view the help file:"
-	@echo "# mkdir -p $$HOME/.local/share/devhelp/pyspotify"
-	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyspotify"
-	@echo "# devhelp"
-
-epub:
-	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
-	@echo
-	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo
-	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
-	@echo "Run \`make' in that directory to run these through (pdf)latex" \
-	      "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
-	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
-	@echo "Running LaTeX files through pdflatex..."
-	$(MAKE) -C $(BUILDDIR)/latex all-pdf
-	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
-	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
-	@echo
-	@echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
-	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
-	@echo
-	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-texinfo:
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo
-	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
-	@echo "Run \`make' in that directory to run these through makeinfo" \
-	      "(use \`make info' here to do that automatically)."
-
-info:
-	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
-	@echo "Running Texinfo files through makeinfo..."
-	make -C $(BUILDDIR)/texinfo info
-	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
-
-gettext:
-	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
-	@echo
-	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
-
-changes:
-	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
-	@echo
-	@echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
-	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
-	@echo
-	@echo "Link check complete; look for any errors in the above output " \
-	      "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
-	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
-	@echo "Testing of doctests in the sources finished, look at the " \
-	      "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000..fc24e7a
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-hacker
\ No newline at end of file
diff --git a/docs/admin_notes.md b/docs/admin_notes.md
new file mode 100644
index 0000000..b8b9792
--- /dev/null
+++ b/docs/admin_notes.md
@@ -0,0 +1,85 @@
+# Notes
+
+## PyPI Release How-To
+
+Notes and information on how to do the PyPI release for the dpkt
+project. For full details on packaging you can reference this page
+[Packaging](https://packaging.python.org/tutorials/packaging-projects/#packaging-your-project)
+
+The following instructions should work, but things change :)
+
+### Package Requirements
+
+``` bash
+- pip install tox
+- pip install --upgrade setuptools wheel
+- pip install twine
+```
+
+### Setup pypirc
+
+The easiest thing to do is setup a \~/.pypirc file with the following
+contents
+
+``` bash
+[distutils]
+index-servers =
+  pypi
+  testpypi
+
+[pypi]
+repository=https://upload.pypi.org/legacy/
+username=<pypi username>
+password=<pypi password>
+
+[testpypi]
+repository=https://test.pypi.org/legacy/
+username=<pypi username>
+password=<pypi password>
+```
+
+### Tox Background
+
+Tox will install the dpkt package into a blank virtualenv and then
+execute all the tests against the newly installed package. So if
+everything goes okay, you know the pypi package installed fine and the
+tests (which pull from the installed dpkt package) also ran okay.
+
+### Make sure ALL tests pass
+
+``` bash
+$ cd dpkt
+$ tox 
+```
+
+If ALL the test above pass...
+
+### Create the TEST PyPI Release
+
+``` bash
+$ vi dpkt/__init__.py and bump the version
+$ python setup.py sdist bdist_wheel
+$ twine upload dist/* -r testpypi
+```
+
+### Install the TEST PyPI Release
+
+``` bash
+$ pip install --index-url https://test.pypi.org/simple dpkt
+```
+
+### Create the REAL PyPI Release
+
+``` bash
+$ twine upload dist/* -r pypi
+```
+
+### Push changes to Github
+
+``` bash
+$ git add dpkt/__init__.py
+$ get commit -m "dpkt version 1.8.7 (or whatever)"
+$ git tag v1.8.7 (or whatever)
+$ git push --tags
+$ git push
+```
diff --git a/docs/admin_notes.rst b/docs/admin_notes.rst
deleted file mode 100644
index c0a5aec..0000000
--- a/docs/admin_notes.rst
+++ /dev/null
@@ -1,42 +0,0 @@
-
-Notes
-======
-
-PyPI Release How-To
--------------------
-Notes and information on how to do the PyPI release for the dpkt project.
-
-Package Requirements
-~~~~~~~~~~~~~~~~~~~~
-
-- pip install tox
-- pip install wheel
-
-Tox Background
-~~~~~~~~~~~~~~
-Tox will install the dpkt package into a blank virtualenv and then execute all the tests against the newly installed package. So if everything goes okay, you know the pypi package installed fine and the tests (which pull from the installed dpkt package) also ran okay.
-
-Create the PyPI Release
-~~~~~~~~~~~~~~~~~~~~~~~
-.. code-block:: bash
-
- $ cd dpkt
- $ tox 
- $ vi dpkt/__init__.py and bump the version
- $ python setup.py release
-   <enter your pypi password>
-
-If everything above went okay...
-
-.. code-block:: bash
-
- $ git add dpkt/__init__.py
- $ get commit -m "dpkt version 1.8.7 (or whatever)"
- $ git tag v1.8.7 (or whatever)
- $ git push --tags
- $ git push
- 
-Git Releases (discussion)
-~~~~~~~~~~~~~~~~~~~~~~~~~
-You can also do a 'release' on GitHub (the tags above are perfect for that). In general this is discouraged, people should always do a $pip install dpkt. If people want older releases they can do a $pip install dpkt==<old version>. Providing tarballs/zip file on GitHub will just confuse new users and they'll have a 'bad experience' when trying to deal with a tarball.
-
diff --git a/docs/api/api_auto.rst b/docs/api/api_auto.rst
index 3837c90..ead17f2 100644
--- a/docs/api/api_auto.rst
+++ b/docs/api/api_auto.rst
@@ -79,14 +79,6 @@
     :undoc-members:
     :show-inheritance:
 
-dpkt.decorators module
-----------------------
-
-.. automodule:: dpkt.decorators
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
 dpkt.dhcp module
 ----------------
 
diff --git a/docs/authors.md b/docs/authors.md
new file mode 100644
index 0000000..c9d9157
--- /dev/null
+++ b/docs/authors.md
@@ -0,0 +1,76 @@
+# Authors
+
+## Original author
+
+Dug Song \<<dugsong@monkey.org>\>
+
+## Contributors
+
+  - Timur Alperovich \<<timuralp@umich.edu>\>  
+    radiotap module
+
+  - Nic Bellamy \<<nic.bellamy@vadacom.co.nz>\>  
+    HTTP header parsing fix
+
+  - the grugq \<<thegrugq@gmail.com>\>  
+    better RTP module
+
+  - David Helder \<<dhelder@gizmolabs.org>\>  
+    bug fixes
+
+  - Przemyslaw Karwasiecki \<<karwas@gmail.com>\>  
+    TABLE\_DUMP in MRT module
+
+  - Reza Lotun \<<rlotun@cs.ubc.ca>\>  
+    MetaPacket cleanup
+
+  - Jeff Nathan \<<jeff@snort.org>\>  
+    bug fixes
+
+  - Tim Newsham \<<newsham@lava.net>\>  
+    IPv6 bugfixing and improvements
+
+  - <keisuke.nishimoto@gmail.com>  
+    Snoop file parser
+
+  - Jon Oberheide \<<jon@oberheide.org>\>  
+    STUN, H.225, TPKT, NTP, RIP, Diameter, SCTP, BGP, MRT, RX modules
+
+  - <plotnikoff@gmail.com>  
+    handle dynamic imports from py2exe/freeze.py/zipped egg packages
+
+  - <simdream@gmail.com>  
+    handle multiple cookie values in HTTP
+
+  - Owen Stephens \<<owen@owenstephens.co.uk>\>  
+    IP6 extension header support
+
+  - Robert Stone \<<otaku@monkey.org>\>  
+    Netflow and QQ modules
+
+  - Thomas Taranowski \<<thomastaranowski@yahoo.com>\>  
+    dnet IP checksum bug on i386
+
+  - Jirka Vejrazka  
+    bug fixes
+
+  - Tim Yardley \<<yardley@gmail.com>\>  
+    DHCP definitions
+
+  - Oscar Ibatullin \<<oscar.ibatullin@gmail.com>\>  
+    pcapng module, core improvements, bit fields, pretty print, creating
+    parsers doc
+
+  - Kyle Keppler \<<kyle.keppler@gmail.com>\>  
+    Python 3 port
+
+  - Hao Sun \<<sunhao2013@gmail.com>\>  
+    Python 3 port
+
+  - Brian Wylie \<<briford.wylie@gmail.com>\>  
+    Examples, Docs, Tests, CI, Python 3 port
+
+  - crocogorical \<<crocogorical@gmail.com>\>  
+    Extend test coverage
+
+If you want to contribute to dpkt, see `contributing`.
diff --git a/docs/authors.rst b/docs/authors.rst
deleted file mode 100644
index 238aa6b..0000000
--- a/docs/authors.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-*******
-Authors
-*******
-
-.. include:: ../AUTHORS
-
-If you want to contribute to dpkt, see :doc:`contributing`.
diff --git a/docs/badges.md b/docs/badges.md
new file mode 100644
index 0000000..b9dea1c
--- /dev/null
+++ b/docs/badges.md
@@ -0,0 +1,16 @@
+[![Travis-CI Build
+Status](http://img.shields.io/travis/kbandla/dpkt.svg)](https://travis-ci.org/kbandla/dpkt)
+[![Coverage
+Status](http://img.shields.io/coveralls/kbandla/dpkt.svg)](https://coveralls.io/r/kbandla/dpkt)
+[![Code Quality
+Status](https://landscape.io/github/kbandla/dpkt/master/landscape.svg)](https://landscape.io/github/kbandla/dpkt/master)
+[![PyPI Package monthly
+downloads](http://img.shields.io/pypi/dm/dpkt.svg)](https://pypi.python.org/pypi/dpkt)  
+[![PyPI Package latest
+release](http://img.shields.io/pypi/v/dpkt.svg)](https://pypi.python.org/pypi/dpkt)
+[![PyPI
+Wheel](https://img.shields.io/pypi/wheel/dpkt.svg)](https://pypi.python.org/pypi/dpkt)
+[![Supported
+versions](https://img.shields.io/pypi/pyversions/dpkt.svg)](https://pypi.python.org/pypi/dpkt)
+[![Supported
+implementations](https://img.shields.io/pypi/implementation/dpkt.svg)](https://pypi.python.org/pypi/dpkt)
diff --git a/docs/badges.rst b/docs/badges.rst
deleted file mode 100644
index 00618d1..0000000
--- a/docs/badges.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-| |travis| |coveralls| |landscape| |downloads| 
-| |version| |wheel| |supported-versions| |supported-implementations|
-
-.. |travis| image:: http://img.shields.io/travis/kbandla/dpkt.svg
-    :alt: Travis-CI Build Status
-    :target: https://travis-ci.org/kbandla/dpkt
-
-.. |coveralls| image:: http://img.shields.io/coveralls/kbandla/dpkt.svg
-    :alt: Coverage Status
-    :target: https://coveralls.io/r/kbandla/dpkt
-
-.. |landscape| image:: https://landscape.io/github/kbandla/dpkt/master/landscape.svg
-    :target: https://landscape.io/github/kbandla/dpkt/master
-    :alt: Code Quality Status
-
-.. |version| image:: http://img.shields.io/pypi/v/dpkt.svg
-    :alt: PyPI Package latest release
-    :target: https://pypi.python.org/pypi/dpkt
-
-.. |downloads| image:: http://img.shields.io/pypi/dm/dpkt.svg
-    :alt: PyPI Package monthly downloads
-    :target: https://pypi.python.org/pypi/dpkt
-
-.. |wheel| image:: https://img.shields.io/pypi/wheel/dpkt.svg 
-    :alt: PyPI Wheel
-    :target: https://pypi.python.org/pypi/dpkt
-
-.. |supported-versions| image:: https://img.shields.io/pypi/pyversions/dpkt.svg 
-    :alt: Supported versions
-    :target: https://pypi.python.org/pypi/dpkt
-
-.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/dpkt.svg
-    :alt: Supported implementations
-    :target: https://pypi.python.org/pypi/dpkt
\ No newline at end of file
diff --git a/docs/changelog.md b/docs/changelog.md
new file mode 100644
index 0000000..b994528
--- /dev/null
+++ b/docs/changelog.md
@@ -0,0 +1,47 @@
+# Changelog
+
+## 1.9.7.2
+**[2021-08-16]**
+- Fixed performance regression (https://github.com/kbandla/dpkt/issues/611)
+
+## 1.9.7
+**[2021-08-16]**
+- Moved the project documentation from Read the Docs(RST) to github.io(MarkDown)
+- Added a new mechanism for creating bit-sized field definitions in the protocol parsers (Packet.\_\_bit_fields\_\_)
+- Added pretty printing capability aka Packet.pprint(), Packet.\_\_pprint_funcs\_\_
+- Added documentation on developing protocol parsers in dpkt (creating_parsers.md)
+- Added a universal pcap+pcapng reader (dpkt.pcap.UniversalReader)
+- Improved TLS ClientHello and ServerHello parsing: return an "Unknown" ciphersuite instead of raising an exception, add codes for rfc8701, GREASE ciphersutes
+- Added function to get IP protocol name
+- Modified Packet.\_\_getitem\_\_() and added Packet.\_\_contains\_\_() to address the nested protocol layers
+- Fixed payload length interpretation in AH decoder
+- Improved handling of invalid chunks in HTTP and SCTP
+- Fixed decoding of IPv6 fragments after the 1st fragment
+- Support rfc3540 nonce sum flag in TCP
+
+## 1.9.6
+**[2021-05-21]**
+- Added in the TLS 1.3 Cipher Suite from the RFC 8446 dated August 2018
+- Added support for Linux cooked capture v2, SLL2.
+
+## 1.9.5
+**[2021-02-07]** 
+
+- New example showing how to process truncated DNS packets (examples/print_dns_truncated.py).
+- Corrected typo in BGP.notification attribute.
+- BGP.Update.Attribute.MPReachNLRI.SNPA now inherits from dpkt.Packet.
+- Byteorder is now specified when packing GRE optional fields.
+- \#517: Improvement to Radiotap class, supporting multi-byte and misaligned flags fields. Endianness is now enforced.
+- Github issue template added for bug reporting.
+- Compliance with flake8 formatting.
+- asn1.py::utctime method now returns time in UTC, instead of local.
+- Allow multiple InterfaceDescriptionBlocks with pcapng.Writer.
+- SCTP decoder DATA chunk padding aligned to 4-bytes, and improved handling of .data field.
+- IEEE80211 DELBA frame now works on big and little-endian architectures.
+- Introduce compat.ntole which converts from network byte order to little-endian byte order, regardless of host endianness.
+- Ethernet class now attempts to unpack the padding and trailer if present.
+- Added anonymous property to cipher suites, which returns True if the cipher suite starts with 'anon'.
+- Added pfs (Perfect Forward Secrecy) and aead (Authenticated Encryption with Additional Data) properties to cipher suites.
+- Added old CHACHA20-POLY1305 related cipher suites to TLS CipherSuite list.
+- Remove redundant num_compression_methods from TLSClientHello
+- Testing improved from 90% coverage to over 99%.
diff --git a/docs/changelog.rst b/docs/changelog.rst
deleted file mode 100644
index ac7e237..0000000
--- a/docs/changelog.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-*********
-Changelog
-*********
-
diff --git a/docs/conf.py b/docs/conf.py
deleted file mode 100644
index 6dfc9fc..0000000
--- a/docs/conf.py
+++ /dev/null
@@ -1,76 +0,0 @@
-# encoding: utf-8
-
-"""dpkt documentation build configuration file"""
-
-from __future__ import unicode_literals
-
-import os
-import re
-import sys
-import types
-import mock
-
-
-def get_version(filename):
-    init_py = open(filename).read()
-    metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", init_py))
-    return metadata['version']
-
-
-# -- Workarounds to have autodoc generate API docs ----------------------------
-
-sys.path.insert(0, os.path.abspath('..'))
-
-
-# Mock any objects that we might need to
-foo = mock.Mock()
-foo.__version__ = '0.1.1'
-sys.modules['foo'] = foo
-
-
-# -- General configuration ----------------------------------------------------
-needs_sphinx = '1.0'
-extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.extlinks',
-    'sphinx.ext.intersphinx',
-    'sphinx.ext.viewcode',
-    'sphinx.ext.autosummary',
-    'sphinxcontrib.napoleon'
-]
-
-templates_path = ['_templates']
-source_suffix = '.rst'
-master_doc = 'index'
-
-project = 'dpkt'
-copyright = '2009-2015 Dug Song and contributors'
-
-release = get_version('../dpkt/__init__.py')
-version = '.'.join(release.split('.')[:2])
-
-exclude_patterns = ['_build']
-
-pygments_style = 'sphinx'
-
-modindex_common_prefix = ['dpkt.']
-
-autodoc_default_flags = ['members', 'undoc-members', 'show-inheritance']
-autodoc_member_order = 'bysource'
-
-
-# -- Options for HTML output --------------------------------------------------
-html_theme = 'default'
-html_static_path = ['_static']
-
-html_use_modindex = True
-html_use_index = True
-html_split_index = False
-html_show_sourcelink = True
-
-htmlhelp_basename = 'dpkt'
-
-# -- Options for extlink extension --------------------------------------------
-extlinks = {
-    'issue': ('https://github.com/kbandla/dpkt/issues/%s', '#'),
-}
diff --git a/docs/contributing.md b/docs/contributing.md
new file mode 100644
index 0000000..cb889b2
--- /dev/null
+++ b/docs/contributing.md
@@ -0,0 +1,34 @@
+# Contributing
+
+## Report a Bug or Make a Feature Request
+
+Please go to the GitHub Issues page:
+<https://github.com/kbandla/dpkt/issues>.
+
+## Checkout the Code
+
+```
+git clone https://github.com/kbandla/dpkt.git
+```
+
+## Become a Developer
+
+The dpkt package uses the 'GitHub Flow' model: [GitHub
+Flow](http://scottchacon.com/2011/08/31/github-flow.html)
+
+If you'd like to submit a PR to fix/improve dpkt, you should create a
+'fork' of the repository and open a Pull Request (PR) and one of the
+developers will review the PR and give feedback. The following page has good instructions on creating a PR from a fork: make a fork and then create a Pull Request <https://gist.github.com/Chaser324/ce0505fbed06b947d962>.
+
+### New Feature or Bug
+
+```
+$ git checkout -b my-awesome
+$ git push -u origin my-awesome
+$ <code for a bit>; git push
+$ <code for a bit>; git push
+$ pytest dpkt (this will run all the tests)
+```
+
+- Go to github and hit 'New pull request'
+- Someone reviews it and says 'AOK/give feedback and merges
diff --git a/docs/contributing.rst b/docs/contributing.rst
deleted file mode 100644
index 5327b86..0000000
--- a/docs/contributing.rst
+++ /dev/null
@@ -1,40 +0,0 @@
-============
-Contributing
-============
-
-Report a Bug or Make a Feature Request
---------------------------------------
-Please go to the GitHub Issues page: https://github.com/kbandla/dpkt/issues.
-
-Checkout the Code
------------------
-
-::
-
-    git clone https://github.com/kblandla/dpkt.git
-
-
-Become a Developer
-------------------
-dpkt uses the 'GitHub Flow' model: `GitHub Flow <http://scottchacon.com/2011/08/31/github-flow.html>`_ 
-
-- To work on something new, create a descriptively named branch off of master (ie: my-awesome)
-- Commit to that branch locally and regularly push your work to the same named branch on the server
-- When you need feedback or help, or you think the branch is ready for merging, open a pull request
-- After someone else has reviewed and signed off on the feature, they or you can merge it into master
-
-New Feature or Bug
-~~~~~~~~~~~~~~~~~~
-
-    ::
-
-    $ git checkout -b my-awesome
-    $ git push -u origin my-awesome
-    $ <code for a bit>; git push
-    $ <code for a bit>; git push
-    $ tox (this will run all the tests)
-
-    - Go to github and hit 'New pull request'
-    - Someone reviews it and says 'AOK'
-    - Merge the pull request (green button)
-
diff --git a/docs/creating_parsers.md b/docs/creating_parsers.md
new file mode 100644
index 0000000..4fdf8b1
--- /dev/null
+++ b/docs/creating_parsers.md
@@ -0,0 +1,264 @@
+# Key concepts of creating protocol parsers in dpkt
+
+by Oscar Ibatullin \[<https://github.com/obormot>\] a
+contributor/maintainer of dpkt.
+
+## Parser class definition
+
+Let's look at the IPv4 parser, defined in `dpkt/ip.py`, as an example.
+
+```python
+class IP(dpkt.Packet):
+    """Internet Protocol."""
+
+    __hdr__ = (
+        ('_v_hl', 'B', (4 << 4) | (20 >> 2)),
+        ('tos', 'B', 0),
+        ('len', 'H', 20),
+        ('id', 'H', 0),
+        ('_flags_offset', 'H', 0),
+        ('ttl', 'B', 64),
+        ('p', 'B', 0),
+        ('sum', 'H', 0),
+        ('src', '4s', b'\x00' * 4),
+        ('dst', '4s', b'\x00' * 4)
+    )
+    __bit_fields__ = {
+        '_v_hl': (
+            ('v', 4),   # version, 4 bits
+            ('hl', 4),  # header len, 4 bits
+        ),
+        '_flags_offset': (
+            ('rf', 1),  # reserved bit
+            ('df', 1),  # don't fragment
+            ('mf', 1),  # more fragments
+            ('offset', 13),  # fragment offset, 13 bits
+        )
+    }
+    __pprint_funcs__ = {
+        'dst': inet_to_str,
+        'src': inet_to_str,
+        'p': get_ip_proto_name
+    }
+```
+A lot is going on in the header, before we even got to `__init__`\! Here
+is the breakdown:
+
+1.  Note the main `class IP` inherits from `dpkt.Packet`
+
+2.  `__hdr__` defines a list of fields in the protocol header as 3-item
+    tuples: *(field name, python struct format, default value)*. The
+    fields are arranged in the order they appear on the wire.
+    
+    Field names generally follow the protocol definitions (e.g. RFC),
+    but there are some rules to naming the fields that affect `dpkt`
+    processing:
+    
+      * a name that doesn't start with an underscore represents a
+        regular public protocol field. *Examples:* `tos`, `len`, `id`
+
+      * a name that starts with an underscore and contains NO more
+        underscores is considered private and gets hidden in `__repr__`
+        and `pprint()` outputs; this is useful for hiding fields
+        reserved for future use, or fields that should be decoded
+        according to some custom rules. *Example:* `_reserved`
+        
+      * a name that starts with an underscore and DOES contain more
+        underscores is similarly considered private and hidden, but gets
+        processed as a collection of multiple protocol fields, separated
+        by underscore. Each field name may contain up to 1 underscore as
+        well. These fields are only created when the class definition
+        contains matching property definitions, which could be defined
+        explicitly or created automagically via `__bit_fields__` (more
+        on this later). *Examples:*
+        
+        * `_foo_bar_m_flag` will map to fields named `foo`, `bar`,
+          `m_flag`, when the class contains properties with these
+          names (note `foo_bar_m` will be ignored since it
+          contains two underscores).
+        
+        * in the IP class the `_v_hl` field itself is hidden in
+          the output of `__repr__` and `pprint()`, and is decoded
+          into `v` and `hl` fields that are displayed instead.
+    
+    The second component of the tuple specifies the format of the
+    protocol field, as it corresponds to Python's native `struct`
+    module. `'B'` means the field will decode to an unsigned byte, 
+    `'H'` - to an unsigned word, etc. The default byte order is big 
+    endian (network order). Endianness can be changed to little 
+    endian by specifying `__byte_order__ = '<'` in the class 
+    definition.
+
+3.  Next, `__bit_fields__` is an optional dict that helps decode
+    compound protocol fields, such as `_v_hl` or `_flags_offset` in the
+    IP class. Each field name (as it appears in `__hdr__`) maps to a
+    list (technically a tuple) of tuples, defining the bit fields in the
+    network order (from high to low). Each tuple is *(bit field name,
+    size in bits)*.
+    
+    The total sum of bit sizes must match the overall size of the
+    placeholder field. For example, `_v_hl` is decoded to 1 byte
+    (`'B'`), or 8 bits; `v` (the IP version) occupies the high 4 bits
+    and `hl` (IP header length) occupies the lower 4 bits.
+    
+    `_flags_offset` that is 2 bytes long (`'H'`) is decoded into 3 1-bit
+    flags followed by a 13-bit offset, total of 16 bytes.
+    
+    Similarly to the naming rules of `__hdr__`, a bit field name
+    starting with an underscore is made invisible in the output.
+    
+    When dpkt processes `__bit_fields__` it auto-creates class
+    properties that enable interfacing with the bit fields directly,
+    specifically: get the value (`ip.v`), modify the value (`ip.v = 6`),
+    and reset the value back to its default (`del ip.v`).
+    
+    In certain cases, auto-properties can't be applied; they still can
+    be created explicitly. Look at `class SMB` inside `dpkt/smb.py` in
+    how it decodes the `pid` protocol field.
+
+4.  Next, `__pprint_funcs__` is an optional dict that does not control
+    protocol decoding, but helps with pretty printing of the decoded
+    packet using the `pprint()` method. Each key in this map is a name
+    of the protocol field, and each value is a callable that will be run
+    with a single argument of the protocol field value.
+    
+    For example, it's nice to see human readable IP addresses for `src`
+    and `dst` fields by passing the raw bytes to `inet_to_str` function.
+
+## Standard methods
+
+Let's look at the standard methods of the `Packet` class and how they
+contribute to parsing (aka unpacking or deserializing) and constructing
+(aka packing or serializing) the packet.
+
+```python
+class IP(dpkt.Packet):
+    ...
+    def __init__(self, *args, **kwargs):
+        super(IP, self).__init__(*args, **kwargs)
+        ...
+
+    def __len__(self):
+        return self.__hdr_len__ + len(self.opts) + len(self.data)
+
+    def __bytes__(self):
+        # calculate IP checksum
+        if self.sum == 0:
+            self.sum = dpkt.in_cksum(self.pack_hdr() + bytes(self.opts))
+        ...
+        return self.pack_hdr() + bytes(self.opts) + bytes(self.data)
+
+    def unpack(self, buf):
+        dpkt.Packet.unpack(self, buf)
+        ...
+        self.opts = ...  # add IP options
+        ...
+        self.data = ...  # bytes that remain after unpacking
+
+    def pack_hdr(self):
+        buf = dpkt.Packet.pack_hdr(self)
+        ...
+        return buf
+```
+Instantiating the class with a bytes buffer (`ip = dpkt.ip.IP(buf)`)
+will trigger the unpacking sequence as follows:
+
+1.  `__init__(buf)` calls `self.unpack(buf)`
+2.  `Packet.unpack()` creates protocol fields given in `__hdr__` as
+    class attributes, and sets `self.data` to the remaining unparsed
+    bytes in the buffer.
+
+Child classes typically extend the `Packet.unpack()` method to create
+additional custom attributes, that are not given in the `__hdr__` (such
+as `opts` for IP options below).
+
+Packing is the opposite of unpacking of course; given an instance of a
+parsed packet, packing will return serialized packet as a `bytes` object
+(`bytes(ip) => buf`). It goes as follows:
+
+1.  Calling `bytes(obj)` invokes `self.__bytes__(obj)`
+
+2.  `Packet.__bytes()__` calls `self.pack_hdr()` and returns its result
+    with appended `bytes(self.data)`. The latter recursively triggers
+    serialization of `self.data`, which could be another packet class,
+    e.g. `Ethernet(.., data=IP(.., data=TCP(...)))`, so everything
+    gets serialized.
+
+3.  `Packet.pack_hdr()` iterates over the protocol fields given in
+    `__hdr__`, calls `struct.pack()` on them and returns the resulting
+    bytes.
+
+Child classes typically extend the `Packet.__bytes__()` method to
+process custom attributes, that are not given in the `__hdr__`, or to
+override some values before `pack_hdr()` turns them into bytes. See how
+the IP parser overrides `__bytes__` to calculate the IP checksum prior
+to packing, and insert `bytes(self.opts)` between the packed header and
+data.
+
+### \_\_len\_\_
+
+`__len__()` returns the size of the serialized packet and is typically
+invoked when calling `len(obj)`. Note how in the IP class, this method
+calls other functions to calculate size, then sums the lengths together,
+and it **does not** perform serialization. It may be tempting to
+implement `__len__` by serializing the packet into bytes and returning
+the size of the resulting buffer (`return len(bytes(self))`). While this
+works and is acceptable in some cases, dpkt views this as an
+anti-pattern that should be avoided.
+
+### \_\_repr\_\_ and pprint()
+
+These methods are provided by `dpkt.Packet` and are typically not
+overridden in the child class. However they are important to understand
+when developing protocol parsers. Both `repr()` and `pprint()` are
+responsible for the output, and both produce valid interpretable Python,
+but there are some differences:
+
+1.  `__repr__` returns a short one-liner printable string, while
+    `pprint()` actually prints and returns nothing
+2.  `__repr__` does not include protocol fields if their value is
+    default, i.e. it will only display a field when it differs from the
+    default. *Example:* in IPv4 the version always equals 4 so normally
+    field `v` is not included.
+3.  `pprint()` is verbose; its output is one field per line, indented,
+    outdented and commented, and contrary to `__repr__` it includes all
+    protocol fields, even when their value IS default.
+4.  `__repr__` does not use the `__pprint_funcs__` and returns raw
+    values. See below how `src` and `dst` IP addresses get human
+    readable interpretation with `pprint()`, but not with `__repr__`.
+
+```python
+# repr()
+>>> ip
+IP(len=34, p=17, sum=29376, src=b'\x01\x02\x03\x04', dst=b'\x01\x02\x03\x04', opts=b'', data=UDP(sport=111, dport=222, ulen=14, sum=48949, data=b'foobar'))
+
+# IP version field is default and is not returned by repr()
+>>> ip.v
+4
+
+>>> ip.pprint()
+IP(
+  v=4,
+  hl=5,
+  tos=0,
+  len=34,
+  id=0,
+  rf=0,
+  df=0,
+  mf=0,
+  offset=0,
+  ttl=64,
+  p=17,  # UDP
+  sum=29376,
+  src=b'\x01\x02\x03\x04',  # 1.2.3.4
+  dst=b'\x01\x02\x03\x04',  # 1.2.3.4
+  opts=b'',
+  data=UDP(
+    sport=111,
+    dport=222,
+    ulen=14,
+    sum=48949,
+    data=b'foobar'
+  )  # UDP
+)  # IP
+```
diff --git a/docs/examples.rst b/docs/examples.rst
deleted file mode 100644
index 3b54a55..0000000
--- a/docs/examples.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-
-Examples
-========
-
-Examples in dpkt/examples
--------------------------
-
-.. toctree::
-    :maxdepth: 2
-
-    print_packets
-    print_icmp
-    print_http_requests
-
-Jon Oberheide's Examples
--------------------------
-[@jonoberheide's](https://twitter.com/jonoberheide) old examples still
-apply:
-
--  `dpkt Tutorial #1: ICMP
-   Echo <https://jon.oberheide.org/blog/2008/08/25/dpkt-tutorial-1-icmp-echo/>`__
--  `dpkt Tutorial #2: Parsing a PCAP
-   File <https://jon.oberheide.org/blog/2008/10/15/dpkt-tutorial-2-parsing-a-pcap-file/>`__
--  `dpkt Tutorial #3: dns
-   spoofing <https://jon.oberheide.org/blog/2008/12/20/dpkt-tutorial-3-dns-spoofing/>`__
--  `dpkt Tutorial #4: AS Paths from
-   MRT/BGP <https://jon.oberheide.org/blog/2009/03/25/dpkt-tutorial-4-as-paths-from-mrt-bgp/>`__
-
-Jeff Silverman Docs/Code
-------------------------
-`Jeff Silverman <https://github.com/jeffsilverm>`__ has some
-`code <https://github.com/jeffsilverm/dpkt_doc>`__ and
-`documentation <http://www.commercialventvac.com/dpkt.html>`__.
-
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..7103341
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,29 @@
+
+# dpkt
+
+The dpkt project is a python module for fast, simple packet parsing, with definitions for the basic TCP/IP protocols.
+
+### Installation
+```
+pip install dpkt
+```
+
+### Examples
+- [Print Packets](print_packets.md)
+- [Print ICMP](print_icmp.md)
+- [Print HTTP](print_http_requests.md)
+
+### Documentation
+- [DPKT Key Concepts for Creating Parsers](creating_parsers.md)
+- [Changelog](changelog.md)
+- [Authors](authors.md)
+- [Contributing](contributing.md)
+- [Project Plans (TBD)](plans.md)
+- [Admin Notes](admin_notes.md)
+
+## About
+This code is based on [dpkt code](https://code.google.com/p/dpkt/) lead by Dug Song and is now being maintained and improved by an extended set of [developers](https://github.com/kbandla/dpkt/graphs/contributors).
+
+## LICENSE
+
+BSD 3-Clause
diff --git a/docs/index.rst b/docs/index.rst
deleted file mode 100644
index 23f5ab9..0000000
--- a/docs/index.rst
+++ /dev/null
@@ -1,39 +0,0 @@
-dpkt
-====
-.. include:: badges.rst
-dpkt is a python module for fast, simple packet creation / parsing, with definitions for the basic TCP/IP protocols
-
-
-Getting Started
-===============
-.. toctree::
-    :maxdepth: 2
-
-    installation
-    examples
-
-API Reference
-=============
-.. toctree::
-    :maxdepth: 1
-
-    api/index
-
-About dpkt
-==========
-.. toctree::
-    :maxdepth: 2
-
-    authors
-    changelog
-    plans
-    contributing
-    license
-
-Administration
-===============
-.. toctree::
-    :maxdepth: 2
-
-    admin_notes
-
diff --git a/docs/installation.rst b/docs/installation.rst
deleted file mode 100644
index c632929..0000000
--- a/docs/installation.rst
+++ /dev/null
@@ -1,21 +0,0 @@
-============
-Installation
-============
-
-DKPT is now available directly from pypi :)
-
-Install the Code
-----------------
-
-::
-
-    pip install dpkt
-
-Checkout the Code
------------------
-
-::
-
-    git clone https://github.com/kbandla/dpkt.git
-
-
diff --git a/docs/license.rst b/docs/license.md
similarity index 74%
rename from docs/license.rst
rename to docs/license.md
index 56c7238..25f5613 100644
--- a/docs/license.rst
+++ b/docs/license.md
@@ -1,4 +1,3 @@
+# License
 
-License
-=======
 BSD 3-Clause License, as the upstream project
diff --git a/docs/plans.md b/docs/plans.md
new file mode 100644
index 0000000..cb9a459
--- /dev/null
+++ b/docs/plans.md
@@ -0,0 +1,3 @@
+# Development plans
+
+TBD: Insert Stuff Here
diff --git a/docs/plans.rst b/docs/plans.rst
deleted file mode 100644
index a6b1ca3..0000000
--- a/docs/plans.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-*****************
-Development plans
-*****************
-
-Current plans
-=============
-
-- Be Awesome
-
-Future plans
-============
-
-- Maintain the Awesome
diff --git a/docs/print_http_requests.md b/docs/print_http_requests.md
new file mode 100644
index 0000000..896ce16
--- /dev/null
+++ b/docs/print_http_requests.md
@@ -0,0 +1,72 @@
+# Print HTTP Requests Example
+
+This example expands on the print\_packets example. It checks for HTTP
+request headers and displays their contents.
+
+**NOTE:** We are not reconstructing 'flows' so the request (and response
+if you tried to parse it) will only parse correctly if they fit within a
+single packet. Requests can often fit in a single packet but Responses
+almost never will. For proper reconstruction of flows you may want to
+look at other projects that use DPKT (<http://chains.readthedocs.io> and
+others)
+
+**Code Excerpt**
+
+``` python
+# For each packet in the pcap process the contents
+for timestamp, buf in pcap:
+
+    # Unpack the Ethernet frame (mac src/dst, ethertype)
+    eth = dpkt.ethernet.Ethernet(buf)
+
+    # Make sure the Ethernet data contains an IP packet
+    if not isinstance(eth.data, dpkt.ip.IP):
+        print 'Non IP Packet type not supported %s\n' % eth.data.__class__.__name__
+        continue
+
+    # Now grab the data within the Ethernet frame (the IP packet)
+    ip = eth.data
+
+    # Check for TCP in the transport layer
+    if isinstance(ip.data, dpkt.tcp.TCP):
+
+        # Set the TCP data
+        tcp = ip.data
+
+        # Now see if we can parse the contents as a HTTP request
+        try:
+            request = dpkt.http.Request(tcp.data)
+        except (dpkt.dpkt.NeedData, dpkt.dpkt.UnpackError):
+            continue
+
+        # Pull out fragment information (flags and offset all packed into off field, so use bitmasks)
+        do_not_fragment = bool(ip.off & dpkt.ip.IP_DF)
+        more_fragments = bool(ip.off & dpkt.ip.IP_MF)
+        fragment_offset = ip.off & dpkt.ip.IP_OFFMASK
+
+        # Print out the info
+        print 'Timestamp: ', str(datetime.datetime.utcfromtimestamp(timestamp))
+        print 'Ethernet Frame: ', mac_addr(eth.src), mac_addr(eth.dst), eth.type
+        print 'IP: %s -> %s   (len=%d ttl=%d DF=%d MF=%d offset=%d)' % \
+              (inet_to_str(ip.src), inet_to_str(ip.dst), ip.len, ip.ttl, do_not_fragment, more_fragments, fragment_offset)
+        print 'HTTP request: %s\n' % repr(request)
+```
+
+**Example Output**
+
+```
+Timestamp:  2004-05-13 10:17:08.222534
+Ethernet Frame:  00:00:01:00:00:00 fe:ff:20:00:01:00 2048
+IP: 145.254.160.237 -> 65.208.228.223   (len=519 ttl=128 DF=1 MF=0 offset=0)
+HTTP request: Request(body='', uri='/download.html', headers={'accept-language': 'en-us,en;q=0.5', 'accept-encoding': 'gzip,deflate', 'connection': 'keep-alive', 'keep-alive': '300', 'accept': 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1', 'user-agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113', 'accept-charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'host': 'www.ethereal.com', 'referer': 'http://www.ethereal.com/development.html'}, version='1.1', data='', method='GET')
+
+Timestamp:  2004-05-13 10:17:10.295515
+Ethernet Frame:  00:00:01:00:00:00 fe:ff:20:00:01:00 2048
+IP: 145.254.160.237 -> 216.239.59.99   (len=761 ttl=128 DF=1 MF=0 offset=0)
+HTTP request: Request(body='', uri='/pagead/ads?client=ca-pub-2309191948673629&random=1084443430285&lmt=1082467020&format=468x60_as&output=html&url=http%3A%2F%2Fwww.ethereal.com%2Fdownload.html&color_bg=FFFFFF&color_text=333333&color_link=000000&color_url=666633&color_border=666633', headers={'accept-language': 'en-us,en;q=0.5', 'accept-encoding': 'gzip,deflate', 'connection': 'keep-alive', 'keep-alive': '300', 'accept': 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1', 'user-agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113', 'accept-charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'host': 'pagead2.googlesyndication.com', 'referer': 'http://www.ethereal.com/download.html'}, version='1.1', data='', method='GET')
+
+...
+```
+
+**See full code at: <https://github.com/kbandla/dpkt/blob/master/examples/print_http_requests.py>**
+
diff --git a/docs/print_http_requests.rst b/docs/print_http_requests.rst
deleted file mode 100644
index a4785f5..0000000
--- a/docs/print_http_requests.rst
+++ /dev/null
@@ -1,73 +0,0 @@
-
-Print HTTP Requests Example
-===========================
-
-This example expands on the print_packets example. It checks for HTTP request headers and displays their contents.
-
-**NOTE:** We are not reconstructing 'flows' so the request (and response if you tried to parse it) will only
-parse correctly if they fit within a single packet. Requests can often fit in a single packet but
-Responses almost never will. For proper reconstruction of flows you may want to look at other projects
-that use DPKT (http://chains.readthedocs.io and others)
-
-**Code Excerpt**
-
-.. code-block:: python
-
-    # For each packet in the pcap process the contents
-    for timestamp, buf in pcap:
-
-        # Unpack the Ethernet frame (mac src/dst, ethertype)
-        eth = dpkt.ethernet.Ethernet(buf)
-
-        # Make sure the Ethernet data contains an IP packet
-        if not isinstance(eth.data, dpkt.ip.IP):
-            print 'Non IP Packet type not supported %s\n' % eth.data.__class__.__name__
-            continue
-
-        # Now grab the data within the Ethernet frame (the IP packet)
-        ip = eth.data
-
-        # Check for TCP in the transport layer
-        if isinstance(ip.data, dpkt.tcp.TCP):
-
-            # Set the TCP data
-            tcp = ip.data
-
-            # Now see if we can parse the contents as a HTTP request
-            try:
-                request = dpkt.http.Request(tcp.data)
-            except (dpkt.dpkt.NeedData, dpkt.dpkt.UnpackError):
-                continue
-
-            # Pull out fragment information (flags and offset all packed into off field, so use bitmasks)
-            do_not_fragment = bool(ip.off & dpkt.ip.IP_DF)
-            more_fragments = bool(ip.off & dpkt.ip.IP_MF)
-            fragment_offset = ip.off & dpkt.ip.IP_OFFMASK
-
-            # Print out the info
-            print 'Timestamp: ', str(datetime.datetime.utcfromtimestamp(timestamp))
-            print 'Ethernet Frame: ', mac_addr(eth.src), mac_addr(eth.dst), eth.type
-            print 'IP: %s -> %s   (len=%d ttl=%d DF=%d MF=%d offset=%d)' % \
-                  (inet_to_str(ip.src), inet_to_str(ip.dst), ip.len, ip.ttl, do_not_fragment, more_fragments, fragment_offset)
-            print 'HTTP request: %s\n' % repr(request)
-
-
-**Example Output**
-
-.. code-block:: json
-
-        Timestamp:  2004-05-13 10:17:08.222534
-        Ethernet Frame:  00:00:01:00:00:00 fe:ff:20:00:01:00 2048
-        IP: 145.254.160.237 -> 65.208.228.223   (len=519 ttl=128 DF=1 MF=0 offset=0)
-        HTTP request: Request(body='', uri='/download.html', headers={'accept-language': 'en-us,en;q=0.5', 'accept-encoding': 'gzip,deflate', 'connection': 'keep-alive', 'keep-alive': '300', 'accept': 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1', 'user-agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113', 'accept-charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'host': 'www.ethereal.com', 'referer': 'http://www.ethereal.com/development.html'}, version='1.1', data='', method='GET')
-
-        Timestamp:  2004-05-13 10:17:10.295515
-        Ethernet Frame:  00:00:01:00:00:00 fe:ff:20:00:01:00 2048
-        IP: 145.254.160.237 -> 216.239.59.99   (len=761 ttl=128 DF=1 MF=0 offset=0)
-        HTTP request: Request(body='', uri='/pagead/ads?client=ca-pub-2309191948673629&random=1084443430285&lmt=1082467020&format=468x60_as&output=html&url=http%3A%2F%2Fwww.ethereal.com%2Fdownload.html&color_bg=FFFFFF&color_text=333333&color_link=000000&color_url=666633&color_border=666633', headers={'accept-language': 'en-us,en;q=0.5', 'accept-encoding': 'gzip,deflate', 'connection': 'keep-alive', 'keep-alive': '300', 'accept': 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1', 'user-agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113', 'accept-charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'host': 'pagead2.googlesyndication.com', 'referer': 'http://www.ethereal.com/download.html'}, version='1.1', data='', method='GET')
-
-        ...
-
-**dpkt/examples/print_http_requests.py**
-
-.. automodule:: examples.print_http_requests
diff --git a/docs/print_icmp.md b/docs/print_icmp.md
new file mode 100644
index 0000000..869e44f
--- /dev/null
+++ b/docs/print_icmp.md
@@ -0,0 +1,57 @@
+# Print ICMP Example
+
+This example expands on the print\_packets example. It checks for ICMP
+packets and displays the ICMP contents.
+
+**Code Excerpt**
+
+``` python
+# For each packet in the pcap process the contents
+for timestamp, buf in pcap:
+
+    # Unpack the Ethernet frame (mac src/dst, ethertype)
+    eth = dpkt.ethernet.Ethernet(buf)
+
+    # Make sure the Ethernet data contains an IP packet
+    if not isinstance(eth.data, dpkt.ip.IP):
+        print 'Non IP Packet type not supported %s\n' % eth.data.__class__.__name__
+        continue
+
+    # Now grab the data within the Ethernet frame (the IP packet)
+    ip = eth.data
+
+    # Now check if this is an ICMP packet
+    if isinstance(ip.data, dpkt.icmp.ICMP):
+        icmp = ip.data
+
+        # Pull out fragment information (flags and offset all packed into off field, so use bitmasks)
+        do_not_fragment = bool(ip.off & dpkt.ip.IP_DF)
+        more_fragments = bool(ip.off & dpkt.ip.IP_MF)
+        fragment_offset = ip.off & dpkt.ip.IP_OFFMASK
+
+        # Print out the info
+        print 'Timestamp: ', str(datetime.datetime.utcfromtimestamp(timestamp))
+        print 'Ethernet Frame: ', mac_addr(eth.src), mac_addr(eth.dst), eth.type
+        print 'IP: %s -> %s   (len=%d ttl=%d DF=%d MF=%d offset=%d)' % \
+              (inet_to_str(ip.src), inet_to_str(ip.dst), ip.len, ip.ttl, do_not_fragment, more_fragments, fragment_offset)
+        print 'ICMP: type:%d code:%d checksum:%d data: %s\n' % (icmp.type, icmp.code, icmp.sum, repr(icmp.data))
+```
+
+**Example Output**
+
+```
+Timestamp:  2013-05-30 22:45:17.283187
+Ethernet Frame:  60:33:4b:13:c5:58 02:1a:11:f0:c8:3b 2048
+IP: 192.168.43.9 -> 8.8.8.8   (len=84 ttl=64 DF=0 MF=0 offset=0)
+ICMP: type:8 code:0 checksum:48051 data: Echo(id=55099, data='Q\xa7\xd6}\x00\x04Q\xe4\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567')
+
+Timestamp:  2013-05-30 22:45:17.775391
+Ethernet Frame:  02:1a:11:f0:c8:3b 60:33:4b:13:c5:58 2048
+IP: 8.8.8.8 -> 192.168.43.9   (len=84 ttl=40 DF=0 MF=0 offset=0)
+ICMP: type:0 code:0 checksum:50099 data: Echo(id=55099, data='Q\xa7\xd6}\x00\x04Q\xe4\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567')
+
+...
+```
+
+**Set full code at: <https://github.com/kbandla/dpkt/blob/master/examples/print_icmp.py>**
+
diff --git a/docs/print_icmp.rst b/docs/print_icmp.rst
deleted file mode 100644
index 502218a..0000000
--- a/docs/print_icmp.rst
+++ /dev/null
@@ -1,59 +0,0 @@
-
-Print ICMP Example
-==================
-
-This example expands on the print_packets example. It checks for ICMP packets and displays the ICMP contents.
-
-**Code Excerpt**
-
-.. code-block:: python
-
-    # For each packet in the pcap process the contents
-    for timestamp, buf in pcap:
-
-        # Unpack the Ethernet frame (mac src/dst, ethertype)
-        eth = dpkt.ethernet.Ethernet(buf)
-
-        # Make sure the Ethernet data contains an IP packet
-        if not isinstance(eth.data, dpkt.ip.IP):
-            print 'Non IP Packet type not supported %s\n' % eth.data.__class__.__name__
-            continue
-
-        # Now grab the data within the Ethernet frame (the IP packet)
-        ip = eth.data
-
-        # Now check if this is an ICMP packet
-        if isinstance(ip.data, dpkt.icmp.ICMP):
-            icmp = ip.data
-
-            # Pull out fragment information (flags and offset all packed into off field, so use bitmasks)
-            do_not_fragment = bool(ip.off & dpkt.ip.IP_DF)
-            more_fragments = bool(ip.off & dpkt.ip.IP_MF)
-            fragment_offset = ip.off & dpkt.ip.IP_OFFMASK
-
-            # Print out the info
-            print 'Timestamp: ', str(datetime.datetime.utcfromtimestamp(timestamp))
-            print 'Ethernet Frame: ', mac_addr(eth.src), mac_addr(eth.dst), eth.type
-            print 'IP: %s -> %s   (len=%d ttl=%d DF=%d MF=%d offset=%d)' % \
-                  (inet_to_str(ip.src), inet_to_str(ip.dst), ip.len, ip.ttl, do_not_fragment, more_fragments, fragment_offset)
-            print 'ICMP: type:%d code:%d checksum:%d data: %s\n' % (icmp.type, icmp.code, icmp.sum, repr(icmp.data))
-
-**Example Output**
-
-.. code-block:: json
-
-      Timestamp:  2013-05-30 22:45:17.283187
-      Ethernet Frame:  60:33:4b:13:c5:58 02:1a:11:f0:c8:3b 2048
-      IP: 192.168.43.9 -> 8.8.8.8   (len=84 ttl=64 DF=0 MF=0 offset=0)
-      ICMP: type:8 code:0 checksum:48051 data: Echo(id=55099, data='Q\xa7\xd6}\x00\x04Q\xe4\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567')
-
-      Timestamp:  2013-05-30 22:45:17.775391
-      Ethernet Frame:  02:1a:11:f0:c8:3b 60:33:4b:13:c5:58 2048
-      IP: 8.8.8.8 -> 192.168.43.9   (len=84 ttl=40 DF=0 MF=0 offset=0)
-      ICMP: type:0 code:0 checksum:50099 data: Echo(id=55099, data='Q\xa7\xd6}\x00\x04Q\xe4\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567')
-
-      ...
-
-**dpkt/examples/print_icmp.py**
-
-.. automodule:: examples.print_icmp
diff --git a/docs/print_packets.md b/docs/print_packets.md
new file mode 100644
index 0000000..f0c2619
--- /dev/null
+++ b/docs/print_packets.md
@@ -0,0 +1,85 @@
+# Print Packets Example
+
+This example uses DPKT to read in a pcap file and print out the contents
+of the packets. This example is focused on the fields in the Ethernet
+Frame and IP packet.
+
+**Code Excerpt**
+
+``` python
+# For each packet in the pcap process the contents
+for timestamp, buf in pcap:
+
+    # Print out the timestamp in UTC
+    print 'Timestamp: ', str(datetime.datetime.utcfromtimestamp(timestamp))
+
+    # Unpack the Ethernet frame (mac src/dst, ethertype)
+    eth = dpkt.ethernet.Ethernet(buf)
+    print 'Ethernet Frame: ', mac_addr(eth.src), mac_addr(eth.dst), eth.type
+
+    # Make sure the Ethernet frame contains an IP packet
+    if not isinstance(eth.data, dpkt.ip.IP):
+        print 'Non IP Packet type not supported %s\n' % eth.data.__class__.__name__
+        continue
+
+    # Now access the data within the Ethernet frame (the IP packet)
+    # Pulling out src, dst, length, fragment info, TTL, and Protocol
+    ip = eth.data
+
+    # Print out the info, including the fragment flags and offset
+    print('IP: %s -> %s   (len=%d ttl=%d DF=%d MF=%d offset=%d)\n' %
+          (inet_to_str(ip.src), inet_to_str(ip.dst), ip.len, ip.ttl, ip.df, ip.mf, ip.offset))
+
+# Pretty print the last packet
+print('** Pretty print demo **\n')
+eth.pprint()
+```
+
+**Example Output**
+
+```
+Timestamp:  2004-05-13 10:17:07.311224
+Ethernet Frame:  00:00:01:00:00:00 fe:ff:20:00:01:00 2048
+IP: 145.254.160.237 -> 65.208.228.223   (len=48 ttl=128 DF=1 MF=0 offset=0)
+
+Timestamp:  2004-05-13 10:17:08.222534
+Ethernet Frame:  fe:ff:20:00:01:00 00:00:01:00:00:00 2048
+IP: 65.208.228.223 -> 145.254.160.237   (len=48 ttl=47 DF=1 MF=0 offset=0)
+
+** Pretty print demo **
+
+Ethernet(
+  dst=b'\x00\x00\x01\x00\x00\x00',  # 00:00:01:00:00:00
+  src=b'\xfe\xff \x00\x01\x00',  # fe:ff:20:00:01:00
+  type=2048,
+  data=IP(
+    v=4,
+    hl=5,
+    tos=0,
+    len=40,
+    id=0,
+    off=16384,
+    ttl=47,
+    p=6,
+    sum=62004,  # 0xf234
+    src=b'A\xd0\xe4\xdf',  # 65.208.228.223
+    dst=b'\x91\xfe\xa0\xed',  # 145.254.160.237
+    opts=b'',
+    data=TCP(
+      sport=80,
+      dport=3372,
+      seq=290236745,
+      ack=951058420,
+      off=5,
+      flags=16,  # ACK
+      win=6432,
+      sum=15459,  # 0x3c63
+      urp=0,
+      opts=b'',
+    )  # TCP
+  )  # IP
+)  # Ethernet
+```
+
+**See full code at: <https://github.com/kbandla/dpkt/blob/master/examples/print_packets.py>**
+
diff --git a/docs/print_packets.rst b/docs/print_packets.rst
deleted file mode 100644
index 20e4af7..0000000
--- a/docs/print_packets.rst
+++ /dev/null
@@ -1,55 +0,0 @@
-
-Print Packets Example
-=====================
-This example uses DPKT to read in a pcap file and print out the contents of the packets This example is
-focused on the fields in the Ethernet Frame and IP packet
-
-**Code Excerpt**
-
-.. code-block:: python
-
-    # For each packet in the pcap process the contents
-    for timestamp, buf in pcap:
-
-        # Print out the timestamp in UTC
-        print 'Timestamp: ', str(datetime.datetime.utcfromtimestamp(timestamp))
-
-        # Unpack the Ethernet frame (mac src/dst, ethertype)
-        eth = dpkt.ethernet.Ethernet(buf)
-        print 'Ethernet Frame: ', mac_addr(eth.src), mac_addr(eth.dst), eth.type
-
-        # Make sure the Ethernet frame contains an IP packet
-        if not isinstance(eth.data, dpkt.ip.IP):
-            print 'Non IP Packet type not supported %s\n' % eth.data.__class__.__name__
-            continue
-
-        # Now unpack the data within the Ethernet frame (the IP packet)
-        # Pulling out src, dst, length, fragment info, TTL, and Protocol
-        ip = eth.data
-
-        # Pull out fragment information (flags and offset all packed into off field, so use bitmasks)
-        do_not_fragment = bool(ip.off & dpkt.ip.IP_DF)
-        more_fragments = bool(ip.off & dpkt.ip.IP_MF)
-        fragment_offset = ip.off & dpkt.ip.IP_OFFMASK
-
-        # Print out the info
-        print 'IP: %s -> %s   (len=%d ttl=%d DF=%d MF=%d offset=%d)\n' % \
-              (inet_to_str(ip.src), inet_to_str(ip.dst), ip.len, ip.ttl, do_not_fragment, more_fragments, fragment_offset)
-
-**Example Output**
-
-.. code-block:: json
-
-        Timestamp:  2004-05-13 10:17:07.311224
-        Ethernet Frame:  00:00:01:00:00:00 fe:ff:20:00:01:00 2048
-        IP: 145.254.160.237 -> 65.208.228.223   (len=48 ttl=128 DF=1 MF=0 offset=0)
-
-        Timestamp:  2004-05-13 10:17:08.222534
-        Ethernet Frame:  fe:ff:20:00:01:00 00:00:01:00:00:00 2048
-        IP: 65.208.228.223 -> 145.254.160.237   (len=48 ttl=47 DF=1 MF=0 offset=0)
-
-        ...
-
-**dpkt/examples/print_packets.py**
-
-.. automodule:: examples.print_packets
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index 0e91234..0000000
--- a/docs/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-mock >= 1.0
-Sphinx >= 1.0
-sphinxcontrib-napoleon
diff --git a/dpkt/__init__.py b/dpkt/__init__.py
index 2eaeae4..5123d48 100644
--- a/dpkt/__init__.py
+++ b/dpkt/__init__.py
@@ -3,13 +3,11 @@
 from __future__ import division
 import sys
 
-__author__ = 'Dug Song'
-__author_email__ = 'dugsong@monkey.org'
-__license__ = 'BSD'
-__url__ = 'http://dpkt.googlecode.com/'
-__version__ = '1.9.1'
-
-
+__author__ = 'Various'
+__author_email__ = ''
+__license__ = 'BSD-3-Clause'
+__url__ = 'https://github.com/kbandla/dpkt'
+__version__ = '1.9.7.2'
 
 from .dpkt import *
 
@@ -64,6 +62,7 @@
 from . import sctp
 from . import sip
 from . import sll
+from . import sll2
 from . import smb
 from . import ssl
 from . import stp
diff --git a/dpkt/ah.py b/dpkt/ah.py
index 90d9532..d177dce 100644
--- a/dpkt/ah.py
+++ b/dpkt/ah.py
@@ -5,6 +5,7 @@
 from __future__ import absolute_import
 
 from . import dpkt
+from . import ip
 
 
 class AH(dpkt.Packet):
@@ -29,9 +30,9 @@
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
-        self.auth = self.data[:self.len]
-        buf = self.data[self.len:]
-        from . import ip
+        auth_len = max(4*self.len - 4, 0)  # see RFC 4302, section 2.2
+        self.auth = self.data[:auth_len]
+        buf = self.data[auth_len:]
 
         try:
             self.data = ip.IP.get_proto(self.nxt)(buf)
@@ -44,3 +45,42 @@
 
     def __bytes__(self):
         return self.pack_hdr() + bytes(self.auth) + bytes(self.data)
+
+
+def test_default_creation():
+    ah = AH()
+    assert ah.nxt == 0
+    assert ah.len == 0
+    assert ah.rsvd == 0
+    assert ah.spi == 0
+    assert ah.seq == 0
+    assert len(ah) == ah.__hdr_len__
+    assert bytes(ah) == b'\x00' * 12
+
+
+def test_creation_from_buf():
+    from binascii import unhexlify
+    buf_ip = unhexlify(
+        '04'  # IP
+        '0000000000000000000000'
+        '4500002200000000401172c001020304'
+        '01020304006f00de000ebf35666f6f626172'
+    )
+
+    ah = AH(buf_ip)
+    assert ah.nxt == 4  # IP
+    assert isinstance(ah.data, ip.IP)
+    assert len(ah) == 46
+    assert bytes(ah) == buf_ip
+
+    buf_not_ip = unhexlify(
+        '37'  # Not registered
+        '0000000000000000000000'
+        '4500002200000000401172c001020304'
+        '01020304006f00de000ebf35666f6f626172'
+    )
+    ah_not_ip = AH(buf_not_ip)
+    assert ah_not_ip.nxt == 0x37
+    assert isinstance(ah_not_ip.data, bytes)
+    assert len(ah_not_ip) == 46
+    assert bytes(ah_not_ip) == buf_not_ip
diff --git a/dpkt/aim.py b/dpkt/aim.py
index 5b7bd4c..93144c9 100644
--- a/dpkt/aim.py
+++ b/dpkt/aim.py
@@ -14,14 +14,14 @@
 class FLAP(dpkt.Packet):
     """Frame Layer Protocol.
 
-    See more about the FLAP on \
+    See more about the FLAP on
     https://en.wikipedia.org/wiki/OSCAR_protocol#FLAP_header
 
     Attributes:
         __hdr__: Header fields of FLAP.
         data: Message data.
     """
-    
+
     __hdr__ = (
         ('ast', 'B', 0x2a),  # '*'
         ('type', 'B', 0),
@@ -40,13 +40,13 @@
 class SNAC(dpkt.Packet):
     """Simple Network Atomic Communication.
 
-    See more about the SNAC on \
+    See more about the SNAC on
     https://en.wikipedia.org/wiki/OSCAR_protocol#SNAC_data
 
     Attributes:
         __hdr__: Header fields of SNAC.
     """
-    
+
     __hdr__ = (
         ('family', 'H', 0),
         ('subtype', 'H', 0),
@@ -58,49 +58,78 @@
 def tlv(buf):
     n = 4
     try:
-        t, l = struct.unpack('>HH', buf[:n])
+        t, l_ = struct.unpack('>HH', buf[:n])
     except struct.error:
         raise dpkt.UnpackError('invalid type, length fields')
-    v = buf[n:n + l]
-    if len(v) < l:
-        raise dpkt.NeedData('%d left, %d needed' % (len(v), l))
-    buf = buf[n + l:]
-    return t, l, v, buf
+    v = buf[n:n + l_]
+    if len(v) < l_:
+        raise dpkt.NeedData('%d left, %d needed' % (len(v), l_))
+    buf = buf[n + l_:]
+    return t, l_, v, buf
 
 # TOC 1.0: http://jamwt.com/Py-TOC/PROTOCOL
 
 # TOC 2.0: http://www.firestuff.org/projects/firetalk/doc/toc2.txt
 
+
 def testAIM():
-    testdata = b'*\x02\xac\xf3\x00\x81\x00\x03\x00\x0b\x00\x00\xfaEUd\x0eusrnameremoved\x00\x00\x00\n\x00\x01\x00\x02\x12\x90\x00D\x00\x01\x00\x00\x03\x00\x04X\x90T6\x00E\x00\x04\x00\x00\x0f\x93\x00!\x00\x08\x00\x85\x00}\x00}\x00\x00\x00A\x00\x01\x00\x007\x00\x04\x00\x00\x00\x00\x00\r\x00\x00\x00\x19\x00\x00\x00\x1d\x00$\x00\x00\x00\x05\x02\x01\xd2\x04r\x00\x01\x00\x05\x02\x01\xd2\x04r\x00\x03\x00\x05+\x00\x00*\xcc\x00\x81\x00\x05+\x00\x00\x13\xf1'
+    testdata = (
+        b'\x2a\x02\xac\xf3\x00\x81\x00\x03\x00\x0b\x00\x00\xfa\x45\x55\x64\x0e\x75\x73\x72\x6e\x61'
+        b'\x6d\x65\x72\x65\x6d\x6f\x76\x65\x64\x00\x00\x00\x0a\x00\x01\x00\x02\x12\x90\x00\x44\x00'
+        b'\x01\x00\x00\x03\x00\x04\x58\x90\x54\x36\x00\x45\x00\x04\x00\x00\x0f\x93\x00\x21\x00\x08'
+        b'\x00\x85\x00\x7d\x00\x7d\x00\x00\x00\x41\x00\x01\x00\x00\x37\x00\x04\x00\x00\x00\x00\x00'
+        b'\x0d\x00\x00\x00\x19\x00\x00\x00\x1d\x00\x24\x00\x00\x00\x05\x02\x01\xd2\x04\x72\x00\x01'
+        b'\x00\x05\x02\x01\xd2\x04\x72\x00\x03\x00\x05\x2b\x00\x00\x2a\xcc\x00\x81\x00\x05\x2b\x00'
+        b'\x00\x13\xf1'
+    )
 
     flap = FLAP(testdata)
     assert flap.ast == 0x2a
     assert flap.type == 0x02
     assert flap.seq == 44275
     assert flap.len == 129
-    assert flap.data == b'\x00\x03\x00\x0b\x00\x00\xfaEUd\x0eusrnameremoved\x00\x00\x00\n\x00\x01\x00\x02\x12\x90\x00D\x00\x01\x00\x00\x03\x00\x04X\x90T6\x00E\x00\x04\x00\x00\x0f\x93\x00!\x00\x08\x00\x85\x00}\x00}\x00\x00\x00A\x00\x01\x00\x007\x00\x04\x00\x00\x00\x00\x00\r\x00\x00\x00\x19\x00\x00\x00\x1d\x00$\x00\x00\x00\x05\x02\x01\xd2\x04r\x00\x01\x00\x05\x02\x01\xd2\x04r\x00\x03\x00\x05+\x00\x00*\xcc\x00\x81\x00\x05+\x00\x00\x13\xf1'
+    assert flap.data == (
+        b'\x00\x03\x00\x0b\x00\x00\xfa\x45\x55\x64\x0e\x75\x73\x72\x6e\x61\x6d\x65\x72\x65\x6d\x6f'
+        b'\x76\x65\x64\x00\x00\x00\x0a\x00\x01\x00\x02\x12\x90\x00\x44\x00\x01\x00\x00\x03\x00\x04'
+        b'\x58\x90\x54\x36\x00\x45\x00\x04\x00\x00\x0f\x93\x00\x21\x00\x08\x00\x85\x00\x7d\x00\x7d'
+        b'\x00\x00\x00\x41\x00\x01\x00\x00\x37\x00\x04\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x19\x00'
+        b'\x00\x00\x1d\x00\x24\x00\x00\x00\x05\x02\x01\xd2\x04\x72\x00\x01\x00\x05\x02\x01\xd2\x04'
+        b'\x72\x00\x03\x00\x05\x2b\x00\x00\x2a\xcc\x00\x81\x00\x05\x2b\x00\x00\x13\xf1'
+    )
 
     snac = SNAC(flap.data)
     assert snac.family == 3
     assert snac.subtype == 11
     assert snac.flags == 0
     assert snac.reqid == 0xfa455564
-    assert snac.data == b'\x0eusrnameremoved\x00\x00\x00\n\x00\x01\x00\x02\x12\x90\x00D\x00\x01\x00\x00\x03\x00\x04X\x90T6\x00E\x00\x04\x00\x00\x0f\x93\x00!\x00\x08\x00\x85\x00}\x00}\x00\x00\x00A\x00\x01\x00\x007\x00\x04\x00\x00\x00\x00\x00\r\x00\x00\x00\x19\x00\x00\x00\x1d\x00$\x00\x00\x00\x05\x02\x01\xd2\x04r\x00\x01\x00\x05\x02\x01\xd2\x04r\x00\x03\x00\x05+\x00\x00*\xcc\x00\x81\x00\x05+\x00\x00\x13\xf1'
+    assert snac.data == (
+        b'\x0e\x75\x73\x72\x6e\x61\x6d\x65\x72\x65\x6d\x6f\x76\x65\x64\x00\x00\x00\x0a\x00\x01\x00'
+        b'\x02\x12\x90\x00\x44\x00\x01\x00\x00\x03\x00\x04\x58\x90\x54\x36\x00\x45\x00\x04\x00\x00'
+        b'\x0f\x93\x00\x21\x00\x08\x00\x85\x00\x7d\x00\x7d\x00\x00\x00\x41\x00\x01\x00\x00\x37\x00'
+        b'\x04\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x19\x00\x00\x00\x1d\x00\x24\x00\x00\x00\x05\x02'
+        b'\x01\xd2\x04\x72\x00\x01\x00\x05\x02\x01\xd2\x04\x72\x00\x03\x00\x05\x2b\x00\x00\x2a\xcc'
+        b'\x00\x81\x00\x05\x2b\x00\x00\x13\xf1'
+    )
 
-    #skip over the buddyname and TLV count in Oncoming Buddy message
+    # skip over the buddyname and TLV count in Oncoming Buddy message
     tlvdata = snac.data[19:]
 
     tlvCount = 0
     while tlvdata:
-        t, l, v, tlvdata = tlv(tlvdata)
+        t, l_, v, tlvdata = tlv(tlvdata)
         tlvCount += 1
         if tlvCount == 1:
             # just check function return for first TLV
             assert t == 0x01
-            assert l == 2
+            assert l_ == 2
             assert v == b'\x12\x90'
-            assert tlvdata == b'\x00D\x00\x01\x00\x00\x03\x00\x04X\x90T6\x00E\x00\x04\x00\x00\x0f\x93\x00!\x00\x08\x00\x85\x00}\x00}\x00\x00\x00A\x00\x01\x00\x007\x00\x04\x00\x00\x00\x00\x00\r\x00\x00\x00\x19\x00\x00\x00\x1d\x00$\x00\x00\x00\x05\x02\x01\xd2\x04r\x00\x01\x00\x05\x02\x01\xd2\x04r\x00\x03\x00\x05+\x00\x00*\xcc\x00\x81\x00\x05+\x00\x00\x13\xf1'
+            assert tlvdata == (
+                b'\x00\x44\x00\x01\x00\x00\x03\x00\x04\x58\x90\x54\x36\x00\x45\x00\x04\x00\x00\x0f'
+                b'\x93\x00\x21\x00\x08\x00\x85\x00\x7d\x00\x7d\x00\x00\x00\x41\x00\x01\x00\x00\x37'
+                b'\x00\x04\x00\x00\x00\x00\x00\x0d\x00\x00\x00\x19\x00\x00\x00\x1d\x00\x24\x00\x00'
+                b'\x00\x05\x02\x01\xd2\x04\x72\x00\x01\x00\x05\x02\x01\xd2\x04\x72\x00\x03\x00\x05'
+                b'\x2b\x00\x00\x2a\xcc\x00\x81\x00\x05\x2b\x00\x00\x13\xf1'
+            )
 
     # make sure we extracted 10 TLVs
     assert tlvCount == 10
@@ -109,20 +138,22 @@
 def testExceptions():
     testdata = b'xxxxxx'
     try:
-        flap = FLAP(testdata)
+        FLAP(testdata)
     except dpkt.UnpackError as e:
         assert str(e) == 'invalid FLAP header'
+
     testdata = b'*\x02\x12\x34\x00\xff'
     try:
-        flap = FLAP(testdata)
+        FLAP(testdata)
     except dpkt.NeedData as e:
         assert str(e) == '0 left, 255 needed'
+
     try:
-        t, l, v, _ = tlv(b'x')
+        t, l_, v, _ = tlv(b'x')
     except dpkt.UnpackError as e:
         assert str(e) == 'invalid type, length fields'
 
     try:
-        t, l, v, _ = tlv(b'\x00\x01\x00\xff')
+        t, l_, v, _ = tlv(b'\x00\x01\x00\xff')
     except dpkt.NeedData as e:
         assert str(e) == '0 left, 255 needed'
diff --git a/dpkt/aoe.py b/dpkt/aoe.py
index 1ed1c39..bca6ed4 100644
--- a/dpkt/aoe.py
+++ b/dpkt/aoe.py
@@ -5,13 +5,13 @@
 import struct
 
 from . import dpkt
-from .decorators import deprecated
 from .compat import iteritems
 
+
 class AOE(dpkt.Packet):
     """ATA over Ethernet Protocol.
 
-    See more about the AOE on \
+    See more about the AOE on
     https://en.wikipedia.org/wiki/ATA_over_Ethernet
 
     Attributes:
@@ -20,27 +20,21 @@
     """
 
     __hdr__ = (
-        ('ver_fl', 'B', 0x10),
+        ('_ver_fl', 'B', 0x10),
         ('err', 'B', 0),
         ('maj', 'H', 0),
         ('min', 'B', 0),
         ('cmd', 'B', 0),
         ('tag', 'I', 0),
     )
+    __bit_fields__ = {
+        '_ver_fl': (
+            ('ver', 4),
+            ('fl', 4),
+        )
+    }
     _cmdsw = {}
 
-    @property
-    def ver(self): return self.ver_fl >> 4
-
-    @ver.setter
-    def ver(self, ver): self.ver_fl = (ver << 4) | (self.ver_fl & 0xf)
-
-    @property
-    def fl(self): return self.ver_fl & 0xf
-
-    @fl.setter
-    def fl(self, fl): self.ver_fl = (self.ver_fl & 0xf0) | fl
-
     @classmethod
     def set_cmd(cls, cmd, pktclass):
         cls._cmdsw[cmd] = pktclass
@@ -57,19 +51,13 @@
         except (KeyError, struct.error, dpkt.UnpackError):
             pass
 
-    def pack_hdr(self):
-        try:
-            return dpkt.Packet.pack_hdr(self)
-        except struct.error as e:
-            raise dpkt.PackError(str(e))
-
 
 AOE_CMD_ATA = 0
 AOE_CMD_CFG = 1
 AOE_FLAG_RSP = 1 << 3
 
 
-def __load_cmds():
+def _load_cmds():
     prefix = 'AOE_CMD_'
     g = globals()
 
@@ -86,4 +74,75 @@
 def _mod_init():
     """Post-initialization called when all dpkt modules are fully loaded"""
     if not AOE._cmdsw:
-        __load_cmds()
+        _load_cmds()
+
+
+def test_creation():
+    aoe = AOE()
+    # hdr fields
+    assert aoe._ver_fl == 0x10
+    assert aoe.err == 0
+    assert aoe.maj == 0
+    assert aoe.min == 0
+    assert aoe.cmd == 0
+    assert aoe.tag == 0
+    assert bytes(aoe) == b'\x10' + b'\x00' * 9
+
+
+def test_properties():
+    aoe = AOE()
+    # property getters
+    assert aoe.ver == 1
+    assert aoe.fl == 0
+
+    # property setters
+    aoe.ver = 2
+    assert aoe.ver == 2
+    assert aoe._ver_fl == 0x20
+
+    aoe.fl = 12
+    assert aoe.fl == 12
+    assert aoe._ver_fl == 0x2C
+
+
+def test_unpack():
+    from binascii import unhexlify
+    buf = unhexlify(
+        '1000000000'
+        '00'          # cmd: AOE_CMD_ATA
+        '00000000'    # tag
+    )
+    aoe = AOE(buf)
+    # AOE_CMD_ATA specified, but no data supplied
+    assert aoe.data == b''
+
+    buf = unhexlify(
+        '1000000000'
+        '00'          # cmd: AOE_CMD_ATA
+        '00000000'    # tag
+
+        # AOEDATA specification
+        '030a6b190000000045000028941f0000e30699b4232b2400de8e8442abd100500035e1'
+        '2920d9000000229bf0e204656b'
+    )
+    aoe = AOE(buf)
+    assert aoe.aoeata == aoe.data
+
+
+def test_cmds():
+    import dpkt
+    assert AOE.get_cmd(AOE_CMD_ATA) == dpkt.aoeata.AOEATA
+    assert AOE.get_cmd(AOE_CMD_CFG) == dpkt.aoecfg.AOECFG
+
+
+def test_cmd_loading():
+    # this test checks that failing to load a module isn't catastrophic
+    standard_cmds = AOE._cmdsw
+    # delete the existing code->module mappings
+    AOE._cmdsw = {}
+    assert not AOE._cmdsw
+    # create a new global constant pointing to a module which doesn't exist
+    globals()['AOE_CMD_FAIL'] = "FAIL"
+    _mod_init()
+    # check that the same modules were loaded, ignoring the fail
+    assert AOE._cmdsw == standard_cmds
diff --git a/dpkt/aoeata.py b/dpkt/aoeata.py
index ad9da73..81feeaf 100644
--- a/dpkt/aoeata.py
+++ b/dpkt/aoeata.py
@@ -11,14 +11,14 @@
 class AOEATA(dpkt.Packet):
     """ATA over Ethernet ATA command.
 
-    See more about the AOEATA on \
+    See more about the AOEATA on
     https://en.wikipedia.org/wiki/ATA_over_Ethernet
 
     Attributes:
         __hdr__: Header fields of AOEATA.
         data: Message data.
     """
-    
+
     __hdr__ = (
         ('aflags', 'B', 0),
         ('errfeat', 'B', 0),
@@ -37,11 +37,7 @@
 
 
 def test_aoeata():
-    s = b'\x03\x0a\x6b\x19\x00\x00\x00\x00\x45\x00\x00\x28\x94\x1f\x00\x00\xe3\x06\x99\xb4\x23\x2b\x24\x00\xde\x8e\x84\x42\xab\xd1\x00\x50\x00\x35\xe1\x29\x20\xd9\x00\x00\x00\x22\x9b\xf0\xe2\x04\x65\x6b'
+    s = (b'\x03\x0a\x6b\x19\x00\x00\x00\x00\x45\x00\x00\x28\x94\x1f\x00\x00\xe3\x06\x99\xb4\x23\x2b'
+         b'\x24\x00\xde\x8e\x84\x42\xab\xd1\x00\x50\x00\x35\xe1\x29\x20\xd9\x00\x00\x00\x22\x9b\xf0\xe2\x04\x65\x6b')
     aoeata = AOEATA(s)
     assert (bytes(aoeata) == s)
-
-
-if __name__ == '__main__':
-    test_aoeata()
-    print('Tests Successful...')
diff --git a/dpkt/aoecfg.py b/dpkt/aoecfg.py
index a7416f5..0dd5495 100644
--- a/dpkt/aoecfg.py
+++ b/dpkt/aoecfg.py
@@ -16,7 +16,7 @@
         __hdr__: Header fields of AOECFG.
         data: Message data.
     """
-    
+
     __hdr__ = (
         ('bufcnt', 'H', 0),
         ('fwver', 'H', 0),
@@ -27,11 +27,7 @@
 
 
 def test_aoecfg():
-    s = b'\x01\x02\x03\x04\x05\x06\x11\x12\x13\x14\x15\x16\x88\xa2\x10\x00\x00\x01\x02\x01\x80\x00\x00\x00\x12\x34\x00\x00\x00\x00\x04\x00' + b'\0xed' * 1024
+    s = (b'\x01\x02\x03\x04\x05\x06\x11\x12\x13\x14\x15\x16\x88\xa2\x10\x00\x00\x01\x02\x01\x80'
+         b'\x00\x00\x00\x12\x34\x00\x00\x00\x00\x04\x00' + b'\0xed' * 1024)
     aoecfg = AOECFG(s[14 + 10:])
     assert (aoecfg.bufcnt == 0x1234)
-
-
-if __name__ == '__main__':
-    test_aoecfg()
-    print('Tests Successful...')
diff --git a/dpkt/arp.py b/dpkt/arp.py
index 77d20aa..81d30c7 100644
--- a/dpkt/arp.py
+++ b/dpkt/arp.py
@@ -28,15 +28,15 @@
     Attributes:
         __hdr__: Header fields of ARP.
     """
-    
+
     __hdr__ = (
         ('hrd', 'H', ARP_HRD_ETH),
         ('pro', 'H', ARP_PRO_IP),
         ('hln', 'B', 6),  # hardware address length
         ('pln', 'B', 4),  # protocol address length
         ('op', 'H', ARP_OP_REQUEST),
-        ('sha', '6s', ''),
-        ('spa', '4s', ''),
-        ('tha', '6s', ''),
-        ('tpa', '4s', '')
+        ('sha', '6s', b''),
+        ('spa', '4s', b''),
+        ('tha', '6s', b''),
+        ('tpa', '4s', b'')
     )
diff --git a/dpkt/asn1.py b/dpkt/asn1.py
index 3d15c9f..3b3f958 100644
--- a/dpkt/asn1.py
+++ b/dpkt/asn1.py
@@ -5,7 +5,7 @@
 from __future__ import print_function
 
 import struct
-import time
+from calendar import timegm
 
 from . import dpkt
 from .compat import compat_ord
@@ -46,7 +46,6 @@
     Returns:
         A floating point number, indicates seconds since the Epoch.
     """
-
     yy = int(buf[:2])
     mn = int(buf[2:4])
     dd = int(buf[4:6])
@@ -58,13 +57,14 @@
     except TypeError:
         ss = 0
         buf = buf[10:]
+
     if buf[0] == '+':
         hh -= int(buf[1:3])
         mm -= int(buf[3:5])
     elif buf[0] == '-':
         hh += int(buf[1:3])
         mm += int(buf[3:5])
-    return time.mktime((2000 + yy, mn, dd, hh, mm, ss, 0, 0, 0))
+    return timegm((2000 + yy, mn, dd, hh, mm, ss, 0, 0, 0))
 
 
 def decode(buf):
@@ -81,31 +81,30 @@
     Raises:
         UnpackError: An error occurred the ASN.1 length exceed.
     """
-
     msg = []
     while buf:
         t = compat_ord(buf[0])
         constructed = t & CONSTRUCTED
         tag = t & TAGMASK
-        l = compat_ord(buf[1])
+        l_ = compat_ord(buf[1])
         c = 0
-        if constructed and l == 128:
+        if constructed and l_ == 128:
             # XXX - constructed, indefinite length
             msg.append((t, decode(buf[2:])))
-        elif l >= 128:
-            c = l & 127
+        elif l_ >= 128:
+            c = l_ & 127
             if c == 1:
-                l = compat_ord(buf[2])
+                l_ = compat_ord(buf[2])
             elif c == 2:
-                l = struct.unpack('>H', buf[2:4])[0]
+                l_ = struct.unpack('>H', buf[2:4])[0]
             elif c == 3:
-                l = struct.unpack('>I', buf[1:5])[0] & 0xfff
+                l_ = struct.unpack('>I', buf[1:5])[0] & 0xfff
                 c = 2
             elif c == 4:
-                l = struct.unpack('>I', buf[2:6])[0]
+                l_ = struct.unpack('>I', buf[2:6])[0]
             else:
                 # XXX - can be up to 127 bytes, but...
-                raise dpkt.UnpackError('excessive long-form ASN.1 length %d' % l)
+                raise dpkt.UnpackError('excessive long-form ASN.1 length %d' % l_)
 
         # Skip type, length
         buf = buf[2 + c:]
@@ -114,33 +113,199 @@
         if constructed:
             msg.append((t, decode(buf)))
         elif tag == INTEGER:
-            if l == 0:
+            if l_ == 0:
                 n = 0
-            elif l == 1:
+            elif l_ == 1:
                 n = compat_ord(buf[0])
-            elif l == 2:
+            elif l_ == 2:
                 n = struct.unpack('>H', buf[:2])[0]
-            elif l == 3:
+            elif l_ == 3:
                 n = struct.unpack('>I', buf[:4])[0] >> 8
-            elif l == 4:
+            elif l_ == 4:
                 n = struct.unpack('>I', buf[:4])[0]
             else:
-                raise dpkt.UnpackError('excessive integer length > %d bytes' % l)
+                raise dpkt.UnpackError('excessive integer length > %d bytes' % l_)
             msg.append((t, n))
         elif tag == UTC_TIME:
-            msg.append((t, utctime(buf[:l])))
+            msg.append((t, utctime(buf[:l_])))
         else:
-            msg.append((t, buf[:l]))
+            msg.append((t, buf[:l_]))
 
         # Skip content
-        buf = buf[l:]
+        buf = buf[l_:]
     return msg
 
 
 def test_asn1():
-    s = b'0\x82\x02Q\x02\x01\x0bc\x82\x02J\x04xcn=Douglas J Song 1, ou=Information Technology Division, ou=Faculty and Staff, ou=People, o=University of Michigan, c=US\n\x01\x00\n\x01\x03\x02\x01\x00\x02\x01\x00\x01\x01\x00\x87\x0bobjectclass0\x82\x01\xb0\x04\rmemberOfGroup\x04\x03acl\x04\x02cn\x04\x05title\x04\rpostalAddress\x04\x0ftelephoneNumber\x04\x04mail\x04\x06member\x04\thomePhone\x04\x11homePostalAddress\x04\x0bobjectClass\x04\x0bdescription\x04\x18facsimileTelephoneNumber\x04\x05pager\x04\x03uid\x04\x0cuserPassword\x04\x08joinable\x04\x10associatedDomain\x04\x05owner\x04\x0erfc822ErrorsTo\x04\x08ErrorsTo\x04\x10rfc822RequestsTo\x04\nRequestsTo\x04\tmoderator\x04\nlabeledURL\x04\nonVacation\x04\x0fvacationMessage\x04\x05drink\x04\x0elastModifiedBy\x04\x10lastModifiedTime\x04\rmodifiersname\x04\x0fmodifytimestamp\x04\x0ccreatorsname\x04\x0fcreatetimestamp'
-    assert (decode(s) == [(48, [(2, 11), (99, [(4, b'cn=Douglas J Song 1, ou=Information Technology Division, ou=Faculty and Staff, ou=People, o=University of Michigan, c=US'), (10, b'\x00'), (10, b'\x03'), (2, 0), (2, 0), (1, b'\x00'), (135, b'objectclass'), (48, [(4, b'memberOfGroup'), (4, b'acl'), (4, b'cn'), (4, b'title'), (4, b'postalAddress'), (4, b'telephoneNumber'), (4, b'mail'), (4, b'member'), (4, b'homePhone'), (4, b'homePostalAddress'), (4, b'objectClass'), (4, b'description'), (4, b'facsimileTelephoneNumber'), (4, b'pager'), (4, b'uid'), (4, b'userPassword'), (4, b'joinable'), (4, b'associatedDomain'), (4, b'owner'), (4, b'rfc822ErrorsTo'), (4, b'ErrorsTo'), (4, b'rfc822RequestsTo'), (4, b'RequestsTo'), (4, b'moderator'), (4, b'labeledURL'), (4, b'onVacation'), (4, b'vacationMessage'), (4, b'drink'), (4, b'lastModifiedBy'), (4, b'lastModifiedTime'), (4, b'modifiersname'), (4, b'modifytimestamp'), (4, b'creatorsname'), (4, b'createtimestamp')])])])])
+    s = (
+        b'0\x82\x02Q\x02\x01\x0bc\x82\x02J\x04xcn=Douglas J Song 1, ou=Information Technology Division,'
+        b' ou=Faculty and Staff, ou=People, o=University of Michigan, c=US\n\x01\x00\n\x01\x03\x02\x01'
+        b'\x00\x02\x01\x00\x01\x01\x00\x87\x0bobjectclass0\x82\x01\xb0\x04\rmemberOfGroup\x04\x03acl'
+        b'\x04\x02cn\x04\x05title\x04\rpostalAddress\x04\x0ftelephoneNumber\x04\x04mail\x04\x06member'
+        b'\x04\thomePhone\x04\x11homePostalAddress\x04\x0bobjectClass\x04\x0bdescription\x04\x18'
+        b'facsimileTelephoneNumber\x04\x05pager\x04\x03uid\x04\x0cuserPassword\x04\x08joinable\x04\x10'
+        b'associatedDomain\x04\x05owner\x04\x0erfc822ErrorsTo\x04\x08ErrorsTo\x04\x10rfc822RequestsTo\x04\n'
+        b'RequestsTo\x04\tmoderator\x04\nlabeledURL\x04\nonVacation\x04\x0fvacationMessage\x04\x05drink\x04\x0e'
+        b'lastModifiedBy\x04\x10lastModifiedTime\x04\rmodifiersname\x04\x0fmodifytimestamp\x04\x0ccreatorsname'
+        b'\x04\x0fcreatetimestamp'
+    )
+    assert decode(s) == [
+        (48, [
+            (2, 11),
+            (99, [
+                (4, (
+                    b'cn=Douglas J Song 1, '
+                    b'ou=Information Technology Division, '
+                    b'ou=Faculty and Staff, '
+                    b'ou=People, '
+                    b'o=University of Michigan, '
+                    b'c=US'
+                )),
+                (10, b'\x00'),
+                (10, b'\x03'),
+                (2, 0),
+                (2, 0),
+                (1, b'\x00'),
+                (135, b'objectclass'),
+                (48, [
+                    (4, b'memberOfGroup'),
+                    (4, b'acl'),
+                    (4, b'cn'),
+                    (4, b'title'),
+                    (4, b'postalAddress'),
+                    (4, b'telephoneNumber'),
+                    (4, b'mail'),
+                    (4, b'member'),
+                    (4, b'homePhone'),
+                    (4, b'homePostalAddress'),
+                    (4, b'objectClass'),
+                    (4, b'description'),
+                    (4, b'facsimileTelephoneNumber'),
+                    (4, b'pager'),
+                    (4, b'uid'),
+                    (4, b'userPassword'),
+                    (4, b'joinable'),
+                    (4, b'associatedDomain'),
+                    (4, b'owner'),
+                    (4, b'rfc822ErrorsTo'),
+                    (4, b'ErrorsTo'),
+                    (4, b'rfc822RequestsTo'),
+                    (4, b'RequestsTo'),
+                    (4, b'moderator'),
+                    (4, b'labeledURL'),
+                    (4, b'onVacation'),
+                    (4, b'vacationMessage'),
+                    (4, b'drink'),
+                    (4, b'lastModifiedBy'),
+                    (4, b'lastModifiedTime'),
+                    (4, b'modifiersname'),
+                    (4, b'modifytimestamp'),
+                    (4, b'creatorsname'),
+                    (4, b'createtimestamp'),
+                ])
+            ])
+        ])
+    ]
 
-if __name__ == '__main__':
-    test_asn1()
-    print('Tests Successful...')
+
+def test_utctime():
+    buf = (
+        '201005'  # yymndd
+        '012345'  # hhmmss
+        '+1234'   # +hhmm
+    )
+    assert utctime(buf) == 1601815785.0
+
+    buf = (
+        '201005'  # yymndd
+        '012345'  # hhmmss
+        '-1234'   # -hhmm
+    )
+    assert utctime(buf) == 1601906265.0
+
+
+def test_decode():
+    import pytest
+    from binascii import unhexlify
+    buf = unhexlify(
+        '20'   # CONSTRUCTED
+        '80'   # 128 | 0
+    )
+    assert decode(buf) == [(32, []), (32, [])]
+
+    # unpacking UTC_TIME
+    buf = unhexlify(
+        '17'  # t: code: UTC_TIME
+        '81'  # l_: code: 128 | 1 (constructed
+        '22'  # data len
+        '3230313030353031323334352b30303030'
+
+    )
+    assert decode(buf) == [(23, 1601861025.0)]
+
+    # unpacking 2-byte size; zero-length integer
+    buf = unhexlify(
+        '02'    # t: INTEGER
+        '82'    # l_: 128 | 2
+        '0000'  # new l_
+    )
+    assert decode(buf) == [(2, 0)]
+
+    # unpacking 3-byte size
+    buf = unhexlify(
+        '02'        # t: INTEGER
+        '83'        # l_: 128 | 3
+        '000001'    # new l_
+    )
+    assert decode(buf) == [(2, 1)]
+
+    # unpacking 4-byte size
+    buf = unhexlify(
+        '02'        # t: INTEGER
+        '84'        # l_: 128 | 4
+        '00000002'  # new l_
+        'abcd'
+    )
+    assert decode(buf) == [(2, 43981)]
+
+    # unpacking 4-byte size
+    buf = unhexlify(
+        '02'        # t: INTEGER
+        '85'        # l_: 128 | 5
+    )
+    with pytest.raises(dpkt.UnpackError, match="excessive long-form ASN.1 length 133"):
+        decode(buf)
+
+    # unpacking 1-byte size; 4-byte integer
+    buf = unhexlify(
+        '02'        # t: INTEGER
+        '81'        # l_: 128 | 1
+        '04'        # new l_
+        '12345678'  # integer
+    )
+    assert decode(buf) == [(2, 305419896)]
+
+    # unpacking 1-byte size; 4-byte integer
+    buf = unhexlify(
+        '02'        # t: INTEGER
+        '81'        # l_: 128 | 1
+        '05'        # new l_
+    )
+    with pytest.raises(dpkt.UnpackError, match="excessive integer length > 5 bytes"):
+        decode(buf)
+
+    # unpacking 1-byte size; 3-byte integer
+    buf = unhexlify(
+        '02'        # t: INTEGER
+        '81'        # l_: 128 | 1
+        '03'        # new l_
+        '123456'    # integer
+
+        '02'        # t: INTEGER
+        '81'        # l_: 128 | 1
+        '00'        # new l_
+    )
+    assert decode(buf) == [
+        (2, 1193046),
+        (2, 0),
+    ]
diff --git a/dpkt/bgp.py b/dpkt/bgp.py
old mode 100644
new mode 100755
index 6ef4c77..352dfa0
--- a/dpkt/bgp.py
+++ b/dpkt/bgp.py
@@ -8,7 +8,7 @@
 import socket
 
 from . import dpkt
-from .decorators import deprecated
+from .compat import compat_ord
 
 
 # Border Gateway Protocol 4 - RFC 4271
@@ -20,6 +20,8 @@
 # Cease Subcodes - RFC 4486
 # NOPEER Community - RFC 3765
 # Multiprotocol Extensions - 2858
+# Advertisement of Multiple Paths in BGP - RFC 7911
+# BGP Support for Four-Octet Autonomous System (AS) Number Spac - RFC 6793
 
 # Message Types
 OPEN = 1
@@ -62,11 +64,13 @@
 # Common AFI types
 AFI_IPV4 = 1
 AFI_IPV6 = 2
+AFI_L2VPN = 25
 
 # Multiprotocol SAFI types
 SAFI_UNICAST = 1
 SAFI_MULTICAST = 2
 SAFI_UNICAST_MULTICAST = 3
+SAFI_EVPN = 70
 
 # OPEN Message Optional Parameters
 AUTHENTICATION = 1
@@ -126,7 +130,7 @@
     """Border Gateway Protocol.
 
     BGP is an inter-AS routing protocol.
-    See more about the BGP on \
+    See more about the BGP on
     https://en.wikipedia.org/wiki/Border_Gateway_Protocol
 
     Attributes:
@@ -148,7 +152,7 @@
         elif self.type == UPDATE:
             self.data = self.update = self.Update(self.data)
         elif self.type == NOTIFICATION:
-            self.data = self.notifiation = self.Notification(self.data)
+            self.data = self.notification = self.Notification(self.data)
         elif self.type == KEEPALIVE:
             self.data = self.keepalive = self.Keepalive(self.data)
         elif self.type == ROUTE_REFRESH:
@@ -168,14 +172,14 @@
 
         def unpack(self, buf):
             dpkt.Packet.unpack(self, buf)
-            l = []
+            l_ = []
             plen = self.param_len
             while plen > 0:
                 param = self.Parameter(self.data)
                 self.data = self.data[len(param):]
                 plen -= len(param)
-                l.append(param)
-            self.data = self.parameters = l
+                l_.append(param)
+            self.data = self.parameters = l_
 
         def __len__(self):
             return self.__hdr_len__ + sum(map(len, self.parameters))
@@ -228,44 +232,47 @@
             # Withdrawn Routes
             wlen = struct.unpack('>H', self.data[:2])[0]
             self.data = self.data[2:]
-            l = []
+            l_ = []
             while wlen > 0:
                 route = RouteIPV4(self.data)
                 self.data = self.data[len(route):]
                 wlen -= len(route)
-                l.append(route)
-            self.withdrawn = l
+                l_.append(route)
+            self.withdrawn = l_
 
             # Path Attributes
             plen = struct.unpack('>H', self.data[:2])[0]
             self.data = self.data[2:]
-            l = []
+            l_ = []
             while plen > 0:
                 attr = self.Attribute(self.data)
                 self.data = self.data[len(attr):]
                 plen -= len(attr)
-                l.append(attr)
-            self.attributes = l
+                l_.append(attr)
+            self.attributes = l_
 
             # Announced Routes
-            l = []
+            l_ = []
             while self.data:
-                route = RouteIPV4(self.data)
+                if len(self.data) % 9 == 0:
+                    route = ExtendedRouteIPV4(self.data)
+                else:
+                    route = RouteIPV4(self.data)
                 self.data = self.data[len(route):]
-                l.append(route)
-            self.announced = l
+                l_.append(route)
+            self.announced = l_
 
         def __len__(self):
             return 2 + sum(map(len, self.withdrawn)) + \
-                   2 + sum(map(len, self.attributes)) + \
-                   sum(map(len, self.announced))
+                2 + sum(map(len, self.attributes)) + \
+                sum(map(len, self.announced))
 
         def __bytes__(self):
             return struct.pack('>H', sum(map(len, self.withdrawn))) + \
-                   b''.join(map(bytes, self.withdrawn)) + \
-                   struct.pack('>H', sum(map(len, self.attributes))) + \
-                   b''.join(map(bytes, self.attributes)) + \
-                   b''.join(map(bytes, self.announced))
+                b''.join(map(bytes, self.withdrawn)) + \
+                struct.pack('>H', sum(map(len, self.attributes))) + \
+                b''.join(map(bytes, self.attributes)) + \
+                b''.join(map(bytes, self.announced))
 
         class Attribute(dpkt.Packet):
             __hdr__ = (
@@ -368,12 +375,16 @@
 
                 def unpack(self, buf):
                     self.data = buf
-                    l = []
+                    l_ = []
+                    as4 = len(self.data) == 6
                     while self.data:
-                        seg = self.ASPathSegment(self.data)
+                        if as4:
+                            seg = self.ASPathSegment4(self.data)
+                        else:
+                            seg = self.ASPathSegment(self.data)
                         self.data = self.data[len(seg):]
-                        l.append(seg)
-                    self.data = self.segments = l
+                        l_.append(seg)
+                    self.data = self.segments = l_
 
                 def __len__(self):
                     return sum(map(len, self.data))
@@ -389,12 +400,12 @@
 
                     def unpack(self, buf):
                         dpkt.Packet.unpack(self, buf)
-                        l = []
+                        l_ = []
                         for i in range(self.len):
                             AS = struct.unpack('>H', self.data[:2])[0]
                             self.data = self.data[2:]
-                            l.append(AS)
-                        self.data = self.path = l
+                            l_.append(AS)
+                        self.data = self.path = l_
 
                     def __len__(self):
                         return self.__hdr_len__ + 2 * len(self.path)
@@ -405,6 +416,31 @@
                             as_str += struct.pack('>H', AS)
                         return self.pack_hdr() + as_str
 
+                class ASPathSegment4(dpkt.Packet):
+                    __hdr__ = (
+                        ('type', 'B', 0),
+                        ('len', 'B', 0)
+                    )
+
+                    def unpack(self, buf):
+                        dpkt.Packet.unpack(self, buf)
+                        l_ = []
+                        for i in range(self.len):
+                            if len(self.data) >= 4:
+                                AS = struct.unpack('>I', self.data[:4])[0]
+                                self.data = self.data[4:]
+                                l_.append(AS)
+                        self.path = l_
+
+                    def __len__(self):
+                        return self.__hdr_len__ + 4 * len(self.path)
+
+                    def __bytes__(self):
+                        as_str = b''
+                        for AS in self.path:
+                            as_str += struct.pack('>I', AS)
+                        return self.pack_hdr() + as_str
+
             class NextHop(dpkt.Packet):
                 __hdr__ = (
                     ('ip', 'I', 0),
@@ -443,7 +479,7 @@
 
                 def unpack(self, buf):
                     self.data = buf
-                    l = []
+                    l_ = []
                     while self.data:
                         val = struct.unpack('>I', self.data[:4])[0]
                         if (0x00000000 <= val <= 0x0000ffff) or (0xffff0000 <= val <= 0xffffffff):
@@ -451,8 +487,8 @@
                         else:
                             comm = self.Community(self.data[:4])
                         self.data = self.data[len(comm):]
-                        l.append(comm)
-                    self.data = self.list = l
+                        l_.append(comm)
+                    self.data = self.list = l_
 
                 def __len__(self):
                     return sum(map(len, self.data))
@@ -483,12 +519,12 @@
 
                 def unpack(self, buf):
                     self.data = buf
-                    l = []
+                    l_ = []
                     while self.data:
                         id = struct.unpack('>I', self.data[:4])[0]
                         self.data = self.data[4:]
-                        l.append(id)
-                    self.data = self.list = l
+                        l_.append(id)
+                    self.data = self.list = l_
 
                 def __len__(self):
                     return 4 * len(self.list)
@@ -509,51 +545,63 @@
                     dpkt.Packet.unpack(self, buf)
 
                     # Next Hop
+                    hop_len = 4
+                    if self.afi == AFI_IPV6:
+                        hop_len = 16
+                    l_ = []
                     nlen = struct.unpack('B', self.data[:1])[0]
                     self.data = self.data[1:]
+                    # next_hop is kept for backward compatibility
                     self.next_hop = self.data[:nlen]
-                    self.data = self.data[nlen:]
+                    while nlen > 0:
+                        hop = self.data[:hop_len]
+                        l_.append(hop)
+                        self.data = self.data[hop_len:]
+                        nlen -= hop_len
+                    self.next_hops = l_
 
                     # SNPAs
-                    l = []
+                    l_ = []
                     num_snpas = struct.unpack('B', self.data[:1])[0]
                     self.data = self.data[1:]
                     for i in range(num_snpas):
                         snpa = self.SNPA(self.data)
                         self.data = self.data[len(snpa):]
-                        l.append(snpa)
-                    self.snpas = l
+                        l_.append(snpa)
+                    self.snpas = l_
 
                     if self.afi == AFI_IPV4:
                         Route = RouteIPV4
                     elif self.afi == AFI_IPV6:
                         Route = RouteIPV6
+                    elif self.afi == AFI_L2VPN:
+                        Route = RouteEVPN
                     else:
                         Route = RouteGeneric
 
                     # Announced Routes
-                    l = []
+                    l_ = []
                     while self.data:
                         route = Route(self.data)
                         self.data = self.data[len(route):]
-                        l.append(route)
-                    self.data = self.announced = l
+                        l_.append(route)
+                    self.data = self.announced = l_
 
                 def __len__(self):
                     return self.__hdr_len__ + \
-                           1 + len(self.next_hop) + \
-                           1 + sum(map(len, self.snpas)) + \
-                           sum(map(len, self.announced))
+                        1 + sum(map(len, self.next_hops)) + \
+                        1 + sum(map(len, self.snpas)) + \
+                        sum(map(len, self.announced))
 
                 def __bytes__(self):
                     return self.pack_hdr() + \
-                           struct.pack('B', len(self.next_hop)) + \
-                           bytes(self.next_hop) + \
-                           struct.pack('B', len(self.snpas)) + \
-                           b''.join(map(bytes, self.snpas)) + \
-                           b''.join(map(bytes, self.announced))
+                        struct.pack('B', sum(map(len, self.next_hops))) + \
+                        b''.join(map(bytes, self.next_hops)) + \
+                        struct.pack('B', len(self.snpas)) + \
+                        b''.join(map(bytes, self.snpas)) + \
+                        b''.join(map(bytes, self.announced))
 
-                class SNPA(object):
+                class SNPA(dpkt.Packet):
                     __hdr__ = (
                         ('len', 'B', 0),
                     )
@@ -575,16 +623,18 @@
                         Route = RouteIPV4
                     elif self.afi == AFI_IPV6:
                         Route = RouteIPV6
+                    elif self.afi == AFI_L2VPN:
+                        Route = RouteEVPN
                     else:
                         Route = RouteGeneric
 
                     # Withdrawn Routes
-                    l = []
+                    l_ = []
                     while self.data:
                         route = Route(self.data)
                         self.data = self.data[len(route):]
-                        l.append(route)
-                    self.data = self.withdrawn = l
+                        l_.append(route)
+                    self.data = self.withdrawn = l_
 
                 def __len__(self):
                     return self.__hdr_len__ + sum(map(len, self.data))
@@ -652,6 +702,17 @@
         return self.pack_hdr() + self.prefix[:(self.len + 7) // 8]
 
 
+class ExtendedRouteIPV4(RouteIPV4):
+    __hdr__ = (
+        ('path_id', 'I', 0),
+        ('len', 'B', 0),
+    )
+
+    def __repr__(self):
+        cidr = '%s/%d PathId %d' % (socket.inet_ntoa(self.prefix), self.len, self.path_id)
+        return '%s(%s)' % (self.__class__.__name__, cidr)
+
+
 class RouteIPV6(dpkt.Packet):
     __hdr__ = (
         ('len', 'B', 0),
@@ -670,10 +731,116 @@
         return self.pack_hdr() + self.prefix[:(self.len + 7) // 8]
 
 
+class RouteEVPN(dpkt.Packet):
+    __hdr__ = (
+        ('type', 'B', 0),
+        ('len', 'B', 0)
+    )
+
+    def unpack(self, buf):
+        dpkt.Packet.unpack(self, buf)
+        self.route_data = buf = self.data[:self.len]
+        self.data = self.data[self.len:]
+
+        # Get route distinguisher.
+        self.rd = buf[:8]
+        buf = buf[8:]
+
+        # Get route information.  Not all fields are present on all route types.
+        if self.type != 0x3:
+            self.esi = buf[:10]
+            buf = buf[10:]
+
+        if self.type != 0x4:
+            self.eth_id = buf[:4]
+            buf = buf[4:]
+
+        if self.type == 0x2:
+            self.mac_address_length = compat_ord(buf[0])
+            if self.mac_address_length == 48:
+                self.mac_address = buf[1:7]
+                buf = buf[7:]
+            else:
+                self.mac_address = None
+                buf = buf[1:]
+
+        if self.type != 0x1:
+            self.ip_address_length = compat_ord(buf[0])
+            if self.ip_address_length == 128:
+                self.ip_address = buf[1:17]
+                buf = buf[17:]
+            elif self.ip_address_length == 32:
+                self.ip_address = buf[1:5]
+                buf = buf[5:]
+            else:
+                self.ip_address = None
+                buf = buf[1:]
+
+        if self.type in [0x1, 0x2]:
+            self.mpls_label_stack = buf[:3]
+            buf = buf[3:]
+            if self.len > len(buf):
+                self.mpls_label_stack += buf[:3]
+
+    def __len__(self):
+        return self.__hdr_len__ + self.len
+
+    def __bytes__(self):
+        return self.pack_hdr() + self.route_data
+
+
 __bgp1 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x13\x04'
-__bgp2 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x63\x02\x00\x00\x00\x48\x40\x01\x01\x00\x40\x02\x0a\x01\x02\x01\xf4\x01\xf4\x02\x01\xfe\xbb\x40\x03\x04\xc0\xa8\x00\x0f\x40\x05\x04\x00\x00\x00\x64\x40\x06\x00\xc0\x07\x06\xfe\xba\xc0\xa8\x00\x0a\xc0\x08\x0c\xfe\xbf\x00\x01\x03\x16\x00\x04\x01\x54\x00\xfa\x80\x09\x04\xc0\xa8\x00\x0f\x80\x0a\x04\xc0\xa8\x00\xfa\x16\xc0\xa8\x04'
-__bgp3 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x79\x02\x00\x00\x00\x62\x40\x01\x01\x00\x40\x02\x00\x40\x05\x04\x00\x00\x00\x64\xc0\x10\x08\x00\x02\x01\x2c\x00\x00\x01\x2c\xc0\x80\x24\x00\x00\xfd\xe9\x40\x01\x01\x00\x40\x02\x04\x02\x01\x15\xb3\x40\x05\x04\x00\x00\x00\x2c\x80\x09\x04\x16\x05\x05\x05\x80\x0a\x04\x16\x05\x05\x05\x90\x0e\x00\x1e\x00\x01\x80\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x04\x04\x04\x00\x60\x18\x77\x01\x00\x00\x01\xf4\x00\x00\x01\xf4\x85'
-__bgp4 = b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x2d\x01\x04\x00\xed\x00\x5a\xc6\x6e\x83\x7d\x10\x02\x06\x01\x04\x00\x01\x00\x01\x02\x02\x80\x00\x02\x02\x02\x00'
+__bgp2 = (
+    b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x63\x02\x00\x00\x00\x48\x40\x01'
+    b'\x01\x00\x40\x02\x0a\x01\x02\x01\xf4\x01\xf4\x02\x01\xfe\xbb\x40\x03\x04\xc0\xa8\x00\x0f\x40\x05\x04'
+    b'\x00\x00\x00\x64\x40\x06\x00\xc0\x07\x06\xfe\xba\xc0\xa8\x00\x0a\xc0\x08\x0c\xfe\xbf\x00\x01\x03\x16'
+    b'\x00\x04\x01\x54\x00\xfa\x80\x09\x04\xc0\xa8\x00\x0f\x80\x0a\x04\xc0\xa8\x00\xfa\x16\xc0\xa8\x04'
+)
+__bgp3 = (
+    b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x79\x02\x00\x00\x00\x62\x40\x01'
+    b'\x01\x00\x40\x02\x00\x40\x05\x04\x00\x00\x00\x64\xc0\x10\x08\x00\x02\x01\x2c\x00\x00\x01\x2c\xc0\x80'
+    b'\x24\x00\x00\xfd\xe9\x40\x01\x01\x00\x40\x02\x04\x02\x01\x15\xb3\x40\x05\x04\x00\x00\x00\x2c\x80\x09'
+    b'\x04\x16\x05\x05\x05\x80\x0a\x04\x16\x05\x05\x05\x90\x0e\x00\x1e\x00\x01\x80\x0c\x00\x00\x00\x00\x00'
+    b'\x00\x00\x00\x0c\x04\x04\x04\x00\x60\x18\x77\x01\x00\x00\x01\xf4\x00\x00\x01\xf4\x85'
+)
+__bgp4 = (
+    b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x2d\x01\x04\x00\xed\x00\x5a\xc6'
+    b'\x6e\x83\x7d\x10\x02\x06\x01\x04\x00\x01\x00\x01\x02\x02\x80\x00\x02\x02\x02\x00'
+)
+
+# BGP-EVPN type 1-4 packets for testing.
+__bgp5 = (
+    b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x60\x02\x00\x00\x00\x49\x40\x01'
+    b'\x01\x00\x40\x02\x00\x40\x05\x04\x00\x00\x00\x64\xc0\x10\x10\x03\x0c\x00\x00\x00\x00\x00\x08\x00\x02'
+    b'\x03\xe8\x00\x00\x00\x02\x90\x0e\x00\x24\x00\x19\x46\x04\x01\x01\x01\x02\x00\x01\x19\x00\x01\x01\x01'
+    b'\x01\x02\x00\x02\x05\x00\x00\x03\xe8\x00\x00\x04\x00\x00\x00\x00\x00\x02\x00\x00\x02'
+)
+__bgp6 = (
+    b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x6f\x02\x00\x00\x00\x58\x40\x01'
+    b'\x01\x00\x40\x02\x00\x40\x05\x04\x00\x00\x00\x64\xc0\x10\x10\x03\x0c\x00\x00\x00\x00\x00\x08\x00\x02'
+    b'\x03\xe8\x00\x00\x00\x02\x90\x0e\x00\x33\x00\x19\x46\x04\x01\x01\x01\x02\x00\x02\x28\x00\x01\x01\x01'
+    b'\x01\x02\x00\x02\x05\x00\x00\x03\xe8\x00\x00\x04\x00\x00\x00\x00\x00\x02\x30\xcc\xaa\x02\x9c\xd8\x29'
+    b'\x20\xc0\xb4\x01\x02\x00\x00\x02\x00\x00\x00'
+)
+__bgp7 = (
+    b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x58\x02\x00\x00\x00\x41\x40\x01'
+    b'\x01\x00\x40\x02\x00\x40\x05\x04\x00\x00\x00\x64\xc0\x10\x10\x03\x0c\x00\x00\x00\x00\x00\x08\x00\x02'
+    b'\x03\xe8\x00\x00\x00\x02\x90\x0e\x00\x1c\x00\x19\x46\x04\x01\x01\x01\x02\x00\x03\x11\x00\x01\x01\x01'
+    b'\x01\x02\x00\x02\x00\x00\x00\x02\x20\xc0\xb4\x01\x02'
+)
+__bgp8 = (
+    b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x5f\x02\x00\x00\x00\x48\x40\x01'
+    b'\x01\x00\x40\x02\x00\x40\x05\x04\x00\x00\x00\x64\xc0\x10\x10\x03\x0c\x00\x00\x00\x00\x00\x08\x00\x02'
+    b'\x03\xe8\x00\x00\x00\x02\x90\x0e\x00\x23\x00\x19\x46\x04\x01\x01\x01\x02\x00\x04\x18\x00\x01\x01\x01'
+    b'\x01\x02\x00\x02\x05\x00\x00\x03\xe8\x00\x00\x04\x00\x00\x20\xc0\xb4\x01\x02'
+)
+__bgp9 = (
+    b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x7b\x02\x00\x00\x00\x64\x40\x01'
+    b'\x01\x00\x40\x02\x00\x40\x05\x04\x00\x00\x00\x64\xc0\x10\x10\x03\x0c\x00\x00\x00\x00\x00\x08\x00\x02'
+    b'\x03\xe8\x00\x00\x00\x02\x90\x0e\x00\x3f\x00\x19\x46\x04\x01\x01\x01\x02\x00\x02\x34\x00\x01\x01\x01'
+    b'\x01\x02\x00\x02\x05\x00\x00\x03\xe8\x00\x00\x04\x00\x00\x00\x00\x00\x02\x30\xcc\xaa\x02\x9c\xd8\x29'
+    b'\x80\xc0\xb4\x01\x02\xc0\xb4\x01\x02\xc0\xb4\x01\x02\xc0\xb4\x01\x02\x00\x00\x02\x00\x00\x00'
+)
 
 
 def test_pack():
@@ -681,6 +848,11 @@
     assert (__bgp2 == bytes(BGP(__bgp2)))
     assert (__bgp3 == bytes(BGP(__bgp3)))
     assert (__bgp4 == bytes(BGP(__bgp4)))
+    assert (__bgp5 == bytes(BGP(__bgp5)))
+    assert (__bgp6 == bytes(BGP(__bgp6)))
+    assert (__bgp7 == bytes(BGP(__bgp7)))
+    assert (__bgp8 == bytes(BGP(__bgp8)))
+    assert (__bgp9 == bytes(BGP(__bgp9)))
 
 
 def test_unpack():
@@ -721,19 +893,19 @@
     assert (len(b3.update.announced) == 0)
     assert (len(b3.update.attributes) == 6)
     a = b3.update.attributes[0]
-    assert (a.optional == False)
-    assert (a.transitive == True)
-    assert (a.partial == False)
-    assert (a.extended_length == False)
+    assert (not a.optional)
+    assert (a.transitive)
+    assert (not a.partial)
+    assert (not a.extended_length)
     assert (a.type == ORIGIN)
     assert (a.len == 1)
     o = a.origin
     assert (o.type == ORIGIN_IGP)
     a = b3.update.attributes[5]
-    assert (a.optional == True)
-    assert (a.transitive == False)
-    assert (a.partial == False)
-    assert (a.extended_length == True)
+    assert (a.optional)
+    assert (not a.transitive)
+    assert (not a.partial)
+    assert (a.extended_length)
     assert (a.type == MP_REACH_NLRI)
     assert (a.len == 30)
     m = a.mp_reach_nlri
@@ -760,8 +932,652 @@
     assert (c.code == CAP_ROUTE_REFRESH)
     assert (c.len == 0)
 
+    b5 = BGP(__bgp5)
+    assert (b5.len == 96)
+    assert (b5.type == UPDATE)
+    assert (len(b5.update.withdrawn) == 0)
+    a = b5.update.attributes[-1]
+    assert (a.type == MP_REACH_NLRI)
+    assert (a.len == 36)
+    m = a.mp_reach_nlri
+    assert (m.afi == AFI_L2VPN)
+    assert (m.safi == SAFI_EVPN)
+    r = m.announced[0]
+    assert (r.type == 1)
+    assert (r.len == 25)
+    assert (r.rd == b'\x00\x01\x01\x01\x01\x02\x00\x02')
+    assert (r.esi == b'\x05\x00\x00\x03\xe8\x00\x00\x04\x00\x00')
+    assert (r.eth_id == b'\x00\x00\x00\x02')
+    assert (r.mpls_label_stack == b'\x00\x00\x02')
 
-if __name__ == '__main__':
-    test_pack()
-    test_unpack()
-    print('Tests Successful...')
+    b6 = BGP(__bgp6)
+    assert (b6.len == 111)
+    assert (b6.type == UPDATE)
+    assert (len(b6.update.withdrawn) == 0)
+    a = b6.update.attributes[-1]
+    assert (a.type == MP_REACH_NLRI)
+    assert (a.len == 51)
+    m = a.mp_reach_nlri
+    assert (m.afi == AFI_L2VPN)
+    assert (m.safi == SAFI_EVPN)
+    r = m.announced[0]
+    assert (r.type == 2)
+    assert (r.len == 40)
+    assert (r.rd == b'\x00\x01\x01\x01\x01\x02\x00\x02')
+    assert (r.esi == b'\x05\x00\x00\x03\xe8\x00\x00\x04\x00\x00')
+    assert (r.eth_id == b'\x00\x00\x00\x02')
+    assert (r.mac_address_length == 48)
+    assert (r.mac_address == b'\xcc\xaa\x02\x9c\xd8\x29')
+    assert (r.ip_address_length == 32)
+    assert (r.ip_address == b'\xc0\xb4\x01\x02')
+    assert (r.mpls_label_stack == b'\x00\x00\x02\x00\x00\x00')
+
+    b7 = BGP(__bgp7)
+    assert (b7.len == 88)
+    assert (b7.type == UPDATE)
+    assert (len(b7.update.withdrawn) == 0)
+    a = b7.update.attributes[-1]
+    assert (a.type == MP_REACH_NLRI)
+    assert (a.len == 28)
+    m = a.mp_reach_nlri
+    assert (m.afi == AFI_L2VPN)
+    assert (m.safi == SAFI_EVPN)
+    r = m.announced[0]
+    assert (r.type == 3)
+    assert (r.len == 17)
+    assert (r.rd == b'\x00\x01\x01\x01\x01\x02\x00\x02')
+    assert (r.eth_id == b'\x00\x00\x00\x02')
+    assert (r.ip_address_length == 32)
+    assert (r.ip_address == b'\xc0\xb4\x01\x02')
+
+    b8 = BGP(__bgp8)
+    assert (b8.len == 95)
+    assert (b8.type == UPDATE)
+    assert (len(b8.update.withdrawn) == 0)
+    a = b8.update.attributes[-1]
+    assert (a.type == MP_REACH_NLRI)
+    assert (a.len == 35)
+    m = a.mp_reach_nlri
+    assert (m.afi == AFI_L2VPN)
+    assert (m.safi == SAFI_EVPN)
+    r = m.announced[0]
+    assert (r.type == 4)
+    assert (r.len == 24)
+    assert (r.rd == b'\x00\x01\x01\x01\x01\x02\x00\x02')
+    assert (r.esi == b'\x05\x00\x00\x03\xe8\x00\x00\x04\x00\x00')
+    assert (r.ip_address_length == 32)
+    assert (r.ip_address == b'\xc0\xb4\x01\x02')
+
+    b9 = BGP(__bgp9)
+    assert (b9.len == 123)
+    assert (b9.type == UPDATE)
+    assert (len(b9.update.withdrawn) == 0)
+    a = b9.update.attributes[-1]
+    assert (a.type == MP_REACH_NLRI)
+    assert (a.len == 63)
+    m = a.mp_reach_nlri
+    assert (m.afi == AFI_L2VPN)
+    assert (m.safi == SAFI_EVPN)
+    r = m.announced[0]
+    assert (r.type == 2)
+    assert (r.len == 52)
+    assert (r.rd == b'\x00\x01\x01\x01\x01\x02\x00\x02')
+    assert (r.esi == b'\x05\x00\x00\x03\xe8\x00\x00\x04\x00\x00')
+    assert (r.eth_id == b'\x00\x00\x00\x02')
+    assert (r.mac_address_length == 48)
+    assert (r.mac_address == b'\xcc\xaa\x02\x9c\xd8\x29')
+    assert (r.ip_address_length == 128)
+    assert (r.ip_address == b'\xc0\xb4\x01\x02\xc0\xb4\x01\x02\xc0\xb4\x01\x02\xc0\xb4\x01\x02')
+    assert (r.mpls_label_stack == b'\x00\x00\x02\x00\x00\x00')
+
+
+def test_bgp_mp_nlri_20_1_mp_reach_nlri_next_hop():
+    # test for https://github.com/kbandla/dpkt/issues/485
+    __bgp = (
+        b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x6c\x02\x00\x00\x00\x55\x40\x01'
+        b'\x01\x00\x40\x02\x04\x02\x01\xfd\xe9\x80\x04\x04\x00\x00\x00\x00\x80\x0e\x40\x00\x02\x01\x20\x20\x01'
+        b'\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xfe\x80\x00\x00\x00\x00\x00\x00\xc0\x01\x0b'
+        b'\xff\xfe\x7e\x00\x00\x00\x40\x20\x01\x0d\xb8\x00\x01\x00\x02\x40\x20\x01\x0d\xb8\x00\x01\x00\x01\x40'
+        b'\x20\x01\x0d\xb8\x00\x01\x00\x00'
+    )
+    assert (__bgp == bytes(BGP(__bgp)))
+    bgp = BGP(__bgp)
+    assert (len(bgp.data) == 89)
+    assert (bgp.type == UPDATE)
+    assert (len(bgp.update.withdrawn) == 0)
+    assert (len(bgp.update.announced) == 0)
+    assert (len(bgp.update.attributes) == 4)
+
+    attribute = bgp.update.attributes[0]
+    assert (attribute.type == ORIGIN)
+    assert (not attribute.optional)
+    assert (attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.len == 1)
+    o = attribute.origin
+    assert (o.type == ORIGIN_IGP)
+
+    attribute = bgp.update.attributes[1]
+    assert (attribute.type == AS_PATH)
+    assert (not attribute.optional)
+    assert (attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.flags == 64)
+    assert (attribute.len == 4)
+    assert (len(attribute.as_path.segments) == 1)
+    segment = attribute.as_path.segments[0]
+    assert (segment.type == AS_SEQUENCE)
+    assert (segment.len == 1)
+    assert (len(segment.path) == 1)
+    assert (segment.path[0] == 65001)
+
+    attribute = bgp.update.attributes[2]
+    assert (attribute.type == MULTI_EXIT_DISC)
+    assert (attribute.optional)
+    assert (not attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.flags == 0x80)
+    assert (attribute.len == 4)
+    assert (attribute.multi_exit_disc.value == 0)
+
+    attribute = bgp.update.attributes[3]
+    assert (attribute.type == MP_REACH_NLRI)
+    assert (attribute.optional)
+    assert (not attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.flags == 0x80)
+    assert (attribute.len == 64)
+    mp_reach_nlri = attribute.mp_reach_nlri
+    assert (mp_reach_nlri.afi == AFI_IPV6)
+    assert (mp_reach_nlri.safi == SAFI_UNICAST)
+    assert (len(mp_reach_nlri.snpas) == 0)
+    assert (len(mp_reach_nlri.announced) == 3)
+    prefix = mp_reach_nlri.announced[0]
+    assert (socket.inet_ntop(socket.AF_INET6, prefix.prefix) == '2001:db8:1:2::')
+    assert (prefix.len == 64)
+    prefix = mp_reach_nlri.announced[1]
+    assert (socket.inet_ntop(socket.AF_INET6, prefix.prefix) == '2001:db8:1:1::')
+    assert (prefix.len == 64)
+    prefix = mp_reach_nlri.announced[2]
+    assert (socket.inet_ntop(socket.AF_INET6, prefix.prefix) == '2001:db8:1::')
+    assert (prefix.len == 64)
+    assert (len(mp_reach_nlri.next_hops) == 2)
+    assert (socket.inet_ntop(socket.AF_INET6, mp_reach_nlri.next_hops[0]) == '2001:db8::1')
+    assert (socket.inet_ntop(socket.AF_INET6, mp_reach_nlri.next_hops[1]) == 'fe80::c001:bff:fe7e:0')
+    assert (mp_reach_nlri.next_hop == b''.join(mp_reach_nlri.next_hops))
+
+
+def test_bgp_add_path_6_1_as_path():
+    # test for https://github.com/kbandla/dpkt/issues/481
+    # Error processing BGP data: packet 6 : message 1 of bgp-add-path.cap
+    # https://packetlife.net/media/captures/bgp-add-path.cap
+    __bgp = (
+        b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x59\x02\x00\x00\x00\x30\x40\x01'
+        b'\x01\x00\x40\x02\x06\x02\x01\x00\x00\xfb\xff\x40\x03\x04\x0a\x00\x0e\x01\x80\x04\x04\x00\x00\x00\x00'
+        b'\x40\x05\x04\x00\x00\x00\x64\x80\x0a\x04\x0a\x00\x22\x04\x80\x09\x04\x0a\x00\x0f\x01\x00\x00\x00\x01'
+        b'\x20\x05\x05\x05\x05\x00\x00\x00\x01\x20\xc0\xa8\x01\x05'
+    )
+    bgp = BGP(__bgp)
+    assert (__bgp == bytes(bgp))
+    assert (len(bgp) == 89)
+    assert (bgp.type == UPDATE)
+    assert (len(bgp.update.withdrawn) == 0)
+    announced = bgp.update.announced
+    assert (len(announced) == 2)
+    assert (announced[0].len == 32)
+    assert (announced[0].path_id == 1)
+    assert (socket.inet_ntop(socket.AF_INET, bytes(announced[0].prefix)) == '5.5.5.5')
+    assert (announced[1].len == 32)
+    assert (announced[1].path_id == 1)
+    assert (socket.inet_ntop(socket.AF_INET, bytes(announced[1].prefix)) == '192.168.1.5')
+
+    assert (len(bgp.update.attributes) == 7)
+
+    attribute = bgp.update.attributes[0]
+    assert (attribute.type == ORIGIN)
+    assert (not attribute.optional)
+    assert (attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.flags == 0x40)
+    assert (attribute.len == 1)
+    assert (attribute.origin.type == ORIGIN_IGP)
+
+    attribute = bgp.update.attributes[1]
+    assert (attribute.type == AS_PATH)
+    assert (not attribute.optional)
+    assert (attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.flags == 0x40)
+    assert (attribute.len == 6)
+    assert (len(attribute.as_path.segments) == 1)
+    segment = attribute.as_path.segments[0]
+    assert (segment.type == AS_SEQUENCE)
+    assert (segment.len == 1)
+    assert (len(segment.path) == 1)
+    assert (segment.path[0] == 64511)
+
+    attribute = bgp.update.attributes[2]
+    assert (attribute.type == NEXT_HOP)
+    assert (not attribute.optional)
+    assert (attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.flags == 0x40)
+    assert (attribute.len == 4)
+    assert (socket.inet_ntop(socket.AF_INET, bytes(attribute.next_hop)) == '10.0.14.1')
+
+    attribute = bgp.update.attributes[3]
+    assert (attribute.type == MULTI_EXIT_DISC)
+    assert (attribute.optional)
+    assert (not attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.flags == 0x80)
+    assert (attribute.len == 4)
+    assert (attribute.multi_exit_disc.value == 0)
+
+    attribute = bgp.update.attributes[4]
+    assert (attribute.type == LOCAL_PREF)
+    assert (not attribute.optional)
+    assert (attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.flags == 0x40)
+    assert (attribute.len == 4)
+    assert (attribute.local_pref.value == 100)
+
+    attribute = bgp.update.attributes[5]
+    assert (attribute.type == CLUSTER_LIST)
+    assert (attribute.optional)
+    assert (not attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.flags == 0x80)
+    assert (attribute.len == 4)
+    assert (socket.inet_ntop(socket.AF_INET, bytes(attribute.cluster_list)) == '10.0.34.4')
+
+    attribute = bgp.update.attributes[6]
+    assert (attribute.type == ORIGINATOR_ID)
+    assert (attribute.optional)
+    assert (not attribute.transitive)
+    assert (not attribute.partial)
+    assert (not attribute.extended_length)
+    assert (attribute.flags == 0x80)
+    assert (attribute.len == 4)
+    assert (socket.inet_ntop(socket.AF_INET, bytes(attribute.originator_id)) == '10.0.15.1')
+
+
+def test_attribute_accessors():
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '00'  # flags
+        '01'  # type (ORIGIN)
+
+        '01'  # length
+        '00'  # Origin type
+    )
+    attribute = BGP.Update.Attribute(buf)
+    assert isinstance(attribute.data, BGP.Update.Attribute.Origin)
+    for attr in ['optional', 'transitive', 'partial', 'extended_length']:
+        assert getattr(attribute, attr) == 0
+
+        # check we can set..
+        setattr(attribute, attr, 1)
+        assert getattr(attribute, attr) == 1
+
+        # and also unset
+        setattr(attribute, attr, 0)
+        assert getattr(attribute, attr) == 0
+
+
+def test_snpa():
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '04'    # len (in semi-octets)
+        '1234'  # data
+    )
+    snpa = BGP.Update.Attribute.MPReachNLRI.SNPA(buf)
+    assert snpa.len == 4  # length of the data in semi-octets
+    assert len(snpa) == 3  # length of the snpa in bytes (including header)
+    assert bytes(snpa) == buf
+
+
+def test_mpreachnlri():
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '0000'  # afi
+        '00'    # safi
+
+        '00'    # nlen
+        '01'    # num SNPAs
+
+        # SNPA
+        '04'    # len
+        '1234'  # data
+    )
+    mp = BGP.Update.Attribute.MPReachNLRI(buf)
+    assert len(mp.snpas) == 1
+    assert bytes(mp) == buf
+
+
+def test_notification():
+    from binascii import unhexlify
+
+    buf_notification = unhexlify(
+        '11'   # code
+        '22'   # subcode
+
+        '33'   # error
+    )
+    notification = BGP.Notification(buf_notification)
+    assert notification.code == 0x11
+    assert notification.subcode == 0x22
+    assert notification.error == b'\x33'
+    assert bytes(notification) == buf_notification
+
+    buf_bgp_hdr = unhexlify(
+        '11111111111111111111111111111111'  # marker
+        '0016'  # len
+        '03'    # type (NOTIFICATION)
+    )
+    bgp = BGP(buf_bgp_hdr + buf_notification)
+
+    assert hasattr(bgp, 'notification')
+    assert isinstance(bgp.data, BGP.Notification)
+    assert bgp.data.code == 0x11
+    assert bgp.data.subcode == 0x22
+    assert bgp.data.error == b'\x33'
+    assert bytes(bgp) == buf_bgp_hdr + buf_notification
+
+
+def test_keepalive():
+    keepalive = BGP.Keepalive(b'\x11')
+    assert len(keepalive) == 0
+    assert bytes(keepalive) == b''
+
+
+def test_routegeneric():
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '08'  # len (bits)
+        '11'  # prefix
+    )
+    routegeneric = RouteGeneric(buf)
+    assert routegeneric.len == 8
+    assert routegeneric.prefix == b'\x11'
+
+    assert bytes(routegeneric) == buf
+    assert len(routegeneric) == 2
+
+
+def test_routeipv4():
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '08'  # len (bits)
+
+        '11'  # prefix
+    )
+    routeipv4 = RouteIPV4(buf)
+    assert routeipv4.len == 8  # prefix len in bits
+    assert routeipv4.prefix == b'\x11\x00\x00\x00'
+
+    assert repr(routeipv4) == "RouteIPV4(17.0.0.0/8)"
+    assert bytes(routeipv4) == buf
+    assert len(routeipv4) == 2  # header + prefix(bytes)
+
+
+def test_routeipv6():
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '08'  # len (bits)
+        '22'  # prefix
+    )
+
+    routeipv6 = RouteIPV4(buf)
+    assert routeipv6.len == 8  # prefix len in bits
+    assert routeipv6.prefix == b'\x22\x00\x00\x00'
+
+    assert bytes(routeipv6) == buf
+    assert len(routeipv6) == 2  # header + prefix(bytes)
+
+
+def test_extendedrouteipv4():
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '00000001'  # path_id
+        '20'        # len (bits)
+        '05050505'  # prefix
+    )
+    extendedrouteipv4 = ExtendedRouteIPV4(buf)
+    assert extendedrouteipv4.path_id == 1
+    assert extendedrouteipv4.len == 32
+    assert extendedrouteipv4.prefix == unhexlify('05050505')
+    assert repr(extendedrouteipv4) == "ExtendedRouteIPV4(5.5.5.5/32 PathId 1)"
+
+    assert bytes(extendedrouteipv4) == buf
+    assert len(extendedrouteipv4) == len(buf)
+
+
+def test_routeevpn():
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '02'  # type
+        '1a'  # len
+
+        # route distinguisher
+        '1111111111111111'
+
+        # esi
+        '22222222222222222222'
+
+        # eth_id
+        '33333333'
+
+        # mac address
+        '00'  # len (bits)
+
+        # ip address
+        '00'  # len (bits)
+
+        # mpls
+        '6666'  # label stack
+    )
+
+    routeevpn = RouteEVPN(buf)
+    assert routeevpn.type == 2
+    assert routeevpn.len == 26
+
+    assert routeevpn.esi == unhexlify('22222222222222222222')
+    assert routeevpn.eth_id == unhexlify('33333333')
+
+    assert routeevpn.mac_address_length == 0
+    assert routeevpn.mac_address is None
+
+    assert routeevpn.ip_address_length == 0
+    assert routeevpn.ip_address is None
+
+    assert routeevpn.mpls_label_stack == unhexlify('6666')
+
+    assert bytes(routeevpn) == buf
+    assert len(routeevpn) == len(buf)
+
+
+def test_route_refresh():
+    from binascii import unhexlify
+    buf_route_refresh = unhexlify(
+        '1111'  # afi
+        '22'    # rsvd
+        '33'    # safi
+    )
+    route_refresh = BGP.RouteRefresh(buf_route_refresh)
+    assert route_refresh.afi == 0x1111
+    assert route_refresh.rsvd == 0x22
+    assert route_refresh.safi == 0x33
+    assert bytes(route_refresh) == buf_route_refresh
+
+    buf_bgp_hdr = unhexlify(
+        '11111111111111111111111111111111'  # marker
+        '0017'  # len
+        '05'    # type (ROUTE_REFRESH)
+    )
+    bgp = BGP(buf_bgp_hdr + buf_route_refresh)
+
+    assert hasattr(bgp, 'route_refresh')
+    assert isinstance(bgp.data, BGP.RouteRefresh)
+    assert bgp.data.afi == 0x1111
+    assert bgp.data.rsvd == 0x22
+    assert bgp.data.safi == 0x33
+    assert bytes(bgp) == buf_bgp_hdr + buf_route_refresh
+
+
+def test_mpunreachnlri():
+    from binascii import unhexlify
+    buf_routeipv4 = unhexlify(
+        '08'  # len (bits)
+        '11'  # prefix
+    )
+
+    buf_routeipv6 = unhexlify(
+        '08'  # len (bits)
+        '22'  # prefix
+    )
+
+    buf_routeevpn = unhexlify(
+        '02'  # type
+        '1a'  # len
+
+        # route distinguisher
+        '1111111111111111'
+
+        # esi
+        '22222222222222222222'
+
+        # eth_id
+        '33333333'
+
+        # mac address
+        '00'  # len (bits)
+
+        # ip address
+        '00'  # len (bits)
+
+        # mpls
+        '6666'  # label stack
+    )
+
+    buf_routegeneric = unhexlify(
+        '08'  # len (bits)
+        '33'  # prefix
+    )
+
+    afi = struct.Struct('>H')
+    routes = (
+        (AFI_IPV4, buf_routeipv4, RouteIPV4),
+        (AFI_IPV6, buf_routeipv6, RouteIPV6),
+        (AFI_L2VPN, buf_routeevpn, RouteEVPN),
+        # this afi does not exist, so we will parse as RouteGeneric
+        (1234, buf_routegeneric, RouteGeneric),
+    )
+
+    for afi_id, buf, cls in routes:
+        buf = afi.pack(afi_id) + b'\xcc' + buf
+        mpu = BGP.Update.Attribute.MPUnreachNLRI(buf)
+
+        assert mpu.afi == afi_id
+        assert mpu.safi == 0xcc
+        assert len(mpu.data) == 1
+        route = mpu.data[0]
+        assert isinstance(route, cls)
+
+        assert bytes(mpu) == buf
+        assert len(mpu) == len(buf)
+
+    # test the unpacking of the routes, as an Attribute
+    attribute_hdr = struct.Struct('BBB')
+    for afi_id, buf, cls in routes:
+        buf_mpunreachnlri = afi.pack(afi_id) + b'\xcc' + buf
+        buf_attribute_hdr = attribute_hdr.pack(0, MP_UNREACH_NLRI, len(buf_mpunreachnlri))
+        buf = buf_attribute_hdr + buf_mpunreachnlri
+
+        attribute = BGP.Update.Attribute(buf)
+        assert isinstance(attribute.data, BGP.Update.Attribute.MPUnreachNLRI)
+        routes = attribute.data.data
+        assert len(routes) == 1
+        assert isinstance(routes[0], cls)
+
+
+def test_update_withdrawn():
+    from binascii import unhexlify
+    buf_ipv4 = unhexlify(
+        '08'  # len (bits)
+        '11'  # prefix
+    )
+    packed_length = struct.Struct('>H').pack
+    wlen, plen = packed_length(len(buf_ipv4)), packed_length(0)
+
+    buf = wlen + buf_ipv4 + plen
+    update = BGP.Update(buf)
+
+    assert len(update.withdrawn) == 1
+    route = update.withdrawn[0]
+    assert isinstance(route, RouteIPV4)
+    assert bytes(update) == buf
+
+
+def test_parameters():
+    from binascii import unhexlify
+    buf = unhexlify(
+        '44'        # v
+        '1111'      # asn
+        '2222'      # holdtime
+        '33333333'  # identifier
+        '03'        # param_len
+
+        # Parameter
+        '01'  # type (AUTHENTICATION)
+        '01'  # len
+
+        # Authentication
+        '11'  # code
+    )
+    bgp_open = BGP.Open(buf)
+    assert len(bgp_open.parameters) == 1
+    parameter = bgp_open.parameters[0]
+
+    assert isinstance(parameter, BGP.Open.Parameter)
+    assert isinstance(parameter.data, BGP.Open.Parameter.Authentication)
+
+    assert bytes(bgp_open) == buf
+    assert len(bgp_open) == len(buf)
+
+
+def test_reservedcommunities():
+    from binascii import unhexlify
+    buf = unhexlify(
+        # ReservedCommunity
+        '00002222'  # value
+    )
+    communities = BGP.Update.Attribute.Communities(buf)
+    assert len(communities.data) == 1
+
+    community = communities.data[0]
+    assert isinstance(community, BGP.Update.Attribute.Communities.ReservedCommunity)
+    assert len(community) == 4
+    assert bytes(community) == buf
+
+    assert len(communities) == 4
+    assert bytes(communities) == buf
diff --git a/dpkt/cdp.py b/dpkt/cdp.py
index 36127c5..daeb220 100644
--- a/dpkt/cdp.py
+++ b/dpkt/cdp.py
@@ -3,8 +3,6 @@
 """Cisco Discovery Protocol."""
 from __future__ import absolute_import
 
-import struct
-
 from . import dpkt
 
 CDP_DEVID = 1  # string
@@ -29,7 +27,7 @@
 class CDP(dpkt.Packet):
     """Cisco Discovery Protocol.
 
-    See more about the BGP on \
+    See more on
     https://en.wikipedia.org/wiki/Cisco_Discovery_Protocol
 
     Attributes:
@@ -43,7 +41,34 @@
         ('sum', 'H', 0)
     )
 
-    class Address(dpkt.Packet):
+    class TLV(dpkt.Packet):
+        """When constructing the packet, len is not mandatory:
+        if not provided, then self.data must be this exact TLV payload
+        """
+
+        __hdr__ = (
+            ('type', 'H', 0),
+            ('len', 'H', 0)
+        )
+
+        def data_len(self):
+            if self.len:
+                return self.len - self.__hdr_len__
+            return len(self.data)
+
+        def unpack(self, buf):
+            dpkt.Packet.unpack(self, buf)
+            self.data = self.data[:self.data_len()]
+
+        def __len__(self):
+            return self.__hdr_len__ + len(self.data)
+
+        def __bytes__(self):
+            if hasattr(self, 'len') and not self.len:
+                self.len = len(self)
+            return self.pack_hdr() + bytes(self.data)
+
+    class Address(TLV):
         # XXX - only handle NLPID/IP for now
         __hdr__ = (
             ('ptype', 'B', 1),  # protocol type (NLPID)
@@ -52,60 +77,112 @@
             ('alen', 'H', 4)  # address length
         )
 
-        def unpack(self, buf):
-            dpkt.Packet.unpack(self, buf)
-            self.data = self.data[:self.alen]
+        def data_len(self):
+            return self.alen
 
-    class TLV(dpkt.Packet):
+    class TLV_Addresses(TLV):
         __hdr__ = (
-            ('type', 'H', 0),
-            ('len', 'H', 4)
+            ('type', 'H', CDP_ADDRESS),
+            ('len', 'H', 0),    # 17),
+            ('Addresses', 'L', 1),
         )
 
-        def unpack(self, buf):
-            dpkt.Packet.unpack(self, buf)
-            self.data = self.data[:self.len - 4]
-            if self.type == CDP_ADDRESS:
-                n = struct.unpack('>I', self.data[:4])[0]
-                buf = self.data[4:]
-                l = []
-                for i in range(n):
-                    a = CDP.Address(buf)
-                    l.append(a)
-                    buf = buf[len(a):]
-                self.data = l
-
-        def __len__(self):
-            if self.type == CDP_ADDRESS:
-                n = 4 + sum(map(len, self.data))
-            else:
-                n = len(self.data)
-            return self.__hdr_len__ + n
-
-        def __bytes__(self):
-            self.len = len(self)
-            if self.type == CDP_ADDRESS:
-                s = struct.pack('>I', len(self.data)) + \
-                    b''.join(map(bytes, self.data))
-            else:
-                s = self.data
-            return self.pack_hdr() + s
-
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
         buf = self.data
-        l = []
+        l_ = []
         while buf:
-            tlv = self.TLV(buf)
-            l.append(tlv)
+            # find the right TLV according to Type value
+            tlv_find_type = self.TLV(buf).type
+            # if this TLV is not in tlv_types, use the default TLV class
+            tlv = self.tlv_types.get(tlv_find_type, self.TLV)(buf)
+            l_.append(bytes(tlv))
             buf = buf[len(tlv):]
-        self.data = l
+        self.tlvs = l_
+        self.data = b''.join(l_)
 
     def __len__(self):
-        return self.__hdr_len__ + sum(map(len, self.data))
+        return self.__hdr_len__ + len(self.data)
 
     def __bytes__(self):
-        data = b''.join(map(bytes, self.data))
+        data = bytes(self.data)
         if not self.sum:
             self.sum = dpkt.in_cksum(self.pack_hdr() + data)
         return self.pack_hdr() + data
+
+    # keep here the TLV classes whose header is different from the generic TLV header (example : TLV_Addresses)
+    tlv_types = {CDP_ADDRESS: TLV_Addresses}
+
+
+def test_cdp():
+    import socket
+    from . import ethernet
+
+    ss = (b'\x02\xb4\xdf\x93\x00\x01\x00\x09\x63\x69\x73\x63\x6f\x00\x02\x00\x11\x00\x00\x00\x01'
+          b'\x01\x01\xcc\x00\x04\xc0\xa8\x01\x67')
+    rr1 = CDP(ss)
+    assert bytes(rr1) == ss
+
+    # construction
+    ss = (b'\x02\xb4\xdf\x93\x00\x01\x00\x09\x63\x69\x73\x63\x6f\x00\x02\x00\x11\x00\x00\x00\x01'
+          b'\x01\x01\xcc\x00\x04\xc0\xa8\x01\x67')
+    p1 = CDP.TLV_Addresses(data=CDP.Address(data=socket.inet_aton('192.168.1.103')))
+    p2 = CDP.TLV(type=CDP_DEVID, data=b'cisco')
+    data = p2.pack() + p1.pack()
+    rr2 = CDP(data=data)
+    assert bytes(rr2) == ss
+
+    s = (b'\x01\x00\x0c\xcc\xcc\xcc\xc4\x022k\x00\x00\x01T\xaa\xaa\x03\x00\x00\x0c \x00\x02\xb4,B'
+         b'\x00\x01\x00\x06R2\x00\x05\x00\xffCisco IOS Software, 3700 Software (C3745-ADVENTERPRI'
+         b'SEK9_SNA-M), Version 12.4(25d), RELEASE SOFTWARE (fc1)\nTechnical Support: http://www.'
+         b'cisco.com/techsupport\nCopyright (c) 1986-2010 by Cisco Systems, Inc.\nCompiled Wed 18'
+         b'-Aug-10 08:18 by prod_rel_team\x00\x06\x00\x0eCisco 3745\x00\x02\x00\x11\x00\x00\x00\x01'
+         b'\x01\x01\xcc\x00\x04\n\x00\x00\x02\x00\x03\x00\x13FastEthernet0/0\x00\x04\x00\x08\x00'
+         b'\x00\x00)\x00\t\x00\x04\x00\x0b\x00\x05\x00')
+    eth = ethernet.Ethernet(s)
+    assert isinstance(eth.data.data, CDP)
+    assert len(eth.data.data.tlvs) == 8  # number of CDP TLVs; ensures they are decoded
+    assert str(eth) == str(s)
+    assert len(eth) == len(s)
+
+
+def test_tlv():
+    from binascii import unhexlify
+    # len field set to 0
+    buf_no_len = unhexlify(
+        '0000'  # type
+        '0000'  # len
+        'abcd'  # data
+    )
+
+    buf_with_len = unhexlify(
+        '0000'  # type
+        '0006'  # len
+        'abcd'  # data
+    )
+    tlv = CDP.TLV(buf_no_len)
+    assert tlv.type == 0
+    assert tlv.len == 0
+    assert tlv.data_len() == 2
+    assert tlv.data == b'\xab\xcd'
+    assert bytes(tlv) == buf_with_len
+
+    # len field set manually
+    tlv = CDP.TLV(buf_with_len)
+    assert tlv.type == 0
+    assert tlv.len == 6
+    assert tlv.data_len() == 2
+    assert tlv.data == b'\xab\xcd'
+    assert bytes(tlv) == buf_with_len
+
+
+def test_address():
+    from binascii import unhexlify
+    buf = unhexlify(
+        '00'    # ptype
+        '11'    # plen
+        '22'    # p
+        '3333'  # alen
+    )
+    address = CDP.Address(buf)
+    assert address.data_len() == 0x3333
diff --git a/dpkt/compat.py b/dpkt/compat.py
index 9f688bd..43030e9 100644
--- a/dpkt/compat.py
+++ b/dpkt/compat.py
@@ -1,5 +1,6 @@
 from __future__ import absolute_import
 
+from struct import pack, unpack
 import sys
 
 if sys.version_info < (3,):
@@ -19,14 +20,34 @@
 except ImportError:
     from io import StringIO
 
-try: 
+try:
     from BytesIO import BytesIO
-except ImportError: 
+except ImportError:
     from io import BytesIO
-        
+
 if sys.version_info < (3,):
     def iteritems(d, **kw):
         return d.iteritems(**kw)
+
+    def intround(num):
+        return int(round(num))
+
 else:
     def iteritems(d, **kw):
         return iter(d.items(**kw))
+
+    # python3 will return an int if you round to 0 decimal places
+    intround = round
+
+
+def ntole(v):
+    """convert a 2-byte word from the network byte order (big endian) to little endian;
+    replaces socket.ntohs() to work on both little and big endian architectures
+    """
+    return unpack('<H', pack('!H', v))[0]
+
+
+def isstr(s):
+    """True if 's' is an instance of basestring in py2, or of str in py3"""
+    bs = getattr(__builtins__, 'basestring', str)
+    return isinstance(s, bs)
diff --git a/dpkt/crc32c.py b/dpkt/crc32c.py
index bb761b1..a576307 100644
--- a/dpkt/crc32c.py
+++ b/dpkt/crc32c.py
@@ -97,8 +97,3 @@
 
     assert cksum(b'') == 0
     assert cksum(b'123456789') == bswap32(0xe3069283)
-
-
-if __name__ == '__main__':
-    test_crc32c()
-    print('Tests Successful...')
diff --git a/dpkt/decorators.py b/dpkt/decorators.py
deleted file mode 100644
index acd0117..0000000
--- a/dpkt/decorators.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import print_function
-from __future__ import absolute_import
-
-import warnings
-
-
-def decorator_with_args(decorator_to_enhance):
-    """
-    This is decorator for decorator. It allows any decorator to get additional arguments
-    """
-    def decorator_maker(*args, **kwargs):
-        def decorator_wrapper(func):
-            return decorator_to_enhance(func, *args, **kwargs)
-
-        return decorator_wrapper
-
-    return decorator_maker
-
-
-@decorator_with_args
-def deprecated(deprecated_method, func_name=None):
-    def _deprecated(*args, **kwargs):
-        # Print only the first occurrence of the DeprecationWarning, regardless of location
-        warnings.simplefilter('once', DeprecationWarning)
-        # Display the deprecation warning message
-        if func_name:  # If the function, should be used instead, is received
-            warnings.warn("Call to deprecated method %s; use %s instead" % (deprecated_method.__name__, func_name),
-                          category=DeprecationWarning, stacklevel=2)
-        else:
-            warnings.warn("Call to deprecated method %s" % deprecated_method.__name__,
-                          category=DeprecationWarning, stacklevel=2)
-        return deprecated_method(*args, **kwargs)  # actually call the method
-
-    return _deprecated
-
-
-class TestDeprecatedDecorator(object):
-    def new_method(self):
-        return
-
-    @deprecated('new_method')
-    def old_method(self):
-        return
-
-    @deprecated()
-    def deprecated_decorator(self):
-        return
-
-    def test_deprecated_decorator(self):
-        import sys
-        from .compat import StringIO
-
-        saved_stderr = sys.stderr
-        try:
-            out = StringIO()
-            sys.stderr = out
-            self.deprecated_decorator()
-            assert ('DeprecationWarning: Call to deprecated method deprecated_decorator' in out.getvalue())
-            out.truncate(0)  # clean the buffer
-            self.old_method()
-            assert ('DeprecationWarning: Call to deprecated method old_method; use new_method instead' in out.getvalue())
-            out.truncate(0)  # clean the buffer
-            self.new_method()
-            assert ('DeprecationWarning' not in out.getvalue())
-        finally:
-            sys.stderr = saved_stderr
-
-
-if __name__ == '__main__':
-    a = TestDeprecatedDecorator()
-    a.test_deprecated_decorator()
-    print('Tests Successful...')
diff --git a/dpkt/dhcp.py b/dpkt/dhcp.py
index a247db5..54dc00a 100644
--- a/dpkt/dhcp.py
+++ b/dpkt/dhcp.py
@@ -137,7 +137,7 @@
 
     def __len__(self):
         return self.__hdr_len__ + \
-               sum([2 + len(o[1]) for o in self.opts]) + 1 + len(self.data)
+            sum([2 + len(o[1]) for o in self.opts]) + 1 + len(self.data)
 
     def __bytes__(self):
         return self.pack_hdr() + self.pack_opts() + bytes(self.data)
@@ -146,17 +146,17 @@
         """Return packed options string."""
         if not self.opts:
             return b''
-        l = []
+        l_ = []
         for t, data in self.opts:
-            l.append(struct.pack("BB%is"%len(data), t, len(data), data))
-        l.append(b'\xff')
-        return b''.join(l)
+            l_.append(struct.pack("BB%is" % len(data), t, len(data), data))
+        l_.append(b'\xff')
+        return b''.join(l_)
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
         self.chaddr = self.chaddr[:self.hln]
         buf = self.data
-        l = []
+        l_ = []
         while buf:
             t = compat_ord(buf[0])
             if t == 0xff:
@@ -166,16 +166,31 @@
                 buf = buf[1:]
             else:
                 n = compat_ord(buf[1])
-                l.append((t, buf[2:2 + n]))
+                l_.append((t, buf[2:2 + n]))
                 buf = buf[2 + n:]
-        self.opts = l
+        self.opts = l_
         self.data = buf
 
 
 def test_dhcp():
-    s = b'\x01\x01\x06\x00\xadS\xc8c\xb8\x87\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02U\x82\xf3\xa6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00c\x82Sc5\x01\x01\xfb\x01\x01=\x07\x01\x00\x02U\x82\xf3\xa62\x04\n\x00\x01e\x0c\tGuinevere<\x08MSFT 5.07\n\x01\x0f\x03\x06,./\x1f!+\xff\x00\x00\x00\x00\x00'
+    s = (
+        b'\x01\x01\x06\x00\xad\x53\xc8\x63\xb8\x87\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x02\x55\x82\xf3\xa6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x63\x82\x53\x63\x35\x01\x01\xfb\x01\x01\x3d\x07\x01\x00'
+        b'\x02\x55\x82\xf3\xa6\x32\x04\x0a\x00\x01\x65\x0c\x09\x47\x75\x69\x6e\x65\x76\x65\x72\x65\x3c\x08\x4d'
+        b'\x53\x46\x54\x20\x35\x2e\x30\x37\x0a\x01\x0f\x03\x06\x2c\x2e\x2f\x1f\x21\x2b\xff\x00\x00\x00\x00\x00'
+    )
+
     dhcp = DHCP(s)
     assert (s == bytes(dhcp))
+    assert len(dhcp) == 300
     assert isinstance(dhcp.chaddr, bytes)
     assert isinstance(dhcp.sname, bytes)
     assert isinstance(dhcp.file, bytes)
@@ -186,6 +201,34 @@
     assert isinstance(dhcp.sname, bytes)
     assert isinstance(dhcp.file, bytes)
 
-if __name__ == '__main__':
-    test_dhcp()
-    print('Tests Successful...')
+
+def test_no_opts():
+    from binascii import unhexlify
+    buf_small_hdr = unhexlify(
+        '00'        # op
+        '00'        # hrd
+        '06'        # hln
+        '12'        # hops
+        'deadbeef'  # xid
+        '1234'      # secs
+        '9866'      # flags
+        '00000000'  # ciaddr
+        '00000000'  # yiaddr
+        '00000000'  # siaddr
+        '00000000'  # giaddr
+    )
+
+    buf = b''.join([
+        buf_small_hdr,
+        b'\x00' * 16,   # chaddr
+        b'\x11' * 64,   # sname
+        b'\x22' * 128,  # file
+        b'\x44' * 4,    # magic
+
+        b'\x00'         # data
+    ])
+
+    dhcp = DHCP(buf)
+    assert dhcp.opts == []
+    assert dhcp.data == b''
+    assert dhcp.pack_opts() == b''
diff --git a/dpkt/diameter.py b/dpkt/diameter.py
index 2a17a36..4fbbc84 100644
--- a/dpkt/diameter.py
+++ b/dpkt/diameter.py
@@ -7,7 +7,6 @@
 import struct
 
 from . import dpkt
-from .decorators import deprecated
 from .compat import compat_ord
 
 # Diameter Base Protocol - RFC 3588
@@ -15,7 +14,7 @@
 
 # Request/Answer Command Codes
 ABORT_SESSION = 274
-ACCOUTING = 271
+ACCOUNTING = 271
 CAPABILITIES_EXCHANGE = 257
 DEVICE_WATCHDOG = 280
 DISCONNECT_PEER = 282
@@ -78,19 +77,19 @@
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
         self.cmd = (compat_ord(self.cmd[0]) << 16) | \
-                    (compat_ord(self.cmd[1]) << 8) | \
-                    (compat_ord(self.cmd[2]))
+                   (compat_ord(self.cmd[1]) << 8) | \
+                   (compat_ord(self.cmd[2]))
         self.len = (compat_ord(self.len[0]) << 16) | \
-                    (compat_ord(self.len[1]) << 8) | \
-                    (compat_ord(self.len[2]))
+                   (compat_ord(self.len[1]) << 8) | \
+                   (compat_ord(self.len[2]))
         self.data = self.data[:self.len - self.__hdr_len__]
 
-        l = []
+        l_ = []
         while self.data:
             avp = AVP(self.data)
-            l.append(avp)
+            l_.append(avp)
             self.data = self.data[len(avp):]
-        self.data = self.avps = l
+        self.data = self.avps = l_
 
     def pack_hdr(self):
         self.len = struct.pack("BBB", (self.len >> 16) & 0xff, (self.len >> 8) & 0xff, self.len & 0xff)
@@ -103,6 +102,7 @@
     def __bytes__(self):
         return self.pack_hdr() + b''.join(map(bytes, self.data))
 
+
 class AVP(dpkt.Packet):
     __hdr__ = (
         ('code', 'I', 0),
@@ -137,8 +137,8 @@
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
         self.len = (compat_ord(self.len[0]) << 16) | \
-                    (compat_ord(self.len[1]) << 8) | \
-                    (compat_ord(self.len[2]))
+                   (compat_ord(self.len[1]) << 8) | \
+                   (compat_ord(self.len[2]))
 
         if self.vendor_flag:
             self.vendor = struct.unpack('>I', self.data[:4])[0]
@@ -160,15 +160,19 @@
         return length
 
 
-__s = b'\x01\x00\x00\x28\x80\x00\x01\x18\x00\x00\x00\x00\x00\x00\x41\xc8\x00\x00\x00\x0c\x00\x00\x01\x08\x40\x00\x00\x0c\x68\x30\x30\x32\x00\x00\x01\x28\x40\x00\x00\x08'
-__t = b'\x01\x00\x00\x2c\x80\x00\x01\x18\x00\x00\x00\x00\x00\x00\x41\xc8\x00\x00\x00\x0c\x00\x00\x01\x08\xc0\x00\x00\x10\xde\xad\xbe\xef\x68\x30\x30\x32\x00\x00\x01\x28\x40\x00\x00\x08'
+__s = (b'\x01\x00\x00\x28\x80\x00\x01\x18\x00\x00\x00\x00\x00\x00\x41\xc8\x00\x00\x00\x0c\x00\x00'
+       b'\x01\x08\x40\x00\x00\x0c\x68\x30\x30\x32\x00\x00\x01\x28\x40\x00\x00\x08')
+__t = (b'\x01\x00\x00\x2c\x80\x00\x01\x18\x00\x00\x00\x00\x00\x00\x41\xc8\x00\x00\x00\x0c\x00\x00'
+       b'\x01\x08\xc0\x00\x00\x10\xde\xad\xbe\xef\x68\x30\x30\x32\x00\x00\x01\x28\x40\x00\x00\x08')
 
 
 def test_pack():
     d = Diameter(__s)
     assert (__s == bytes(d))
+    assert len(d) == len(__s)
     d = Diameter(__t)
     assert (__t == bytes(d))
+    assert len(d) == len(__t)
 
 
 def test_unpack():
@@ -198,7 +202,19 @@
     assert (avp.data == b'\x68\x30\x30\x32')
 
 
-if __name__ == '__main__':
-    test_pack()
-    test_unpack()
-    print('Tests Successful...')
+def test_diameter_properties():
+    diameter = Diameter()
+    for prop in ['request_flag', 'proxiable_flag', 'error_flag', 'retransmit_flag']:
+        assert hasattr(diameter, prop)
+        assert getattr(diameter, prop) == 0
+        setattr(diameter, prop, 1)
+        assert getattr(diameter, prop) == 1
+
+
+def test_avp_properties():
+    avp = AVP()
+    for prop in ['vendor_flag', 'mandatory_flag', 'protected_flag']:
+        assert hasattr(avp, prop)
+        assert getattr(avp, prop) == 0
+        setattr(avp, prop, 1)
+        assert getattr(avp, prop) == 1
diff --git a/dpkt/dns.py b/dpkt/dns.py
index 90c5a96..9075927 100644
--- a/dpkt/dns.py
+++ b/dpkt/dns.py
@@ -8,7 +8,6 @@
 import codecs
 
 from . import dpkt
-from .decorators import deprecated
 from .compat import compat_ord
 
 DNS_Q = 0
@@ -76,7 +75,7 @@
     for i, label in enumerate(labels):
         key = b'.'.join(labels[i:]).upper()
         ptr = label_ptrs.get(key)
-        if not ptr:
+        if ptr is None:
             if len(key) > 1:
                 ptr = off + len(buf)
                 if ptr < 0xc000:
@@ -266,23 +265,22 @@
             elif self.type == DNS_PTR:
                 return pack_name(self.ptrname, off, label_ptrs)
             elif self.type == DNS_SOA:
-                l = []
-                l.append(pack_name(self.mname, off, label_ptrs))
-                l.append(pack_name(self.rname, off + len(l[0]), label_ptrs))
-                l.append(struct.pack('>IIIII', self.serial, self.refresh,
-                                     self.retry, self.expire, self.minimum))
-                return b''.join(l)
+                l_ = []
+                l_.append(pack_name(self.mname, off, label_ptrs))
+                l_.append(pack_name(self.rname, off + len(l_[0]), label_ptrs))
+                l_.append(struct.pack('>IIIII', self.serial, self.refresh,
+                                      self.retry, self.expire, self.minimum))
+                return b''.join(l_)
             elif self.type == DNS_MX:
                 return struct.pack('>H', self.preference) + \
-                       pack_name(self.mxname, off + 2, label_ptrs)
+                    pack_name(self.mxname, off + 2, label_ptrs)
             elif self.type == DNS_TXT or self.type == DNS_HINFO:
-                return b''.join(['%s%s' % (chr(len(x)), x)
-                                for x in self.text])
+                return b''.join(struct.pack('B', len(x)) + x for x in self.text)
             elif self.type == DNS_AAAA:
                 return self.ip6
             elif self.type == DNS_SRV:
                 return struct.pack('>HHH', self.priority, self.weight, self.port) + \
-                       pack_name(self.srvname, off + 6, label_ptrs)
+                    pack_name(self.srvname, off + 6, label_ptrs)
             elif self.type == DNS_OPT:
                 return b''  # self.rdata
             else:
@@ -300,8 +298,8 @@
             elif self.type == DNS_SOA:
                 self.mname, off = unpack_name(buf, off)
                 self.rname, off = unpack_name(buf, off)
-                self.serial, self.refresh, self.retry, self.expire, \
-                self.minimum = struct.unpack('>IIIII', buf[off:off + 20])
+                self.serial, self.refresh, self.retry, self.expire, self.minimum = \
+                    struct.unpack('>IIIII', buf[off:off + 20])
             elif self.type == DNS_MX:
                 self.preference = struct.unpack('>H', self.rdata[:2])
                 self.mxname, off = unpack_name(buf, off + 2)
@@ -372,7 +370,7 @@
 
     def __len__(self):
         # XXX - cop out
-        return len(str(self))
+        return len(bytes(self))
 
     def __bytes__(self):
         # XXX - compress names on the fly
@@ -388,36 +386,185 @@
         return buf
 
 
-def test_basic():
-    from . import ip
+# TESTS
 
-    s = b'E\x00\x02\x08\xc15\x00\x00\x80\x11\x92aBk0\x01Bk0w\x005\xc07\x01\xf4\xda\xc2d\xd2\x81\x80\x00\x01\x00\x03\x00\x0b\x00\x0b\x03www\x06google\x03com\x00\x00\x01\x00\x01\xc0\x0c\x00\x05\x00\x01\x00\x00\x03V\x00\x17\x03www\x06google\x06akadns\x03net\x00\xc0,\x00\x01\x00\x01\x00\x00\x01\xa3\x00\x04@\xe9\xabh\xc0,\x00\x01\x00\x01\x00\x00\x01\xa3\x00\x04@\xe9\xabc\xc07\x00\x02\x00\x01\x00\x00KG\x00\x0c\x04usw5\x04akam\xc0>\xc07\x00\x02\x00\x01\x00\x00KG\x00\x07\x04usw6\xc0t\xc07\x00\x02\x00\x01\x00\x00KG\x00\x07\x04usw7\xc0t\xc07\x00\x02\x00\x01\x00\x00KG\x00\x08\x05asia3\xc0t\xc07\x00\x02\x00\x01\x00\x00KG\x00\x05\x02za\xc07\xc07\x00\x02\x00\x01\x00\x00KG\x00\x0f\x02zc\x06akadns\x03org\x00\xc07\x00\x02\x00\x01\x00\x00KG\x00\x05\x02zf\xc07\xc07\x00\x02\x00\x01\x00\x00KG\x00\x05\x02zh\xc0\xd5\xc07\x00\x02\x00\x01\x00\x00KG\x00\x07\x04eur3\xc0t\xc07\x00\x02\x00\x01\x00\x00KG\x00\x07\x04use2\xc0t\xc07\x00\x02\x00\x01\x00\x00KG\x00\x07\x04use4\xc0t\xc0\xc1\x00\x01\x00\x01\x00\x00\xfb4\x00\x04\xd0\xb9\x84\xb0\xc0\xd2\x00\x01\x00\x01\x00\x001\x0c\x00\x04?\xf1\xc76\xc0\xed\x00\x01\x00\x01\x00\x00\xfb4\x00\x04?\xd7\xc6S\xc0\xfe\x00\x01\x00\x01\x00\x001\x0c\x00\x04?\xd00.\xc1\x0f\x00\x01\x00\x01\x00\x00\n\xdf\x00\x04\xc1-\x01g\xc1"\x00\x01\x00\x01\x00\x00\x101\x00\x04?\xd1\xaa\x88\xc15\x00\x01\x00\x01\x00\x00\r\x1a\x00\x04PCC\xb6\xc0o\x00\x01\x00\x01\x00\x00\x10\x7f\x00\x04?\xf1I\xd6\xc0\x87\x00\x01\x00\x01\x00\x00\n\xdf\x00\x04\xce\x84dl\xc0\x9a\x00\x01\x00\x01\x00\x00\n\xdf\x00\x04A\xcb\xea\x1b\xc0\xad\x00\x01\x00\x01\x00\x00\x0b)\x00\x04\xc1l\x9a\t'
-    ip = ip.IP(s)
-    my_dns = DNS(ip.udp.data)
-    assert my_dns.qd[0].name == 'www.google.com' and my_dns.an[1].name == 'www.google.akadns.net'
-    s = b'\x05\xf5\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x03cnn\x03com\x00\x00\x01\x00\x01'
-    my_dns = DNS(s)
-    assert s == bytes(my_dns)
+def define_testdata():
+    """
+    Reference test data is stored in the dynamically defined class.
+
+    It is created in this way so that we can import unhexlify only during
+    testing, and not during normal use.
+    """
+    from binascii import unhexlify
+
+    class TestData(object):
+        a_resp = unhexlify(
+            "059c8180000100010000000106676f6f676c6503636f6d0000010001c00c00010"
+            "0010000012b0004d83ace2e0000290200000000000000"
+        )
+        aaaa_resp = unhexlify(
+            "7f228180000100010000000005676d61696c03636f6d00001c0001c00c001c000"
+            "10000012b00102a001450400908020000000000002005"
+        )
+        cname_resp = unhexlify(
+            "a154818000010001000000000377777705676d61696c03636f6d0000010001c00"
+            "c000500010000545f000e046d61696c06676f6f676c65c016"
+        )
+        invalid_rr = unhexlify(
+            "000001000000000100000000046e616d650000150001000000000000"
+        )
+        mx_resp = unhexlify(
+            "053b8180000100010000000006676f6f676c6503636f6d00000f0001c00c000f0"
+            "001000002570011001e04616c7432056173706d78016cc00c"
+        )
+        null_resp = unhexlify(
+            "12b0840000010001000000000b626c6168626c616836363606706972617465037"
+            "3656100000a0001c00c000a00010000000000095641434b4403c5e901"
+        )
+        opt_resp = unhexlify(
+            "8d6e0110000100000000000104783131310678787878313106616b616d6169036"
+            "e657400000100010000290fa0000080000000"
+        )
+        ptr_resp = unhexlify(
+            "67028180000100010003000001310131033231310331343107696e2d616464720"
+            "46172706100000c0001c00c000c000100000d3600240764656661756c740a762d"
+            "756d63652d69667305756d6e657405756d6963680365647500c00e00020001000"
+            "00d36000d0673686162627903696673c04fc00e0002000100000d36000f0c6669"
+            "73682d6c6963656e7365c06dc00e0002000100000d36000b04646e73320369746"
+            "4c04f"
+        )
+        soa_resp = unhexlify(
+            "851f8180000100010000000006676f6f676c6503636f6d0000060001c00c00060"
+            "001000000230026036e7332c00c09646e732d61646d696ec00c0a747447000003"
+            "8400000384000007080000003c"
+        )
+        srv_resp = unhexlify(
+            "7f2281800001000100000000075f6a6162626572045f746370066a61626265720"
+            "3636f6d0000210001c00c0021000100000e0f001a000a000014950764656e6a61"
+            "6232066a616262657203636f6d00"
+        )
+        txt_resp = unhexlify(
+            "10328180000100010000000006676f6f676c6503636f6d0000100001c00c00100"
+            "0010000010e00100f763d7370663120707472203f616c6c"
+        )
+    return TestData()
+
+
+def test_basic():
+    buf = define_testdata().a_resp
+    my_dns = DNS(buf)
+
+    assert my_dns.qd[0].name == 'google.com'
+    assert my_dns.an[0].name == 'google.com'
+    assert bytes(my_dns) == buf
+
+
+class TryExceptException:
+    def __init__(self, exception_type, msg=''):
+        self.exception_type = exception_type
+        self.msg = msg
+
+    def __call__(self, f, *args, **kwargs):
+        def wrapper(*args, **kwargs):
+            try:
+                f()
+            except self.exception_type as e:
+                if self.msg:
+                    assert str(e) == self.msg
+            else:
+                raise Exception("There should have been an Exception raised")
+        return wrapper
+
+
+@TryExceptException(Exception, msg='There should have been an Exception raised')
+def test_TryExceptException():
+    """Check that we can catch a function which does not throw an exception when it is supposed to"""
+    @TryExceptException(NotImplementedError)
+    def fun():
+        pass
+
+    try:
+        fun()
+    except Exception as e:
+        raise e
+
+
+@TryExceptException(NotImplementedError)
+def test_Q_len():
+    """Test in place for when the method is written"""
+    q = DNS.Q()
+    len(q)
+
+
+@TryExceptException(NotImplementedError)
+def test_Q_unpack():
+    """Test in place for when the method is written"""
+    q = DNS.Q()
+    q.unpack(None)
+
+
+def property_runner(prop, ops, set_to=None):
+    if set_to is None:
+        set_to = [False, True, False]
+    buf = define_testdata().a_resp
+    dns = DNS(buf)
+
+    for set_to, op in zip(set_to, ops):
+        setattr(dns, prop, set_to)
+        assert dns.op == op
+        assert getattr(dns, prop) == set_to
+
+
+def test_qr():
+    property_runner('qr', ops=[384, 33152, 384])
+
+
+def test_opcode():
+    property_runner('opcode', ops=[33152, 35200, 33152])
+
+
+def test_aa():
+    property_runner('aa', ops=[33152, 34176, 33152])
+
+
+def test_tc():
+    property_runner('tc', ops=[33152, 33664, 33152])
+
+
+def test_rd():
+    property_runner('rd', ops=[32896, 33152, 32896])
+
+
+def test_ra():
+    property_runner('ra', ops=[33024, 33152, 33024])
+
+
+def test_zero():
+    property_runner('zero', ops=[33152, 33216, 33152])
+
+
+def test_rcode():
+    property_runner('rcode', ops=[33152, 33153, 33152])
 
 
 def test_PTR():
-    s = b'g\x02\x81\x80\x00\x01\x00\x01\x00\x03\x00\x00\x011\x011\x03211\x03141\x07in-addr\x04arpa\x00\x00\x0c\x00\x01\xc0\x0c\x00\x0c\x00\x01\x00\x00\r6\x00$\x07default\nv-umce-ifs\x05umnet\x05umich\x03edu\x00\xc0\x0e\x00\x02\x00\x01\x00\x00\r6\x00\r\x06shabby\x03ifs\xc0O\xc0\x0e\x00\x02\x00\x01\x00\x00\r6\x00\x0f\x0cfish-license\xc0m\xc0\x0e\x00\x02\x00\x01\x00\x00\r6\x00\x0b\x04dns2\x03itd\xc0O'
-    my_dns = DNS(s)
+    buf = define_testdata().ptr_resp
+    my_dns = DNS(buf)
     assert my_dns.qd[0].name == '1.1.211.141.in-addr.arpa' and \
            my_dns.an[0].ptrname == 'default.v-umce-ifs.umnet.umich.edu' and \
            my_dns.ns[0].nsname == 'shabby.ifs.umich.edu' and \
            my_dns.ns[1].ttl == 3382 and \
            my_dns.ns[2].nsname == 'dns2.itd.umich.edu'
-    assert s == bytes(my_dns)
+    assert buf == bytes(my_dns)
 
 
 def test_OPT():
-    s = b'\x8dn\x01\x10\x00\x01\x00\x00\x00\x00\x00\x01\x04x111\x06xxxx11\x06akamai\x03net\x00\x00\x01\x00\x01\x00\x00)\x0f\xa0\x00\x00\x80\x00\x00\x00'
-    my_dns = DNS(s)
+    buf = define_testdata().opt_resp
+    my_dns = DNS(buf)
     my_rr = my_dns.ar[0]
     assert my_rr.type == DNS_OPT
     assert my_rr.rlen == 0 and my_rr.rdata == b''
-    assert bytes(my_dns) == s
+    assert bytes(my_dns) == buf
 
     my_rr.rdata = b'\x00\x00\x00\x02\x00\x00'  # add 1 attribute tlv
     my_dns2 = DNS(bytes(my_dns))
@@ -431,52 +578,37 @@
     assert x == b'\0'
 
 
+@TryExceptException(dpkt.UnpackError)
+def test_unpack_name():
+    """If the offset is longer than the buffer, there will be an UnpackError"""
+    unpack_name(b' ', 0)
+
+
+@TryExceptException(dpkt.UnpackError)
 def test_random_data():
-    try:
-        DNS(b'\x83z0\xd2\x9a\xec\x94_7\xf3\xb7+\x85"?\xf0\xfb')
-    except dpkt.UnpackError:
-        pass
-    except:
-        assert False
-    else:
-        assert False
+    DNS(b'\x83z0\xd2\x9a\xec\x94_7\xf3\xb7+\x85"?\xf0\xfb')
 
 
+@TryExceptException(dpkt.UnpackError)
 def test_circular_pointers():
-    try:
-        DNS(b'\xc0\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\xc0\x00')
-    except dpkt.UnpackError:
-        pass
-    except:
-        assert False
-    else:
-        assert False
+    DNS(b'\xc0\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\xc0\x00')
 
 
+@TryExceptException(dpkt.UnpackError)
 def test_very_long_name():
-    try:
-        DNS(b'\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00' + (b'\x10abcdef0123456789' * 16) + b'\x00')
-    except dpkt.UnpackError:
-        pass
-    except:
-        assert False
-    else:
-        assert False
+    DNS(b'\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00' + (b'\x10abcdef0123456789' * 16) + b'\x00')
 
 
 def test_null_response():
-    s = b'\x12\xb0\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x0bblahblah666\x06pirate\x03sea\x00\x00\n\x00\x01\xc0\x0c\x00\n\x00\x01\x00\x00\x00\x00\x00\tVACKD\x03\xc5\xe9\x01'
-    my_dns = DNS(s)
+    buf = define_testdata().null_resp
+    my_dns = DNS(buf)
     assert my_dns.qd[0].name == 'blahblah666.pirate.sea' and \
            my_dns.an[0].null == b'5641434b4403c5e901'
-    assert str(s) == str(my_dns)
+    assert str(buf) == str(my_dns)
 
 
 def test_txt_response():
-    buf = (
-        b'\x10\x32\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\x06\x67\x6f\x6f\x67\x6c\x65\x03\x63\x6f'
-        b'\x6d\x00\x00\x10\x00\x01\xc0\x0c\x00\x10\x00\x01\x00\x00\x01\x0e\x00\x10\x0f\x76\x3d\x73'
-        b'\x70\x66\x31\x20\x70\x74\x72\x20\x3f\x61\x6c\x6c')
+    buf = define_testdata().txt_resp
     my_dns = DNS(buf)
     my_rr = my_dns.an[0]
     assert my_rr.type == DNS_TXT
@@ -486,17 +618,302 @@
     assert bytes(my_dns) == buf
 
 
-if __name__ == '__main__':
-    # Runs all the test associated with this class/file
-    test_basic()
-    test_PTR()
-    test_OPT()
-    test_pack_name()
-    test_random_data()
-    test_circular_pointers()
-    test_very_long_name()
-    test_null_response()
-    test_txt_response()
-    test_deprecated_methods()
-    test_deprecated_method_performance()
-    print('Tests Successful...')
+def test_rdata_TXT():
+    rr = DNS.RR(
+        type=DNS_TXT,
+        text=[b'v=spf1 ptr ?all', b'a=something']
+    )
+    packdata = rr.pack_rdata(0, {})
+    correct = b'\x0fv=spf1 ptr ?all\x0ba=something'
+    assert packdata == correct
+
+
+def test_rdata_HINFO():
+    rr = DNS.RR(
+        type=DNS_HINFO,
+        text=[b'v=spf1 ptr ?all', b'a=something']
+    )
+    packdata = rr.pack_rdata(0, {})
+    correct = b'\x0fv=spf1 ptr ?all\x0ba=something'
+    assert packdata == correct
+
+
+def test_rdata_rdata():
+    rr = DNS.RR(
+        name='zc.akadns.org',
+        ttl=123446,
+        rdata=b'?\xf1\xc76',
+    )
+    packdata = rr.pack_rdata(0, {})
+    correct = b'?\xf1\xc76'
+    assert packdata == correct
+
+
+def test_rdata_A():
+    rr = DNS.RR(
+        name='zc.akadns.org',
+        ttl=123446,
+        ip=b'?\xf1\xc76',
+        type=DNS_A,
+    )
+    packdata = rr.pack_rdata(0, {})
+    correct = b'?\xf1\xc76'
+    assert packdata == correct
+
+
+def test_rdata_NS():
+    rr = DNS.RR(
+        nsname='zc.akadns.org',
+        ttl=123446,
+        ip=b'?\xf1\xc76',
+        type=DNS_NS,
+    )
+    packdata = rr.pack_rdata(0, {})
+    correct = b'\x02zc\x06akadns\x03org\x00'
+    assert packdata == correct
+
+
+def test_rdata_CNAME():
+    rr = DNS.RR(
+        cname='zc.akadns.org',
+        ttl=123446,
+        ip=b'?\xf1\xc76',
+        type=DNS_CNAME,
+    )
+    packdata = rr.pack_rdata(0, {})
+    correct = b'\x02zc\x06akadns\x03org\x00'
+    assert packdata == correct
+
+
+def test_rdata_PTR():
+    rr = DNS.RR(
+        ptrname='default.v-umce-ifs.umnet.umich.edu',
+        ttl=1236,
+        ip=b'?\xf1\xc76',
+        type=DNS_PTR,
+    )
+    packdata = rr.pack_rdata(0, {})
+    correct = b'\x07default\nv-umce-ifs\x05umnet\x05umich\x03edu\x00'
+    assert packdata == correct
+
+
+def test_rdata_SOA():
+    rr = DNS.RR(
+        mname='blah.google.com',
+        rname='moo.blah.com',
+        serial=12345666,
+        refresh=123463,
+        retry=209834,
+        minimum=9000,
+        expire=28341,
+        type=DNS_SOA,
+    )
+    packdata = rr.pack_rdata(0, {})
+    correct = (
+        b'\x04blah\x06google\x03com\x00\x03moo\x04blah\xc0\x0c\x00\xbcaB'
+        b'\x00\x01\xe2G\x00\x033\xaa\x00\x00n\xb5\x00\x00#(')
+    assert packdata == correct
+
+
+def test_rdata_MX():
+    rr = DNS.RR(
+        type=DNS_MX,
+        preference=2124,
+        mxname='mail.google.com',
+    )
+
+    packdata = rr.pack_rdata(0, {})
+    correct = b'\x08L\x04mail\x06google\x03com\x00'
+    assert packdata == correct
+
+
+def test_rdata_AAAA():
+    ip6 = b'&\x07\xf8\xb0@\x0c\x0c\x03\x00\x00\x00\x00\x00\x00\x00\x1a'
+    rr = DNS.RR(
+        type=DNS_AAAA,
+        ip6=ip6,
+    )
+
+    packdata = rr.pack_rdata(0, {})
+    correct = ip6
+    assert packdata == correct
+
+
+def test_rdata_SRV():
+    rr = DNS.RR(
+        type=DNS_SRV,
+        ttl=86400,
+        priority=0,
+        weight=5,
+        port=5060,
+        srvname='_sip._tcp.example.com',
+    )
+
+    packdata = rr.pack_rdata(0, {})
+    correct = b'\x00\x00\x00\x05\x13\xc4\x04_sip\x04_tcp\x07example\x03com\x00'
+    assert packdata == correct
+
+
+def test_rdata_OPT():
+    rr = DNS.RR(
+        type=DNS_OPT,
+    )
+
+    # TODO: This is hardcoded to return b''. Is this intentional?
+    packdata = rr.pack_rdata(0, {})
+    correct = b''
+    assert packdata == correct
+
+
+def test_dns_len():
+    my_dns = DNS()
+    assert len(my_dns) == 12
+
+
+@TryExceptException(dpkt.PackError)
+def test_rdata_FAIL():
+    DNS.RR(type=12345666).pack_rdata(0, {})
+
+
+def test_soa():
+    buf = define_testdata().soa_resp
+    soa = DNS(buf)
+
+    assert soa.id == 34079
+    assert soa.op == 33152
+
+    assert len(soa.qd) == 1
+    q = soa.qd[0]
+    assert q.name == 'google.com'
+    assert q.type == DNS_SOA
+    assert q.cls == DNS_IN
+
+    assert len(soa.an) == 1
+    a = soa.an[0]
+    assert a.name == 'google.com'
+    assert a.type == DNS_SOA
+    assert a.cls == DNS_IN
+    assert a.ttl == 35
+    assert a.retry == 900
+    assert a.mname == 'ns2.google.com'
+    assert a.minimum == 60
+    assert a.refresh == 900
+    assert a.expire == 1800
+    assert a.serial == 175404103
+    assert a.rlen == 38
+    assert a.rname == 'dns-admin.google.com'
+    assert a.rdata == b'\x03ns2\xc0\x0c\tdns-admin\xc0\x0c\nttG\x00\x00\x03\x84\x00\x00\x03\x84\x00\x00\x07\x08\x00\x00\x00<'
+    assert soa.ar == []
+
+
+def test_mx():
+    buf = define_testdata().mx_resp
+    mx = DNS(buf)
+
+    assert mx.id == 1339
+    assert mx.op == 33152
+
+    assert len(mx.qd) == 1
+    q = mx.qd[0]
+    assert q.name == 'google.com'
+    assert q.type == DNS_MX
+    assert q.cls == DNS_IN
+
+    assert len(mx.an) == 1
+    a = mx.an[0]
+    assert a.type == DNS_MX
+    assert a.cls == DNS_IN
+    assert a.name == 'google.com'
+    assert a.ttl == 599
+    assert a.mxname == 'alt2.aspmx.l.google.com'
+    assert a.preference == (30,)
+
+    assert a.rlen == 17
+    assert a.rdata == b'\x00\x1e\x04alt2\x05aspmx\x01l\xc0\x0c'
+    assert mx.ar == []
+
+
+def test_aaaa():
+    buf = define_testdata().aaaa_resp
+    aaaa = DNS(buf)
+
+    aaaa.id = 32546
+    aaaa.op = 33152
+
+    assert len(aaaa.qd) == 1
+    q = aaaa.qd[0]
+    assert q.type == DNS_AAAA
+    assert q.name == 'gmail.com'
+
+    assert len(aaaa.an) == 1
+    a = aaaa.an[0]
+    assert a.type == DNS_AAAA
+    assert a.cls == DNS_IN
+    assert a.name == 'gmail.com'
+    assert a.ttl == 299
+    assert a.ip6 == b'*\x00\x14P@\t\x08\x02\x00\x00\x00\x00\x00\x00 \x05'
+
+    assert a.rlen == 16
+    assert a.rdata == b'*\x00\x14P@\t\x08\x02\x00\x00\x00\x00\x00\x00 \x05'
+    assert aaaa.ar == []
+
+
+def test_srv():
+    buf = define_testdata().srv_resp
+    srv = DNS(buf)
+
+    srv.id = 32546
+    srv.op = 33152
+
+    assert len(srv.qd) == 1
+    q = srv.qd[0]
+    assert q.type == DNS_SRV
+    assert q.name == '_jabber._tcp.jabber.com'
+    assert q.cls == DNS_IN
+
+    assert len(srv.an) == 1
+    a = srv.an[0]
+    assert a.type == DNS_SRV
+    assert a.cls == DNS_IN
+    assert a.name == '_jabber._tcp.jabber.com'
+    assert a.port == 5269
+    assert a.ttl == 3599
+    assert a.srvname == 'denjab2.jabber.com'
+    assert a.priority == 10
+    assert a.weight == 0
+
+    assert a.rlen == 26
+    assert a.rdata == b'\x00\n\x00\x00\x14\x95\x07denjab2\x06jabber\x03com\x00'
+    assert srv.ar == []
+
+
+def test_cname():
+    buf = define_testdata().cname_resp
+    cname = DNS(buf)
+
+    cname.id = 41300
+    cname.op = 33152
+
+    assert len(cname.qd) == 1
+    q = cname.qd[0]
+    assert q.type == DNS_A
+    assert q.cls == DNS_IN
+    assert q.name == 'www.gmail.com'
+
+    assert len(cname.an) == 1
+    a = cname.an[0]
+    assert a.type == DNS_CNAME
+    assert a.cls == DNS_IN
+    assert a.name == 'www.gmail.com'
+    assert a.ttl == 21599
+    assert a.cname == 'mail.google.com'
+
+    assert a.rlen == 14
+    assert a.rdata == b'\x04mail\x06google\xc0\x16'
+    assert cname.ar == []
+
+
+@TryExceptException(dpkt.UnpackError)
+def test_invalid_rr():
+    buf = define_testdata().invalid_rr
+    DNS(buf)
diff --git a/dpkt/dpkt.py b/dpkt/dpkt.py
index 31b2b43..77efcff 100644
--- a/dpkt/dpkt.py
+++ b/dpkt/dpkt.py
@@ -1,15 +1,14 @@
 # $Id: dpkt.py 43 2007-08-02 22:42:59Z jon.oberheide $
 # -*- coding: utf-8 -*-
 """Simple packet creation and parsing."""
-from __future__ import absolute_import 
+from __future__ import absolute_import, print_function
 
 import copy
-import itertools
-import socket
 import struct
-import array
+from functools import partial
+from itertools import chain
 
-from .compat import compat_ord, compat_izip, iteritems
+from .compat import compat_ord, compat_izip, iteritems, ntole
 
 
 class Error(Exception):
@@ -28,6 +27,9 @@
     pass
 
 
+# See the "creating parsers" documentation for how all of this works
+
+
 class _MetaPacket(type):
     def __new__(cls, clsname, clsbases, clsdict):
         t = type.__new__(cls, clsname, clsbases, clsdict)
@@ -41,14 +43,81 @@
             t.__hdr_len__ = struct.calcsize(t.__hdr_fmt__)
             t.__hdr_defaults__ = dict(compat_izip(
                 t.__hdr_fields__, [x[2] for x in st]))
+
+        # process __bit_fields__
+        bit_fields = getattr(t, '__bit_fields__', None)
+        if bit_fields:
+            t.__bit_fields_defaults__ = {}  # bit fields equivalent of __hdr_defaults__
+
+            for (ph_name, ph_struct, ph_default) in t.__hdr__:  # ph: placeholder variable for the bit field
+                if ph_name in bit_fields:
+                    field_defs = bit_fields[ph_name]
+
+                    bits_total = sum(bf[1] for bf in field_defs)  # total size in bits
+                    bits_used = 0
+
+                    # make sure the sum of bits matches the overall size of the placeholder field
+                    assert bits_total == struct.calcsize(ph_struct) * 8, \
+                        "the overall count of bits in [%s] as declared in __bit_fields__ " \
+                        "does not match its struct size in __hdr__" % ph_name
+
+                    for (bf_name, bf_size) in field_defs:
+                        if bf_name.startswith('_'):  # do not create properties for _private fields
+                            bits_used += bf_size
+                            continue
+
+                        shift = bits_total - bits_used - bf_size
+                        mask = (2**bf_size - 1) << shift  # all zeroes except the field bits
+                        mask_inv = (2**bits_total - 1) - mask  # inverse mask
+                        bits_used += bf_size
+
+                        # calculate the default value for the bit field
+                        bf_default = (t.__hdr_defaults__[ph_name] & mask) >> shift
+                        t.__bit_fields_defaults__[bf_name] = bf_default
+
+                        # create getter, setter and delete properties for the bit fields
+
+                        def make_getter(ph_name=ph_name, mask=mask, shift=shift):
+                            def getter_func(self):
+                                ph_val = getattr(self, ph_name)
+                                return (ph_val & mask) >> shift
+                            return getter_func
+
+                        def make_setter(ph_name=ph_name, mask_inv=mask_inv, shift=shift, bf_name=bf_name, max_val=2**bf_size):
+                            def setter_func(self, bf_val):
+                                # ensure the given value fits into the number of bits available
+                                if bf_val >= max_val:
+                                    raise ValueError('value %s is too large for field %s' % (bf_val, bf_name))
+
+                                ph_val = getattr(self, ph_name)
+                                ph_val_new = (bf_val << shift) | (ph_val & mask_inv)
+                                setattr(self, ph_name, ph_val_new)
+                            return setter_func
+
+                        # delete property to set the bit field back to its default value
+                        def make_delete(bf_name=bf_name, bf_default=bf_default):
+                            def delete_func(self):
+                                setattr(self, bf_name, bf_default)
+                            return delete_func
+
+                        setattr(t, bf_name, property(make_getter(), make_setter(), make_delete()))
+
+        # optional map of functions for pretty printing
+        # {field_name: callable(field_value) -> str, ..}
+        # define as needed in the child protocol classes
+        #t.__pprint_funcs__ = {}  - disabled here to keep the base class lightweight
+
+        # placeholder for __public_fields__, a class attribute used in __repr__ and pprint()
+        t.__public_fields__ = None
+
         return t
 
 
 class Packet(_MetaPacket("Temp", (object,), {})):
-    """Base packet class, with metaclass magic to generate members from self.__hdr__.
+    r"""Base packet class, with metaclass magic to generate members from self.__hdr__.
 
     Attributes:
-        __hdr__: Packet header should be defined as a list of 
+        __hdr__: Packet header should be defined as a list of
                  (name, structfmt, default) tuples.
         __byte_order__: Byte order, can be set to override the default ('>')
 
@@ -72,7 +141,6 @@
     >>> Foo('hello, world!')
     Foo(baz=' wor', foo=1751477356L, bar=28460, data='ld!')
     """
-
     def __init__(self, *args, **kwargs):
         """Packet constructor with ([buf], [field=val,...]) prototype.
 
@@ -89,66 +157,169 @@
                 self.unpack(args[0])
             except struct.error:
                 if len(args[0]) < self.__hdr_len__:
-                    raise NeedData
+                    raise NeedData('got %d, %d needed at least' % (len(args[0]), self.__hdr_len__))
                 raise UnpackError('invalid %s: %r' %
                                   (self.__class__.__name__, args[0]))
         else:
-            for k in self.__hdr_fields__:
-                setattr(self, k, copy.copy(self.__hdr_defaults__[k]))
+            if hasattr(self, '__hdr_fields__'):
+                for k in self.__hdr_fields__:
+                    setattr(self, k, copy.copy(self.__hdr_defaults__[k]))
+
             for k, v in iteritems(kwargs):
                 setattr(self, k, v)
 
+        if hasattr(self, '__hdr_fmt__'):
+            self._pack_hdr = partial(struct.pack, self.__hdr_fmt__)
+
     def __len__(self):
         return self.__hdr_len__ + len(self.data)
 
-    def __getitem__(self, k):
+    # legacy
+    def __iter__(self):
+        return iter((fld, getattr(self, fld)) for fld in self.__class__.__hdr_fields__)
+
+    def __getitem__(self, kls):
+        """Return the 1st occurrence of the underlying <kls> data layer, raise KeyError otherwise."""
+        dd = self.data
+        while isinstance(dd, Packet):
+            if dd.__class__ == kls:
+                return dd
+            dd = dd.data
+        raise KeyError(kls)
+
+    def __contains__(self, kls):
+        """Return True is the given <kls> data layer is present in the stack."""
         try:
-            return getattr(self, k)
-        except AttributeError:
-            raise KeyError
+            return bool(self.__getitem__(kls))
+        except KeyError:
+            return False
+
+    def _create_public_fields(self):
+        """Construct __public_fields__ to be used inside __repr__ and pprint"""
+        l_ = []
+        for field_name, _, _ in getattr(self, '__hdr__', []):
+            # public fields defined in __hdr__; "public" means not starting with an underscore
+            if field_name[0] != '_':
+                l_.append(field_name)  # (1)
+
+            # if a field name starts with an underscore, and does NOT contain more underscores,
+            # it is considered hidden and is ignored (good for fields reserved for future use)
+
+            # if a field name starts with an underscore, and DOES contain more underscores,
+            # it is viewed as a complex field where underscores separate the named properties
+            # of the class;
+            elif '_' in field_name[1:]:
+                # (1) search for these properties in __bit_fields__ where they are explicitly defined
+                if field_name in getattr(self, '__bit_fields__', {}):
+                    for (prop_name, _) in self.__bit_fields__[field_name]:
+                        if isinstance(getattr(self.__class__, prop_name, None), property):
+                            l_.append(prop_name)
+
+                # (2) split by underscore into 1- and 2-component names and look for properties with such names;
+                # Example: _foo_bar_baz -> look for properties named "foo", "bar", "baz", "foo_bar" and "bar_baz"
+                # (but not "foo_bar_baz" since it contains more than one underscore)
+                else:
+                    fns = field_name[1:].split('_')
+                    for prop_name in chain(fns, ('_'.join(x) for x in zip(fns, fns[1:]))):
+                        if isinstance(getattr(self.__class__, prop_name, None), property):
+                            l_.append(prop_name)
+
+        # check for duplicates, there shouldn't be any
+        assert len(l_) == len(set(l_))
+        self.__class__.__public_fields__ = l_  # store it in the class attribute
 
     def __repr__(self):
+        if self.__public_fields__ is None:
+            self._create_public_fields()
+
         # Collect and display protocol fields in order:
         # 1. public fields defined in __hdr__, unless their value is default
-        # 2. properties derived from _private fields defined in __hdr__
+        # 2. properties derived from _private fields defined in __hdr__ and __bit_fields__
         # 3. dynamically added fields from self.__dict__, unless they are _private
         # 4. self.data when it's present
+        l_ = []
 
-        l = []
-        # maintain order of fields as defined in __hdr__
-        for field_name, _, _ in getattr(self, '__hdr__', []):
+        # (1) and (2) are done via __public_fields__; just filter out defaults here
+        for field_name in self.__public_fields__:
             field_value = getattr(self, field_name)
-            if field_value != self.__hdr_defaults__[field_name]:
-                if field_name[0] != '_':
-                    l.append('%s=%r' % (field_name, field_value))  # (1)
-                else:
-                    # interpret _private fields as name of properties joined by underscores
-                    for prop_name in field_name.split('_'):        # (2)
-                        if isinstance(getattr(self.__class__, prop_name, None), property):
-                            l.append('%s=%r' % (prop_name, getattr(self, prop_name)))
+
+            if (hasattr(self, '__hdr_defaults__') and
+               field_name in self.__hdr_defaults__ and
+               field_value == self.__hdr_defaults__[field_name]):
+                continue
+
+            if (hasattr(self, '__bit_fields_defaults__') and
+               field_name in self.__bit_fields_defaults__ and
+               field_value == self.__bit_fields_defaults__[field_name]):
+                continue
+
+            l_.append('%s=%r' % (field_name, field_value))
+
         # (3)
-        l.extend(
+        l_.extend(
             ['%s=%r' % (attr_name, attr_value)
              for attr_name, attr_value in iteritems(self.__dict__)
-             if attr_name[0] != '_'                   # exclude _private attributes
-             and attr_name != self.data.__class__.__name__.lower()])  # exclude fields like ip.udp
+             if attr_name[0] != '_' and                   # exclude _private attributes
+                attr_name != self.data.__class__.__name__.lower()])  # exclude fields like ip.udp
         # (4)
         if self.data:
-            l.append('data=%r' % self.data)
-        return '%s(%s)' % (self.__class__.__name__, ', '.join(l))
+            l_.append('data=%r' % self.data)
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(l_))
+
+    def pprint(self, indent=1):
+        """Human friendly pretty-print."""
+        if self.__public_fields__ is None:
+            self._create_public_fields()
+
+        l_ = []
+
+        def add_field(fn, fv):
+            """name=value,  # pretty-print form (if available)"""
+            try:
+                l_.append('%s=%r,  # %s' % (fn, fv, self.__pprint_funcs__[fn](fv)))
+            except (AttributeError, KeyError):
+                l_.append('%s=%r,' % (fn, fv))
+
+        for field_name in self.__public_fields__:
+            add_field(field_name, getattr(self, field_name))
+
+        for attr_name, attr_value in iteritems(self.__dict__):
+            if (attr_name[0] != '_' and                   # exclude _private attributes
+               attr_name != self.data.__class__.__name__.lower()):  # exclude fields like ip.udp
+                if type(attr_value) == list and attr_value:  # expand non-empty lists to print one item per line
+                    l_.append('%s=[' % attr_name)
+                    for av1 in attr_value:
+                        l_.append('  ' + repr(av1) + ',')  # XXX - TODO: support pretty-print
+                    l_.append('],')
+                else:
+                    add_field(attr_name, attr_value)
+
+        print('%s(' % self.__class__.__name__)  # class name, opening brace
+        for ii in l_:
+            print(' ' * indent, '%s' % ii)
+
+        if self.data:
+            if isinstance(self.data, Packet):  # recursively descend to lower layers
+                print(' ' * indent, 'data=', end='')
+                self.data.pprint(indent=indent + 2)
+            else:
+                print(' ' * indent, 'data=%r' % self.data)
+        print(' ' * (indent - 1), end='')
+        print(')  # %s' % self.__class__.__name__)  # closing brace  # class name
 
     def __str__(self):
         return str(self.__bytes__())
-    
+
     def __bytes__(self):
         return self.pack_hdr() + bytes(self.data)
 
     def pack_hdr(self):
         """Return packed header string."""
         try:
-            return struct.pack(self.__hdr_fmt__,
-                               *[getattr(self, k) for k in self.__hdr_fields__])
-        except struct.error:
+            return self._pack_hdr(
+                *[getattr(self, k) for k in self.__hdr_fields__]
+            )
+        except (TypeError, struct.error):
             vals = []
             for k in self.__hdr_fields__:
                 v = getattr(self, k)
@@ -172,8 +343,13 @@
             setattr(self, k, v)
         self.data = buf[self.__hdr_len__:]
 
+
 # XXX - ''.join([(len(`chr(x)`)==3) and chr(x) or '.' for x in range(256)])
-__vis_filter = b'................................ !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~.................................................................................................................................'
+__vis_filter = (
+    b'................................ !"#$%&\'()*+,-./0123456789:;<=>?'
+    b'@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~.'
+    b'................................................................'
+    b'................................................................')
 
 
 def hexdump(buf, length=16):
@@ -192,16 +368,17 @@
 def in_cksum_add(s, buf):
     n = len(buf)
     cnt = (n // 2) * 2
-    a = array.array('H', buf[:cnt])
+    a = struct.unpack('<{}H'.format(n // 2), buf[:cnt])  # unpack as little endian words
+    res = s + sum(a)
     if cnt != n:
-        a.append(compat_ord(buf[-1]))
-    return s + sum(a)
+        res += compat_ord(buf[-1])
+    return res
 
 
 def in_cksum_done(s):
     s = (s >> 16) + (s & 0xffff)
     s += (s >> 16)
-    return socket.ntohs(~s & 0xffff)
+    return ntole(~s & 0xffff)
 
 
 def in_cksum(buf):
@@ -214,5 +391,148 @@
     __hd = '  0000:  00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e     ...............'
     h = hexdump(__buf)
     assert (h == __hd)
+    assert in_cksum_add(0, __buf) == 12600  # endianness
     c = in_cksum(__buf)
     assert (c == 51150)
+
+
+# test Packet.__getitem__ and __contains__ methods
+def test_getitem_contains():
+    import pytest
+
+    class Foo(Packet):
+        __hdr__ = (('foo', 'I', 0),)
+
+    class Bar(Packet):
+        __hdr__ = (('bar', 'I', 0),)
+
+    class Baz(Packet):
+        __hdr__ = (('baz', 'I', 0),)
+
+    class Zeb(Packet):
+        pass
+
+    ff = Foo(foo=1, data=Bar(bar=2, data=Baz(attr=Zeb())))
+
+    # __contains__
+    assert Bar in ff
+    assert Baz in ff
+    assert Baz in ff.data
+    assert Zeb not in ff
+    assert Zeb not in Baz()
+
+    # __getitem__
+    assert isinstance(ff[Bar], Bar)
+    assert isinstance(ff[Baz], Baz)
+
+    assert isinstance(ff[Bar][Baz], Baz)
+    with pytest.raises(KeyError):
+        ff[Baz][Bar]
+
+    with pytest.raises(KeyError):
+        ff[Zeb]
+
+    with pytest.raises(KeyError):
+        Bar()[Baz]
+
+
+def test_pack_hdr_overflow():
+    """Try to fit too much data into struct packing"""
+    import pytest
+
+    class Foo(Packet):
+        __hdr__ = (
+            ('foo', 'I', 1),
+            ('bar', 'I', (1, 2)),
+        )
+
+    foo = Foo(foo=2**32)
+    with pytest.raises(PackError):
+        bytes(foo)
+
+
+def test_bit_fields_overflow():
+    """Try to fit too much data into too few bits"""
+    import pytest
+
+    class Foo(Packet):
+        __hdr__ = (
+            ('_a_b', 'B', 0),
+        )
+        __bit_fields__ = {
+            '_a_b': (
+                ('a', 2),
+                ('b', 6),
+            )
+        }
+
+    foo = Foo()
+    with pytest.raises(ValueError):
+        foo.a = 5
+
+
+def test_pack_hdr_tuple():
+    """Test the unpacking of a tuple for a single format string"""
+    class Foo(Packet):
+        __hdr__ = (
+            ('bar', 'II', (1, 2)),
+        )
+
+    foo = Foo()
+    b = bytes(foo)
+    assert b == b'\x00\x00\x00\x01\x00\x00\x00\x02'
+
+
+def test_unpacking_failure():
+    # during dynamic-sized unpacking in the subclass there may be struct.errors raised,
+    # but if the header has unpacked correctly, a different error is raised by the superclass
+    import pytest
+
+    class TestPacket(Packet):
+        __hdr__ = (('test', 'B', 0),)
+
+        def unpack(self, buf):
+            Packet.unpack(self, buf)
+            self.attribute = struct.unpack('B', buf[1:])
+
+    with pytest.raises(UnpackError, match="invalid TestPacket: "):
+        TestPacket(b'\x00')  # header will unpack successfully
+
+
+def test_repr():
+    """complex test for __repr__, __public_fields__"""
+
+    class TestPacket(Packet):
+        __hdr__ = (
+            ('_a_b', 'B', 1),     # 'a' and 'b' bit fields
+            ('_rsv', 'B', 0),     # hidden reserved field
+            ('_c_flag', 'B', 1),  # 'c_flag' property
+            ('d', 'B', 0)         # regular field
+        )
+
+        __bit_fields__ = {
+            '_a_b': (
+                ('a', 4),
+                ('b', 4),
+            ),
+        }
+
+        @property
+        def c_flag(self):
+            return (self.a | self.b)
+
+    # init with default values
+    test_packet = TestPacket()
+
+    # test repr with all default values so expect no output
+    # (except for the explicitly defined property, where dpkt doesn't process defaults yet)
+    assert repr(test_packet) == "TestPacket(c_flag=1)"
+
+    # init with non-default values
+    test_packet = TestPacket(b'\x12\x11\x00\x04')
+
+    # ensure the display fields were cached and propagated via class attribute
+    assert test_packet.__public_fields__ == ['a', 'b', 'c_flag', 'd']
+
+    # verify repr
+    assert repr(test_packet) == "TestPacket(a=1, b=2, c_flag=3, d=4)"
diff --git a/dpkt/dtp.py b/dpkt/dtp.py
index c570276..ba166be 100644
--- a/dpkt/dtp.py
+++ b/dpkt/dtp.py
@@ -2,11 +2,13 @@
 # -*- coding: utf-8 -*-
 """Dynamic Trunking Protocol."""
 from __future__ import absolute_import
-
 import struct
 
 from . import dpkt
 
+TRUNK_NAME = 0x01
+MAC_ADDR = 0x04
+
 
 class DTP(dpkt.Packet):
     """Dynamic Trunking Protocol.
@@ -17,7 +19,7 @@
         __hdr__: Header fields of DTP.
         TODO.
     """
-    
+
     __hdr__ = (
         ('v', 'B', 0),
     )  # rest is TLVs
@@ -27,11 +29,34 @@
         buf = self.data
         tvs = []
         while buf:
-            t, l = struct.unpack('>HH', buf[:4])
-            v, buf = buf[4:4 + l], buf[4 + l:]
+            t, l_ = struct.unpack('>HH', buf[:4])
+            v, buf = buf[4:4 + l_], buf[4 + l_:]
             tvs.append((t, v))
         self.data = tvs
 
+    def __bytes__(self):
+        return b''.join([struct.pack('>HH', t, len(v)) + v for t, v in self.data])
 
-TRUNK_NAME = 0x01
-MAC_ADDR = 0x04
+
+def test_creation():
+    dtp1 = DTP()
+    assert dtp1.v == 0
+
+    from binascii import unhexlify
+    buf = unhexlify(
+        '04'
+        '0001'  # type
+        '0002'  # length
+        '1234'  # value
+    )
+
+    dtp2 = DTP(buf)
+    assert dtp2.v == 4
+    assert len(dtp2.data) == 1
+    tlvs = dtp2.data
+    tlv = tlvs[0]
+    key, value = tlv
+    assert key == 1
+    assert value == unhexlify('1234')
+
+    assert bytes(dtp2) == buf[1:]
diff --git a/dpkt/edp.py b/dpkt/edp.py
new file mode 100644
index 0000000..187978c
--- /dev/null
+++ b/dpkt/edp.py
@@ -0,0 +1,77 @@
+"""Extreme Discovery Protocol."""
+from __future__ import absolute_import
+
+import dpkt
+
+
+class EDP(dpkt.Packet):
+    __hdr__ = (
+        ('version', 'B', 1),
+        ('reserved', 'B', 0),
+        ('hlen', 'H', 0),
+        ('sum', 'H', 0),
+        ('seq', 'H', 0),
+        ('mid', 'H', 0),
+        ('mac', '6s', b'')
+    )
+
+    def __bytes__(self):
+        if not self.sum:
+            self.sum = dpkt.in_cksum(dpkt.Packet.__bytes__(self))
+        return dpkt.Packet.__bytes__(self)
+
+
+class TestEDP(object):
+    """
+    Test basic EDP functionality.
+    """
+
+    @classmethod
+    def setup_class(cls):
+        from binascii import unhexlify
+        cls.buf = unhexlify(
+            '01'      # version
+            '00'      # reserved
+            '013c'    # hlen
+            '9e76'    # sum
+            '001b'    # seq
+            '0000'    # mid
+            '080027'  # mac
+            '2d90ed990200240000000000000000000000000f020207000000000000000000000000000000009901010445584f532d32000000000000000'
+            '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
+            '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
+            '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
+            '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
+            '00000000000000000000000000000000099000004'
+        )
+        cls.p = EDP(cls.buf)
+
+    def test_version(self):
+        assert (self.p.version == 1)
+
+    def test_reserved(self):
+        assert (self.p.reserved == 0)
+
+    def test_hlen(self):
+        assert (self.p.hlen == 316)
+
+    def test_sum(self):
+        assert (self.p.sum == 40566)
+
+    def test_seq(self):
+        assert (self.p.seq == 27)
+
+    def test_mid(self):
+        assert (self.p.mid == 0)
+
+    def test_mac(self):
+        assert (self.p.mac == b"\x08\x00'-\x90\xed")
+
+    def test_bytes(self):
+        assert bytes(self.p) == self.buf
+
+        # force recalculation of the checksum
+        edp = EDP(self.buf)
+        edp.sum = 0
+        assert edp.sum == 0
+        assert bytes(edp) == self.buf
diff --git a/dpkt/esp.py b/dpkt/esp.py
index 9156959..c3917bb 100644
--- a/dpkt/esp.py
+++ b/dpkt/esp.py
@@ -15,7 +15,7 @@
         __hdr__: Header fields of ESP.
         TODO.
     """
-    
+
     __hdr__ = (
         ('spi', 'I', 0),
         ('seq', 'I', 0)
diff --git a/dpkt/ethernet.py b/dpkt/ethernet.py
index 94a8fe0..41efa10 100644
--- a/dpkt/ethernet.py
+++ b/dpkt/ethernet.py
@@ -1,25 +1,19 @@
 # $Id: ethernet.py 65 2010-03-26 02:53:51Z dugsong $
 # -*- coding: utf-8 -*-
-"""Ethernet II, LLC (802.3+802.2), LLC/SNAP, and Novell raw 802.3,
-with automatic 802.1q, MPLS, PPPoE, and Cisco ISL decapsulation."""
+"""
+Ethernet II, LLC (802.3+802.2), LLC/SNAP, and Novell raw 802.3,
+with automatic 802.1q, MPLS, PPPoE, and Cisco ISL decapsulation.
+"""
 from __future__ import print_function
 from __future__ import absolute_import
 
 import struct
-import codecs
 from zlib import crc32
 
 from . import dpkt
 from . import llc
-from .compat import iteritems
-
-try:
-    isinstance("", basestring)
-    def isstr(s):
-        return isinstance(s, basestring)
-except NameError:
-    def isstr(s):
-        return isinstance(s, str)
+from .utils import mac_to_str
+from .compat import compat_ord, iteritems, isstr
 
 ETH_CRC_LEN = 4
 ETH_HDR_LEN = 14
@@ -31,6 +25,8 @@
 ETH_MIN = (ETH_LEN_MIN - ETH_HDR_LEN - ETH_CRC_LEN)
 
 # Ethernet payload types - http://standards.ieee.org/regauth/ethertype
+ETH_TYPE_UNKNOWN = 0x0000
+ETH_TYPE_EDP = 0x00bb  # Extreme Networks Discovery Protocol
 ETH_TYPE_PUP = 0x0200  # PUP protocol
 ETH_TYPE_IP = 0x0800  # IP protocol
 ETH_TYPE_ARP = 0x0806  # address resolution protocol
@@ -39,6 +35,9 @@
 ETH_TYPE_DTP = 0x2004  # Cisco Dynamic Trunking Protocol
 ETH_TYPE_REVARP = 0x8035  # reverse addr resolution protocol
 ETH_TYPE_8021Q = 0x8100  # IEEE 802.1Q VLAN tagging
+ETH_TYPE_8021AD = 0x88a8  # IEEE 802.1ad
+ETH_TYPE_QINQ1 = 0x9100  # Legacy QinQ
+ETH_TYPE_QINQ2 = 0x9200  # Legacy QinQ
 ETH_TYPE_IPX = 0x8137  # Internetwork Packet Exchange
 ETH_TYPE_IP6 = 0x86DD  # IPv6 protocol
 ETH_TYPE_PPP = 0x880B  # PPP
@@ -48,6 +47,10 @@
 ETH_TYPE_PPPoE = 0x8864  # PPP Over Ethernet Session Stage
 ETH_TYPE_LLDP = 0x88CC  # Link Layer Discovery Protocol
 ETH_TYPE_TEB = 0x6558  # Transparent Ethernet Bridging
+ETH_TYPE_PROFINET = 0x8892  # PROFINET protocol
+
+# all QinQ types for fast checking
+_ETH_TYPES_QINQ = frozenset([ETH_TYPE_8021Q, ETH_TYPE_8021AD, ETH_TYPE_QINQ1, ETH_TYPE_QINQ2])
 
 
 class Ethernet(dpkt.Packet):
@@ -60,24 +63,31 @@
         __hdr__: Header fields of Ethernet.
         TODO.
     """
-    
+
     __hdr__ = (
-        ('dst', '6s', ''),
-        ('src', '6s', ''),
+        ('dst', '6s', b''),
+        ('src', '6s', b''),
         ('type', 'H', ETH_TYPE_IP)
     )
     _typesw = {}
     _typesw_rev = {}  # reverse mapping
 
+    __pprint_funcs__ = {
+        'dst': mac_to_str,
+        'src': mac_to_str,
+    }
+
     def __init__(self, *args, **kwargs):
+        self._next_type = None
         dpkt.Packet.__init__(self, *args, **kwargs)
         # if data was given in kwargs, try to unpack it
-        if self.data: 
+        if self.data:
             if isstr(self.data) or isinstance(self.data, bytes):
                 self._unpack_data(self.data)
 
     def _unpack_data(self, buf):
-        if self.type == ETH_TYPE_8021Q:
+        # unpack vlan tag and mpls label stacks
+        if self._next_type in _ETH_TYPES_QINQ:
             self.vlan_tags = []
 
             # support up to 2 tags (double tagging aka QinQ)
@@ -85,13 +95,13 @@
                 tag = VLANtag8021Q(buf)
                 buf = buf[tag.__hdr_len__:]
                 self.vlan_tags.append(tag)
-                self.type = tag.type
-                if self.type != ETH_TYPE_8021Q:
+                self._next_type = tag.type
+                if self._next_type != ETH_TYPE_8021Q:
                     break
             # backward compatibility, use the 1st tag
             self.vlanid, self.priority, self.cfi = self.vlan_tags[0].as_tuple()
 
-        elif self.type == ETH_TYPE_MPLS or self.type == ETH_TYPE_MPLS_MCAST:
+        elif self._next_type == ETH_TYPE_MPLS or self._next_type == ETH_TYPE_MPLS_MCAST:
             self.labels = []  # old list containing labels as tuples
             self.mpls_labels = []  # new list containing labels as instances of MPLSlabel
 
@@ -103,10 +113,23 @@
                 self.labels.append(lbl.as_tuple())
                 if lbl.s:  # bottom of stack
                     break
-            self.type = ETH_TYPE_IP
+
+            # poor man's heuristics to guessing the next type
+            if compat_ord(buf[0]) == 0x45:  # IP version 4 + header len 20 bytes
+                self._next_type = ETH_TYPE_IP
+
+            elif compat_ord(buf[0]) & 0xf0 == 0x60:  # IP version 6 
+                self._next_type = ETH_TYPE_IP6
+
+            # pseudowire Ethernet
+            elif len(buf) >= self.__hdr_len__:
+                if buf[:2] == b'\x00\x00':  # looks like the control word (ECW)
+                    buf = buf[4:]  # skip the ECW
+                self._next_type = ETH_TYPE_TEB  # re-use TEB class mapping to decode Ethernet
 
         try:
-            self.data = self._typesw[self.type](buf)
+            eth_type = self._next_type or self.type
+            self.data = self._typesw[eth_type](buf)
             setattr(self, self.data.__class__.__name__.lower(), self.data)
         except (KeyError, dpkt.UnpackError):
             self.data = buf
@@ -115,6 +138,7 @@
         dpkt.Packet.unpack(self, buf)
         if self.type > 1500:
             # Ethernet II
+            self._next_type = self.type
             self._unpack_data(self.data)
 
         elif (self.dst.startswith(b'\x01\x00\x0c\x00\x00') or
@@ -131,30 +155,52 @@
             self.type = ETH_TYPE_IPX
             self.data = self.ipx = self._typesw[ETH_TYPE_IPX](self.data[2:])
 
+        elif self.type == ETH_TYPE_UNKNOWN:
+            # Unknown type, assume Ethernet
+            self._unpack_data(self.data)
+
         else:
             # IEEE 802.3 Ethernet - LLC
-            # try to unpack FCS here; we follow the same heuristic approach as Wireshark:
-            # if the upper layer len(self.data) can be fully decoded and returns its size,
-            # and there's a difference with size in the Eth header, then assume the last
-            # 4 bytes is the FCS and remaining bytes are a trailer.
-            eth_len = self.type
-            if len(self.data) > eth_len:
-                tail_len = len(self.data) - eth_len
-                if tail_len >= 4:
-                    self.fcs = struct.unpack('>I', self.data[-4:])[0]
-                    self.trailer = self.data[eth_len:-4]
+            # try to unpack FCS, padding and trailer here
+            # we follow a heuristic approach similar to that of Wireshark
+
+            # size of eth body, not including the header
+            eth_len = self.len = self.type
+
+            # actual size of the remaining data, could include eth body, padding, fcs, trailer
+            data_len = len(self.data)
+            if data_len > eth_len:
+                # everything after eth body
+                tail = self.data[eth_len:]
+
+                # could be padding + fcs, possibly trailer
+                if len(tail) > 4:
+                    # determine size of padding
+                    if eth_len < 46:  # 46=60-14; 14=size of eth hdr; all padded to 60 bytes
+                        pad_len = 46 - eth_len
+                        padding = tail[:pad_len]
+
+                        # heuristic
+                        if padding == pad_len * b'\x00':  # padding is likely zeroes
+                            self.padding = padding
+                            tail = tail[pad_len:]
+                        # else proceed to decode as fcs+trailer
+
+                # 4 bytes FCS and possible trailer
+                if len(tail) >= 4:
+                    self.fcs = struct.unpack('>I', tail[:4])[0]
+                    tail = tail[4:]
+
+                if tail:
+                    self.trailer = tail
+
             self.data = self.llc = llc.LLC(self.data[:eth_len])
 
     def pack_hdr(self):
-        tags_buf =  b''
-        new_type = self.type
+        tags_buf = b''
+        new_type = self.type  # replacement self.type when packing eth header
         is_isl = False  # ISL wraps Ethernet, this determines order of packing
 
-        # initial type is based on next layer, pointed by self.data;
-        # try to find an ETH_TYPE matching the data class
-        if isinstance(self.data, dpkt.Packet):
-            new_type = self._typesw_rev.get(self.data.__class__, new_type)
-
         if getattr(self, 'mpls_labels', None):
             # mark all labels with s=0, last one with s=1
             for lbl in self.mpls_labels:
@@ -162,29 +208,42 @@
             lbl.s = 1
 
             # set encapsulation type
-            if not (self.type == ETH_TYPE_MPLS or self.type == ETH_TYPE_MPLS_MCAST):
+            if new_type not in (ETH_TYPE_MPLS, ETH_TYPE_MPLS_MCAST):
                 new_type = ETH_TYPE_MPLS
             tags_buf = b''.join(lbl.pack_hdr() for lbl in self.mpls_labels)
 
         elif getattr(self, 'vlan_tags', None):
+            # set last tag type to next layer pointed by self.data
+            last_tag_type = self.type  # default
+            if isinstance(self.data, dpkt.Packet):
+                last_tag_type = self._typesw_rev.get(self.data.__class__, self.type)
+
             # set encapsulation types
             t1 = self.vlan_tags[0]
             if len(self.vlan_tags) == 1:
                 if isinstance(t1, VLANtag8021Q):
-                    t1.type = self.type
-                    new_type = ETH_TYPE_8021Q
+                    if new_type not in _ETH_TYPES_QINQ:  # preserve the type if already set
+                        new_type = ETH_TYPE_8021Q
+                    t1.type = last_tag_type
                 elif isinstance(t1, VLANtagISL):
                     t1.type = 0  # 0 means Ethernet
                     is_isl = True
             elif len(self.vlan_tags) == 2:
                 t2 = self.vlan_tags[1]
                 if isinstance(t1, VLANtag8021Q) and isinstance(t2, VLANtag8021Q):
-                    t2.type = self.type
-                    new_type = t1.type = ETH_TYPE_8021Q
+                    t1.type = ETH_TYPE_8021Q
+                    if new_type not in _ETH_TYPES_QINQ:
+                        new_type = ETH_TYPE_8021AD
+                t2.type = last_tag_type
             else:
                 raise dpkt.PackError('maximum is 2 VLAN tags per Ethernet frame')
             tags_buf = b''.join(tag.pack_hdr() for tag in self.vlan_tags)
 
+        # initial type is based on next layer, pointed by self.data;
+        # try to find an ETH_TYPE matching the data class
+        elif isinstance(self.data, dpkt.Packet):
+            new_type = self._typesw_rev.get(self.data.__class__, new_type)
+
         # if self.data is LLC then this is IEEE 802.3 Ethernet and self.type
         # then actually encodes the length of data
         if isinstance(self.data, llc.LLC):
@@ -196,29 +255,35 @@
         else:
             return tags_buf + hdr_buf
 
-    def __str__(self):
+    def __bytes__(self):
         tail = b''
         if isinstance(self.data, llc.LLC):
+            fcs = b''
             if hasattr(self, 'fcs'):
                 if self.fcs:
                     fcs = self.fcs
                 else:
                     # if fcs field is present but 0/None, then compute it and add to the tail
-                    fcs_buf = self.pack_hdr() + bytes(self.data) + getattr(self, 'trailer', '')
+                    fcs_buf = self.pack_hdr() + bytes(self.data)
                     # if ISL header is present, exclude it from the calculation
                     if getattr(self, 'vlan_tags', None):
                         if isinstance(self.vlan_tags[0], VLANtagISL):
                             fcs_buf = fcs_buf[VLANtagISL.__hdr_len__:]
+                    fcs_buf += getattr(self, 'padding', b'')
                     revcrc = crc32(fcs_buf) & 0xffffffff
                     fcs = struct.unpack('<I', struct.pack('>I', revcrc))[0]  # bswap32
-                tail = getattr(self, 'trailer', b'') + struct.pack('>I', fcs)
-        return str(dpkt.Packet.__bytes__(self) + tail)
+                fcs = struct.pack('>I', fcs)
+            tail = getattr(self, 'padding', b'') + fcs + getattr(self, 'trailer', b'')
+        return bytes(dpkt.Packet.__bytes__(self) + tail)
 
     def __len__(self):
         tags = getattr(self, 'mpls_labels', []) + getattr(self, 'vlan_tags', [])
         _len = dpkt.Packet.__len__(self) + sum(t.__hdr_len__ for t in tags)
-        if isinstance(self.data, llc.LLC) and hasattr(self, 'fcs'):
-            _len += len(getattr(self, 'trailer', '')) + 4
+        if isinstance(self.data, llc.LLC):
+            _len += len(getattr(self, 'padding', b''))
+            if hasattr(self, 'fcs'):
+                _len += 4
+            _len += len(getattr(self, 'trailer', b''))
         return _len
 
     @classmethod
@@ -267,24 +332,19 @@
         ('_val_exp_s_ttl', 'I', 0),
     )
     # field names are according to RFC3032
+    __bit_fields__ = {
+        '_val_exp_s_ttl': (
+            ('val', 20),  # label value, 20 bits
+            ('exp', 3),   # experimental use, 3 bits
+            ('s', 1),     # bottom of stack flag, 1 bit
+            ('ttl', 8),   # time to live, 8 bits
+        )
+    }
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
-        self.val = (self._val_exp_s_ttl & 0xfffff000) >> 12  # label value, 20 bits
-        self.exp = (self._val_exp_s_ttl & 0x00000e00) >> 9   # experimental use, 3 bits
-        self.s = (self._val_exp_s_ttl & 0x00000100) >> 8     # bottom of stack flag, 1 bit
-        self.ttl = self._val_exp_s_ttl & 0x000000ff          # time to live, 8 bits
         self.data = b''
 
-    def pack_hdr(self):
-        self._val_exp_s_ttl = (
-            ((self.val & 0xfffff) << 12) |
-            ((self.exp & 7) << 9) |
-            ((self.s & 1) << 8) |
-            ((self.ttl & 0xff))
-        )
-        return dpkt.Packet.pack_hdr(self)
-
     def as_tuple(self):  # backward-compatible representation
         return (self.val, self.exp, self.ttl)
 
@@ -296,22 +356,18 @@
         ('_pri_cfi_id', 'H', 0),
         ('type', 'H', ETH_TYPE_IP)
     )
+    __bit_fields__ = {
+        '_pri_cfi_id': (
+            ('pri', 3),  # priority, 3 bits
+            ('cfi', 1),  # canonical format indicator, 1 bit
+            ('id', 12),  # VLAN id, 12 bits
+        )
+    }
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
-        self.pri = (self._pri_cfi_id & 0xe000) >> 13   # priority, 3 bits
-        self.cfi = (self._pri_cfi_id & 0x1000) >> 12   # canonical format indicator, 1 bit
-        self.id = self._pri_cfi_id & 0x0fff           # VLAN id, 12 bits
         self.data = b''
 
-    def pack_hdr(self):
-        self._pri_cfi_id = (
-            ((self.pri & 7) << 13) |
-            ((self.cfi & 1) << 12) |
-            ((self.id & 0xfff))
-        )
-        return dpkt.Packet.pack_hdr(self)
-
     def as_tuple(self):
         return (self.id, self.pri, self.cfi)
 
@@ -322,7 +378,7 @@
     __hdr__ = (
         ('da', '5s', b'\x01\x00\x0c\x00\x00'),
         ('_type_pri', 'B', 3),
-        ('sa', '6s', ''),
+        ('sa', '6s', b''),
         ('len', 'H', 0),
         ('snap', '3s', b'\xaa\xaa\x03'),
         ('hsa', '3s', b'\x00\x00\x0c'),
@@ -330,26 +386,26 @@
         ('indx', 'H', 0),
         ('res', 'H', 0)
     )
+    __bit_fields__ = {
+        '_type_pri': (
+            ('type', 4),  # encapsulation type, 4 bits; 0 means Ethernet
+            ('pri', 4),   # user defined bits, 2 lo bits are used; means priority
+        ),
+        '_id_bpdu': (
+            ('id', 15),   # vlan id, 15 bits
+            ('bpdu', 1),  # bridge protocol data unit indicator
+        )
+    }
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
-        self.type = (self._type_pri & 0xf0) >> 4  # encapsulation type, 4 bits; 0 means Ethernet
-        self.pri = self._type_pri & 0x03  # user defined bits, 2 bits are used; means priority
-        self.id = self._id_bpdu >> 1  # VLAN id
-        self.bpdu = self._id_bpdu & 1
         self.data = b''
 
-    def pack_hdr(self):
-        self._type_pri = ((self.type & 0xf) << 4) | (self.pri & 0x3)
-        self._id_bpdu = ((self.id & 0x7fff) << 1) | (self.bpdu & 1)
-        return dpkt.Packet.pack_hdr(self)
-
 
 # Unit tests
 
 
 def test_eth():
-    from . import ip  # IPv6 needs this to build its protocol stack
     from . import ip6
     from . import tcp
     s = (b'\x00\xb0\xd0\xe1\x80\x72\x00\x11\x24\x8c\x11\xde\x86\xdd\x60\x00\x00\x00'
@@ -366,6 +422,18 @@
     assert len(eth) == len(s)
 
 
+def test_eth_zero_ethtype():
+    s = (b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+         b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+         b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+         b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x89\x12\x04')
+    eth = Ethernet(s)
+    assert eth
+    assert eth.type == ETH_TYPE_UNKNOWN
+    assert str(eth) == str(s)
+    assert len(eth) == len(s)
+
+
 def test_eth_init_with_data():
     # initialize with a data string, test that it gets unpacked
     from . import arp
@@ -446,16 +514,22 @@
 
 
 def test_eth_802dot1q_stacked():  # 2 VLAN tags
-    from . import arp
+    from binascii import unhexlify
+
+    import pytest
+
     from . import ip
-    s = (b'\x00\x1b\xd4\x1b\xa4\xd8\x00\x13\xc3\xdf\xae\x18\x81\x00\x00\x76\x81\x00\x00\x0a\x08\x00'
-         b'\x45\x00\x00\x64\x00\x0f\x00\x00\xff\x01\x92\x9b\x0a\x76\x0a\x01\x0a\x76\x0a\x02\x08\x00'
-         b'\xce\xb7\x00\x03\x00\x00\x00\x00\x00\x00\x00\x1f\xaf\x70\xab\xcd\xab\xcd\xab\xcd\xab\xcd'
-         b'\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd'
-         b'\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd'
-         b'\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd')
+
+    s = unhexlify(
+        '001bd41ba4d80013c3dfae18810000768100000a0800'
+        '45000064000f0000ff01929b0a760a010a760a020800'
+        'ceb70003000000000000001faf70abcdabcdabcdabcd'
+        'abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd'
+        'abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd'
+        'abcdabcdabcdabcdabcdabcd'
+    )
     eth = Ethernet(s)
-    assert eth.type == ETH_TYPE_IP
+    assert eth.type == ETH_TYPE_8021Q
     assert len(eth.vlan_tags) == 2
     assert eth.vlan_tags[0].id == 118
     assert eth.vlan_tags[1].id == 10
@@ -465,18 +539,28 @@
     assert isinstance(eth.data, ip.IP)
 
     # construction
-    assert str(eth) == str(s), 'pack 1'
-    assert str(eth) == str(s), 'pack 2'
     assert len(eth) == len(s)
+    assert bytes(eth) == s
+
+    # test packing failure with too many tags
+    eth.vlan_tags += eth.vlan_tags[0]  # just duplicate the first tag
+    with pytest.raises(dpkt.PackError, match='maximum is 2 VLAN tags per Ethernet frame'):
+        bytes(eth)
 
     # construction with kwargs
-    eth2 = Ethernet(src=eth.src, dst=eth.dst, vlan_tags=eth.vlan_tags, data=eth.data)
-    assert str(eth2) == str(s)
+    eth2 = Ethernet(src=eth.src, dst=eth.dst, vlan_tags=eth.vlan_tags[:2], data=eth.data)
+
+    # construction sets ip.type to 802.1ad instead of 802.1q so account for it
+    assert str(eth2) == str(s[:12] + b'\x88\xa8' + s[14:])
 
     # construction w/o the tags
     del eth.vlan_tags, eth.cfi, eth.vlanid, eth.priority
     assert str(eth) == str(s[:12] + b'\x08\x00' + s[22:])
 
+
+def test_eth_vlan_arp():
+    from . import arp
+
     # 2 VLAN tags + ARP
     s = (b'\xff\xff\xff\xff\xff\xff\xca\x03\x0d\xb4\x00\x1c\x81\x00\x00\x64\x81\x00\x00\xc8\x08\x06'
          b'\x00\x01\x08\x00\x06\x04\x00\x01\xca\x03\x0d\xb4\x00\x1c\xc0\xa8\x02\xc8\x00\x00\x00\x00'
@@ -488,20 +572,22 @@
     assert isinstance(eth.data, arp.ARP)
 
 
-def test_eth_mpls_stacked():  # 2 MPLS labels
+def test_eth_mpls_stacked():  # Eth - MPLS - MPLS - IP - ICMP
     from . import ip
+    from . import icmp
     s = (b'\x00\x30\x96\xe6\xfc\x39\x00\x30\x96\x05\x28\x38\x88\x47\x00\x01\x20\xff\x00\x01\x01\xff'
          b'\x45\x00\x00\x64\x00\x50\x00\x00\xff\x01\xa7\x06\x0a\x1f\x00\x01\x0a\x22\x00\x01\x08\x00'
-         b'\xbd\x11\x0f\x65\x12\xa0\x00\x00\x00\x00\x00\x53\x9e\xe0\xab\xcd\xab\xcd\xab\xcd\xab\xcd'
-         b'\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd'
-         b'\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd'
-         b'\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd\xab\xcd')
+         b'\xbd\x11\x0f\x65\x12\xa0\x00\x00\x00\x00\x00\x53\x9e\xe0' + b'\xab\xcd' * 32)
     eth = Ethernet(s)
     assert len(eth.mpls_labels) == 2
     assert eth.mpls_labels[0].val == 18
     assert eth.mpls_labels[1].val == 16
     assert eth.labels == [(18, 0, 255), (16, 0, 255)]
     assert isinstance(eth.data, ip.IP)
+    assert isinstance(eth.data.data, icmp.ICMP)
+
+    # exercise .pprint() for the coverage tests
+    eth.pprint()
 
     # construction
     assert str(eth) == str(s), 'pack 1'
@@ -516,6 +602,24 @@
     del eth.labels, eth.mpls_labels
     assert str(eth) == str(s[:12] + b'\x08\x00' + s[22:])
 
+def test_eth_mpls_ipv6():  # Eth - MPLS - IP6 - TCP
+    from . import ip6
+    from . import tcp
+
+    s = ( b'\x00\x30\x96\xe6\xfc\x39\x00\x30\x96\x05\x28\x38\x88\x47\x00\x01'
+          b'\x01\xff\x62\x8c\xed\x7b\x00\x28\x06\xfd\x22\x22\x22\x22\x03\x3f'
+          b'\x53\xd3\x48\xfb\x8b\x5a\x41\x7f\xe6\x17\x11\x11\x11\x11\x40\x0b'
+          b'\x08\x09\x00\x00\x00\x00\x00\x00\x20\x0e\xa1\x8e\x01\xbb\xd6\xde'
+          b'\x73\x17\x00\x00\x00\x00\xa0\x02\xff\xff\x58\x7f\x00\x00\x02\x04'
+          b'\x05\x8c\x04\x02\x08\x0a\x69\x23\xe8\x63\x00\x00\x00\x00\x01\x03'
+          b'\x03\x0a\xaf\x9c\xb6\x93')
+
+    eth = Ethernet(s)
+    assert len(eth.mpls_labels) == 1
+    assert eth.mpls_labels[0].val == 16
+    assert eth.labels == [(16, 0, 255)]
+    assert isinstance(eth.data, ip6.IP6)
+    assert isinstance(eth.data.data, tcp.TCP)
 
 def test_isl_eth_llc_stp():  # ISL - 802.3 Ethernet(w/FCS) - LLC - STP
     from . import stp
@@ -530,9 +634,9 @@
     assert eth.vlan_tags[0].id == 333
     assert eth.vlan_tags[0].pri == 3
 
-    # check that FCS was decoded
+    # check that FCS and padding were decoded
     assert eth.fcs == 0x41c675d6
-    assert eth.trailer == b'\x00' * 8
+    assert eth.padding == b'\x00' * 8
 
     # stack
     assert isinstance(eth.data, llc.LLC)
@@ -545,11 +649,15 @@
 
     # construction with kwargs
     eth2 = Ethernet(src=eth.src, dst=eth.dst, vlan_tags=eth.vlan_tags, data=eth.data)
-    eth2.trailer = eth.trailer
-    eth2.fcs = None
+    eth2.padding = b'\x00' * 8
     # test FCS computation
+    eth2.fcs = None
     assert str(eth2) == str(s)
 
+    # TODO: test padding construction
+    # eth2.padding = None
+    # assert str(eth2) == str(s)
+
     # construction w/o the ISL tag
     del eth.vlan_tags, eth.vlan
     assert str(eth) == str(s[26:])
@@ -569,7 +677,7 @@
     # stack
     assert isinstance(eth.data, llc.LLC)
     assert isinstance(eth.data.data, cdp.CDP)
-    assert len(eth.data.data.data) == 8  # number of CDP TLVs; ensures they are decoded
+    assert len(eth.data.data.tlvs) == 8  # number of CDP TLVs; ensures they are decoded
     assert str(eth) == str(s), 'pack 1'
     assert str(eth) == str(s), 'pack 2'
     assert len(eth) == len(s)
@@ -594,7 +702,6 @@
 
 
 def test_eth_pppoe():   # Eth - PPPoE - IPv6 - UDP - DHCP6
-    from . import ip  # IPv6 needs this to build its protocol stack
     from . import ip6
     from . import ppp
     from . import pppoe
@@ -618,21 +725,149 @@
     assert len(eth) == len(s)
 
 
+def test_eth_2mpls_ecw_eth_llc_stp():  # Eth - MPLS - MPLS - PW ECW - 802.3 Eth(no FCS) - LLC - STP
+    from . import stp
+    s = (b'\xcc\x01\x0d\x5c\x00\x10\xcc\x00\x0d\x5c\x00\x10\x88\x47\x00\x01\x20\xfe\x00\x01\x01\xff'
+         b'\x00\x00\x00\x00\x01\x80\xc2\x00\x00\x00\xcc\x04\x0d\x5c\xf0\x00\x00\x26\x42\x42\x03\x00'
+         b'\x00\x00\x00\x00\x80\x00\xcc\x04\x0d\x5c\x00\x00\x00\x00\x00\x00\x80\x00\xcc\x04\x0d\x5c'
+         b'\x00\x00\x80\x01\x00\x00\x14\x00\x02\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00')
+
+    eth = Ethernet(s)
+    assert len(eth.mpls_labels) == 2
+    assert eth.mpls_labels[0].val == 18
+    assert eth.mpls_labels[1].val == 16
+
+    # stack
+    eth2 = eth.data
+    assert isinstance(eth2, Ethernet)
+    assert eth2.len == 38  # 802.3 Ethernet
+    # no FCS, no trailer, just 8 bytes of padding (60=38+14+8)
+    assert not hasattr(eth2, 'fcs')
+    assert eth2.padding == b'\x00' * 8
+    assert isinstance(eth2.data, llc.LLC)
+    assert isinstance(eth2.data.data, stp.STP)
+    assert eth2.data.data.port_id == 0x8001
+
+    # construction
+    # XXX - FIXME: make packing account for the ECW
+    # assert str(eth) == str(s)
 
 
+# QinQ: Eth - 802.1ad - 802.1Q - IP
+def test_eth_802dot1ad_802dot1q_ip():
+    from . import ip
+    s = (b'\x00\x10\x94\x00\x00\x0c\x00\x10\x94\x00\x00\x14\x88\xa8\x00\x1e\x81\x00\x00\x64\x08\x00'
+         b'\x45\x00\x05\xc2\x54\xb0\x00\x00\xff\xfd\xdd\xbf\xc0\x55\x01\x16\xc0\x55\x01\x0e' +
+         1434 * b'\x00' + b'\x4f\xdc\xcd\x64\x20\x8d\xb6\x4e\xa8\x45\xf8\x80\xdd\x0c\xf9\x72\xc4'
+         b'\xd0\xcf\xcb\x46\x6d\x62\x7a')
 
-if __name__ == '__main__':
-    test_eth()
-    test_eth_init_with_data()
-    test_mpls_label()
-    test_802dot1q_tag()
-    test_isl_tag()
-    test_eth_802dot1q()
-    test_eth_802dot1q_stacked()
-    test_eth_mpls_stacked()
-    test_isl_eth_llc_stp()
-    test_eth_llc_snap_cdp()
-    test_eth_llc_ipx()
-    test_eth_pppoe()
+    eth = Ethernet(s)
+    assert eth.type == ETH_TYPE_8021AD
+    assert eth.vlan_tags[0].id == 30
+    assert eth.vlan_tags[1].id == 100
+    assert isinstance(eth.data, ip.IP)
 
-    print('Tests Successful...')
+    e1 = Ethernet(s[:-1458])  # strip IP data
+
+    # construction
+    e2 = Ethernet(
+        dst=b'\x00\x10\x94\x00\x00\x0c', src=b'\x00\x10\x94\x00\x00\x14',
+        type=ETH_TYPE_8021AD,
+        vlan_tags=[
+            VLANtag8021Q(pri=0, id=30, cfi=0),
+            VLANtag8021Q(pri=0, id=100, cfi=0)
+        ],
+        data=ip.IP(
+            len=1474, id=21680, ttl=255, p=253, sum=56767,
+            src=b'\xc0U\x01\x16', dst=b'\xc0U\x01\x0e', opts=b''
+        )
+    )
+    assert str(e1) == str(e2)
+
+
+def test_eth_pack():
+    eth = Ethernet(data=b'12345')
+    assert str(eth)
+
+
+def test_eth_802dot1q_with_unfamiliar_data():
+    profinet_data = (
+        b'\xfe\xff\x05\x01\x05\x01\x00\x02\x00\x00\x00\x6c\x02'
+        b'\x05\x00\x12\x00\x00\x02\x01\x02\x02\x02\x03\x02\x04\x02\x05\x02'
+        b'\x06\x01\x01\x01\x02\x02\x01\x00\x08\x00\x00\x53\x37\x2d\x33\x30'
+        b'\x30\x02\x02\x00\x22\x00\x00\x70\x6c\x63\x78\x62\x33\x30\x30\x78'
+        b'\x6b\x63\x70\x75\x78\x61\x33\x31\x37\x2d\x32\x78\x61\x70\x6e\x78'
+        b'\x72\x64\x70\x32\x32\x63\x66\x02\x03\x00\x06\x00\x00\x00\x2a\x01'
+        b'\x01\x02\x04\x00\x04\x00\x00\x02\x00\x01\x02\x00\x0e\x00\x01\xc0'
+        b'\xa8\x3c\x87\xff\xff\xff\x00\xc0\xa8\x3c\x87')
+
+    s = (b'\x00\x0c\x29\x65\x1c\x29\x00\x0e\x8c\x8a\xa2\x5e\x81\x00\x00\x00'
+         b'\x88\x92' + profinet_data)
+
+    eth = Ethernet(s)
+    assert eth.type == ETH_TYPE_8021Q
+    assert len(eth.vlan_tags) == 1
+    assert eth.vlan_tags[0].type == ETH_TYPE_PROFINET
+    assert isinstance(eth.data, bytes)
+    assert eth.data == profinet_data
+
+
+def test_eth_802dot1q_with_arp_data():  # https://github.com/kbandla/dpkt/issues/460
+    from .arp import ARP
+    e = Ethernet(src=b'foobar', dst=b'\xff' * 6)
+    v = VLANtag8021Q(pri=0, cfi=0, id=1)
+    e.vlan_tags = [v]
+    a = ARP(sha=b'foobar', spa=b'\x0a\x0a\x0a\x0a',
+            tha=b'', tpa=b'\x0a\x0a\x0a\x05')
+    e.data = a
+    assert bytes(e) == (
+        b'\xff\xff\xff\xff\xff\xfffoobar\x81\x00\x00\x01\x08\x06'  # 0x0806 = next layer is ARP
+        b'\x00\x01\x08\x00\x06\x04\x00\x01foobar\x0a\x0a\x0a\x0a'
+        b'\x00\x00\x00\x00\x00\x00\x0a\x0a\x0a\x05')
+
+
+# 802.3 Ethernet - LLC/STP - Padding - FCS - Metamako trailer
+def test_eth_8023_llc_trailer():  # https://github.com/kbandla/dpkt/issues/438
+    d = (b'\x01\x80\xc2\x00\x00\x00\x78\x0c\xf0\xb4\xd8\x91\x00\x27\x42\x42\x03\x00\x00\x02\x02\x3c'
+         b'\x00\x01\x2c\x33\x11\xf2\x39\xc1\x00\x00\x00\x02\x80\x01\x78\x0c\xf0\xb4\xd8\xbc\x80\xaa'
+         b'\x01\x00\x14\x00\x02\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4d\xb9\x81\x20\x5c\x1e'
+         b'\x5f\xba\x3a\xa5\x47\xfa\x01\x8e\x52\x03')
+    eth = Ethernet(d)
+    assert eth.len == 39
+    assert eth.padding == b'\x00\x00\x00\x00\x00\x00\x00'
+    assert eth.fcs == 0x4db98120
+    assert eth.trailer == b'\x5c\x1e\x5f\xba\x3a\xa5\x47\xfa\x01\x8e\x52\x03'
+    assert isinstance(eth.data, llc.LLC)
+
+    # packing
+    assert bytes(eth) == d
+
+    # FCS computation
+    eth.fcs = None
+    assert bytes(eth) == d
+
+
+def test_eth_novell():
+    from binascii import unhexlify
+
+    import dpkt
+
+    buf = unhexlify(
+        '010203040506'  # dst
+        '0708090a0b0c'  # src
+        '0000'          # type (ignored)
+        'ffff'          # indicates Novell
+
+        # IPX packet
+        '0000'          # sum
+        '0001'          # len
+        '02'            # tc
+        '03'            # pt
+        '0102030405060708090a0b0c'  # dst
+        '0102030405060708090a0b0c'  # src
+    )
+
+    eth = Ethernet(buf)
+    assert isinstance(eth.data, dpkt.ipx.IPX)
+    assert eth.data.tc == 2
+    assert eth.data.data == b''
diff --git a/dpkt/gre.py b/dpkt/gre.py
index 1ec8ab8..6c08611 100644
--- a/dpkt/gre.py
+++ b/dpkt/gre.py
@@ -8,7 +8,6 @@
 
 from . import dpkt
 from . import ethernet
-from .decorators import deprecated
 from .compat import compat_izip
 
 GRE_CP = 0x8000  # Checksum Present
@@ -90,23 +89,23 @@
             self.data = self.data[fmtlen:]
             self.__dict__.update(dict(compat_izip(fields, vals)))
         if self.flags & GRE_RP:
-            l = []
+            l_ = []
             while True:
                 sre = self.SRE(self.data)
                 self.data = self.data[len(sre):]
-                l.append(sre)
+                l_.append(sre)
                 if not sre.len:
                     break
-            self.sre = l
+            self.sre = l_
         try:
             self.data = ethernet.Ethernet._typesw[self.p](self.data)
             setattr(self, self.data.__class__.__name__.lower(), self.data)
         except (KeyError, dpkt.UnpackError):
-            # data alrady set
+            # data already set
             pass
 
     def __len__(self):
-        opt_fmtlen = struct.calcsize(b''.join(self.opt_fields_fmts()[1]))
+        opt_fmtlen = struct.calcsize(''.join(self.opt_fields_fmts()[1]))
         return self.__hdr_len__ + opt_fmtlen + sum(map(len, self.sre)) + len(self.data)
 
     def __bytes__(self):
@@ -115,7 +114,7 @@
             vals = []
             for f in fields:
                 vals.append(getattr(self, f))
-            opt_s = struct.pack(b''.join(fmts), *vals)
+            opt_s = struct.pack('!' + ''.join(fmts), *vals)
         else:
             opt_s = b''
         return self.pack_hdr() + opt_s + b''.join(map(bytes, self.sre)) + bytes(self.data)
@@ -133,6 +132,7 @@
     assert g.callid == 6016
     assert g.len == 103
     assert g.data == b"A" * 103
+    assert len(g) == len(s)
 
     s = codecs.decode("3001880a00b2001100083ab8", 'hex') + b"A" * 178
     g = GRE(s)
@@ -143,7 +143,99 @@
     assert g.callid == 17
     assert g.len == 178
     assert g.data == b"A" * 178
+    assert len(g) == len(s)
 
 
-if __name__ == '__main__':
-    test_gre_v1()
+def test_gre_len():
+    from binascii import unhexlify
+
+    gre = GRE()
+    assert len(gre) == 4
+
+    buf = unhexlify("3081880a0067178000068fb100083a76") + b"\x41" * 103
+    gre = GRE(buf)
+    assert bytes(gre) == buf
+    assert len(gre) == len(buf)
+
+
+def test_gre_accessors():
+    gre = GRE()
+    for attr in ['v', 'recur']:
+        print(attr)
+        assert hasattr(gre, attr)
+        assert getattr(gre, attr) == 0
+        setattr(gre, attr, 1)
+        assert getattr(gre, attr) == 1
+
+
+def test_sre_creation():
+    from binascii import unhexlify
+    buf = unhexlify(
+        '0000'  # family
+        '00'    # off
+        '02'    # len
+
+        'ffff'
+    )
+    sre = GRE.SRE(buf)
+    assert sre.data == b'\xff\xff'
+    assert len(sre) == 6
+    assert bytes(sre) == buf
+
+
+def test_gre_nested_sre():
+    from binascii import unhexlify
+    buf = unhexlify(
+        '4000'  # flags (GRE_RP)
+        '0800'  # p (ETH_TYPE_IP)
+
+        '0001'  # sum
+        '0002'  # off
+
+        # SRE entry
+        '0003'  # family
+        '04'    # off
+        '02'    # len
+
+        'ffff'
+
+        # SRE entry (no len => last element)
+        '0006'  # family
+        '00'    # off
+        '00'    # len
+    )
+
+    gre = GRE(buf)
+    assert hasattr(gre, 'sre')
+    assert isinstance(gre.sre, list)
+    assert len(gre.sre) == 2
+    assert len(gre) == len(buf)
+    assert bytes(gre) == buf
+    assert gre.data == b''
+
+
+def test_gre_next_layer():
+    from binascii import unhexlify
+
+    from . import ipx
+
+    buf = unhexlify(
+        '0000'  # flags (NONE)
+        '8137'  # p (ETH_TYPE_IPX)
+
+        # IPX packet
+        '0000'          # sum
+        '0001'          # len
+        '02'            # tc
+        '03'            # pt
+        '0102030405060708090a0b0c'  # dst
+        'c0b0a0908070605040302010'  # src
+    )
+    gre = GRE(buf)
+    assert hasattr(gre, 'ipx')
+    assert isinstance(gre.data, ipx.IPX)
+    assert gre.data.tc == 2
+    assert gre.data.src == unhexlify('c0b0a0908070605040302010')
+    assert gre.data.dst == unhexlify('0102030405060708090a0b0c')
+    assert len(gre) == len(buf)
+    assert bytes(gre) == buf
diff --git a/dpkt/gzip.py b/dpkt/gzip.py
index aba5a38..9309b24 100644
--- a/dpkt/gzip.py
+++ b/dpkt/gzip.py
@@ -6,7 +6,6 @@
 
 import struct
 import zlib
-import binascii
 
 from . import dpkt
 
@@ -53,7 +52,7 @@
 class GzipExtra(dpkt.Packet):
     __byte_order__ = '<'
     __hdr__ = (
-        ('id', '2s', ''),
+        ('id', '2s', b''),
         ('len', 'H', 0)
     )
 
@@ -67,12 +66,14 @@
         ('mtime', 'I', 0),
         ('xflags', 'B', 0),
         ('os', 'B', GZIP_OS_UNIX),
-
-        ('extra', '0s', ''),  # XXX - GZIP_FEXTRA
-        ('filename', '0s', ''),  # XXX - GZIP_FNAME
-        ('comment', '0s', '')  # XXX - GZIP_FCOMMENT
     )
 
+    def __init__(self, *args, **kwargs):
+        self.extra = None
+        self.filename = None
+        self.comment = None
+        super(Gzip, self).__init__(*args, **kwargs)
+
     def unpack(self, buf):
         super(Gzip, self).unpack(buf)
         if self.flags & GZIP_FEXTRA:
@@ -105,28 +106,36 @@
             self.data = self.data[2:]  # XXX - skip
 
     def pack_hdr(self):
-        l = []
+        l_ = []
         if self.extra:
             self.flags |= GZIP_FEXTRA
             s = bytes(self.extra)
-            l.append(struct.pack('<H', len(s)))
-            l.append(s)
+            l_.append(struct.pack('<H', len(s)))
+            l_.append(s)
         if self.filename:
             self.flags |= GZIP_FNAME
-            l.append(self.filename)
-            l.append(b'\x00')
+            l_.append(self.filename.encode('utf-8'))
+            l_.append(b'\x00')
         if self.comment:
             self.flags |= GZIP_FCOMMENT
-            l.append(self.comment)
-            l.append(b'\x00')
-        l.insert(0, super(Gzip, self).pack_hdr())
-        return b''.join(l)
+            l_.append(self.comment)
+            l_.append(b'\x00')
+        l_.insert(0, super(Gzip, self).pack_hdr())
+        return b''.join(l_)
 
     def compress(self):
         """Compress self.data."""
-        c = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS,
-                             zlib.DEF_MEM_LEVEL, 0)
-        self.data = c.compress(self.data)
+        c = zlib.compressobj(
+            zlib.Z_BEST_COMPRESSION,
+            zlib.DEFLATED,
+            -zlib.MAX_WBITS,
+            zlib.DEF_MEM_LEVEL,
+            zlib.Z_DEFAULT_STRATEGY,
+        )
+        c.compress(self.data)
+
+        # .compress will return nothing if len(self.data) < the window size.
+        self.data = c.flush()
 
     def decompress(self):
         """Return decompressed payload."""
@@ -134,19 +143,19 @@
         return d.decompress(self.data)
 
 
-_hexdecode = binascii.a2b_hex
-
 class TestGzip(object):
-
     """This data is created with the gzip command line tool"""
 
     @classmethod
     def setup_class(cls):
-        cls.data = _hexdecode(b'1F8B' # magic
-                              b'080880C185560003' # header
-                              b'68656C6C6F2E74787400' # filename
-                              b'F348CDC9C95728CF2FCA4951E40200' # data
-                              b'41E4A9B20D000000') # checksum
+        from binascii import unhexlify
+        cls.data = unhexlify(
+            b'1F8B'  # magic
+            b'080880C185560003'  # header
+            b'68656C6C6F2E74787400'  # filename
+            b'F348CDC9C95728CF2FCA4951E40200'  # data
+            b'41E4A9B20D000000'  # checksum
+        )
         cls.p = Gzip(cls.data)
 
     def test_method(self):
@@ -166,14 +175,162 @@
         assert (self.p.os == GZIP_OS_UNIX)
 
     def test_filename(self):
-        assert (self.p.filename == "hello.txt") # always str (utf-8)
+        assert (self.p.filename == "hello.txt")  # always str (utf-8)
 
     def test_decompress(self):
-        assert (self.p.decompress() == b"Hello world!\n") # always bytes
+        assert (self.p.decompress() == b"Hello world!\n")  # always bytes
 
 
-if __name__ == '__main__':
-    import sys
+def test_flags_extra():
+    import pytest
+    from binascii import unhexlify
 
-    gz = Gzip(open(sys.argv[1]).read())
-    print(repr(gz), repr(gz.decompress()))
+    buf = unhexlify(
+        '1F8B'      # magic
+        '08'        # method
+        '04'        # flags (GZIP_FEXTRA)
+        '80C18556'  # mtime
+        '00'        # xflags
+        '03'        # os
+    )
+
+    # not enough data to extract
+    with pytest.raises(dpkt.NeedData, match='Gzip extra'):
+        Gzip(buf)
+
+    buf += unhexlify('0400')  # append the length of the fextra
+    # not enough data to extract in extra section
+    with pytest.raises(dpkt.NeedData, match='Gzip extra'):
+        Gzip(buf)
+
+    buf += unhexlify('494401000102')
+
+    gzip = Gzip(buf)
+    assert gzip.extra.id == b'ID'
+    assert gzip.extra.len == 1
+    assert gzip.data == unhexlify('0102')
+    assert bytes(gzip) == buf
+
+
+def test_flags_filename():
+    import pytest
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '1F8B'      # magic
+        '08'        # method
+        '08'        # flags (GZIP_FNAME)
+        '80C18556'  # mtime
+        '00'        # xflags
+        '03'        # os
+
+        '68656C6C6F2E747874'  # filename
+    )
+    # no trailing null character so unpacking fails
+    with pytest.raises(dpkt.NeedData, match='Gzip end of file name not found'):
+        Gzip(buf)
+
+    buf += unhexlify('00')
+    gzip = Gzip(buf)
+    assert gzip.filename == 'hello.txt'
+    assert gzip.data == b''
+    assert bytes(gzip) == buf
+
+
+def test_flags_comment():
+    import pytest
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '1F8B'      # magic
+        '08'        # method
+        '10'        # flags (GZIP_FCOMMENT)
+        '80C18556'  # mtime
+        '00'        # xflags
+        '03'        # os
+
+        '68656C6C6F2E747874'  # comment
+    )
+    # no trailing null character so unpacking fails
+    with pytest.raises(dpkt.NeedData, match='Gzip end of comment not found'):
+        Gzip(buf)
+
+    buf += unhexlify('00')
+
+    gzip = Gzip(buf)
+    assert gzip.comment == b'hello.txt'
+    assert gzip.data == b''
+    assert bytes(gzip) == buf
+
+
+def test_flags_encrypt():
+    import pytest
+    from binascii import unhexlify
+
+    buf_header = unhexlify(
+        '1F8B'      # magic
+        '08'        # method
+        '20'        # flags (GZIP_FENCRYPT)
+        '80C18556'  # mtime
+        '00'        # xflags
+        '03'        # os
+    )
+    # not enough data
+    with pytest.raises(dpkt.NeedData, match='Gzip encrypt'):
+        Gzip(buf_header)
+
+    encrypted_buffer = unhexlify('0102030405060708090a0b0c')
+    data = unhexlify('0123456789abcdef')
+
+    gzip = Gzip(buf_header + encrypted_buffer + data)
+    assert gzip.data == data
+    assert bytes(gzip) == buf_header + data
+
+
+def test_flags_hcrc():
+    import pytest
+    from binascii import unhexlify
+
+    buf_header = unhexlify(
+        '1F8B'      # magic
+        '08'        # method
+        '02'        # flags (GZIP_FHCRC)
+        '80C18556'  # mtime
+        '00'        # xflags
+        '03'        # os
+    )
+    # not enough data
+    with pytest.raises(dpkt.NeedData, match='Gzip hcrc'):
+        Gzip(buf_header)
+
+    hcrc = unhexlify('0102')
+    data = unhexlify('0123456789abcdef')
+    gzip = Gzip(buf_header + hcrc + data)
+
+    assert gzip.data == data
+    assert bytes(gzip) == buf_header + data
+
+
+def test_compress():
+    from binascii import unhexlify
+
+    buf_header = unhexlify(
+        '1F8B'      # magic
+        '08'        # method
+        '00'        # flags (NONE)
+        '80C18556'  # mtime
+        '00'        # xflags
+        '03'        # os
+    )
+
+    plain_text = b'Hello world!\n'
+    compressed_text = unhexlify('F348CDC9C95728CF2FCA4951E40200')
+
+    gzip = Gzip(buf_header + plain_text)
+    assert gzip.data == plain_text
+
+    gzip.compress()
+    assert gzip.data == compressed_text
+    assert bytes(gzip) == buf_header + compressed_text
+
+    assert gzip.decompress() == plain_text
diff --git a/dpkt/h225.py b/dpkt/h225.py
index d7a1d04..c62558e 100644
--- a/dpkt/h225.py
+++ b/dpkt/h225.py
@@ -131,19 +131,19 @@
         buf = buf[1:]
 
         # Information Elements
-        l = []
+        l_ = []
         while buf:
             ie = self.IE(buf)
-            l.append(ie)
+            l_.append(ie)
             buf = buf[len(ie):]
-        self.data = l
+        self.data = l_
 
     def __len__(self):
         return self.tpkt.__hdr_len__ + self.__hdr_len__ + sum(map(len, self.data))
 
     def __bytes__(self):
         return self.tpkt.pack_hdr() + self.pack_hdr() + self.ref_val + \
-               struct.pack('B', self.type) + b''.join(map(bytes, self.data))
+            struct.pack('B', self.type) + b''.join(map(bytes, self.data))
 
     class IE(dpkt.Packet):
         __hdr__ = (
@@ -157,7 +157,7 @@
             # single-byte IE
             if self.type & 0x80:
                 self.len = 0
-                self.data = None
+                self.data = b''
             # multi-byte IE
             else:
                 # special PER-encoded UUIE
@@ -182,7 +182,7 @@
 
         def __bytes__(self):
             if self.type & 0x80:
-                length_str = None
+                length_str = b''
             else:
                 if self.type == USER_TO_USER:
                     length_str = struct.pack('>H', self.len)
@@ -191,12 +191,50 @@
             return struct.pack('B', self.type) + length_str + self.data
 
 
-__s = b'\x03\x00\x04\x11\x08\x02\x54\x2b\x05\x04\x03\x88\x93\xa5\x28\x0e\x4a\x6f\x6e\x20\x4f\x62\x65\x72\x68\x65\x69\x64\x65\x00\x7e\x03\xf0\x05\x20\xb8\x06\x00\x08\x91\x4a\x00\x04\x01\x40\x0c\x00\x4a\x00\x6f\x00\x6e\x00\x20\x00\x4f\x00\x62\x00\x65\x00\x72\x00\x68\x00\x65\x00\x69\x00\x64\x00\x65\x22\xc0\x09\x00\x00\x3d\x06\x65\x6b\x69\x67\x61\x00\x00\x14\x32\x2e\x30\x2e\x32\x20\x28\x4f\x50\x41\x4c\x20\x76\x32\x2e\x32\x2e\x32\x29\x00\x00\x00\x01\x40\x15\x00\x74\x00\x63\x00\x70\x00\x24\x00\x68\x00\x33\x00\x32\x00\x33\x00\x2e\x00\x76\x00\x6f\x00\x78\x00\x67\x00\x72\x00\x61\x00\x74\x00\x69\x00\x61\x00\x2e\x00\x6f\x00\x72\x00\x67\x00\x42\x87\x23\x2c\x06\xb8\x00\x6a\x8b\x1d\x0c\xb7\x06\xdb\x11\x9e\xca\x00\x10\xa4\x89\x6d\x6a\x00\xc5\x1d\x80\x04\x07\x00\x0a\x00\x01\x7a\x75\x30\x11\x00\x5e\x88\x1d\x0c\xb7\x06\xdb\x11\x9e\xca\x00\x10\xa4\x89\x6d\x6a\x82\x2b\x0e\x30\x40\x00\x00\x06\x04\x01\x00\x4c\x10\x09\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65\x36\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x26\x00\x00\x64\x0c\x10\x09\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65\x36\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x2a\x40\x00\x00\x06\x04\x01\x00\x4c\x10\x09\x00\x00\x3d\x09\x69\x4c\x42\x43\x2d\x31\x33\x6b\x33\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x20\x00\x00\x65\x0c\x10\x09\x00\x00\x3d\x09\x69\x4c\x42\x43\x2d\x31\x33\x6b\x33\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x20\x40\x00\x00\x06\x04\x01\x00\x4e\x0c\x03\x00\x83\x00\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x16\x00\x00\x66\x0e\x0c\x03\x00\x83\x00\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x4b\x40\x00\x00\x06\x04\x01\x00\x4c\x10\xb5\x00\x53\x4c\x2a\x02\x00\x00\x00\x00\x00\x40\x01\x00\x00\x40\x01\x02\x00\x08\x00\x00\x00\x00\x00\x31\x00\x01\x00\x40\x1f\x00\x00\x59\x06\x00\x00\x41\x00\x00\x00\x02\x00\x40\x01\x00\x00\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x41\x00\x00\x67\x0c\x10\xb5\x00\x53\x4c\x2a\x02\x00\x00\x00\x00\x00\x40\x01\x00\x00\x40\x01\x02\x00\x08\x00\x00\x00\x00\x00\x31\x00\x01\x00\x40\x1f\x00\x00\x59\x06\x00\x00\x41\x00\x00\x00\x02\x00\x40\x01\x00\x00\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x32\x40\x00\x00\x06\x04\x01\x00\x4c\x10\x09\x00\x00\x3d\x11\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x4e\x61\x72\x72\x6f\x77\x33\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x28\x00\x00\x68\x0c\x10\x09\x00\x00\x3d\x11\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x4e\x61\x72\x72\x6f\x77\x33\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x1d\x40\x00\x00\x06\x04\x01\x00\x4c\x60\x1d\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x13\x00\x00\x69\x0c\x60\x1d\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x1d\x40\x00\x00\x06\x04\x01\x00\x4c\x20\x1d\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x13\x00\x00\x6a\x0c\x20\x1d\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x01\x00\x01\x00\x01\x00\x01\x00\x81\x03\x02\x80\xf8\x02\x70\x01\x06\x00\x08\x81\x75\x00\x0b\x80\x13\x80\x01\xf4\x00\x01\x00\x00\x01\x00\x00\x01\x00\x00\x0c\xc0\x01\x00\x01\x80\x0b\x80\x00\x00\x20\x20\x09\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65\x36\x80\x00\x01\x20\x20\x09\x00\x00\x3d\x09\x69\x4c\x42\x43\x2d\x31\x33\x6b\x33\x80\x00\x02\x24\x18\x03\x00\xe6\x00\x80\x00\x03\x20\x20\xb5\x00\x53\x4c\x2a\x02\x00\x00\x00\x00\x00\x40\x01\x00\x00\x40\x01\x02\x00\x08\x00\x00\x00\x00\x00\x31\x00\x01\x00\x40\x1f\x00\x00\x59\x06\x00\x00\x41\x00\x00\x00\x02\x00\x40\x01\x00\x00\x80\x00\x04\x20\x20\x09\x00\x00\x3d\x11\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x4e\x61\x72\x72\x6f\x77\x33\x80\x00\x05\x20\xc0\xef\x80\x00\x06\x20\x40\xef\x80\x00\x07\x08\xe0\x03\x51\x00\x80\x01\x00\x80\x00\x08\x08\xd0\x03\x51\x00\x80\x01\x00\x80\x00\x09\x83\x01\x50\x80\x00\x0a\x83\x01\x10\x80\x00\x0b\x83\x01\x40\x00\x80\x01\x03\x06\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x01\x00\x07\x00\x08\x00\x00\x09\x01\x00\x0a\x00\x0b\x07\x01\x00\x32\x80\xa6\xff\x4c\x02\x80\x01\x80'
+__s = (
+    b'\x03\x00\x04\x11\x08\x02\x54\x2b\x05\x04\x03\x88\x93\xa5\x28\x0e\x4a\x6f\x6e\x20\x4f\x62\x65\x72\x68\x65\x69\x64\x65'
+    b'\x00\x7e\x03\xf0\x05\x20\xb8\x06\x00\x08\x91\x4a\x00\x04\x01\x40\x0c\x00\x4a\x00\x6f\x00\x6e\x00\x20\x00\x4f\x00\x62'
+    b'\x00\x65\x00\x72\x00\x68\x00\x65\x00\x69\x00\x64\x00\x65\x22\xc0\x09\x00\x00\x3d\x06\x65\x6b\x69\x67\x61\x00\x00\x14'
+    b'\x32\x2e\x30\x2e\x32\x20\x28\x4f\x50\x41\x4c\x20\x76\x32\x2e\x32\x2e\x32\x29\x00\x00\x00\x01\x40\x15\x00\x74\x00\x63'
+    b'\x00\x70\x00\x24\x00\x68\x00\x33\x00\x32\x00\x33\x00\x2e\x00\x76\x00\x6f\x00\x78\x00\x67\x00\x72\x00\x61\x00\x74\x00'
+    b'\x69\x00\x61\x00\x2e\x00\x6f\x00\x72\x00\x67\x00\x42\x87\x23\x2c\x06\xb8\x00\x6a\x8b\x1d\x0c\xb7\x06\xdb\x11\x9e\xca'
+    b'\x00\x10\xa4\x89\x6d\x6a\x00\xc5\x1d\x80\x04\x07\x00\x0a\x00\x01\x7a\x75\x30\x11\x00\x5e\x88\x1d\x0c\xb7\x06\xdb\x11'
+    b'\x9e\xca\x00\x10\xa4\x89\x6d\x6a\x82\x2b\x0e\x30\x40\x00\x00\x06\x04\x01\x00\x4c\x10\x09\x00\x00\x3d\x0f\x53\x70\x65'
+    b'\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65\x36\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41'
+    b'\x13\x8b\x26\x00\x00\x64\x0c\x10\x09\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65\x36\x80'
+    b'\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x2a\x40\x00\x00\x06\x04\x01\x00\x4c\x10\x09\x00\x00\x3d\x09\x69\x4c'
+    b'\x42\x43\x2d\x31\x33\x6b\x33\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x20\x00\x00'
+    b'\x65\x0c\x10\x09\x00\x00\x3d\x09\x69\x4c\x42\x43\x2d\x31\x33\x6b\x33\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b'
+    b'\x00\x20\x40\x00\x00\x06\x04\x01\x00\x4e\x0c\x03\x00\x83\x00\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98'
+    b'\xa0\x26\x41\x13\x8b\x16\x00\x00\x66\x0e\x0c\x03\x00\x83\x00\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x4b'
+    b'\x40\x00\x00\x06\x04\x01\x00\x4c\x10\xb5\x00\x53\x4c\x2a\x02\x00\x00\x00\x00\x00\x40\x01\x00\x00\x40\x01\x02\x00\x08'
+    b'\x00\x00\x00\x00\x00\x31\x00\x01\x00\x40\x1f\x00\x00\x59\x06\x00\x00\x41\x00\x00\x00\x02\x00\x40\x01\x00\x00\x80\x11'
+    b'\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x41\x00\x00\x67\x0c\x10\xb5\x00\x53\x4c\x2a\x02'
+    b'\x00\x00\x00\x00\x00\x40\x01\x00\x00\x40\x01\x02\x00\x08\x00\x00\x00\x00\x00\x31\x00\x01\x00\x40\x1f\x00\x00\x59\x06'
+    b'\x00\x00\x41\x00\x00\x00\x02\x00\x40\x01\x00\x00\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x32\x40\x00\x00'
+    b'\x06\x04\x01\x00\x4c\x10\x09\x00\x00\x3d\x11\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x4e\x61\x72\x72\x6f\x77\x33\x80'
+    b'\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x28\x00\x00\x68\x0c\x10\x09\x00\x00\x3d\x11'
+    b'\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x4e\x61\x72\x72\x6f\x77\x33\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b'
+    b'\x00\x1d\x40\x00\x00\x06\x04\x01\x00\x4c\x60\x1d\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41'
+    b'\x13\x8b\x13\x00\x00\x69\x0c\x60\x1d\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x1d\x40\x00\x00\x06\x04\x01'
+    b'\x00\x4c\x20\x1d\x80\x11\x1c\x00\x01\x00\x98\xa0\x26\x41\x13\x8a\x00\x98\xa0\x26\x41\x13\x8b\x13\x00\x00\x6a\x0c\x20'
+    b'\x1d\x80\x0b\x0d\x00\x01\x00\x98\xa0\x26\x41\x13\x8b\x00\x01\x00\x01\x00\x01\x00\x01\x00\x81\x03\x02\x80\xf8\x02\x70'
+    b'\x01\x06\x00\x08\x81\x75\x00\x0b\x80\x13\x80\x01\xf4\x00\x01\x00\x00\x01\x00\x00\x01\x00\x00\x0c\xc0\x01\x00\x01\x80'
+    b'\x0b\x80\x00\x00\x20\x20\x09\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65\x36\x80\x00\x01'
+    b'\x20\x20\x09\x00\x00\x3d\x09\x69\x4c\x42\x43\x2d\x31\x33\x6b\x33\x80\x00\x02\x24\x18\x03\x00\xe6\x00\x80\x00\x03\x20'
+    b'\x20\xb5\x00\x53\x4c\x2a\x02\x00\x00\x00\x00\x00\x40\x01\x00\x00\x40\x01\x02\x00\x08\x00\x00\x00\x00\x00\x31\x00\x01'
+    b'\x00\x40\x1f\x00\x00\x59\x06\x00\x00\x41\x00\x00\x00\x02\x00\x40\x01\x00\x00\x80\x00\x04\x20\x20\x09\x00\x00\x3d\x11'
+    b'\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x4e\x61\x72\x72\x6f\x77\x33\x80\x00\x05\x20\xc0\xef\x80\x00\x06\x20\x40\xef'
+    b'\x80\x00\x07\x08\xe0\x03\x51\x00\x80\x01\x00\x80\x00\x08\x08\xd0\x03\x51\x00\x80\x01\x00\x80\x00\x09\x83\x01\x50\x80'
+    b'\x00\x0a\x83\x01\x10\x80\x00\x0b\x83\x01\x40\x00\x80\x01\x03\x06\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00'
+    b'\x06\x01\x00\x07\x00\x08\x00\x00\x09\x01\x00\x0a\x00\x0b\x07\x01\x00\x32\x80\xa6\xff\x4c\x02\x80\x01\x80'
+)
 
 
 def test_pack():
     h = H225(__s)
     assert (__s == bytes(h))
+    assert len(h) == 1038  # len(__s) == 1041
 
 
 def test_unpack():
@@ -219,7 +257,41 @@
     assert (ie.len == 1008)
 
 
-if __name__ == '__main__':
-    test_pack()
-    test_unpack()
-    print('Tests Successful...')
+def test_tpkt_unpack_errors():
+    import pytest
+    from binascii import unhexlify
+
+    # invalid version
+    buf_tpkt_version0 = unhexlify(
+        '00'    # v
+        '00'    # rsvd
+        '0000'  # len
+    )
+    with pytest.raises(dpkt.UnpackError, match="invalid TPKT version"):
+        H225(buf_tpkt_version0)
+
+    # invalid reserved value
+    buf_tpkt_rsvd = unhexlify(
+        '03'    # v
+        'ff'    # rsvd
+        '0000'  # len
+    )
+    with pytest.raises(dpkt.UnpackError, match="invalid TPKT reserved value"):
+        H225(buf_tpkt_rsvd)
+
+    # invalid len
+    buf_tpkt_len = unhexlify(
+        '03'    # v
+        '00'    # rsvd
+        'ffff'  # len
+    )
+    with pytest.raises(dpkt.UnpackError, match="invalid TPKT length"):
+        H225(buf_tpkt_len)
+
+
+def test_unpack_ie():
+    ie = H225.IE(b'\x80')
+    assert ie.len == 0
+    assert ie.data == b''
+    assert len(ie) == 1
+    assert bytes(ie) == b'\x80'
diff --git a/dpkt/hsrp.py b/dpkt/hsrp.py
index 5daa1d8..d477a0e 100644
--- a/dpkt/hsrp.py
+++ b/dpkt/hsrp.py
@@ -28,7 +28,7 @@
         __hdr__: Header fields of HSRP.
         TODO.
     """
-    
+
     __hdr__ = (
         ('version', 'B', 0),
         ('opcode', 'B', 0),
@@ -38,6 +38,6 @@
         ('priority', 'B', 0),
         ('group', 'B', 0),
         ('rsvd', 'B', 0),
-        ('auth', '8s', 'cisco'),
-        ('vip', '4s', '')
+        ('auth', '8s', b'cisco'),
+        ('vip', '4s', b'')
     )
diff --git a/dpkt/http.py b/dpkt/http.py
index 9df1f9b..d04c115 100644
--- a/dpkt/http.py
+++ b/dpkt/http.py
@@ -3,12 +3,7 @@
 """Hypertext Transfer Protocol."""
 from __future__ import print_function
 from __future__ import absolute_import
-try:
-    from collections import OrderedDict
-except ImportError:
-    # Python 2.6
-    OrderedDict = dict
-
+from collections import OrderedDict
 from . import dpkt
 from .compat import BytesIO, iteritems
 
@@ -27,11 +22,12 @@
         line = f.readline().strip().decode("ascii", "ignore")
         if not line:
             break
-        l = line.split(':', 1)
-        if len(l[0].split()) != 1:
+        l_ = line.split(':', 1)
+        if len(l_[0].split()) != 1:
             raise dpkt.UnpackError('invalid header: %r' % line)
-        k = l[0].lower()
-        v = len(l) != 1 and l[1].lstrip() or ''
+
+        k = l_[0].lower()
+        v = len(l_) != 1 and l_[1].lstrip() or ''
         if k in d:
             if not type(d[k]) is list:
                 d[k] = [d[k]]
@@ -44,26 +40,33 @@
 def parse_body(f, headers):
     """Return HTTP body parsed from a file object, given HTTP header dict."""
     if headers.get('transfer-encoding', '').lower() == 'chunked':
-        l = []
+        l_ = []
         found_end = False
         while 1:
             try:
                 sz = f.readline().split(None, 1)[0]
             except IndexError:
                 raise dpkt.UnpackError('missing chunk size')
-            n = int(sz, 16)
+            try:
+                n = int(sz, 16)
+            except ValueError:
+                raise dpkt.UnpackError('invalid chunk size')
+
             if n == 0:
                 found_end = True
             buf = f.read(n)
             if f.readline().strip():
                 break
+
             if n and len(buf) == n:
-                l.append(buf)
+                l_.append(buf)
             else:
+                # only possible when len(buf) < n, which will happen if the
+                # file object ends before reading a complete file chunk
                 break
         if not found_end:
             raise dpkt.NeedData('premature end of chunked body')
-        body = b''.join(l)
+        body = b''.join(l_)
     elif 'content-length' in headers:
         n = int(headers['content-length'])
         body = f.read(n)
@@ -112,6 +115,8 @@
         # Parse body
         if is_body_allowed:
             self.body = parse_body(f, self.headers)
+        else:
+            self.body = b''
         # Save the rest
         self.data = f.read()
 
@@ -125,13 +130,7 @@
         return '%s\r\n%s' % (self.pack_hdr(), self.body.decode("utf8", "ignore"))
 
     def __bytes__(self):
-        # Not using byte interpolation to preserve Python 3.4 compatibility. The extra
-        # \r\n doesn't get trimmed from the bytes, so it's necessary to omit the spacing
-        # one when building the output if there's no body
-        if self.body:
-            return self.pack_hdr().encode("ascii", "ignore") + b'\r\n' + self.body
-        else:
-            return self.pack_hdr().encode("ascii", "ignore")
+        return self.pack_hdr().encode("ascii", "ignore") + b'\r\n' + (self.body or b'')
 
 
 class Request(Message):
@@ -167,20 +166,20 @@
     def unpack(self, buf):
         f = BytesIO(buf)
         line = f.readline().decode("ascii", "ignore")
-        l = line.strip().split()
-        if len(l) < 2:
+        l_ = line.strip().split()
+        if len(l_) < 2:
             raise dpkt.UnpackError('invalid request: %r' % line)
-        if l[0] not in self.__methods:
-            raise dpkt.UnpackError('invalid http method: %r' % l[0])
-        if len(l) == 2:
+        if l_[0] not in self.__methods:
+            raise dpkt.UnpackError('invalid http method: %r' % l_[0])
+        if len(l_) == 2:
             # HTTP/0.9 does not specify a version in the request line
             self.version = '0.9'
         else:
-            if not l[2].startswith(self.__proto):
-                raise dpkt.UnpackError('invalid http version: %r' % l[2])
-            self.version = l[2][len(self.__proto) + 1:]
-        self.method = l[0]
-        self.uri = l[1]
+            if not l_[2].startswith(self.__proto):
+                raise dpkt.UnpackError('invalid http version: %r' % l_[2])
+            self.version = l_[2][len(self.__proto) + 1:]
+        self.method = l_[0]
+        self.uri = l_[1]
         Message.unpack(self, f.read())
 
     def __str__(self):
@@ -213,12 +212,12 @@
     def unpack(self, buf):
         f = BytesIO(buf)
         line = f.readline()
-        l = line.strip().decode("ascii", "ignore").split(None, 2)
-        if len(l) < 2 or not l[0].startswith(self.__proto) or not l[1].isdigit():
+        l_ = line.strip().decode("ascii", "ignore").split(None, 2)
+        if len(l_) < 2 or not l_[0].startswith(self.__proto) or not l_[1].isdigit():
             raise dpkt.UnpackError('invalid response: %r' % line)
-        self.version = l[0][len(self.__proto) + 1:]
-        self.status = l[1]
-        self.reason = l[2] if len(l) > 2 else ''
+        self.version = l_[0][len(self.__proto) + 1:]
+        self.status = l_[1]
+        self.reason = l_[2] if len(l_) > 2 else ''
         # RFC Sec 4.3.
         # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3.
         # For response messages, whether or not a message-body is included with
@@ -238,22 +237,24 @@
 
     def __bytes__(self):
         str_out = '%s/%s %s %s\r\n' % (self.__proto, self.version, self.status,
-                                       self.reason) + Message.__str__(self)
+                                       self.reason)
         return str_out.encode("ascii", "ignore") + Message.__bytes__(self)
 
 
 def test_parse_request():
-    s = b"""POST /main/redirect/ab/1,295,,00.html HTTP/1.0\r\nReferer: http://www.email.com/login/snap/login.jhtml\r\nConnection: Keep-Alive\r\nUser-Agent: Mozilla/4.75 [en] (X11; U; OpenBSD 2.8 i386; Nav)\r\nHost: ltd.snap.com\r\nAccept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\nAccept-Encoding: gzip\r\nAccept-Language: en\r\nAccept-Charset: iso-8859-1,*,utf-8\r\nContent-type: application/x-www-form-urlencoded\r\nContent-length: 61\r\n\r\nsn=em&mn=dtest4&pw=this+is+atest&fr=true&login=Sign+in&od=www"""
+    s = (b"""POST /main/redirect/ab/1,295,,00.html HTTP/1.0\r\nReferer: http://www.email.com/login/snap/login.jhtml\r\n"""
+         b"""Connection: Keep-Alive\r\nUser-Agent: Mozilla/4.75 [en] (X11; U; OpenBSD 2.8 i386; Nav)\r\n"""
+         b"""Host: ltd.snap.com\r\nAccept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\n"""
+         b"""Accept-Encoding: gzip\r\nAccept-Language: en\r\nAccept-Charset: iso-8859-1,*,utf-8\r\n"""
+         b"""Content-type: application/x-www-form-urlencoded\r\nContent-length: 61\r\n\r\n"""
+         b"""sn=em&mn=dtest4&pw=this+is+atest&fr=true&login=Sign+in&od=www""")
     r = Request(s)
     assert r.method == 'POST'
     assert r.uri == '/main/redirect/ab/1,295,,00.html'
     assert r.body == b'sn=em&mn=dtest4&pw=this+is+atest&fr=true&login=Sign+in&od=www'
     assert r.headers['content-type'] == 'application/x-www-form-urlencoded'
-    try:
-        Request(s[:60])
-        assert 'invalid headers parsed!'
-    except dpkt.UnpackError:
-        pass
+
+    Request(s[:60])
 
 
 def test_format_request():
@@ -279,15 +280,42 @@
 
 
 def test_chunked_response():
-    s = b"""HTTP/1.1 200 OK\r\nCache-control: no-cache\r\nPragma: no-cache\r\nContent-Type: text/javascript; charset=utf-8\r\nContent-Encoding: gzip\r\nTransfer-Encoding: chunked\r\nSet-Cookie: S=gmail=agg:gmail_yj=v2s:gmproxy=JkU; Domain=.google.com; Path=/\r\nServer: GFE/1.3\r\nDate: Mon, 12 Dec 2005 22:33:23 GMT\r\n\r\na\r\n\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x00\r\n152\r\nm\x91MO\xc4 \x10\x86\xef\xfe\n\x82\xc9\x9eXJK\xe9\xb6\xee\xc1\xe8\x1e6\x9e4\xf1\xe0a5\x86R\xda\x12Yh\x80\xba\xfa\xef\x85\xee\x1a/\xf21\x99\x0c\xef0<\xc3\x81\xa0\xc3\x01\xe6\x10\xc1<\xa7eYT5\xa1\xa4\xac\xe1\xdb\x15:\xa4\x9d\x0c\xfa5K\x00\xf6.\xaa\xeb\x86\xd5y\xcdHY\x954\x8e\xbc*h\x8c\x8e!L7Y\xe6\'\xeb\x82WZ\xcf>8\x1ed\x87\x851X\xd8c\xe6\xbc\x17Z\x89\x8f\xac \x84e\xde\n!]\x96\x17i\xb5\x02{{\xc2z0\x1e\x0f#7\x9cw3v\x992\x9d\xfc\xc2c8\xea[/EP\xd6\xbc\xce\x84\xd0\xce\xab\xf7`\'\x1f\xacS\xd2\xc7\xd2\xfb\x94\x02N\xdc\x04\x0f\xee\xba\x19X\x03TtW\xd7\xb4\xd9\x92\n\xbcX\xa7;\xb0\x9b\'\x10$?F\xfd\xf3CzPt\x8aU\xef\xb8\xc8\x8b-\x18\xed\xec<\xe0\x83\x85\x08!\xf8"[\xb0\xd3j\x82h\x93\xb8\xcf\xd8\x9b\xba\xda\xd0\x92\x14\xa4a\rc\reM\xfd\x87=X;h\xd9j;\xe0db\x17\xc2\x02\xbd\xb0F\xc2in#\xfb:\xb6\xc4x\x15\xd6\x9f\x8a\xaf\xcf)\x0b^\xbc\xe7i\x11\x80\x8b\x00D\x01\xd8/\x82x\xf6\xd8\xf7J(\xae/\x11p\x1f+\xc4p\t:\xfe\xfd\xdf\xa3Y\xfa\xae4\x7f\x00\xc5\xa5\x95\xa1\xe2\x01\x00\x00\r\n0\r\n\r\n"""
-    r = Response(s)
+    from binascii import unhexlify
+    header = (
+        b"HTTP/1.1 200 OK\r\n"
+        b"Cache-control: no-cache\r\n"
+        b"Pragma: no-cache\r\n"
+        b"Content-Type: text/javascript; charset=utf-8\r\n"
+        b"Content-Encoding: gzip\r\n"
+        b"Transfer-Encoding: chunked\r\n"
+        b"Set-Cookie: S=gmail=agg:gmail_yj=v2s:gmproxy=JkU; Domain=.google.com; Path=/\r\n"
+        b"Server: GFE/1.3\r\n"
+        b"Date: Mon, 12 Dec 2005 22:33:23 GMT\r\n"
+        b"\r\n"
+    )
+    body = unhexlify(
+        '610d0a1f8b08000000000000000d0a3135320d0a6d914d4fc4201086effe0a82c99e58'
+        '4a4be9b6eec1e81e369e34f1e061358652da12596880bafaef85ee1a2ff231990cef30'
+        '3cc381a0c301e610c13ca765595435a1a4ace1db153aa49d0cfa354b00f62eaaeb86d5'
+        '79cd485995348ebc2a688c8e214c3759e627eb82575acf3e381e6487853158d863e6bc'
+        '175a898fac208465de0a215d961769b5027b7bc27a301e0f23379c77337699329dfcc2'
+        '6338ea5b2f4550d6bcce84d0ceabf760271fac53d2c7d2fb94024edc040feeba195803'
+        '547457d7b4d9920abc58a73bb09b2710243f46fdf3437a50748a55efb8c88b2d18edec'
+        '3ce083850821f8225bb0d36a826893b8cfd89bbadad09214a4610d630d654dfd873d58'
+        '3b68d96a3be0646217c202bdb046c2696e23fb3ab6c47815d69f8aafcf290b5ebce769'
+        '11808b004401d82f8278f6d8f74a28ae2f11701f2bc470093afefddfa359faae347f00'
+        'c5a595a1e20100000d0a300d0a0d0a'
+    )
+    buf = header + body
+    r = Response(buf)
     assert r.version == '1.1'
     assert r.status == '200'
     assert r.reason == 'OK'
 
 
 def test_multicookie_response():
-    s = b"""HTTP/1.x 200 OK\r\nSet-Cookie: first_cookie=cookie1; path=/; domain=.example.com\r\nSet-Cookie: second_cookie=cookie2; path=/; domain=.example.com\r\nContent-Length: 0\r\n\r\n"""
+    s = (b"""HTTP/1.x 200 OK\r\nSet-Cookie: first_cookie=cookie1; path=/; domain=.example.com\r\n"""
+         b"""Set-Cookie: second_cookie=cookie2; path=/; domain=.example.com\r\nContent-Length: 0\r\n\r\n""")
     r = Response(s)
     assert type(r.headers['set-cookie']) is list
     assert len(r.headers['set-cookie']) == 2
@@ -300,6 +328,14 @@
     assert bytes(r) == s
 
 
+def test_response_with_body():
+    r = Response()
+    r.body = b'foo'
+    assert str(r) == 'HTTP/1.0 200 OK\r\n\r\nfoo'
+    assert bytes(r) == b'HTTP/1.0 200 OK\r\n\r\nfoo'
+    repr(r)
+
+
 def test_body_forbidden_response():
     s = b'HTTP/1.1 304 Not Modified\r\n'\
         b'Content-Type: text/css\r\n'\
@@ -344,15 +380,13 @@
     assert r.uri == '/'
     assert r.version == '0.9'
 
+    import pytest
     s = b"""GET / CHEESE/1.0\r\n\r\n"""
-    try:
+    with pytest.raises(dpkt.UnpackError, match="invalid http version: u?'CHEESE/1.0'"):
         Request(s)
-        assert "invalid protocol version parsed!"
-    except:
-        pass
 
 
-def test_invalid_header():
+def test_valid_header():
     # valid header.
     s = b'POST /main/redirect/ab/1,295,,00.html HTTP/1.0\r\n' \
         b'Referer: http://www.email.com/login/snap/login.jhtml\r\n' \
@@ -372,7 +406,8 @@
     assert r.body == b'sn=em&mn=dtest4&pw=this+is+atest&fr=true&login=Sign+in&od=www'
     assert r.headers['content-type'] == 'application/x-www-form-urlencoded'
 
-    # invalid header.
+
+def test_weird_end_header():
     s_weird_end = b'POST /main/redirect/ab/1,295,,00.html HTTP/1.0\r\n' \
         b'Referer: http://www.email.com/login/snap/login.jhtml\r\n' \
         b'Connection: Keep-Alive\r\n' \
@@ -389,19 +424,6 @@
     assert r.uri == '/main/redirect/ab/1,295,,00.html'
     assert r.headers['content-type'] == 'application/x-www-form-urlencoded'
 
-    # messy header.
-    s_messy_header = b'aaaaaaaaa\r\nbbbbbbbbb'
-    try:
-        r = Request(s_messy_header)
-    except dpkt.UnpackError:
-        assert True
-    # If the http request is built successfully or raised exceptions
-    # other than UnpackError, then return a false assertion.
-    except:
-        assert False
-    else:
-        assert False
-
 
 def test_gzip_response():
     import zlib
@@ -427,14 +449,126 @@
     assert body.startswith(b'This is a very small file')
 
 
-if __name__ == '__main__':
-    # Runs all the test associated with this class/file
-    test_parse_request()
-    test_format_request()
-    test_chunked_response()
-    test_multicookie_response()
-    test_noreason_response()
-    test_request_version()
-    test_invalid_header()
-    test_body_forbidden_response()
-    print('Tests Successful...')
+def test_message():
+    # s = b'Date: Fri, 10 Mar 2017 20:43:08 GMT\r\n'  # FIXME - unused
+    r = Message(content_length=68)
+    assert r.content_length == 68
+    assert len(r) == 2
+
+
+def test_invalid():
+    import pytest
+
+    s = b'INVALID / HTTP/1.0\r\n'
+    with pytest.raises(dpkt.UnpackError, match="invalid http method: u?'INVALID'"):
+        Request(s)
+
+    s = b'A'
+    with pytest.raises(dpkt.UnpackError, match="invalid response: b?'A'"):
+        Response(s)
+
+    s = b'HTTT 200 OK'
+    with pytest.raises(dpkt.UnpackError, match="invalid response: b?'HTTT 200 OK'"):
+        Response(s)
+
+    s = b'HTTP TWO OK'
+    with pytest.raises(dpkt.UnpackError, match="invalid response: b?'HTTP TWO OK'"):
+        Response(s)
+
+    s = (
+        b'HTTP/1.0 200 OK\r\n'
+        b'Invalid Header: invalid\r\n'
+    )
+    with pytest.raises(dpkt.UnpackError, match="invalid header: "):
+        Response(s)
+
+    s = (
+        b"HTTP/1.1 200 OK\r\n"
+        b"Transfer-Encoding: chunked\r\n"
+        b"\r\n"
+        b"\r\n"
+    )
+    with pytest.raises(dpkt.UnpackError, match="missing chunk size"):
+        Response(s)
+
+    s = (
+        b"HTTP/1.1 200 OK\r\n"
+        b"Transfer-Encoding: chunked\r\n"
+        b"\r\n"
+        b"\x01\r\na"
+    )
+    with pytest.raises(dpkt.UnpackError, match="invalid chunk size"):
+        Response(s)
+
+    s = (
+        b"HTTP/1.1 200 OK\r\n"
+        b"Transfer-Encoding: chunked\r\n"
+        b"\r\n"
+        b"2\r\n"
+        b"abcd"
+    )
+    with pytest.raises(dpkt.NeedData, match="premature end of chunked body"):
+        Response(s)
+
+    s = (
+        b"HTTP/1.1 200 OK\r\n"
+        b"Content-Length: 68\r\n"
+        b"\r\n"
+        b"a\r\n"
+    )
+    with pytest.raises(dpkt.NeedData, match=r"short body \(missing 65 bytes\)"):
+        Response(s)
+
+    # messy header.
+    s_messy_header = b'aaaaaaaaa\r\nbbbbbbbbb'
+    with pytest.raises(dpkt.UnpackError, match="invalid request: u?'aaaaaaaa"):
+        Request(s_messy_header)
+
+
+def test_response_str():
+    s = (
+        b'HTTP/1.0 200 OK\r\n'
+        b'Server: SimpleHTTP/0.6 Python/2.7.12\r\n'
+        b'Date: Fri, 10 Mar 2017 20:43:08 GMT\r\n'
+        b'Content-type: text/plain\r\n'
+    )
+
+    # the headers are processed to lowercase keys
+    resp = [
+        'HTTP/1.0 200 OK',
+        'server: SimpleHTTP/0.6 Python/2.7.12',
+        'date: Fri, 10 Mar 2017 20:43:08 GMT',
+        'content-type: text/plain',
+        '',
+        '',
+    ]
+
+    r_str = str(Response(s))
+
+    s_arr = sorted(resp)
+    resp_arr = sorted(r_str.split('\r\n'))
+
+    for line1, line2 in zip(s_arr, resp_arr):
+        assert line1 == line2
+
+
+def test_request_str():
+    s = b'GET / HTTP/1.0\r\n'
+    r = Request(s)
+    req = 'GET / HTTP/1.0\r\n\r\n'
+    assert req == str(r)
+
+
+def test_parse_body():
+    import pytest
+    from .compat import BytesIO
+    buf = BytesIO(
+        b'05\r\n'  # size
+        b'ERR'     # longer than size
+    )
+    buf.seek(0)
+    headers = {
+        'transfer-encoding': 'chunked',
+    }
+    with pytest.raises(dpkt.NeedData, match="premature end of chunked body"):
+        parse_body(buf, headers)
diff --git a/dpkt/http2.py b/dpkt/http2.py
index 5190f04..be6604a 100644
--- a/dpkt/http2.py
+++ b/dpkt/http2.py
@@ -22,8 +22,8 @@
 HTTP2_FRAME_CONTINUATION = 9
 
 # Flags
-HTTP2_FLAG_END_STREAM = 0x01 # for DATA and HEADERS frames
-HTTP2_FLAG_ACK = 0x01 # for SETTINGS and PING frames
+HTTP2_FLAG_END_STREAM = 0x01  # for DATA and HEADERS frames
+HTTP2_FLAG_ACK = 0x01  # for SETTINGS and PING frames
 HTTP2_FLAG_END_HEADERS = 0x04
 HTTP2_FLAG_PADDED = 0x08
 HTTP2_FLAG_PRIORITY = 0x20
@@ -87,7 +87,6 @@
 
 
 class Frame(dpkt.Packet):
-
     """
     An HTTP/2 frame as defined in RFC 7540
     """
@@ -113,8 +112,8 @@
     def length(self):
         return struct.unpack('!I', b'\x00' + self.length_bytes)[0]
 
-class Priority(dpkt.Packet):
 
+class Priority(dpkt.Packet):
     """
     Payload of a PRIORITY frame, also used in HEADERS frame with FLAG_PRIORITY.
 
@@ -134,8 +133,8 @@
         self.stream_dep &= 0x7fffffff
         self.weight += 1
 
-class Setting(dpkt.Packet):
 
+class Setting(dpkt.Packet):
     """
     A key-value pair used in the SETTINGS frame.
     """
@@ -147,7 +146,6 @@
 
 
 class PaddedFrame(Frame):
-
     """
     Abstract class for frame types that support the FLAG_PADDED flag: DATA,
     HEADERS and PUSH_PROMISE.
@@ -165,8 +163,8 @@
         else:
             self.unpadded_data = self.data
 
-class DataFrame(PaddedFrame):
 
+class DataFrame(PaddedFrame):
     """
     Frame of type DATA.
     """
@@ -175,8 +173,8 @@
     def payload(self):
         return self.unpadded_data
 
-class HeadersFrame(PaddedFrame):
 
+class HeadersFrame(PaddedFrame):
     """
     Frame of type HEADERS.
     """
@@ -191,8 +189,8 @@
         else:
             self.block_fragment = self.unpadded_data
 
-class PriorityFrame(Frame):
 
+class PriorityFrame(Frame):
     """
     Frame of type PRIORITY.
     """
@@ -201,8 +199,8 @@
         Frame.unpack(self, buf)
         self.priority = Priority(self.data)
 
-class RSTStreamFrame(Frame):
 
+class RSTStreamFrame(Frame):
     """
     Frame of type RST_STREAM.
     """
@@ -213,8 +211,8 @@
             raise HTTP2Exception('Invalid number of bytes in RST_STREAM frame (must be 4)')
         self.error_code = struct.unpack('!I', self.data)[0]
 
-class SettingsFrame(Frame):
 
+class SettingsFrame(Frame):
     """
     Frame of type SETTINGS.
     """
@@ -226,11 +224,11 @@
         self.settings = []
         i = 0
         while i < self.length:
-            self.settings.append(Setting(self.data[i:i+6]))
+            self.settings.append(Setting(self.data[i:i + 6]))
             i += 6
 
-class PushPromiseFrame(PaddedFrame):
 
+class PushPromiseFrame(PaddedFrame):
     """
     Frame of type PUSH_PROMISE.
     """
@@ -242,8 +240,8 @@
         self.promised_id = struct.unpack('!I', self.data[:4])[0]
         self.block_fragment = self.unpadded_data[4:]
 
-class PingFrame(Frame):
 
+class PingFrame(Frame):
     """
     Frame of type PING.
     """
@@ -253,8 +251,8 @@
         if self.length != 8:
             raise HTTP2Exception('Invalid number of bytes in PING frame (must be 8)')
 
-class GoAwayFrame(Frame):
 
+class GoAwayFrame(Frame):
     """
     Frame of type GO_AWAY.
     """
@@ -267,8 +265,8 @@
         self.error_code = struct.unpack('!I', self.data[4:8])[0]
         self.debug_data = self.data[8:]
 
-class WindowUpdateFrame(Frame):
 
+class WindowUpdateFrame(Frame):
     """
     Frame of type WINDOW_UPDATE.
     """
@@ -279,8 +277,8 @@
             raise HTTP2Exception('Invalid number of bytes in WINDOW_UPDATE frame (must be 4)')
         self.window_increment = struct.unpack('!I', self.data)[0]
 
-class ContinuationFrame(Frame):
 
+class ContinuationFrame(Frame):
     """
     Frame of type CONTINUATION.
     """
@@ -351,7 +349,6 @@
 
 
 class TestFrame(object):
-
     """Some data found in real traffic"""
 
     @classmethod
@@ -370,20 +367,20 @@
     def test_frame(self):
         import pytest
         # Too short
-        pytest.raises(dpkt.NeedData, Frame, codecs.decode(b'000001' # length
-                                                          b'0000' # type, flags
-                                                          b'deadbeef' # stream id
-                                                          , 'hex'))
+        pytest.raises(dpkt.NeedData, Frame, codecs.decode(b'000001'  # length
+                                                          b'0000'  # type, flags
+                                                          b'deadbeef',  # stream id
+                                                          'hex'))
 
     def test_data(self):
         # Padded DATA frame
-        frame_data_padded = FrameFactory(codecs.decode(b'000008' #length
-                                                       b'0008' # type, flags
-                                                       b'12345678' # stream id
-                                                       b'05' # pad length
-                                                       b'abcd' # data
-                                                       b'1122334455' # padding
-                                                       , 'hex'))
+        frame_data_padded = FrameFactory(codecs.decode(b'000008'  # length
+                                                       b'0008'  # type, flags
+                                                       b'12345678'  # stream id
+                                                       b'05'  # pad length
+                                                       b'abcd'  # data
+                                                       b'1122334455',  # padding
+                                                       'hex'))
         assert (frame_data_padded.length == 8)
         assert (frame_data_padded.type == HTTP2_FRAME_DATA)
         assert (frame_data_padded.flags == HTTP2_FLAG_PADDED)
@@ -394,10 +391,10 @@
         assert (frame_data_padded.payload == b'\xAB\xCD')
 
         # empty DATA frame
-        frame_data_empty_end = FrameFactory(codecs.decode(b'000000' #length
-                                                          b'0001' # type, flags
-                                                          b'deadbeef' # stream id
-                                                          , 'hex'))
+        frame_data_empty_end = FrameFactory(codecs.decode(b'000000'  # length
+                                                          b'0001'  # type, flags
+                                                          b'deadbeef',  # stream id
+                                                          'hex'))
         assert (frame_data_empty_end.length == 0)
         assert (frame_data_empty_end.type == HTTP2_FRAME_DATA)
         assert (frame_data_empty_end.flags == HTTP2_FLAG_END_STREAM)
@@ -409,28 +406,28 @@
         import pytest
         # Invalid padding
         with pytest.raises(HTTP2Exception) as e:
-            x = DataFrame(codecs.decode(b'000000' # length
-                                        b'0008' # type, flags
-                                        b'12345678' # stream id
-                                        b'' # missing padding
-                                        , 'hex'))
+            DataFrame(codecs.decode(b'000000'  # length
+                                    b'0008'  # type, flags
+                                    b'12345678'  # stream id
+                                    b'',  # missing padding
+                                    'hex'))
         assert (str(e.value) == 'Missing padding length in PADDED frame')
 
         with pytest.raises(HTTP2Exception) as e:
-            x = DataFrame(codecs.decode(b'000001' # length
-                                        b'0008' # type, flags
-                                        b'12345678' # stream id
-                                        b'01'
-                                        b'' # missing padding bytes
-                                        , 'hex'))
+            DataFrame(codecs.decode(b'000001'  # length
+                                    b'0008'  # type, flags
+                                    b'12345678'  # stream id
+                                    b'01'
+                                    b'',  # missing padding bytes
+                                    'hex'))
         assert (str(e.value) == 'Missing padding bytes in PADDED frame')
 
     def test_headers(self):
-        frame_headers = FrameFactory(codecs.decode(b'000003' # length
-                                                   b'0100' # type, flags
-                                                   b'deadbeef' # stream id
-                                                   b'f00baa' # block fragment
-                                                   , 'hex'))
+        frame_headers = FrameFactory(codecs.decode(b'000003'  # length
+                                                   b'0100'  # type, flags
+                                                   b'deadbeef'  # stream id
+                                                   b'f00baa',  # block fragment
+                                                   'hex'))
         assert (frame_headers.length == 3)
         assert (frame_headers.type == HTTP2_FRAME_HEADERS)
         assert (frame_headers.flags == 0)
@@ -439,19 +436,19 @@
         assert (frame_headers.unpadded_data == b'\xF0\x0B\xAA')
         assert (frame_headers.block_fragment == b'\xF0\x0B\xAA')
 
-        frame_headers_prio = FrameFactory(codecs.decode(b'000008' # length
-                                                        b'0120' # type, flags
-                                                        b'deadbeef' # stream id
-                                                        b'cafebabe10' # priority
-                                                        b'f00baa' # block fragment
-                                                        , 'hex'))
+        frame_headers_prio = FrameFactory(codecs.decode(b'000008'  # length
+                                                        b'0120'  # type, flags
+                                                        b'deadbeef'  # stream id
+                                                        b'cafebabe10'  # priority
+                                                        b'f00baa',  # block fragment
+                                                        'hex'))
         assert (frame_headers_prio.length == 8)
         assert (frame_headers_prio.type == HTTP2_FRAME_HEADERS)
         assert (frame_headers_prio.flags == HTTP2_FLAG_PRIORITY)
         assert (frame_headers_prio.stream_id == 0xdeadbeef)
         assert (frame_headers_prio.data == b'\xCA\xFE\xBA\xBE\x10\xF0\x0B\xAA')
         assert (frame_headers_prio.unpadded_data == b'\xCA\xFE\xBA\xBE\x10\xF0\x0B\xAA')
-        assert (frame_headers_prio.priority.exclusive == True)
+        assert (frame_headers_prio.priority.exclusive is True)
         assert (frame_headers_prio.priority.stream_dep == 0x4afebabe)
         assert (frame_headers_prio.priority.weight == 0x11)
         assert (frame_headers_prio.block_fragment == b'\xF0\x0B\xAA')
@@ -459,48 +456,48 @@
         import pytest
         # Invalid priority
         with pytest.raises(HTTP2Exception) as e:
-            x = HeadersFrame(codecs.decode(b'000002' # length
-                                           b'0120' # type, flags
-                                           b'deadbeef' # stream id
-                                           b'1234' # invalid priority
-                                           , 'hex'))
+            HeadersFrame(codecs.decode(b'000002'  # length
+                                       b'0120'  # type, flags
+                                       b'deadbeef'  # stream id
+                                       b'1234',  # invalid priority
+                                       'hex'))
         assert (str(e.value) == 'Missing stream dependency in HEADERS frame with PRIORITY flag')
 
     def test_priority(self):
-        frame_priority = FrameFactory(codecs.decode(b'000005' # length
-                                                    b'0200' # type, flags
-                                                    b'deadbeef' # stream id
-                                                    b'cafebabe' # stream dep
-                                                    b'12' # weight
-                                                    , 'hex'))
+        frame_priority = FrameFactory(codecs.decode(b'000005'  # length
+                                                    b'0200'  # type, flags
+                                                    b'deadbeef'  # stream id
+                                                    b'cafebabe'  # stream dep
+                                                    b'12',  # weight
+                                                    'hex'))
         assert (frame_priority.length == 5)
         assert (frame_priority.type == HTTP2_FRAME_PRIORITY)
         assert (frame_priority.flags == 0)
         assert (frame_priority.stream_id == 0xdeadbeef)
         assert (frame_priority.data == b'\xCA\xFE\xBA\xBE\x12')
         assert (frame_priority.priority.data == b'')
-        assert (frame_priority.priority.exclusive == True)
+        assert (frame_priority.priority.exclusive is True)
         assert (frame_priority.priority.stream_dep == 0x4afebabe)
         assert (frame_priority.priority.weight == 0x13)
 
         import pytest
         # Invalid length
         with pytest.raises(HTTP2Exception) as e:
-            x = PriorityFrame(codecs.decode(b'000006' # length
-                                            b'0200' # type, flags
-                                            b'deadbeef' # stream id
-                                            b'cafebabe' # stream dep
-                                            b'12' # weight
-                                            b'00' # unexpected additional payload
-                                            , 'hex'))
+            PriorityFrame(codecs.decode(b'000006'  # length
+                                        b'0200'  # type, flags
+                                        b'deadbeef'  # stream id
+                                        b'cafebabe'  # stream dep
+                                        b'12'  # weight
+                                        b'00',  # unexpected additional payload
+                                        'hex'))
         assert (str(e.value) == 'Invalid number of bytes in PRIORITY frame')
 
     def test_rst_stream(self):
-        frame_rst = FrameFactory(codecs.decode(b'000004' # length
-                                               b'0300' # type, flags
-                                               b'deadbeef' # stream id
-                                               b'0000000c' # error code
-                                               , 'hex'))
+        frame_rst = FrameFactory(codecs.decode(b'000004'  # length
+                                               b'0300'  # type, flags
+                                               b'deadbeef'  # stream id
+                                               b'0000000c',  # error code
+                                               'hex'))
         assert (frame_rst.length == 4)
         assert (frame_rst.type == HTTP2_FRAME_RST_STREAM)
         assert (frame_rst.flags == 0)
@@ -511,24 +508,24 @@
         import pytest
         # Invalid length
         with pytest.raises(HTTP2Exception) as e:
-            x = RSTStreamFrame(codecs.decode(b'000005' # length
-                                             b'0300' # type, flags
-                                             b'deadbeef' # stream id
-                                             b'0000000c' # error code
-                                             b'00' # unexpected additional payload
-                                             , 'hex'))
+            RSTStreamFrame(codecs.decode(b'000005'  # length
+                                         b'0300'  # type, flags
+                                         b'deadbeef'  # stream id
+                                         b'0000000c'  # error code
+                                         b'00',  # unexpected additional payload
+                                         'hex'))
         assert (str(e.value) == 'Invalid number of bytes in RST_STREAM frame (must be 4)')
 
     def test_settings(self):
-        frame_settings = FrameFactory(codecs.decode(b'00000c' # length
-                                                    b'0400' # type, flags
-                                                    b'00000000' # stream id
+        frame_settings = FrameFactory(codecs.decode(b'00000c'  # length
+                                                    b'0400'  # type, flags
+                                                    b'00000000'  # stream id
                                                     # settings
-                                                    b'0004' # setting id
-                                                    b'00020000' # setting value
-                                                    b'0005' # setting id
-                                                    b'00004000' # setting value
-                                                    , 'hex'))
+                                                    b'0004'  # setting id
+                                                    b'00020000'  # setting value
+                                                    b'0005'  # setting id
+                                                    b'00004000',  # setting value
+                                                    'hex'))
         assert (frame_settings.length == 12)
         assert (frame_settings.type == HTTP2_FRAME_SETTINGS)
         assert (frame_settings.flags == 0)
@@ -540,10 +537,10 @@
         assert (frame_settings.settings[1].value == 0x4000)
 
         # Settings ack, with empty payload
-        frame_settings_ack = FrameFactory(codecs.decode(b'000000' # length
-                                                        b'0401' # type, flags
-                                                        b'00000000' # stream id
-                                                        , 'hex'))
+        frame_settings_ack = FrameFactory(codecs.decode(b'000000'  # length
+                                                        b'0401'  # type, flags
+                                                        b'00000000',  # stream id
+                                                        'hex'))
         assert (frame_settings_ack.length == 0)
         assert (frame_settings_ack.type == HTTP2_FRAME_SETTINGS)
         assert (frame_settings_ack.flags == HTTP2_FLAG_ACK)
@@ -553,20 +550,20 @@
         import pytest
         # Invalid length
         with pytest.raises(HTTP2Exception) as e:
-            x = SettingsFrame(codecs.decode(b'000005' # length
-                                            b'0400' # type, flags
-                                            b'deadbeef' # stream id
-                                            b'1234567890' # invalid length
-                                            , 'hex'))
+            SettingsFrame(codecs.decode(b'000005'  # length
+                                        b'0400'  # type, flags
+                                        b'deadbeef'  # stream id
+                                        b'1234567890',  # invalid length
+                                        'hex'))
         assert (str(e.value) == 'Invalid number of bytes in SETTINGS frame (must be multiple of 6)')
 
     def test_push_promise(self):
-        frame_pp = FrameFactory(codecs.decode(b'000007' # length
-                                              b'0500' # type, flags
-                                              b'deadbeef' # stream id
-                                              b'cafebabe' # promised id
-                                              b'123456' # some block fragment
-                                              , 'hex'))
+        frame_pp = FrameFactory(codecs.decode(b'000007'  # length
+                                              b'0500'  # type, flags
+                                              b'deadbeef'  # stream id
+                                              b'cafebabe'  # promised id
+                                              b'123456',  # some block fragment
+                                              'hex'))
         assert (frame_pp.length == 7)
         assert (frame_pp.type == HTTP2_FRAME_PUSH_PROMISE)
         assert (frame_pp.flags == 0)
@@ -577,19 +574,19 @@
         import pytest
         # Invalid length
         with pytest.raises(HTTP2Exception) as e:
-            x = PushPromiseFrame(codecs.decode(b'000003' # length
-                                               b'0500' # type, flags
-                                               b'deadbeef' # stream id
-                                               b'cafeba' # missing promised id
-                                               , 'hex'))
+            PushPromiseFrame(codecs.decode(b'000003'  # length
+                                           b'0500'  # type, flags
+                                           b'deadbeef'  # stream id
+                                           b'cafeba',  # missing promised id
+                                           'hex'))
         assert (str(e.value) == 'Missing promised stream ID in PUSH_PROMISE frame')
 
     def test_ping(self):
-        frame_ping = FrameFactory(codecs.decode(b'000008' # length
-                                                b'0600' # type, flags
-                                                b'deadbeef' # stream id
-                                                b'cafebabe12345678' # user data
-                                                , 'hex'))
+        frame_ping = FrameFactory(codecs.decode(b'000008'  # length
+                                                b'0600'  # type, flags
+                                                b'deadbeef'  # stream id
+                                                b'cafebabe12345678',  # user data
+                                                'hex'))
         assert (frame_ping.length == 8)
         assert (frame_ping.type == HTTP2_FRAME_PING)
         assert (frame_ping.flags == 0)
@@ -599,21 +596,21 @@
         import pytest
         # Invalid length
         with pytest.raises(HTTP2Exception) as e:
-            x = PingFrame(codecs.decode(b'000005' # length
-                                        b'0600' # type, flags
-                                        b'deadbeef' # stream id
-                                        b'1234567890' # invalid length
-                                        , 'hex'))
+            PingFrame(codecs.decode(b'000005'  # length
+                                    b'0600'  # type, flags
+                                    b'deadbeef'  # stream id
+                                    b'1234567890',  # invalid length
+                                    'hex'))
         assert (str(e.value) == 'Invalid number of bytes in PING frame (must be 8)')
 
     def test_goaway(self):
-        frame_goaway = FrameFactory(codecs.decode(b'00000a' # length
-                                                  b'0700' # type, flags
-                                                  b'deadbeef' # stream id
-                                                  b'00000000' # last stream id
-                                                  b'00000000' # error code
-                                                  b'cafe' # debug data
-                                                  , 'hex'))
+        frame_goaway = FrameFactory(codecs.decode(b'00000a'  # length
+                                                  b'0700'  # type, flags
+                                                  b'deadbeef'  # stream id
+                                                  b'00000000'  # last stream id
+                                                  b'00000000'  # error code
+                                                  b'cafe',  # debug data
+                                                  'hex'))
         assert (frame_goaway.length == 10)
         assert (frame_goaway.type == HTTP2_FRAME_GOAWAY)
         assert (frame_goaway.flags == 0)
@@ -625,19 +622,19 @@
         import pytest
         # Invalid length
         with pytest.raises(HTTP2Exception) as e:
-            x = GoAwayFrame(codecs.decode(b'000005' # length
-                                          b'0700' # type, flags
-                                          b'deadbeef' # stream id
-                                          b'1234567890' # invalid length
-                                          , 'hex'))
+            GoAwayFrame(codecs.decode(b'000005'  # length
+                                      b'0700'  # type, flags
+                                      b'deadbeef'  # stream id
+                                      b'1234567890',  # invalid length
+                                      'hex'))
         assert (str(e.value) == 'Invalid number of bytes in GO_AWAY frame')
 
     def test_window_update(self):
-        frame_wu = FrameFactory(codecs.decode(b'000004' # length
-                                              b'0800' # type, flags
-                                              b'deadbeef' # stream id
-                                              b'12345678' # window increment
-                                              , 'hex'))
+        frame_wu = FrameFactory(codecs.decode(b'000004'  # length
+                                              b'0800'  # type, flags
+                                              b'deadbeef'  # stream id
+                                              b'12345678',  # window increment
+                                              'hex'))
         assert (frame_wu.length == 4)
         assert (frame_wu.type == HTTP2_FRAME_WINDOW_UPDATE)
         assert (frame_wu.flags == 0)
@@ -647,26 +644,25 @@
         import pytest
         # Invalid length
         with pytest.raises(HTTP2Exception) as e:
-            x = WindowUpdateFrame(codecs.decode(b'000005' # length
-                                                b'0800' # type, flags
-                                                b'deadbeef' # stream id
-                                                b'1234567890' # invalid length
-                                                , 'hex'))
+            WindowUpdateFrame(codecs.decode(b'000005'  # length
+                                            b'0800'  # type, flags
+                                            b'deadbeef'  # stream id
+                                            b'1234567890',  # invalid length
+                                            'hex'))
         assert (str(e.value) == 'Invalid number of bytes in WINDOW_UPDATE frame (must be 4)')
 
     def test_continuation(self):
-        frame_cont = FrameFactory(codecs.decode(b'000003' # length
-                                                b'0900' # type, flags
-                                                b'deadbeef' # stream id
-                                                b'f00baa' # block fragment
-                                                , 'hex'))
+        frame_cont = FrameFactory(codecs.decode(b'000003'  # length
+                                                b'0900'  # type, flags
+                                                b'deadbeef'  # stream id
+                                                b'f00baa',  # block fragment
+                                                'hex'))
         assert (frame_cont.length == 3)
         assert (frame_cont.type == HTTP2_FRAME_CONTINUATION)
         assert (frame_cont.flags == 0)
         assert (frame_cont.stream_id == 0xdeadbeef)
         assert (frame_cont.block_fragment == b'\xF0\x0B\xAA')
 
-
     def test_factory(self):
         import pytest
         # Too short
@@ -674,20 +670,20 @@
 
         # Invalid type
         with pytest.raises(HTTP2Exception) as e:
-            x = FrameFactory(codecs.decode(b'000000' # length
-                                           b'abcd' # type, flags
-                                           b'deadbeef' # stream id
-                                           , 'hex'))
+            FrameFactory(codecs.decode(b'000000'  # length
+                                       b'abcd'  # type, flags
+                                       b'deadbeef',  # stream id
+                                       'hex'))
         assert (str(e.value) == 'Invalid frame type: 0xab')
 
     def test_preface(self):
         import pytest
         # Preface
         pytest.raises(dpkt.NeedData, Preface,
-                codecs.decode(b'505249202a20485454502f322e300d0a', 'hex'))
+                      codecs.decode(b'505249202a20485454502f322e300d0a', 'hex'))
         pytest.raises(dpkt.NeedData, Preface, b'\x00' * 23)
         with pytest.raises(HTTP2Exception) as e:
-            x = Preface(b'\x00' * 24)
+            Preface(b'\x00' * 24)
         assert (str(e.value) == 'Invalid HTTP/2 preface')
 
     def test_multi(self):
@@ -727,16 +723,15 @@
         assert (self.frames[6].stream_id == 11)
 
         frames, i = frame_multi_factory(
-                codecs.decode(b'505249202a20485454502f322e300d0a', 'hex'),
-                preface=True)
+            codecs.decode(b'505249202a20485454502f322e300d0a', 'hex'),
+            preface=True)
         assert (len(frames) == 0)
         assert (i == 0)
 
         # Only preface was parsed
         frames, i = frame_multi_factory(
-                codecs.decode(b'505249202a20485454502f322e300d0a'
-                              b'0d0a534d0d0a0d0a00000c0400000000', 'hex'),
-                preface=True)
+            codecs.decode(b'505249202a20485454502f322e300d0a'
+                          b'0d0a534d0d0a0d0a00000c0400000000', 'hex'),
+            preface=True)
         assert (len(frames) == 0)
         assert (i == 24)
-
diff --git a/dpkt/icmp.py b/dpkt/icmp.py
index f4fd661..0e8c4a6 100644
--- a/dpkt/icmp.py
+++ b/dpkt/icmp.py
@@ -169,6 +169,17 @@
     assert p.sum == 0x5387
 
 
-if __name__ == '__main__':
-    test_icmp()
-    print('Tests Successful...')
+def test_invalid_data():
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '01'    # type (invalid entry)
+        '00'    # code
+        '0000'  # sum
+
+        'abcd'  # data
+    )
+    icmp = ICMP(buf)
+
+    # no additional attributes have been added due to the type being invalid
+    assert dir(icmp) == dir(ICMP())
diff --git a/dpkt/icmp6.py b/dpkt/icmp6.py
index 9053767..b84e2eb 100644
--- a/dpkt/icmp6.py
+++ b/dpkt/icmp6.py
@@ -22,9 +22,9 @@
 ICMP6_MEMBERSHIP_REDUCTION = 132  # group membership termination
 
 ND_ROUTER_SOLICIT = 133  # router solicitation
-ND_ROUTER_ADVERT = 134  # router advertisment
+ND_ROUTER_ADVERT = 134  # router advertisement
 ND_NEIGHBOR_SOLICIT = 135  # neighbor solicitation
-ND_NEIGHBOR_ADVERT = 136  # neighbor advertisment
+ND_NEIGHBOR_ADVERT = 136  # neighbor advertisement
 ND_REDIRECT = 137  # redirect
 
 ICMP6_ROUTER_RENUMBERING = 138  # router renumbering
@@ -48,7 +48,7 @@
         __hdr__: Header fields of ICMPv6.
         TODO.
     """
-    
+
     __hdr__ = (
         ('type', 'B', 0),
         ('code', 'B', 0),
@@ -63,12 +63,14 @@
             from . import ip6
             self.data = self.ip6 = ip6.IP6(self.data)
 
-    class Unreach(Error): pass
+    class Unreach(Error):
+        pass
 
     class TooBig(Error):
         __hdr__ = (('mtu', 'I', 1232), )
 
-    class TimeExceed(Error): pass
+    class TimeExceed(Error):
+        pass
 
     class ParamProb(Error):
         __hdr__ = (('ptr', 'I', 0), )
diff --git a/dpkt/ieee80211.py b/dpkt/ieee80211.py
index 1ca51f0..1dfbccb 100644
--- a/dpkt/ieee80211.py
+++ b/dpkt/ieee80211.py
@@ -4,11 +4,10 @@
 from __future__ import print_function
 from __future__ import absolute_import
 
-import socket
 import struct
 
 from . import dpkt
-from .decorators import deprecated
+from .compat import ntole
 
 # Frame Types
 MGMT_TYPE = 0
@@ -115,6 +114,7 @@
 # Block ack category action codes
 BLOCK_ACK_CODE_REQUEST = 0
 BLOCK_ACK_CODE_RESPONSE = 1
+BLOCK_ACK_CODE_DELBA = 2
 
 
 class IEEE80211(dpkt.Packet):
@@ -132,6 +132,10 @@
         ('duration', 'H', 0)
     )
 
+    # The standard really defines the entire MAC protocol as little-endian on the wire,
+    # however there is broken logic in the rest of the module preventing this from working right now
+    #  __byte_order__ = '<'
+
     @property
     def version(self):
         return (self.framectl & _VERSION_MASK) >> _VERSION_SHIFT
@@ -330,7 +334,7 @@
 
         # Strip off the FCS field
         if self.fcs_present:
-            self.fcs = struct.unpack('I', self.data[-1 * FCS_LENGTH:])[0]
+            self.fcs = struct.unpack('<I', self.data[-1 * FCS_LENGTH:])[0]
             self.data = self.data[0: -1 * FCS_LENGTH]
 
         if self.type == MGMT_TYPE:
@@ -363,7 +367,7 @@
         if self.type == MGMT_TYPE:
             self.unpack_ies(field.data)
             if self.subtype in FRAMES_WITH_CAPABILITY:
-                self.capability = self.Capability(socket.ntohs(field.capability))
+                self.capability = self.Capability(ntole(field.capability))
 
         if self.type == DATA_TYPE and self.subtype == D_QOS_DATA:
             self.qos_data = self.QoS_Data(field.data)
@@ -422,7 +426,7 @@
         def unpack(self, buf):
             dpkt.Packet.unpack(self, buf)
             self.data = buf[self.__hdr_len__:]
-            self.ctl = socket.ntohs(self.ctl)
+            self.ctl = ntole(self.ctl)
 
             if self.compressed:
                 self.bmp = struct.unpack('8s', self.data[0:_COMPRESSED_BMP_LENGTH])[0]
@@ -517,6 +521,7 @@
                 BLOCK_ACK: {
                     BLOCK_ACK_CODE_REQUEST: ('block_ack_request', IEEE80211.BlockAckActionRequest),
                     BLOCK_ACK_CODE_RESPONSE: ('block_ack_response', IEEE80211.BlockAckActionResponse),
+                    BLOCK_ACK_CODE_DELBA: ('block_ack_delba', IEEE80211.BlockAckActionDelba),
                 },
             }
 
@@ -546,6 +551,14 @@
             ('timeout', 'H', 0),
         )
 
+    class BlockAckActionDelba(dpkt.Packet):
+        __byte_order__ = '<'
+        __hdr__ = (
+            ('delba_param_set', 'H', 0),
+            ('reason_code', 'H', 0),
+            # ('gcr_group_addr', '8s', '\x00' * 8), # Standard says it must be there, but it isn't?
+        )
+
     class Data(dpkt.Packet):
         __hdr__ = (
             ('dst', '6s', '\x00' * 6),
@@ -655,11 +668,26 @@
     assert ieee.wep == 0
     assert ieee.order == 0
     assert ieee.ack.dst == b'\x00\x12\xf0\xb6\x1c\xa4'
-    fcs = struct.unpack('I', s[-4:])[0]
+    fcs = struct.unpack('<I', s[-4:])[0]
     assert ieee.fcs == fcs
 
+
 def test_80211_beacon():
-    s = b'\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x26\xcb\x18\x6a\x30\x00\x26\xcb\x18\x6a\x30\xa0\xd0\x77\x09\x32\x03\x8f\x00\x00\x00\x66\x00\x31\x04\x00\x04\x43\x41\x45\x4e\x01\x08\x82\x84\x8b\x0c\x12\x96\x18\x24\x03\x01\x01\x05\x04\x00\x01\x00\x00\x07\x06\x55\x53\x20\x01\x0b\x1a\x0b\x05\x00\x00\x6e\x00\x00\x2a\x01\x02\x2d\x1a\x6e\x18\x1b\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x14\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x28\x00\x32\x04\x30\x48\x60\x6c\x36\x03\x51\x63\x03\x3d\x16\x01\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\x1e\x05\x00\x8f\x00\x0f\x00\xff\x03\x59\x00\x63\x73\x65\x2d\x33\x39\x31\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x36\x96\x06\x00\x40\x96\x00\x14\x00\xdd\x18\x00\x50\xf2\x02\x01\x01\x80\x00\x03\xa4\x00\x00\x27\xa4\x00\x00\x42\x43\x5e\x00\x62\x32\x2f\x00\xdd\x06\x00\x40\x96\x01\x01\x04\xdd\x05\x00\x40\x96\x03\x05\xdd\x05\x00\x40\x96\x0b\x09\xdd\x08\x00\x40\x96\x13\x01\x00\x34\x01\xdd\x05\x00\x40\x96\x14\x05'
+    s = (
+        b'\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x26\xcb\x18\x6a\x30\x00\x26\xcb\x18\x6a\x30'
+        b'\xa0\xd0\x77\x09\x32\x03\x8f\x00\x00\x00\x66\x00\x31\x04\x00\x04\x43\x41\x45\x4e\x01\x08'
+        b'\x82\x84\x8b\x0c\x12\x96\x18\x24\x03\x01\x01\x05\x04\x00\x01\x00\x00\x07\x06\x55\x53\x20'
+        b'\x01\x0b\x1a\x0b\x05\x00\x00\x6e\x00\x00\x2a\x01\x02\x2d\x1a\x6e\x18\x1b\xff\xff\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x14\x01'
+        b'\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x28\x00\x32\x04\x30'
+        b'\x48\x60\x6c\x36\x03\x51\x63\x03\x3d\x16\x01\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x85\x1e\x05\x00\x8f\x00\x0f\x00\xff\x03\x59\x00'
+        b'\x63\x73\x65\x2d\x33\x39\x31\x32\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x36\x96\x06'
+        b'\x00\x40\x96\x00\x14\x00\xdd\x18\x00\x50\xf2\x02\x01\x01\x80\x00\x03\xa4\x00\x00\x27\xa4'
+        b'\x00\x00\x42\x43\x5e\x00\x62\x32\x2f\x00\xdd\x06\x00\x40\x96\x01\x01\x04\xdd\x05\x00\x40'
+        b'\x96\x03\x05\xdd\x05\x00\x40\x96\x0b\x09\xdd\x08\x00\x40\x96\x13\x01\x00\x34\x01\xdd\x05'
+        b'\x00\x40\x96\x14\x05'
+    )
     ieee = IEEE80211(s, fcs=True)
     assert ieee.version == 0
     assert ieee.type == MGMT_TYPE
@@ -678,47 +706,75 @@
     assert ieee.rate.data == b'\x82\x84\x8b\x0c\x12\x96\x18\x24'
     assert ieee.ds.data == b'\x01'
     assert ieee.tim.data == b'\x00\x01\x00\x00'
-    fcs = struct.unpack('I', s[-4:])[0]
+    fcs = struct.unpack('<I', s[-4:])[0]
     assert ieee.fcs == fcs
 
+
 def test_80211_data():
-    s = b'\x08\x09\x20\x00\x00\x26\xcb\x17\x3d\x91\x00\x16\x44\xb0\xae\xc6\x00\x02\xb3\xd6\x26\x3c\x80\x7e\xaa\xaa\x03\x00\x00\x00\x08\x00\x45\x00\x00\x28\x07\x27\x40\x00\x80\x06\x1d\x39\x8d\xd4\x37\x3d\x3f\xf5\xd1\x69\xc0\x5f\x01\xbb\xb2\xd6\xef\x23\x38\x2b\x4f\x08\x50\x10\x42\x04\xac\x17\x00\x00'
+    s = (
+        b'\x08\x09\x20\x00\x00\x26\xcb\x17\x3d\x91\x00\x16\x44\xb0\xae\xc6\x00\x02\xb3\xd6\x26\x3c'
+        b'\x80\x7e\xaa\xaa\x03\x00\x00\x00\x08\x00\x45\x00\x00\x28\x07\x27\x40\x00\x80\x06\x1d\x39'
+        b'\x8d\xd4\x37\x3d\x3f\xf5\xd1\x69\xc0\x5f\x01\xbb\xb2\xd6\xef\x23\x38\x2b\x4f\x08\x50\x10'
+        b'\x42\x04\xac\x17\x00\x00'
+    )
     ieee = IEEE80211(s, fcs=True)
     assert ieee.type == DATA_TYPE
     assert ieee.subtype == D_DATA
     assert ieee.data_frame.dst == b'\x00\x02\xb3\xd6\x26\x3c'
     assert ieee.data_frame.src == b'\x00\x16\x44\xb0\xae\xc6'
     assert ieee.data_frame.frag_seq == 0x807e
-    assert ieee.data == b'\xaa\xaa\x03\x00\x00\x00\x08\x00\x45\x00\x00\x28\x07\x27\x40\x00\x80\x06\x1d\x39\x8d\xd4\x37\x3d\x3f\xf5\xd1\x69\xc0\x5f\x01\xbb\xb2\xd6\xef\x23\x38\x2b\x4f\x08\x50\x10\x42\x04'
-    assert ieee.fcs == struct.unpack('I', b'\xac\x17\x00\x00')[0]
+    assert ieee.data == (b'\xaa\xaa\x03\x00\x00\x00\x08\x00\x45\x00\x00\x28\x07\x27\x40\x00\x80\x06'
+                         b'\x1d\x39\x8d\xd4\x37\x3d\x3f\xf5\xd1\x69\xc0\x5f\x01\xbb\xb2\xd6\xef\x23'
+                         b'\x38\x2b\x4f\x08\x50\x10\x42\x04')
+    assert ieee.fcs == struct.unpack('<I', b'\xac\x17\x00\x00')[0]
 
     from . import llc
     llc_pkt = llc.LLC(ieee.data_frame.data)
     ip_pkt = llc_pkt.data
     assert ip_pkt.dst == b'\x3f\xf5\xd1\x69'
 
+
 def test_80211_data_qos():
-    s = b'\x88\x01\x3a\x01\x00\x26\xcb\x17\x44\xf0\x00\x23\xdf\xc9\xc0\x93\x00\x26\xcb\x17\x44\xf0\x20\x7b\x00\x00\xaa\xaa\x03\x00\x00\x00\x88\x8e\x01\x00\x00\x74\x02\x02\x00\x74\x19\x80\x00\x00\x00\x6a\x16\x03\x01\x00\x65\x01\x00\x00\x61\x03\x01\x4b\x4c\xa7\x7e\x27\x61\x6f\x02\x7b\x3c\x72\x39\xe3\x7b\xd7\x43\x59\x91\x7f\xaa\x22\x47\x51\xb6\x88\x9f\x85\x90\x87\x5a\xd1\x13\x20\xe0\x07\x00\x00\x68\xbd\xa4\x13\xb0\xd5\x82\x7e\xc7\xfb\xe7\xcc\xab\x6e\x5d\x5a\x51\x50\xd4\x45\xc5\xa1\x65\x53\xad\xb5\x88\x5b\x00\x1a\x00\x2f\x00\x05\x00\x04\x00\x35\x00\x0a\x00\x09\x00\x03\x00\x08\x00\x33\x00\x39\x00\x16\x00\x15\x00\x14\x01\x00\xff\xff\xff\xff'
+    s = (
+        b'\x88\x01\x3a\x01\x00\x26\xcb\x17\x44\xf0\x00\x23\xdf\xc9\xc0\x93\x00\x26\xcb\x17\x44\xf0'
+        b'\x20\x7b\x00\x00\xaa\xaa\x03\x00\x00\x00\x88\x8e\x01\x00\x00\x74\x02\x02\x00\x74\x19\x80'
+        b'\x00\x00\x00\x6a\x16\x03\x01\x00\x65\x01\x00\x00\x61\x03\x01\x4b\x4c\xa7\x7e\x27\x61\x6f'
+        b'\x02\x7b\x3c\x72\x39\xe3\x7b\xd7\x43\x59\x91\x7f\xaa\x22\x47\x51\xb6\x88\x9f\x85\x90\x87'
+        b'\x5a\xd1\x13\x20\xe0\x07\x00\x00\x68\xbd\xa4\x13\xb0\xd5\x82\x7e\xc7\xfb\xe7\xcc\xab\x6e'
+        b'\x5d\x5a\x51\x50\xd4\x45\xc5\xa1\x65\x53\xad\xb5\x88\x5b\x00\x1a\x00\x2f\x00\x05\x00\x04'
+        b'\x00\x35\x00\x0a\x00\x09\x00\x03\x00\x08\x00\x33\x00\x39\x00\x16\x00\x15\x00\x14\x01\x00'
+        b'\xff\xff\xff\xff'
+    )
     ieee = IEEE80211(s, fcs=True)
     assert ieee.type == DATA_TYPE
     assert ieee.subtype == D_QOS_DATA
     assert ieee.data_frame.dst == b'\x00\x26\xcb\x17\x44\xf0'
     assert ieee.data_frame.src == b'\x00\x23\xdf\xc9\xc0\x93'
     assert ieee.data_frame.frag_seq == 0x207b
-    assert ieee.data == b'\xaa\xaa\x03\x00\x00\x00\x88\x8e\x01\x00\x00\x74\x02\x02\x00\x74\x19\x80\x00\x00\x00\x6a\x16\x03\x01\x00\x65\x01\x00\x00\x61\x03\x01\x4b\x4c\xa7\x7e\x27\x61\x6f\x02\x7b\x3c\x72\x39\xe3\x7b\xd7\x43\x59\x91\x7f\xaa\x22\x47\x51\xb6\x88\x9f\x85\x90\x87\x5a\xd1\x13\x20\xe0\x07\x00\x00\x68\xbd\xa4\x13\xb0\xd5\x82\x7e\xc7\xfb\xe7\xcc\xab\x6e\x5d\x5a\x51\x50\xd4\x45\xc5\xa1\x65\x53\xad\xb5\x88\x5b\x00\x1a\x00\x2f\x00\x05\x00\x04\x00\x35\x00\x0a\x00\x09\x00\x03\x00\x08\x00\x33\x00\x39\x00\x16\x00\x15\x00\x14\x01\x00'
+    assert ieee.data == (b'\xaa\xaa\x03\x00\x00\x00\x88\x8e\x01\x00\x00\x74\x02\x02\x00\x74\x19\x80'
+                         b'\x00\x00\x00\x6a\x16\x03\x01\x00\x65\x01\x00\x00\x61\x03\x01\x4b\x4c\xa7'
+                         b'\x7e\x27\x61\x6f\x02\x7b\x3c\x72\x39\xe3\x7b\xd7\x43\x59\x91\x7f\xaa\x22'
+                         b'\x47\x51\xb6\x88\x9f\x85\x90\x87\x5a\xd1\x13\x20\xe0\x07\x00\x00\x68\xbd'
+                         b'\xa4\x13\xb0\xd5\x82\x7e\xc7\xfb\xe7\xcc\xab\x6e\x5d\x5a\x51\x50\xd4\x45'
+                         b'\xc5\xa1\x65\x53\xad\xb5\x88\x5b\x00\x1a\x00\x2f\x00\x05\x00\x04\x00\x35'
+                         b'\x00\x0a\x00\x09\x00\x03\x00\x08\x00\x33\x00\x39\x00\x16\x00\x15\x00\x14\x01\x00')
     assert ieee.qos_data.control == 0x0
-    assert ieee.fcs == struct.unpack('I', b'\xff\xff\xff\xff')[0]
+    assert ieee.fcs == struct.unpack('<I', b'\xff\xff\xff\xff')[0]
+
 
 def test_bug():
-    s = b'\x88\x41\x2c\x00\x00\x26\xcb\x17\x44\xf0\x00\x1e\x52\x97\x14\x11\x00\x1f\x6d\xe8\x18\x00\xd0\x07\x00\x00\x6f\x00\x00\x20\x00\x00\x00\x00'
+    s = (b'\x88\x41\x2c\x00\x00\x26\xcb\x17\x44\xf0\x00\x1e\x52\x97\x14\x11\x00\x1f\x6d\xe8\x18\x00'
+         b'\xd0\x07\x00\x00\x6f\x00\x00\x20\x00\x00\x00\x00')
     ieee = IEEE80211(s)
     assert ieee.wep == 1
 
+
 def test_data_ds():
     # verifying the ToDS and FromDS fields and that we're getting the
     # correct values
 
-    s = b'\x08\x03\x00\x00\x01\x0b\x85\x00\x00\x00\x00\x26\xcb\x18\x73\x50\x01\x0b\x85\x00\x00\x00\x00\x89\x00\x26\xcb\x18\x73\x50'
+    s = (b'\x08\x03\x00\x00\x01\x0b\x85\x00\x00\x00\x00\x26\xcb\x18\x73\x50\x01\x0b\x85\x00\x00\x00'
+         b'\x00\x89\x00\x26\xcb\x18\x73\x50')
     ieee = IEEE80211(s)
     assert ieee.type == DATA_TYPE
     assert ieee.to_ds == 1
@@ -728,7 +784,8 @@
     assert ieee.data_frame.dst == b'\x01\x0b\x85\x00\x00\x00'
     assert ieee.data_frame.da == b'\x01\x0b\x85\x00\x00\x00'
 
-    s = b'\x88\x41\x50\x01\x00\x26\xcb\x17\x48\xc1\x00\x24\x2c\xe7\xfe\x8a\xff\xff\xff\xff\xff\xff\x80\xa0\x00\x00\x09\x1a\x00\x20\x00\x00\x00\x00'
+    s = (b'\x88\x41\x50\x01\x00\x26\xcb\x17\x48\xc1\x00\x24\x2c\xe7\xfe\x8a\xff\xff\xff\xff\xff\xff'
+         b'\x80\xa0\x00\x00\x09\x1a\x00\x20\x00\x00\x00\x00')
     ieee = IEEE80211(s)
     assert ieee.type == DATA_TYPE
     assert ieee.to_ds == 1
@@ -746,8 +803,10 @@
     assert ieee.data_frame.src == b'\x00\x1f\x33\x39\x75\x44'
     assert ieee.data_frame.dst == b'\x00\x02\x44\xac\x27\x70'
 
+
 def test_compressed_block_ack():
-    s = b'\x94\x00\x00\x00\x34\xc0\x59\xd6\x3f\x62\xb4\x75\x0e\x46\x83\xc1\x05\x50\x80\xee\x03\x00\x00\x00\x00\x00\x00\x00\xa2\xe4\x98\x45'
+    s = (b'\x94\x00\x00\x00\x34\xc0\x59\xd6\x3f\x62\xb4\x75\x0e\x46\x83\xc1\x05\x50\x80\xee\x03\x00'
+         b'\x00\x00\x00\x00\x00\x00\xa2\xe4\x98\x45')
     ieee = IEEE80211(s, fcs=True)
     assert ieee.type == CTL_TYPE
     assert ieee.subtype == C_BLOCK_ACK
@@ -758,38 +817,171 @@
     assert ieee.back.ack_policy == 1
     assert ieee.back.tid == 5
 
+
 def test_action_block_ack_request():
-    s = b'\xd0\x00\x3a\x01\x00\x23\x14\x36\x52\x30\xb4\x75\x0e\x46\x83\xc1\xb4\x75\x0e\x46\x83\xc1\x70\x14\x03\x00\x0d\x02\x10\x00\x00\x40\x29\x06\x50\x33\x9e'
+    s = (b'\xd0\x00\x3a\x01\x00\x23\x14\x36\x52\x30\xb4\x75\x0e\x46\x83\xc1\xb4\x75\x0e\x46\x83\xc1'
+         b'\x70\x14\x03\x00\x0d\x02\x10\x00\x00\x40\x29\x06\x50\x33\x9e')
     ieee = IEEE80211(s, fcs=True)
     assert ieee.type == MGMT_TYPE
     assert ieee.subtype == M_ACTION
     assert ieee.action.category == BLOCK_ACK
     assert ieee.action.code == BLOCK_ACK_CODE_REQUEST
     assert ieee.action.block_ack_request.timeout == 0
-    parameters = struct.unpack('H', b'\x10\x02')[0]
+    parameters = struct.unpack('<H', b'\x10\x02')[0]
     assert ieee.action.block_ack_request.parameters == parameters
 
+
 def test_action_block_ack_response():
-    s = b'\xd0\x00\x3c\x00\xb4\x75\x0e\x46\x83\xc1\x00\x23\x14\x36\x52\x30\xb4\x75\x0e\x46\x83\xc1\xd0\x68\x03\x01\x0d\x00\x00\x02\x10\x88\x13\x9f\xc0\x0b\x75'
+    s = (b'\xd0\x00\x3c\x00\xb4\x75\x0e\x46\x83\xc1\x00\x23\x14\x36\x52\x30\xb4\x75\x0e\x46\x83\xc1'
+         b'\xd0\x68\x03\x01\x0d\x00\x00\x02\x10\x88\x13\x9f\xc0\x0b\x75')
     ieee = IEEE80211(s, fcs=True)
     assert ieee.type == MGMT_TYPE
     assert ieee.subtype == M_ACTION
     assert ieee.action.category == BLOCK_ACK
     assert ieee.action.code == BLOCK_ACK_CODE_RESPONSE
-    timeout = struct.unpack('H', b'\x13\x88')[0]
+    timeout = struct.unpack('<H', b'\x13\x88')[0]
     assert ieee.action.block_ack_response.timeout == timeout
-    parameters = struct.unpack('H', b'\x10\x02')[0]
+    parameters = struct.unpack('<H', b'\x10\x02')[0]
     assert ieee.action.block_ack_response.parameters == parameters
 
-if __name__ == '__main__':
-    # Runs all the test associated with this class/file
-    test_802211_ack()
-    test_80211_beacon()
-    test_80211_data()
-    test_80211_data_qos()
-    test_bug()
-    test_data_ds()
-    test_compressed_block_ack()
-    test_action_block_ack_request()
-    test_action_block_ack_response()
-    print('Tests Successful...')
+
+def test_action_block_ack_delete():
+    s = (b'\xd0\x00\x2c\x00\x00\xc1\x41\x06\x13\x0d\x6c\xb2\xae\xae\xde\x80\x6c\xb2\xae\xae\xde\x80'
+         b'\xa0\x52\x03\x02\x00\x08\x01\x00\x74\x5d\x0a\xc6')
+    ieee = IEEE80211(s, fcs=True)
+    assert ieee.type == MGMT_TYPE
+    assert ieee.subtype == M_ACTION
+    assert ieee.action.category == BLOCK_ACK
+    assert ieee.action.code == BLOCK_ACK_CODE_DELBA
+    assert ieee.action.block_ack_delba.delba_param_set == 0x0800
+    assert ieee.action.block_ack_delba.reason_code == 1
+
+
+def test_ieee80211_properties():
+    ieee80211 = IEEE80211()
+    assert ieee80211.version == 0
+    ieee80211.version = 1
+    assert ieee80211.version == 1
+
+    assert ieee80211.type == 0
+    ieee80211.type = 1
+    assert ieee80211.type == 1
+
+    assert ieee80211.subtype == 0
+    ieee80211.subtype = 1
+    assert ieee80211.subtype == 1
+
+    assert ieee80211.to_ds == 0
+    ieee80211.to_ds = 1
+    assert ieee80211.to_ds == 1
+
+    assert ieee80211.from_ds == 0
+    ieee80211.from_ds = 1
+    assert ieee80211.from_ds == 1
+
+    assert ieee80211.more_frag == 0
+    ieee80211.more_frag = 1
+    assert ieee80211.more_frag == 1
+
+    assert ieee80211.retry == 0
+    ieee80211.retry = 0
+    assert ieee80211.retry == 0
+
+    assert ieee80211.pwr_mgt == 0
+    ieee80211.pwr_mgt = 0
+    assert ieee80211.pwr_mgt == 0
+
+    assert ieee80211.more_data == 0
+    ieee80211.more_data = 0
+    assert ieee80211.more_data == 0
+
+    assert ieee80211.wep == 0
+    ieee80211.wep = 1
+    assert ieee80211.wep == 1
+
+    assert ieee80211.order == 0
+    ieee80211.order = 1
+    assert ieee80211.order == 1
+
+
+def test_blockack_properties():
+    blockack = IEEE80211.BlockAck()
+    assert blockack.compressed == 0
+    blockack.compressed = 1
+    assert blockack.compressed == 1
+
+    assert blockack.ack_policy == 0
+    blockack.ack_policy = 1
+    assert blockack.ack_policy == 1
+
+    assert blockack.multi_tid == 0
+    blockack.multi_tid = 1
+    assert blockack.multi_tid == 1
+
+    assert blockack.tid == 0
+    blockack.tid = 1
+    assert blockack.tid == 1
+
+
+def test_ieee80211_unpack():
+    import pytest
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '4000'  # subtype set to M_PROBE_REQ
+        '0000'
+
+        # MGMT_Frame
+        '000000000000'  # dst
+        '000000000000'  # src
+        '000000000000'  # bssid
+        '0000'          # frag_seq
+    )
+    ieee80211 = IEEE80211(buf)
+    assert ieee80211.ies == []
+
+    buf = unhexlify(
+        '9000'  # subtype set to M_ATIM
+        '0000'
+
+        # MGMT_Frame
+        '000000000000'  # dst
+        '000000000000'  # src
+        '000000000000'  # bssid
+        '0000'          # frag_seq
+    )
+    ieee80211 = IEEE80211(buf)
+    assert not hasattr(ieee80211, 'ies')
+
+    buf = unhexlify(
+        '0c00'  # type set to invalid value
+        '0000'
+    )
+    with pytest.raises(dpkt.UnpackError, match="KeyError: type=3 subtype=0"):
+        IEEE80211(buf)
+
+
+def test_blockack_unpack():
+    from binascii import unhexlify
+    # unpack a non-compressed BlockAck
+    buf = unhexlify(
+        '000000000000'
+        '000000000000'
+        '0000'   # compressed flag not set
+        '0000'
+    ) + b'\xff' * 128
+
+    blockack = IEEE80211.BlockAck(buf)
+    assert blockack.bmp == b'\xff' * 128
+    assert blockack.data == b''
+
+
+def test_action_unpack():
+    import pytest
+    from binascii import unhexlify
+    buf = unhexlify(
+        '01'  # category
+        '00'  # code (non-existent)
+    )
+    with pytest.raises(dpkt.UnpackError, match="KeyError: category=1 code=0"):
+        IEEE80211.Action(buf)
diff --git a/dpkt/igmp.py b/dpkt/igmp.py
index d72578c..718b256 100644
--- a/dpkt/igmp.py
+++ b/dpkt/igmp.py
@@ -20,10 +20,30 @@
         ('type', 'B', 0),
         ('maxresp', 'B', 0),
         ('sum', 'H', 0),
-        ('group', 'I', 0)
+        ('group', '4s', b'\x00' * 4)
     )
 
     def __bytes__(self):
         if not self.sum:
             self.sum = dpkt.in_cksum(dpkt.Packet.__bytes__(self))
         return dpkt.Packet.__bytes__(self)
+
+
+def test_construction_no_sum():
+    igmp = IGMP()
+    assert igmp.type == 0
+    assert igmp.maxresp == 0
+    assert igmp.sum == 0
+    assert igmp.group == b'\x00' * 4
+
+    assert bytes(igmp) == b'\x00\x00' + b'\xff\xff' + b'\x00' * 4
+
+
+def test_construction_sum_set():
+    igmp = IGMP(sum=1)
+    assert igmp.type == 0
+    assert igmp.maxresp == 0
+    assert igmp.sum == 1
+    assert igmp.group == b'\x00' * 4
+
+    assert bytes(igmp) == b'\x00\x00\x00\x01' + b'\x00' * 4
diff --git a/dpkt/ip.py b/dpkt/ip.py
index d412d16..30a48d8 100644
--- a/dpkt/ip.py
+++ b/dpkt/ip.py
@@ -5,8 +5,15 @@
 from __future__ import absolute_import
 
 from . import dpkt
-from .decorators import deprecated
 from .compat import iteritems
+from .utils import inet_to_str, deprecation_warning
+
+_ip_proto_names = {}  # {1: 'ICMP', 6: 'TCP', 17: 'UDP', etc.}
+
+
+def get_ip_proto_name(p):
+    return _ip_proto_names.get(p, None)
+
 
 class IP(dpkt.Packet):
     """Internet Protocol.
@@ -23,13 +30,31 @@
         ('tos', 'B', 0),
         ('len', 'H', 20),
         ('id', 'H', 0),
-        ('off', 'H', 0),
+        ('_flags_offset', 'H', 0),  # XXX - previously ip.off
         ('ttl', 'B', 64),
         ('p', 'B', 0),
         ('sum', 'H', 0),
         ('src', '4s', b'\x00' * 4),
         ('dst', '4s', b'\x00' * 4)
     )
+    __bit_fields__ = {
+        '_v_hl': (
+            ('v', 4),   # version, 4 bits
+            ('hl', 4),  # header len, 4 bits
+        ),
+        '_flags_offset': (
+            ('rf', 1),  # reserved bit
+            ('df', 1),  # don't fragment
+            ('mf', 1),  # more fragments
+            ('offset', 13),  # fragment offset, 13 bits
+        )
+    }
+    __pprint_funcs__ = {
+        'dst': inet_to_str,
+        'src': inet_to_str,
+        'sum': hex,  # display checksum in hex
+        'p': get_ip_proto_name
+    }
     _protosw = {}
     opts = b''
 
@@ -41,54 +66,6 @@
         if not args and 'len' not in kwargs:
             self.len = self.__len__()
 
-    @property
-    def v(self):
-        return self._v_hl >> 4
-
-    @v.setter
-    def v(self, v):
-        self._v_hl = (v << 4) | (self._v_hl & 0xf)
-
-    @property
-    def hl(self):
-        return self._v_hl & 0xf
-
-    @hl.setter
-    def hl(self, hl):
-        self._v_hl = (self._v_hl & 0xf0) | hl
-
-    @property
-    def rf(self):
-        return (self.off >> 15) & 0x1
-
-    @rf.setter
-    def rf(self, rf):
-        self.off = (self.off & ~IP_RF) | (rf << 15)
-
-    @property
-    def df(self):
-        return (self.off >> 14) & 0x1
-
-    @df.setter
-    def df(self, df):
-        self.off = (self.off & ~IP_DF) | (df << 14)
-
-    @property
-    def mf(self):
-        return (self.off >> 13) & 0x1
-
-    @mf.setter
-    def mf(self, mf):
-        self.off = (self.off & ~IP_MF) | (mf << 13)
-
-    @property
-    def offset(self):
-        return (self.off & IP_OFFMASK) << 3
-
-    @offset.setter
-    def offset(self, offset):
-        self.off = (self.off & ~IP_OFFMASK) | (offset >> 3)
-
     def __len__(self):
         return self.__hdr_len__ + len(self.opts) + len(self.data)
 
@@ -96,17 +73,23 @@
         self.len = self.__len__()
         if self.sum == 0:
             self.sum = dpkt.in_cksum(self.pack_hdr() + bytes(self.opts))
-            if (self.p == 6 or self.p == 17) and (self.off & (IP_MF | IP_OFFMASK)) == 0 and \
+            if (self.p == 6 or self.p == 17) and (self._flags_offset & (IP_MF | IP_OFFMASK)) == 0 and \
                     isinstance(self.data, dpkt.Packet) and self.data.sum == 0:
                 # Set zeroed TCP and UDP checksums for non-fragments.
                 p = bytes(self.data)
-                s = dpkt.struct.pack('>4s4sxBH', self.src, self.dst,
-                                     self.p, len(p))
+                s = dpkt.struct.pack('>4s4sxBH', self.src, self.dst, self.p, len(p))
                 s = dpkt.in_cksum_add(0, s)
                 s = dpkt.in_cksum_add(s, p)
                 self.data.sum = dpkt.in_cksum_done(s)
+
+                # RFC 768 (Fields):
+                # If the computed checksum is zero, it is transmitted as all
+                # ones (the equivalent in one's complement arithmetic). An all
+                # zero transmitted checksum value means that the transmitter
+                # generated no checksum (for debugging or for higher level
+                # protocols that don't care).
                 if self.p == 17 and self.data.sum == 0:
-                    self.data.sum = 0xffff  # RFC 768
+                    self.data.sum = 0xffff
                     # XXX - skip transports which don't need the pseudoheader
         return self.pack_hdr() + bytes(self.opts) + bytes(self.data)
 
@@ -134,6 +117,18 @@
     def get_proto(cls, p):
         return cls._protosw[p]
 
+    # XXX - compatibility; to be deprecated
+    @property
+    def off(self):
+        deprecation_warning("IP.off is deprecated")
+        return self._flags_offset
+
+    @off.setter
+    def off(self, val):
+        deprecation_warning("IP.off is deprecated")
+        self.offset = val
+
+
 # IP Headers
 IP_ADDR_LEN = 0x04
 IP_ADDR_BITS = 0x20
@@ -329,10 +324,11 @@
     g = globals()
     for k, v in iteritems(g):
         if k.startswith('IP_PROTO_'):
-            name = k[9:].lower()
+            name = k[9:]
+            _ip_proto_names[v] = name
             try:
-                mod = __import__(name, g, level=1)
-                IP.set_proto(v, getattr(mod, name.upper()))
+                mod = __import__(name.lower(), g, level=1)
+                IP.set_proto(v, getattr(mod, name))
             except (ImportError, AttributeError):
                 continue
 
@@ -363,8 +359,19 @@
     assert (ip.udp.data == b'foobar')
 
 
-def test_hl():  # Todo chack this test method
-    s = b'BB\x03\x00\x00\x00\x00\x00\x00\x00\xd0\x00\xec\xbc\xa5\x00\x00\x00\x03\x80\x00\x00\xd0\x01\xf2\xac\xa5"0\x01\x00\x14\x00\x02\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+def test_dict():
+    ip = IP(id=0, src=b'\x01\x02\x03\x04', dst=b'\x01\x02\x03\x04', p=17)
+    d = dict(ip)
+
+    assert (d['src'] == b'\x01\x02\x03\x04')
+    assert (d['dst'] == b'\x01\x02\x03\x04')
+    assert (d['id'] == 0)
+    assert (d['p'] == 17)
+
+
+def test_hl():  # Todo check this test method
+    s = (b'BB\x03\x00\x00\x00\x00\x00\x00\x00\xd0\x00\xec\xbc\xa5\x00\x00\x00\x03\x80\x00\x00\xd0'
+         b'\x01\xf2\xac\xa5"0\x01\x00\x14\x00\x02\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00')
     try:
         IP(s)
     except dpkt.UnpackError:
@@ -372,7 +379,9 @@
 
 
 def test_opt():
-    s = b'\x4f\x00\x00\x3c\xae\x08\x00\x00\x40\x06\x18\x10\xc0\xa8\x0a\x26\xc0\xa8\x0a\x01\x07\x27\x08\x01\x02\x03\x04\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+    s = (b'\x4f\x00\x00\x3c\xae\x08\x00\x00\x40\x06\x18\x10\xc0\xa8\x0a\x26\xc0\xa8\x0a\x01\x07\x27'
+         b'\x08\x01\x02\x03\x04\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+         b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
     ip = IP(s)
     ip.sum = 0
     assert (bytes(ip) == s)
@@ -381,15 +390,16 @@
 def test_zerolen():
     from . import tcp
     d = b'X' * 2048
-    s = b'E\x00\x00\x004\xce@\x00\x80\x06\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\xccN\x0c8`\xff\xc6N_\x8a\x12\x98P\x18@):\xa3\x00\x00' + d
+    s = (b'\x45\x00\x00\x00\x34\xce\x40\x00\x80\x06\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\xcc\x4e'
+         b'\x0c\x38\x60\xff\xc6\x4e\x5f\x8a\x12\x98\x50\x18\x40\x29\x3a\xa3\x00\x00') + d
     ip = IP(s)
     assert (isinstance(ip.data, tcp.TCP))
     assert (ip.tcp.data == d)
 
 
 def test_constuctor():
-    ip1 = IP(data = b"Hello world!")
-    ip2 = IP(data = b"Hello world!", len = 0)
+    ip1 = IP(data=b"Hello world!")
+    ip2 = IP(data=b"Hello world!", len=0)
     ip3 = IP(bytes(ip1))
     ip4 = IP(bytes(ip2))
     assert (bytes(ip1) == bytes(ip3))
@@ -397,9 +407,14 @@
     assert (bytes(ip2) == bytes(ip4))
     assert (bytes(ip2) == b'E\x00\x00 \x00\x00\x00\x00@\x00z\xdf\x00\x00\x00\x00\x00\x00\x00\x00Hello world!')
 
+
 def test_frag():
     from . import ethernet
-    s = b"\x00\x23\x20\xd4\x2a\x8c\x00\x23\x20\xd4\x2a\x8c\x08\x00\x45\x00\x00\x54\x00\x00\x40\x00\x40\x01\x25\x8d\x0a\x00\x00\x8f\x0a\x00\x00\x8e\x08\x00\x2e\xa0\x01\xff\x23\x73\x20\x48\x4a\x4d\x00\x00\x00\x00\x78\x85\x02\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37"
+    s = (b'\x00\x23\x20\xd4\x2a\x8c\x00\x23\x20\xd4\x2a\x8c\x08\x00\x45\x00\x00\x54\x00\x00\x40\x00'
+         b'\x40\x01\x25\x8d\x0a\x00\x00\x8f\x0a\x00\x00\x8e\x08\x00\x2e\xa0\x01\xff\x23\x73\x20\x48'
+         b'\x4a\x4d\x00\x00\x00\x00\x78\x85\x02\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17'
+         b'\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d'
+         b'\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37')
     ip = ethernet.Ethernet(s).ip
     assert (ip.rf == 0)
     assert (ip.df == 1)
@@ -417,11 +432,42 @@
     assert (ip.offset == 1480)
 
 
-if __name__ == '__main__':
-    test_ip()
-    test_hl()
-    test_opt()
-    test_zerolen()
-    test_constuctor()
-    test_frag()
-    print('Tests Successful...')
+def test_property_setters():
+    ip = IP()
+    assert ip.v == 4
+    ip.v = 6
+    assert ip.v == 6
+    # test property delete
+    del ip.v
+    assert ip.v == 4  # back to default
+
+    assert ip.hl == 5
+    ip.hl = 7
+    assert ip.hl == 7
+    del ip.hl
+    assert ip.hl == 5
+
+    # coverage
+    ip.off = 10
+    assert ip.off == 10
+
+
+def test_default_udp_checksum():
+    from dpkt.udp import UDP
+
+    udp = UDP(sport=1, dport=0xffdb)
+    ip = IP(src=b'\x00\x00\x00\x01', dst=b'\x00\x00\x00\x01', p=17, data=udp)
+    assert ip.p == 17
+    assert ip.data.sum == 0
+
+    # this forces recalculation of the data layer checksum
+    bytes(ip)
+
+    # during calculation the checksum was evaluated to 0x0000
+    # this was then conditionally set to 0xffff per RFC768
+    assert ip.data.sum == 0xffff
+
+
+def test_get_proto_name():
+    assert get_ip_proto_name(6) == 'TCP'
+    assert get_ip_proto_name(999) is None
diff --git a/dpkt/ip6.py b/dpkt/ip6.py
index b19e407..8902d51 100644
--- a/dpkt/ip6.py
+++ b/dpkt/ip6.py
@@ -6,8 +6,15 @@
 
 from . import dpkt
 from . import ip
-from .decorators import deprecated
+from . import tcp
 from .compat import compat_ord
+from .utils import inet_to_str
+import struct
+
+# The allowed extension headers and their classes (in order according to RFC).
+EXT_HDRS = [ip.IP_PROTO_HOPOPTS, ip.IP_PROTO_ROUTING, ip.IP_PROTO_FRAGMENT, ip.IP_PROTO_AH, ip.IP_PROTO_ESP,
+            ip.IP_PROTO_DSTOPTS]
+# EXT_HDRS_CLS - classes is below - after all the used classes are defined.
 
 
 class IP6(dpkt.Packet):
@@ -25,40 +32,35 @@
         ('plen', 'H', 0),  # payload length (not including header)
         ('nxt', 'B', 0),  # next header protocol
         ('hlim', 'B', 0),  # hop limit
-        ('src', '16s', ''),
-        ('dst', '16s', '')
+        ('src', '16s', b''),
+        ('dst', '16s', b'')
     )
-
+    __bit_fields__ = {
+        '_v_fc_flow': (
+            ('v', 4),      # version, 4 hi bits
+            ('fc', 8),     # traffic class, 8 bits
+            ('flow', 20),  # flow label, 20 lo bits
+        )
+    }
+    __pprint_funcs__ = {
+        'src': inet_to_str,
+        'dst': inet_to_str
+    }
     _protosw = ip.IP._protosw
 
-    @property
-    def v(self):
-        return self._v_fc_flow >> 28
-
-    @v.setter
-    def v(self, v):
-        self._v_fc_flow = (self._v_fc_flow & ~0xf0000000) | (v << 28)
-
-    @property
-    def fc(self):
-        return (self._v_fc_flow >> 20) & 0xff
-
-    @fc.setter
-    def fc(self, v):
-        self._v_fc_flow = (self._v_fc_flow & ~0xff00000) | (v << 20)
-
-    @property
-    def flow(self):
-        return self._v_fc_flow & 0xfffff
-
-    @flow.setter
-    def flow(self, v):
-        self._v_fc_flow = (self._v_fc_flow & ~0xfffff) | (v & 0xfffff)
-
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
         self.extension_hdrs = {}
 
+        # NOTE: self.extension_hdrs is not accurate, as it doesn't support duplicate header types.
+        # According to RFC-1883 "Each extension header should occur at most once, except for the
+        # Destination Options header which should occur at most twice".
+        # Secondly, the .headers_str() method attempts to pack the extension headers in order as
+        # defined in the RFC, however it doesn't adjust the next header (nxt) pointer accordingly.
+        # Here we introduce the new field .all_extension_headers; it allows duplicate types and
+        # keeps the original order.
+        self.all_extension_headers = []
+
         if self.plen:
             buf = self.data[:self.plen]
         else:  # due to jumbo payload or TSO
@@ -66,9 +68,10 @@
 
         next_ext_hdr = self.nxt
 
-        while next_ext_hdr in ext_hdrs:
-            ext = ext_hdrs_cls[next_ext_hdr](buf)
+        while next_ext_hdr in EXT_HDRS:
+            ext = EXT_HDRS_CLS[next_ext_hdr](buf)
             self.extension_hdrs[next_ext_hdr] = ext
+            self.all_extension_headers.append(ext)
             buf = buf[ext.length:]
             next_ext_hdr = getattr(ext, 'nxt', None)
 
@@ -76,6 +79,12 @@
         if next_ext_hdr is not None:
             self.p = next_ext_hdr
 
+        # do not decode fragments after the first fragment
+        # https://github.com/kbandla/dpkt/issues/575
+        if self.nxt == 44 and ext.frag_off > 0:  # 44 = IP_PROTO_FRAGMENT
+            self.data = buf
+            return
+
         try:
             self.data = self._protosw[next_ext_hdr](buf)
             setattr(self, self.data.__class__.__name__.lower(), self.data)
@@ -83,27 +92,43 @@
             self.data = buf
 
     def headers_str(self):
-        """Output extension headers in order defined in RFC1883 (except dest opts)"""
+        nxt = self.nxt
+        # If all_extension_headers is available, return the headers as they originally appeared
+        if hasattr(self, 'all_extension_headers') and self.all_extension_headers:
+            # get the nxt header from the last one
+            nxt = self.all_extension_headers[-1].nxt
+            return nxt, b''.join(bytes(ext) for ext in self.all_extension_headers)
 
+        # Output extension headers in order defined in RFC1883 (except dest opts)
         header_str = b""
-
-        for hdr in ext_hdrs:
-            if hdr in self.extension_hdrs:
-                header_str += bytes(self.extension_hdrs[hdr])
-        return header_str
+        if hasattr(self, 'extension_hdrs'):
+            for hdr in EXT_HDRS:
+                if hdr in self.extension_hdrs:
+                    nxt = self.extension_hdrs[hdr].nxt
+                    header_str += bytes(self.extension_hdrs[hdr])
+        return nxt, header_str
 
     def __bytes__(self):
-        if (self.p == 6 or self.p == 17 or self.p == 58) and not self.data.sum:
-            # XXX - set TCP, UDP, and ICMPv6 checksums
+        self.p, hdr_str = self.headers_str()
+
+        # set TCP, UDP, and ICMPv6 checksums
+        if ((self.p == 6 or self.p == 17 or self.p == 58) and
+           hasattr(self.data, 'sum') and not self.data.sum):
             p = bytes(self.data)
-            s = dpkt.struct.pack('>16s16sxBH', self.src, self.dst, self.nxt, len(p))
+            s = struct.pack('>16s16sxBH', self.src, self.dst, self.p, len(p))
             s = dpkt.in_cksum_add(0, s)
             s = dpkt.in_cksum_add(s, p)
-            try:
-                self.data.sum = dpkt.in_cksum_done(s)
-            except AttributeError:
-                pass
-        return self.pack_hdr() + self.headers_str() + bytes(self.data)
+            self.data.sum = dpkt.in_cksum_done(s)
+
+        return self.pack_hdr() + hdr_str + bytes(self.data)
+
+    def __len__(self):
+        baselen = self.__hdr_len__ + len(self.data)
+        if hasattr(self, 'all_extension_headers') and self.all_extension_headers:
+            return baselen + sum(len(hh) for hh in self.all_extension_headers)
+        elif hasattr(self, 'extension_hdrs') and self.extension_hdrs:
+            return baselen + sum(len(hh) for hh in self.extension_hdrs.values())
+        return baselen
 
     @classmethod
     def set_proto(cls, p, pktclass):
@@ -125,7 +150,7 @@
 class IP6OptsHeader(IP6ExtensionHeader):
     __hdr__ = (
         ('nxt', 'B', 0),  # next extension header protocol
-        ('len', 'B', 0)  # option data length in 8 octect units (ignoring first 8 octets) so, len 0 == 64bit header
+        ('len', 'B', 0)  # option data length in 8 octet units (ignoring first 8 octets) so, len 0 == 64bit header
     )
 
     def unpack(self, buf):
@@ -136,27 +161,35 @@
         index = 0
 
         while index < self.length - 2:
-            opt_type = compat_ord(self.data[index])
+            try:
+                opt_type = compat_ord(self.data[index])
 
-            # PAD1 option
-            if opt_type == 0:
-                index += 1
-                continue
+                # PAD1 option
+                if opt_type == 0:
+                    index += 1
+                    continue
 
-            opt_length = compat_ord(self.data[index + 1])
+                opt_length = compat_ord(self.data[index + 1])
 
-            if opt_type == 1:  # PADN option
-                # PADN uses opt_length bytes in total
+                if opt_type == 1:  # PADN option
+                    # PADN uses opt_length bytes in total
+                    index += opt_length + 2
+                    continue
+
+                options.append({
+                    'type': opt_type,
+                    'opt_length': opt_length,
+                    'data': self.data[index + 2:index + 2 + opt_length]
+                })
+
+                # add the two chars and the option_length, to move to the next option
                 index += opt_length + 2
-                continue
 
-            options.append(
-                {'type': opt_type, 'opt_length': opt_length, 'data': self.data[index + 2:index + 2 + opt_length]})
-
-            # add the two chars and the option_length, to move to the next option
-            index += opt_length + 2
+            except IndexError:
+                raise dpkt.NeedData
 
         self.options = options
+        self.data = buf[2:self.length]  # keep raw data with all pad options, but not the following data
 
 
 class IP6HopOptsHeader(IP6OptsHeader):
@@ -170,19 +203,17 @@
 class IP6RoutingHeader(IP6ExtensionHeader):
     __hdr__ = (
         ('nxt', 'B', 0),  # next extension header protocol
-        ('len', 'B', 0),  # extension data length in 8 octect units (ignoring first 8 octets) (<= 46 for type 0)
+        ('len', 'B', 0),  # extension data length in 8 octet units (ignoring first 8 octets) (<= 46 for type 0)
         ('type', 'B', 0),  # routing type (currently, only 0 is used)
         ('segs_left', 'B', 0),  # remaining segments in route, until destination (<= 23)
-        ('rsvd_sl_bits', 'I', 0),  # reserved (1 byte), strict/loose bitmap for addresses
+        ('_rsvd_sl_bits', 'I', 0)
     )
-
-    @property
-    def sl_bits(self):
-        return self.rsvd_sl_bits & 0xffffff
-
-    @sl_bits.setter
-    def sl_bits(self, v):
-        self.rsvd_sl_bits = (self.rsvd_sl_bits & ~0xfffff) | (v & 0xfffff)
+    __bit_fields__ = {
+        '_rsvd_sl_bits': (
+            ('_rsvd', 8),     # reserved (1 byte)
+            ('sl_bits', 24),  # strict/loose bitmap for addresses
+        )
+    }
 
     def unpack(self, buf):
         hdr_size = 8
@@ -205,38 +236,29 @@
 class IP6FragmentHeader(IP6ExtensionHeader):
     __hdr__ = (
         ('nxt', 'B', 0),  # next extension header protocol
-        ('resv', 'B', 0),  # reserved, set to 0
-        ('frag_off_resv_m', 'H', 0),  # frag offset (13 bits), reserved zero (2 bits), More frags flag
+        ('_resv', 'B', 0),  # reserved, set to 0
+        ('_frag_off_resv_m', 'H', 0),
         ('id', 'I', 0)  # fragments id
     )
+    __bit_fields__ = {
+        '_frag_off_resv_m': (
+            ('frag_off', 13),  # frag offset, 13 bits
+            ('_resv', 2),      # reserved zero (2 bits)
+            ('m_flag', 1),     # more frags flag
+        )
+    }
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
         self.length = self.__hdr_len__
         self.data = b''
 
-    @property
-    def frag_off(self):
-        return self.frag_off_resv_m >> 3
-
-    @frag_off.setter
-    def frag_off(self, v):
-        self.frag_off_resv_m = (self.frag_off_resv_m & ~0xfff8) | (v << 3)
-
-    @property
-    def m_flag(self):
-        return self.frag_off_resv_m & 1
-
-    @m_flag.setter
-    def m_flag(self, v):
-        self.frag_off_resv_m = (self.frag_off_resv_m & ~0xfffe) | v
-
 
 class IP6AHHeader(IP6ExtensionHeader):
     __hdr__ = (
         ('nxt', 'B', 0),  # next extension header protocol
         ('len', 'B', 0),  # length of header in 4 octet units (ignoring first 2 units)
-        ('resv', 'H', 0),  # reserved, 2 bytes of 0
+        ('_resv', 'H', 0),  # reserved, 2 bytes of 0
         ('spi', 'I', 0),  # SPI security parameter index
         ('seq', 'I', 0)  # sequence no.
     )
@@ -258,74 +280,106 @@
         self.length = self.__hdr_len__ + len(self.data)
 
 
-ext_hdrs = [ip.IP_PROTO_HOPOPTS, ip.IP_PROTO_ROUTING, ip.IP_PROTO_FRAGMENT, ip.IP_PROTO_AH, ip.IP_PROTO_ESP,
-            ip.IP_PROTO_DSTOPTS]
-ext_hdrs_cls = {ip.IP_PROTO_HOPOPTS: IP6HopOptsHeader,
+EXT_HDRS_CLS = {ip.IP_PROTO_HOPOPTS: IP6HopOptsHeader,
                 ip.IP_PROTO_ROUTING: IP6RoutingHeader,
                 ip.IP_PROTO_FRAGMENT: IP6FragmentHeader,
                 ip.IP_PROTO_ESP: IP6ESPHeader,
                 ip.IP_PROTO_AH: IP6AHHeader,
                 ip.IP_PROTO_DSTOPTS: IP6DstOptsHeader}
 
+# Unit tests
+
 
 def test_ipg():
-    s = b'`\x00\x00\x00\x00(\x06@\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x11$\xff\xfe\x8c\x11\xde\xfe\x80\x00\x00\x00\x00\x00\x00\x02\xb0\xd0\xff\xfe\xe1\x80r\xcd\xca\x00\x16\x04\x84F\xd5\x00\x00\x00\x00\xa0\x02\xff\xff\xf8\t\x00\x00\x02\x04\x05\xa0\x01\x03\x03\x00\x01\x01\x08\n}\x185?\x00\x00\x00\x00'
+    s = (b'\x60\x00\x00\x00\x00\x28\x06\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x11\x24\xff\xfe\x8c'
+         b'\x11\xde\xfe\x80\x00\x00\x00\x00\x00\x00\x02\xb0\xd0\xff\xfe\xe1\x80\x72\xcd\xca\x00\x16'
+         b'\x04\x84\x46\xd5\x00\x00\x00\x00\xa0\x02\xff\xff\xf8\x09\x00\x00\x02\x04\x05\xa0\x01\x03'
+         b'\x03\x00\x01\x01\x08\x0a\x7d\x18\x35\x3f\x00\x00\x00\x00')
     _ip = IP6(s)
-    # print `ip`
+
+    # basic properties
+    assert _ip.v == 6
+    assert _ip.fc == 0
+    assert _ip.flow == 0
+
     _ip.data.sum = 0
     s2 = bytes(_ip)
-    IP6(s)
-    # print `ip2`
-    assert (s == s2)
+    assert s == s2
+
+
+def test_dict():
+    s = (b'\x60\x00\x00\x00\x00\x28\x06\x40\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x11\x24\xff\xfe\x8c'
+         b'\x11\xde\xfe\x80\x00\x00\x00\x00\x00\x00\x02\xb0\xd0\xff\xfe\xe1\x80\x72\xcd\xca\x00\x16'
+         b'\x04\x84\x46\xd5\x00\x00\x00\x00\xa0\x02\xff\xff\xf8\x09\x00\x00\x02\x04\x05\xa0\x01\x03'
+         b'\x03\x00\x01\x01\x08\x0a\x7d\x18\x35\x3f\x00\x00\x00\x00')
+    _ip = IP6(s)
+    d = dict(_ip)
+
+    # basic properties
+    assert d['src'] == b'\xfe\x80\x00\x00\x00\x00\x00\x00\x02\x11\x24\xff\xfe\x8c\x11\xde'
+    assert d['dst'] == b'\xfe\x80\x00\x00\x00\x00\x00\x00\x02\xb0\xd0\xff\xfe\xe1\x80\x72'
 
 
 def test_ip6_routing_header():
-    s = b'`\x00\x00\x00\x00<+@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x06\x04\x00\x02\x00\x00\x00\x00 \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91\x7f\x00\x00'
-    ip = IP6(s)
-    s2 = bytes(ip)
+    s = (b'\x60\x00\x00\x00\x00\x3c\x2b\x40\x20\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+         b'\xde\xca\x20\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x06\x04\x00\x02'
+         b'\x00\x00\x00\x00\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca\x20\x22'
+         b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca\x00\x14\x00\x50\x00\x00\x00\x00'
+         b'\x00\x00\x00\x00\x50\x02\x20\x00\x91\x7f\x00\x00')
+    _ip = IP6(s)
+    s2 = bytes(_ip)
     # 43 is Routing header id
-    assert (len(ip.extension_hdrs[43].addresses) == 2)
-    assert ip.tcp
-    assert (s == s2)
-    assert bytes(ip) == s
+    assert len(_ip.extension_hdrs[43].addresses) == 2
+    assert _ip.tcp
+    assert s == s2
 
 
 def test_ip6_fragment_header():
-    s = b'\x06\xee\xff\xfb\x00\x00\xff\xff'
+    s = b'\x06\xee\xff\xf9\x00\x00\xff\xff'
     fh = IP6FragmentHeader(s)
     # s2 = str(fh) variable 's2' is not used
-    bytes(fh)
-    assert (fh.nxt == 6)
-    assert (fh.id == 65535)
-    assert (fh.frag_off == 8191)
-    assert (fh.m_flag == 1)
+    assert fh.nxt == 6
+    assert fh.id == 65535
+    assert fh.frag_off == 8191
+    assert fh.m_flag == 1
+
+    # test packing
+    fh._frag_off_resv_m = 0
+    fh.frag_off = 8191
+    fh.m_flag = 1
     assert bytes(fh) == s
 
     # IP6 with fragment header
-    s = b'\x60\x00\x00\x00\x00\x10\x2c\x00\x02\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x03\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x29\x00\x00\x01\x00\x00\x00\x00\x60\x00\x00\x00\x00\x10\x2c\x00'
-    ip = IP6(s)
-    assert bytes(ip) == s
+    s = (b'\x60\x00\x00\x00\x00\x10\x2c\x00\x02\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+         b'\x00\x02\x03\x33\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x29\x00\x00\x01'
+         b'\x00\x00\x00\x00\x60\x00\x00\x00\x00\x10\x2c\x00')
+    _ip = IP6(s)
+    assert bytes(_ip) == s
 
 
 def test_ip6_options_header():
-    s = b';\x04\x01\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\xc2\x04\x00\x00\x00\x00\x05\x02\x00\x00\x01\x02\x00\x00'
+    s = (b'\x3b\x04\x01\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+         b'\x00\x00\x01\x00\xc2\x04\x00\x00\x00\x00\x05\x02\x00\x00\x01\x02\x00\x00')
     options = IP6OptsHeader(s).options
-    assert (len(options) == 3)
+    assert len(options) == 3
     assert bytes(IP6OptsHeader(s)) == s
 
 
 def test_ip6_ah_header():
-    s = b';\x04\x00\x00\x02\x02\x02\x02\x01\x01\x01\x01\x78\x78\x78\x78\x78\x78\x78\x78'
+    s = b'\x3b\x04\x00\x00\x02\x02\x02\x02\x01\x01\x01\x01\x78\x78\x78\x78\x78\x78\x78\x78'
     ah = IP6AHHeader(s)
-    assert (ah.length == 24)
-    assert (ah.auth_data == b'xxxxxxxx')
-    assert (ah.spi == 0x2020202)
-    assert (ah.seq == 0x1010101)
+    assert ah.length == 24
+    assert ah.auth_data == b'xxxxxxxx'
+    assert ah.spi == 0x2020202
+    assert ah.seq == 0x1010101
     assert bytes(ah) == s
 
 
 def test_ip6_esp_header():
-    s = b'\x00\x00\x01\x00\x00\x00\x00\x44\xe2\x4f\x9e\x68\xf3\xcd\xb1\x5f\x61\x65\x42\x8b\x78\x0b\x4a\xfd\x13\xf0\x15\x98\xf5\x55\x16\xa8\x12\xb3\xb8\x4d\xbc\x16\xb2\x14\xbe\x3d\xf9\x96\xd4\xa0\x39\x1f\x85\x74\x25\x81\x83\xa6\x0d\x99\xb6\xba\xa3\xcc\xb6\xe0\x9a\x78\xee\xf2\xaf\x9a'
+    s = (b'\x00\x00\x01\x00\x00\x00\x00\x44\xe2\x4f\x9e\x68\xf3\xcd\xb1\x5f\x61\x65\x42\x8b\x78\x0b'
+         b'\x4a\xfd\x13\xf0\x15\x98\xf5\x55\x16\xa8\x12\xb3\xb8\x4d\xbc\x16\xb2\x14\xbe\x3d\xf9\x96'
+         b'\xd4\xa0\x39\x1f\x85\x74\x25\x81\x83\xa6\x0d\x99\xb6\xba\xa3\xcc\xb6\xe0\x9a\x78\xee\xf2'
+         b'\xaf\x9a')
     esp = IP6ESPHeader(s)
     assert esp.length == 68
     assert esp.spi == 256
@@ -333,26 +387,191 @@
 
 
 def test_ip6_extension_headers():
-    p = b'`\x00\x00\x00\x00<+@ H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x06\x04\x00\x02\x00\x00\x00\x00 \x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00\x91\x7f\x00\x00'
-    ip = IP6(p)
-    o = b';\x04\x01\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\xc2\x04\x00\x00\x00\x00\x05\x02\x00\x00\x01\x02\x00\x00'
-    options = IP6HopOptsHeader(o)
-    ip.extension_hdrs[0] = options
+    p = (b'\x60\x00\x00\x00\x00\x3c\x2b\x40\x20\x48\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+         b'\xde\xca\x20\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xca\xfe\x06\x04\x00\x02'
+         b'\x00\x00\x00\x00\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca\x20\x22'
+         b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\xca\x00\x14\x00\x50\x00\x00\x00\x00'
+         b'\x00\x00\x00\x00\x50\x02\x20\x00\x91\x7f\x00\x00')
+    _ip = IP6(p)
+    o = (b'\x3b\x04\x01\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+         b'\x00\x00\x01\x00\xc2\x04\x00\x00\x00\x00\x05\x02\x00\x00\x01\x02\x00\x00')
+    _ip.extension_hdrs[0] = IP6HopOptsHeader(o)
     fh = b'\x06\xee\xff\xfb\x00\x00\xff\xff'
-    ip.extension_hdrs[44] = IP6FragmentHeader(fh)
-    ah = b';\x04\x00\x00\x02\x02\x02\x02\x01\x01\x01\x01\x78\x78\x78\x78\x78\x78\x78\x78'
-    ip.extension_hdrs[51] = IP6AHHeader(ah)
-    do = b';\x02\x01\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
-    ip.extension_hdrs[60] = IP6DstOptsHeader(do)
-    assert (len(ip.extension_hdrs) == 5)
+    _ip.extension_hdrs[44] = IP6FragmentHeader(fh)
+    ah = b'\x3b\x04\x00\x00\x02\x02\x02\x02\x01\x01\x01\x01\x78\x78\x78\x78\x78\x78\x78\x78'
+    _ip.extension_hdrs[51] = IP6AHHeader(ah)
+    do = b'\x3b\x02\x01\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+    _ip.extension_hdrs[60] = IP6DstOptsHeader(do)
+    assert len(_ip.extension_hdrs) == 5
+
+    # this is a legacy unit test predating the addition of .all_extension_headers
+    # this way of adding extension headers does not update .all_extension_headers
+    # so we need to kick .all_extension_headers to force the __len__() method pick up
+    # the updated legacy attribute and calculate the len correctly
+    del _ip.all_extension_headers
+    assert len(_ip) == len(p) + len(o) + len(fh) + len(ah) + len(do)
 
 
-if __name__ == '__main__':
-    test_ipg()
-    test_ip6_routing_header()
-    test_ip6_fragment_header()
-    test_ip6_options_header()
-    test_ip6_ah_header()
-    test_ip6_esp_header()
-    test_ip6_extension_headers()
-    print('Tests Successful...')
+def test_ip6_all_extension_headers():  # https://github.com/kbandla/dpkt/pull/403
+    s = (b'\x60\x00\x00\x00\x00\x47\x3c\x40\xfe\xd0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01'
+         b'\x00\x02\xfe\xd0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x3c\x00\x01\x04'
+         b'\x00\x00\x00\x00\x3c\x00\x01\x04\x00\x00\x00\x00\x2c\x00\x01\x04\x00\x00\x00\x00\x2c\x00'
+         b'\x00\x00\x00\x00\x00\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x2c\x00\x01\x04\x00\x00\x00\x00'
+         b'\x3a\x00\x00\x00\x00\x00\x00\x00\x80\x00\xd8\xe5\x0c\x1a\x00\x00\x50\x61\x79\x4c\x6f\x61'
+         b'\x64')
+    _ip = IP6(s)
+    assert _ip.p == 58  # ICMPv6
+    hdrs = _ip.all_extension_headers
+    assert len(hdrs) == 7
+    assert isinstance(hdrs[0], IP6DstOptsHeader)
+    assert isinstance(hdrs[3], IP6FragmentHeader)
+    assert isinstance(hdrs[5], IP6DstOptsHeader)
+    assert bytes(_ip) == s
+    assert len(_ip) == len(s)
+
+
+def test_ip6_gen_tcp_ack():
+    t = tcp.TCP()
+    t.win = 8192
+    t.dport = 80
+    t.sport = 4711
+    t.flags = tcp.TH_ACK
+    t.seq = 22
+    t.ack = 33
+    ipp = IP6()
+    ipp.src = b'\xfd\x00\x00\x00\x00\x00\x00\x00\xc8\xba\x88\x88\x00\xaa\xbb\x01'
+    ipp.dst = b'\x00d\xff\x9b\x00\x00\x00\x00\x00\x00\x00\x00\xc1\n@*'
+    ipp.hlim = 64
+    ipp.nxt = ip.IP_PROTO_TCP
+    ipp.data = t
+    ipp.plen = ipp.data.ulen = len(ipp.data)
+
+    assert len(bytes(ipp)) == 60
+    assert ipp.p == ip.IP_PROTO_TCP
+
+    # Second part of testing - with ext headers.
+    ipp.p = 0
+    o = (b'\x3b\x04\x01\x02\x00\x00\xc9\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+         b'\x00\x00\x01\x00\xc2\x04\x00\x00\x00\x00\x05\x02\x00\x00\x01\x02\x00\x00')
+    ipp.extension_hdrs = {}
+    ipp.extension_hdrs[0] = IP6HopOptsHeader(o)
+    ipp.extension_hdrs[0].nxt = ip.IP_PROTO_TCP
+    ipp.nxt = ip.proto = ip.IP_PROTO_HOPOPTS
+    _p, exthdrs = ipp.headers_str()
+    ipp.plen = len(exthdrs) + len(ipp.data)
+
+    assert bytes(ipp)
+
+    assert ipp.p == ip.IP_PROTO_TCP
+    assert ipp.nxt == ip.IP_PROTO_HOPOPTS
+
+
+def test_ip6_opts():
+    import pytest
+    # https://github.com/kbandla/dpkt/issues/477
+    s = (b'\x52\x54\x00\xf3\x83\x6f\x52\x54\x00\x86\x33\xd9\x86\xdd\x60\x00\x00\x00\x05\x08\x3a\xff'
+         b'\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xfd\x00\x00\x00\x00\x00'
+         b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\xd2\xf3\x00\x00\x05\x00\x00\x00\x00\x00'
+         b'\x00\x00\x00\x00\x00\x01\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02'
+         b'\x00\x50\xd4\x34\x1a\x48\x24\x50\x6d\x8d\xb3\xc2\x80\x10\x01\xf6\x46\xe8\x00\x00\x01\x01'
+         b'\x08\x0a\xd7\x9d\x6b\x8a\x3a\xd1\xf4\x58\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61'
+         b'\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61'
+         b'\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61'
+         b'\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x0a')
+
+    from dpkt.ethernet import Ethernet
+
+    assert Ethernet(s)
+    assert Ethernet(s).ip6
+    assert Ethernet(s).ip6.icmp6
+    assert Ethernet(s).ip6.icmp6.data
+
+    with pytest.raises(dpkt.NeedData):
+        IP6(Ethernet(s).ip6.icmp6.data)  # should raise NeedData
+
+    from binascii import unhexlify
+    buf_ip6_opts = unhexlify(
+        '00'  # nxt
+        '00'  # len
+
+        '000000000000'  # only padding
+    )
+    ip6opt = IP6OptsHeader(buf_ip6_opts)
+    assert ip6opt.options == []
+    assert ip6opt.data == b'\x00' * 6
+
+
+def test_ip6_routing_properties():
+    ip6rh = IP6RoutingHeader()
+    assert ip6rh.sl_bits == 0
+    ip6rh.sl_bits = 1024
+    assert ip6rh.sl_bits == 1024
+
+
+def test_ip6_fragment_properties():
+    ip6fh = IP6FragmentHeader()
+    assert ip6fh.frag_off == 0
+    ip6fh.frag_off = 1234
+    assert ip6fh.frag_off == 1234
+
+    assert ip6fh.m_flag == 0
+    ip6fh.m_flag = 1
+    assert ip6fh.m_flag == 1
+
+
+def test_ip6_properties():
+    ip6 = IP6()
+
+    assert ip6.v == 6
+    ip6.v = 10
+    assert ip6.v == 10
+
+    assert ip6.fc == 0
+    ip6.fc = 5
+    assert ip6.fc == 5
+
+    assert ip6.flow == 0
+    ip6.flow = 4
+    assert ip6.flow == 4
+
+    # property delete
+    del ip6.v
+    del ip6.fc
+    del ip6.flow
+    assert ip6.v == 6
+    assert ip6.fc == 0
+    assert ip6.flow == 0
+
+
+def test_proto_accessors():
+    class Proto:
+        pass
+
+    assert 'PROTO' not in IP6._protosw
+    IP6.set_proto('PROTO', Proto)
+    assert IP6.get_proto('PROTO') == Proto
+
+
+def test_ip6_fragment_no_decode():  # https://github.com/kbandla/dpkt/issues/575
+    from . import udp
+
+    # fragment 0
+    s = (b'\x60\x00'
+         b'\x00\x00\x00\x2c\x11\x3f\x20\x01\x06\x38\x05\x01\x8e\xfe\xcc\x4a'
+         b'\x48\x39\xfa\x79\x04\xdc\x20\x01\x05\x00\x00\x60\x00\x00\x00\x00'
+         b'\x00\x00\x00\x00\x00\x30\xde\xf2\x00\x35\x00\x2c\x61\x50\x4d\x8b'
+         b'\x01\x20\x00\x01\x00\x00\x00\x00\x00\x01\x03\x69\x73\x63\x03\x6f'
+         b'\x72\x67\x00\x00\xff\x00\x01\x00\x00\x29\x10\x00\x00\x00\x80\x00'
+         b'\x00\x00')
+    frag0 = IP6(s)
+    assert type(frag0.data) == udp.UDP
+
+    s = (b'\x60\x00\x00\x00\x01\x34\x2c\x35\x20\x01\x05\x00\x00\x60\x00\x00'
+         b'\x00\x00\x00\x00\x00\x00\x00\x30\x20\x01\x06\x38\x05\x01\x8e\xfe'
+         b'\xcc\x4a\x48\x39\xfa\x79\x04\xdc'
+         b'\x11\x72\x31\xb9\xc1\x0f\xcf\x7c\x61\x62\x63\x64\x65\x66\x67\x68')  # partial data
+    frag2 = IP6(s)
+    assert type(frag2.data) == bytes
+
+    # test packing
+    assert bytes(frag2) == s
diff --git a/dpkt/ipx.py b/dpkt/ipx.py
index 26d36a9..7b22e69 100644
--- a/dpkt/ipx.py
+++ b/dpkt/ipx.py
@@ -17,12 +17,12 @@
         __hdr__: Header fields of IPX.
         TODO.
     """
-    
+
     __hdr__ = (
         ('sum', 'H', 0xffff),
         ('len', 'H', IPX_HDR_LEN),
         ('tc', 'B', 0),
         ('pt', 'B', 0),
-        ('dst', '12s', ''),
-        ('src', '12s', '')
+        ('dst', '12s', b''),
+        ('src', '12s', b'')
     )
diff --git a/dpkt/llc.py b/dpkt/llc.py
index ae4f9f8..b911831 100644
--- a/dpkt/llc.py
+++ b/dpkt/llc.py
@@ -93,6 +93,65 @@
     assert str(llc_pkt) == str(b'\x06\x06\x03' + s[8:])
 
 
-if __name__ == '__main__':
-    test_llc()
-    print('Tests Successful...')
+def test_unpack_sap_ip():
+    from binascii import unhexlify
+
+    from . import ip
+
+    buf_llc = unhexlify(
+        '06'  # dsap (SAP_IP)
+        'aa'  # ssap
+        '03'  # ctl
+    )
+    buf_ip = unhexlify(
+        '45'    # _v_hl
+        '00'    # tos
+        '0014'  # len
+        '0000'  # id
+        '0000'  # off
+        '80'    # ttl
+        '06'    # p
+        'd47e'  # sum
+        '11111111'  # src
+        '22222222'  # dst
+    )
+
+    buf = buf_llc + buf_ip
+    llc = LLC(buf)
+    assert isinstance(llc.data, ip.IP)
+
+
+def test_unpack_exception_handling():
+    from binascii import unhexlify
+
+    buf_llc = unhexlify(
+        'aa'  # dsap (SAP_IP)
+        'aa'  # ssap
+        '03'  # ctl
+
+        '111111'  # oui
+        '2222'    # type (not valid ethertype)
+    )
+
+    llc = LLC(buf_llc)
+    assert not isinstance(llc.data, dpkt.Packet)
+
+
+def test_pack_hdr_invalid_class():
+    from binascii import unhexlify
+
+    class InvalidClass(dpkt.Packet):
+        __hdr__ = (('test', 'B', 0x22),)
+
+    llc = LLC(dsap=0xaa, ssap=0xaa, ctl=3, oui=0x111111, data=InvalidClass())
+    correct = unhexlify(
+        'aa'       # dsap
+        'aa'       # ssap
+        '03'       # ctl
+
+        '111111'   # oui
+        '0000'     # type
+
+        '22'       # data in test class header
+    )
+    assert bytes(llc) == correct
diff --git a/dpkt/loopback.py b/dpkt/loopback.py
index 2664102..c4eca7c 100644
--- a/dpkt/loopback.py
+++ b/dpkt/loopback.py
@@ -1,6 +1,9 @@
 # $Id: loopback.py 38 2007-03-17 03:33:16Z dugsong $
 # -*- coding: utf-8 -*-
 """Platform-dependent loopback header."""
+
+# https://wiki.wireshark.org/NullLoopback
+
 from __future__ import absolute_import
 
 from . import dpkt
@@ -18,18 +21,65 @@
         __hdr__: Header fields of Loopback.
         TODO.
     """
-    
+
     __hdr__ = (('family', 'I', 0), )
     __byte_order__ = '@'
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
-        if self.family == 2:
-            self.data = ip.IP(self.data)
-        elif self.family == 0x02000000:
+        if self.family in (0x02, 0x02000000):
             self.family = 2
             self.data = ip.IP(self.data)
-        elif self.family in (24, 28, 30):
+
+        elif self.family in (0x18, 0x18000000):
+            self.family = 24
             self.data = ip6.IP6(self.data)
-        elif self.family > 1500:
+
+        elif self.family in (0x1c, 0x1c000000):
+            self.family = 28
+            self.data = ip6.IP6(self.data)
+
+        elif self.family in (0x1e, 0x1e000000):
+            self.family = 30
+            self.data = ip6.IP6(self.data)
+
+        else:
             self.data = ethernet.Ethernet(self.data)
+
+
+def test_ethernet_unpack():
+    buf = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x08\x00'
+    hdr = b'\x00\x02\x00\x02'
+
+    lo = Loopback(hdr + buf)
+    assert lo.family in (0x02000200, 0x00020002)  # little endian, big endian
+    assert isinstance(lo.data, ethernet.Ethernet)
+    assert lo.data.src == b'\x07\x08\t\n\x0b\x0c'
+    assert lo.data.dst == b'\x01\x02\x03\x04\x05\x06'
+
+
+def test_ip_unpack():
+    buf = b'E\x00\x004\xbd\x04@\x00@\x06\x7f\xbd\x7f\x00\x00\x02\x7f\x00\x00\x01'
+
+    for hdr in (b'\x00\x00\x00\x02', b'\x02\x00\x00\x00'):
+        lo = Loopback(hdr + buf)
+        assert lo.family == 2
+        assert isinstance(lo.data, ip.IP)
+        assert lo.data.src == b'\x7f\x00\x00\x02'
+        assert lo.data.dst == b'\x7f\x00\x00\x01'
+
+
+def test_ip6_unpack():
+    import struct
+    buf = (b'\x60\x00\x00\x00\x00\x14\x06\x38\x26\x07\xf8\xb0\x40\x0c\x0c\x03\x00\x00\x00\x00\x00\x00'
+           b'\x00\x1a\x20\x01\x04\x70\xe5\xbf\xde\xad\x49\x57\x21\x74\xe8\x2c\x48\x87')
+    hdr_suffix = b'\x00' * 3
+
+    for family in (24, 28, 30):
+        hdr = struct.pack('B', family) + hdr_suffix
+
+        lo = Loopback(hdr + buf)
+        assert lo.family == family
+        assert isinstance(lo.data, ip6.IP6)
+        assert lo.data.src == b'&\x07\xf8\xb0@\x0c\x0c\x03\x00\x00\x00\x00\x00\x00\x00\x1a'
+        assert lo.data.dst == b' \x01\x04p\xe5\xbf\xde\xadIW!t\xe8,H\x87'
diff --git a/dpkt/mrt.py b/dpkt/mrt.py
index 6c39968..1386dcb 100644
--- a/dpkt/mrt.py
+++ b/dpkt/mrt.py
@@ -66,13 +66,13 @@
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
         plen = self.attr_len
-        l = []
+        l_ = []
         while plen > 0:
             attr = bgp.BGP.Update.Attribute(self.data)
             self.data = self.data[len(attr):]
             plen -= len(attr)
-            l.append(attr)
-        self.attributes = l
+            l_.append(attr)
+        self.attributes = l_
 
 
 class BGP4MPMessage(dpkt.Packet):
@@ -95,3 +95,42 @@
         ('src_ip', 'I', 0),
         ('dst_ip', 'I', 0)
     )
+
+
+def test_tabledump():
+    from binascii import unhexlify
+    buf_tabledump = unhexlify(
+        '0001'           # view
+        '0002'           # seq
+        '00000003'       # prefix
+        '04'             # prefix_len
+        '05'             # status
+        '00000006'       # originated_ts
+        '00000007'       # peer_ip
+        '0008'           # peer_as
+
+        '0002'           # attr_len
+    )
+    buf_attrs = unhexlify(
+        '01'  # flags
+        '01'  # type (ORIGIN)
+
+        '01'  # length
+        '02'  # Origin.type (INCOMPLETE)
+    )
+    buf = buf_tabledump + buf_attrs
+    table_dump = TableDump(buf)
+    assert table_dump.view == 1
+    assert table_dump.seq == 2
+    assert table_dump.prefix == 3
+    assert table_dump.prefix_len == 4
+    assert table_dump.status == 5
+    assert table_dump.originated_ts == 6
+    assert table_dump.peer_ip == 7
+    assert table_dump.peer_as == 8
+    assert table_dump.attr_len == 2
+
+    assert len(table_dump.attributes) == 1
+    attr = table_dump.attributes[0]
+    assert isinstance(attr, bgp.BGP.Update.Attribute)
+    assert isinstance(attr.data, bgp.BGP.Update.Attribute.Origin)
diff --git a/dpkt/netbios.py b/dpkt/netbios.py
index 022889e..82c0820 100644
--- a/dpkt/netbios.py
+++ b/dpkt/netbios.py
@@ -7,27 +7,90 @@
 
 from . import dpkt
 from . import dns
+from .compat import compat_ord
 
 
 def encode_name(name):
-    """Return the NetBIOS first-level encoded name."""
-    l = []
-    for c in struct.pack('16s', name):
-        c = ord(c)
-        l.append(chr((c >> 4) + 0x41))
-        l.append(chr((c & 0xf) + 0x41))
-    return ''.join(l)
+    """
+    Return the NetBIOS first-level encoded name.
+
+    14.1.  FIRST LEVEL ENCODING
+
+    The first level representation consists of two parts:
+
+     -  NetBIOS name
+     -  NetBIOS scope identifier
+
+    The 16 byte NetBIOS name is mapped into a 32 byte wide field using a
+    reversible, half-ASCII, biased encoding.  Each half-octet of the
+    NetBIOS name is encoded into one byte of the 32 byte field.  The
+    first half octet is encoded into the first byte, the second half-
+    octet into the second byte, etc.
+
+    Each 4-bit, half-octet of the NetBIOS name is treated as an 8-bit,
+    right-adjusted, zero-filled binary number.  This number is added to
+    value of the ASCII character 'A' (hexadecimal 41).  The resulting 8-
+    bit number is stored in the appropriate byte.  The following diagram
+    demonstrates this procedure:
+
+
+                         0 1 2 3 4 5 6 7
+                        +-+-+-+-+-+-+-+-+
+                        |a b c d|w x y z|          ORIGINAL BYTE
+                        +-+-+-+-+-+-+-+-+
+                            |       |
+                   +--------+       +--------+
+                   |                         |     SPLIT THE NIBBLES
+                   v                         v
+            0 1 2 3 4 5 6 7           0 1 2 3 4 5 6 7
+           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
+           |0 0 0 0 a b c d|         |0 0 0 0 w x y z|
+           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
+                   |                         |
+                   +                         +     ADD 'A'
+                   |                         |
+            0 1 2 3 4 5 6 7           0 1 2 3 4 5 6 7
+           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
+           |0 1 0 0 0 0 0 1|         |0 1 0 0 0 0 0 1|
+           +-+-+-+-+-+-+-+-+         +-+-+-+-+-+-+-+-+
+
+    This encoding results in a NetBIOS name being represented as a
+    sequence of 32 ASCII, upper-case characters from the set
+    {A,B,C...N,O,P}.
+
+    The NetBIOS scope identifier is a valid domain name (without a
+    leading dot).
+
+    An ASCII dot (2E hexadecimal) and the scope identifier are appended
+    to the encoded form of the NetBIOS name, the result forming a valid
+    domain name.
+    """
+    l_ = []
+    for c in struct.pack('16s', name.encode()):
+        c = compat_ord(c)
+        l_.append(chr((c >> 4) + 0x41))
+        l_.append(chr((c & 0xf) + 0x41))
+    return ''.join(l_)
 
 
 def decode_name(nbname):
-    """Return the NetBIOS first-level decoded nbname."""
+    """
+    Return the NetBIOS first-level decoded nbname.
+
+    """
     if len(nbname) != 32:
         return nbname
-    l = []
+
+    l_ = []
     for i in range(0, 32, 2):
-        l.append(chr(((ord(nbname[i]) - 0x41) << 4) |
-                     ((ord(nbname[i + 1]) - 0x41) & 0xf)))
-    return ''.join(l).split('\x00', 1)[0]
+        l_.append(
+            chr(
+                ((ord(nbname[i]) - 0x41) << 4) |
+                ((ord(nbname[i + 1]) - 0x41) & 0xf)
+            )
+        )
+    return ''.join(l_).split('\x00', 1)[0]
+
 
 # RR types
 NS_A = 0x01  # IP address
@@ -97,38 +160,50 @@
 
 
 class NS(dns.DNS):
-    """NetBIOS Name Service."""
+    """
+    NetBIOS Name Service.
+
+    RFC1002: https://tools.ietf.org/html/rfc1002
+    """
 
     class Q(dns.DNS.Q):
         pass
 
     class RR(dns.DNS.RR):
-        """NetBIOS resource record."""
+        """NetBIOS resource record.
+
+        RFC1001: 14.  REPRESENTATION OF NETBIOS NAMES
+
+        NetBIOS names as seen across the client interface to NetBIOS are
+        exactly 16 bytes long.  Within the NetBIOS-over-TCP protocols, a
+        longer representation is used.
+
+        There are two levels of encoding.  The first level maps a NetBIOS
+        name into a domain system name.  The second level maps the domain
+        system name into the "compressed" representation required for
+        interaction with the domain name system.
+
+        Except in one packet, the second level representation is the only
+        NetBIOS name representation used in NetBIOS-over-TCP packet formats.
+        The exception is the RDATA field of a NODE STATUS RESPONSE packet.
+        """
+
+        _node_name_struct = struct.Struct('>15s B H')
+        _node_name_len = _node_name_struct.size
 
         def unpack_rdata(self, buf, off):
             if self.type == NS_A:
                 self.ip = self.rdata
             elif self.type == NS_NBSTAT:
-                num = ord(self.rdata[0])
-                off = 1
-                l = []
-                for i in range(num):
-                    name = self.rdata[off:off + 15].split(None, 1)[0].split('\x00', 1)[0]
-                    service = ord(self.rdata[off + 15])
-                    off += 16
-                    flags = struct.unpack('>H', self.rdata[off:off + 2])[0]
-                    off += 2
-                    l.append((name, service, flags))
-                self.nodenames = l
+                num_names = compat_ord(self.rdata[0])
+
+                self.nodenames = [
+                    self._node_name_struct.unpack_from(
+                        self.rdata, 1+idx*self._node_name_len
+                    ) for idx in range(num_names)
+                ]
                 # XXX - skip stats
 
-    def pack_name(self, buf, name):
-        return dns.DNS.pack_name(self, buf, encode_name(name))
-
-    def unpack_name(self, buf, off):
-        name, off = dns.DNS.unpack_name(self, buf, off)
-        return decode_name(name), off
-
 
 class Session(dpkt.Packet):
     """NetBIOS Session Service."""
@@ -167,3 +242,87 @@
 DGRAM_QUERY = 0x14
 DGRAM_POSITIVE = 0x15
 DGRAM_NEGATIVE = 0x16
+
+
+def test_encode_name():
+    assert encode_name('The NetBIOS name') == 'FEGIGFCAEOGFHEECEJEPFDCAGOGBGNGF'
+    # rfc1002
+    assert encode_name('FRED            ') == 'EGFCEFEECACACACACACACACACACACACA'
+    # https://github.com/kbandla/dpkt/issues/458
+    assert encode_name('*') == 'CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
+
+
+def test_decode_name():
+    assert decode_name('FEGIGFCAEOGFHEECEJEPFDCAGOGBGNGF') == 'The NetBIOS name'
+    # original botched example from rfc1001
+    assert decode_name('FEGHGFCAEOGFHEECEJEPFDCAHEGBGNGF') == 'Tge NetBIOS tame'
+    assert decode_name('CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') == '*'
+
+    # decode a name which is not 32 chars long
+    assert decode_name('CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB') == 'CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB'
+
+
+def test_node_to_service_name():
+    svcname = node_to_service_name(("ISS", 0x00, 0x0800))
+    assert svcname == "Workstation Service"
+
+
+def test_node_to_service_name_keyerror():
+    svcname = node_to_service_name(("ISS", 0xff, 0x0800))
+    assert svcname == ""
+
+
+def test_rr():
+    import pytest
+    from binascii import unhexlify
+
+    rr = NS.RR()
+    with pytest.raises(NotImplementedError):
+        len(rr)
+
+    buf = unhexlify(''.join([
+        '01',        # A record
+        '0001',      # DNS_IN
+        '00000000',  # TTL
+        '0000',      # rlen
+    ]))
+    rr.unpack_rdata(buf, 0)
+    assert rr.ip == rr.rdata
+
+
+def test_rr_nbstat():
+    from binascii import unhexlify
+    buf = unhexlify(''.join([
+        '41' * 1025,  # Name
+        '0033',       # NS_NBSTAT
+        '0001',       # DNS_IN
+        '00000000',   # TTL
+        '0004',       # rlen
+    ]))
+    rdata = (
+        b'\x02'  # NUM_NAMES
+        b'ABCDEFGHIJKLMNO\x2f\x01\x02'
+        b'PQRSTUVWXYZABCD\x43\x03\x04'
+    )
+    rr = NS.RR(
+        type=NS_NBSTAT,
+        rdata=rdata,
+    )
+
+    assert rr.type == NS_NBSTAT
+    rr.unpack_rdata(buf, 0)
+    assert rr.nodenames == [
+        (b'ABCDEFGHIJKLMNO', 0x2f, 0x0102),
+        (b'PQRSTUVWXYZABCD', 0x43, 0x0304),
+    ]
+
+
+def test_ns():
+    from binascii import unhexlify
+    ns = NS()
+    correct = unhexlify(
+        '0000'
+        '0100'
+        '0000000000000000'
+    )
+    assert bytes(ns) == correct
diff --git a/dpkt/netflow.py b/dpkt/netflow.py
index 007d0b3..04036c6 100644
--- a/dpkt/netflow.py
+++ b/dpkt/netflow.py
@@ -9,6 +9,7 @@
 from . import dpkt
 from .compat import compat_izip
 
+
 class NetflowBase(dpkt.Packet):
     """Base class for Cisco Netflow packets.
 
@@ -38,12 +39,12 @@
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
         buf = self.data
-        l = []
+        l_ = []
         while buf:
             flow = self.NetflowRecord(buf)
-            l.append(flow)
+            l_.append(flow)
             buf = buf[len(flow):]
-        self.data = l
+        self.data = l_
 
     class NetflowRecordBase(dpkt.Packet):
         """Base class for netflow v1-v7 netflow records.
@@ -67,7 +68,7 @@
         def unpack(self, buf):
             # don't bother with data
             for k, v in compat_izip(self.__hdr_fields__,
-                                       struct.unpack(self.__hdr_fmt__, buf[:self.__hdr_len__])):
+                                    struct.unpack(self.__hdr_fmt__, buf[:self.__hdr_len__])):
                 setattr(self, k, v)
             self.data = b""
 
@@ -265,33 +266,86 @@
             ('router_sc', 'I', 0),
         )
 
+
 # No support for v8 or v9 yet.
-
-__sample_v1 = b"\x00\x01\x00\x18gza<B\x00\xfc\x1c$\x93\x08p\xac\x01 W\xc0\xa8c\xf7\n\x00\x02\x01\x00\x03\x00\n\x00\x00\x00\x01\x00\x00\x02(gz7,gz7,\\\x1b\x00P\xac\x01\x11,\x10\x00\x00\x00\x00\x04\x00\x1b\xac\x01\x18S\xac\x18\xd9\xaa\xc0\xa82\x02\x00\x03\x00\x19\x00\x00\x00\x01\x00\x00\x05\xdcgz7|gz7|\xd8\xe3\x00P\xac\x01\x06,\x10\x00\x00\x00\x00\x04\x00\x1b\xac\x01\x14\x18\xac\x18\x8d\xcd\xc0\xa82f\x00\x03\x00\x07\x00\x00\x00\x01\x00\x00\x05\xdcgz7\x90gz7\x90\x8a\x81\x17o\xac\x01\x066\x10\x00\x00\x00\x00\x04\x00\x03\xac\x0f'$\xac\x01\xe5\x1d\xc0\xa82\x06\x00\x04\x00\x1b\x00\x00\x00\x01\x00\x00\x02(gz:8gz:8\xa3Q\x126\xac)\x06\xfd\x18\x00\x00\x00\x00\x04\x00\x1b\xac\x01\x16E\xac#\x17\x8e\xc0\xa82\x06\x00\x03\x00\x1b\x00\x00\x00\x01\x00\x00\x02(gz:Lgz:L\xc9\xff\x00P\xac\x1f\x06\x86\x02\x00\x00\x00\x00\x03\x00\x1b\xac\r\t\xff\xac\x01\x99\x95\xc0\xa82\x06\x00\x04\x00\x1b\x00\x00\x00\x01\x00\x00\x05\xdcgz:Xgz:X\xee9\x00\x17\xac\x01\x06\xde\x10\x00\x00\x00\x00\x04\x00\x03\xac\x0eJ\xd8\xac\x01\xae/\xc0\xa82\x06\x00\x04\x00\x1b\x00\x00\x00\x01\x00\x00\x05\xdcgz:hgz:h\xb3n\x00\x15\xac\x01\x06\x81\x10\x00\x00\x00\x00\x04\x00\x1b\xac\x01#8\xac\x01\xd9*\xc0\xa82\x06\x00\x03\x00\x1b\x00\x00\x00\x01\x00\x00\x05\xdcgz:tgz:t\x00\x00\x83P\xac!\x01\xab\x10\x00\x00\x00\x00\x03\x00\x1b\xac\n`7\xac*\x93J\xc0\xa82\x06\x00\x04\x00\x1b\x00\x00\x00\x01\x00\x00\x05\xdcgz:tgz:t\x00\x00\x00\x00\xac\x012\xa9\x10\x00\x00\x00\x00\x04\x00\x07\xac\nG\x1f\xac\x01\xfdJ\xc0\xa82\x06\x00\x04\x00\x1b\x00\x00\x00\x01\x00\x00\x00(gz:\x88gz:\x88!\x99i\x87\xac\x1e\x06~\x02\x00\x00\x00\x00\x03\x00\x1b\xac\x01(\xc9\xac\x01B\xc4\xc0\xa82\x02\x00\x03\x00\x19\x00\x00\x00\x01\x00\x00\x00(gz:\x88gz:\x88}6\x00P\xac\x01\x06\xfe\x10\x00\x00\x00\x00\x04\x00\x1b\xac\x0b\x08\xe8\xac\x01F\xe2\xc0\xa82\x02\x00\x04\x00\x19\x00\x00\x00\x01\x00\x00\x05\xdcgz:\x9cgz:\x9c`ii\x87\xac\x01\x06;\x10\x00\x00\x00\x00\x04\x00\x1b\xac\x01\x1d$\xac<\xf0\xc3\xc0\xa82\x06\x00\x03\x00\x1b\x00\x00\x00\x01\x00\x00\x05\xdcgz:\x9cgz:\x9cF2\x00\x14\xac\x01\x06s\x18\x00\x00\x00\x00\x04\x00\x03\xac\x0b\x11Q\xac\x01\xde\x06\xc0\xa82\x06\x00\x04\x00\x1b\x00\x00\x00\x01\x00\x00\x05\xdcgz:\xb0gz:\xb0\xef#\x1a+\xac)\x06\xe9\x10\x00\x00\x00\x00\x04\x00\x1b\xac\x0cR\xd9\xac\x01o\xe8\xc0\xa82\x02\x00\x04\x00\x19\x00\x00\x00\x01\x00\x00\x05\xdcgz:\xc4gz:\xc4\x13n\x00n\xac\x19\x06\xa8\x10\x00\x00\x00\x00\x03\x00\x19\xac\x01=\xdd\xac\x01}\xee\xc0\xa82f\x00\x03\x00\x07\x00\x00\x00\x01\x00\x00\x00(gz:\xc4gz:\xc4\x00\x00\xdc\xbb\xac\x01\x01\xd3\x10\x00\x00\x00\x00\x04\x00\x1b\xac\x0f(\xd1\xac\x01\xcc\xa5\xc0\xa82\x06\x00\x04\x00\x1b\x00\x00\x00\x01\x00\x00\x05\xdcgz:\xd8gz:\xd8\xc5s\x17o\xac\x19\x06#\x18\x00\x00\x00\x00\x03\x00\x07\xac\n\x85[\xc0\xa8cn\n\x00\x02\x01\x00\x04\x00\n\x00\x00\x00\x01\x00\x00\x05\xdcgz:\xe4gz:\xe4\xbfl\x00P\xac\x01\x06\xcf\x10\x00\x00\x00\x00\x04\x00\x07\xac\x010\x1f\xac\x18!E\xc0\xa82f\x00\x03\x00\x07\x00\x00\x00\x01\x00\x00\x05\xdcgz;\x00gz;\x00\x11\x95\x04\xbe\xc0\xa8\x06\xea\x10\x00\x00\x00\x00\x03\x00\n\xac\x010\xb6\xac\x1e\xf4\xaa\xc0\xa82\x06\x00\x03\x00\x1b\x00\x00\x00\x01\x00\x00\x05\xdcgz;4gz;4\x88d\x00\x17\xac\x01\x06\x1f\x10\x00\x00\x00\x00\x04\x00\x1b\xac\x01#_\xac\x1e\xb0\t\xc0\xa82\x06\x00\x03\x00\x1b\x00\x00\x00\x01\x00\x00\x05\xdcgz;Hgz;H\x81S\x00P\xac \x06N\x10\x00\x00\x00\x00\x03\x00\x1b\xac\x01\x04\xd9\xac\x01\x94c\xc0\xa82\x06\x00\x03\x00\x1b\x00\x00\x00\x01\x00\x00\x02(gz;\\gz;\\U\x10\x00P\xac\x01\x06P\x18\x00\x00\x00\x00\x04\x00\x1b\xac\x01<\xae\xac*\xac!\xc0\xa82\x06\x00\x03\x00\x1b\x00\x00\x00\x01\x00\x00\x00\xfagz;\x84gz;\x84\x0c\xe7\x00P\xac\x01\x11\xfd\x10\x00\x00\x00\x00\x04\x00\x1b\xac\x01\x1f\x1f\xac\x17\xedi\xc0\xa82\x02\x00\x03\x00\x19\x00\x00\x00\x01\x00\x00\x05\xdcgz;\x98gz;\x98\xba\x17\x00\x16\xac\x01\x06|\x10\x00\x00\x00\x00\x03\x00\x07"
-__sample_v5 = b'\x00\x05\x00\x1d\xb5\xfa\xc9\xd0:\x0bAB&Vw\xde\x9bsv1\x00\x01\x00\x00\xac\n\x86\xa6\xac\x01\xaa\xf7\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x02(\xb5\xfa\x81\x14\xb5\xfa\x81\x1452\x00P\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\x91D\xac\x14C\xe4\xc0\xa82\x16\x00i\x02q\x00\x00\x00\x01\x00\x00\x00(\xb5\xfa\x9b\xbd\xb5\xfa\x9b\xbd\x00P\x85\xd7\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x17\xe2\xd7\xac\x01\x8cV\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfao\xb8\xb5\xfao\xb8v\xe8\x17o\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x0e\xf2\xe5\xac\x01\x91\xb2\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x00\xfa\xb5\xfa\x81\xee\xb5\xfa\x81\xee\xd0\xeb\x00\x15\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\nCj\xac)\xa7\t\n\x00\x02\x01\x02q\x00\xdb\x00\x00\x00\x01\x00\x00\x02(\xb5\xfa\x85\x92\xb5\xfa\x85\x92\x8c\xb0\x005\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\x96=\xac\x15\x1a\xa8\xc0\xa82\x16\x00i\x02q\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa\x86\xe0\xb5\xfa\x86\xe0\xb4\xe7\x00\xc2\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01V\xd1\xac\x01\x86\x15\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa}:\xb5\xfa}:[Q\x00P\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac2\xf1\xb1\xac)\x19\xca\n\x00\x02\x01\x02q\x00\xdb\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa\x83\xc3\xb5\xfa\x83\xc3\x16,\x00\x15\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x0cA4\xac\x01\x9az\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa\x8d\xa7\xb5\xfa\x8d\xa7\x173\x00\x15\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x1e\xd2\x84\xac)\xd8\xd2\n\x00\x02\x01\x02q\x00\xdb\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa\x8e\x97\xb5\xfa\x8e\x977*\x17o\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\x85J\xac \x11\xfc\xc0\xa82\x16\x00i\x02q\x00\x00\x00\x01\x00\x00\x02(\xb5\xfa\x884\xb5\xfa\x884\xf5\xdd\x00\x8f\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\x04\x80\xac<[n\n\x00\x02\x01\x02q\x00\xdb\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa\x9dr\xb5\xfa\x9drs$\x00\x16\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\xb9J\xac"\xc9\xd7\xc0\xa82\x16\x00i\x02q\x00\x00\x00\x01\x00\x00\x00(\xb5\xfa\x90r\xb5\xfa\x90r\x0f\x8d\x00\xc2\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac*\xa3\x10\xac\x01\xb4\x19\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x00(\xb5\xfa\x92\x03\xb5\xfa\x92\x03pf\x00\x15\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\xabo\xac\x1e\x7fi\xc0\xa82\x16\x00i\x02q\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa\x93\x7f\xb5\xfa\x93\x7f\x00P\x0b\x98\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x0c\n\xea\xac\x01\xa1\x15\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfay\xcf\xb5\xfay\xcf[3\x17\xe0\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\xbb\xb3\xac)u\x8c\n\x00\x02\x01\x00i\x00\xdb\x00\x00\x00\x01\x00\x00\x00\xfa\xb5\xfa\x943\xb5\xfa\x943\x00P\x1e\xca\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x0fJ`\xac\x01\xab\x94\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x02(\xb5\xfa\x87[\xb5\xfa\x87[\x9a\xd6/\xab\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac*\x0f\x93\xac\x01\xb8\xa3\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x00(\xb5\xfa\x89\xbb\xb5\xfa\x89\xbbn\xe1\x00P\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\x93\xa1\xac\x16\x80\x0c\xc0\xa82\x16\x00i\x02q\x00\x00\x00\x01\x00\x00\x00(\xb5\xfa\x87&\xb5\xfa\x87&\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\x83Z\xac\x1fR\xcd\xc0\xa82\x16\x00i\x02q\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa\x90\r\xb5\xfa\x90\r\xf7*\x00\x8a\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x0c\xe0\xad\xac\x01\xa8V\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa\x9c\xf6\xb5\xfa\x9c\xf6\xe5|\x1a+\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x1e\xccT\xac<x&\n\x00\x02\x01\x02q\x00\xdb\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa\x80\xea\xb5\xfa\x80\xea\x00\x00\x00\x00\x00\x00/\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\xbb\x18\xac\x01|z\xc0\xa82\x16\x00i\x02q\x00\x00\x00\x01\x00\x00\x00\xfa\xb5\xfa\x88p\xb5\xfa\x88p\x00P\x0b}\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x17\x0er\xac\x01\x8f\xdd\xc0\xa822\x02q\x00i\x00\x00\x00\x01\x00\x00\x02(\xb5\xfa\x89\xf7\xb5\xfa\x89\xf7\r\xf7\x00\x8a\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\n\xbb\x04\xac<\xb0\x15\n\x00\x02\x01\x02q\x00\xdb\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa\x90\xa9\xb5\xfa\x90\xa9\x9c\xd0\x00\x8f\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\nz?\xac)\x03\xc8\n\x00\x02\x01\x02q\x00\xdb\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfaue\xb5\xfaue\xee\xa6\x00P\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac\x01\xb5\x05\xc0\xa8c\x9f\n\x00\x02\x01\x00i\x00\xdb\x00\x00\x00\x01\x00\x00\x05\xdc\xb5\xfa{\xc7\xb5\xfa{\xc7\x00P\x86\xa9\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\xac2\xa5\x1b\xac)0\xbf\n\x00\x02\x01\x02q\x00\xdb\x00\x00\x00\x01\x00\x00\x00\xfa\xb5\xfa\x9bZ\xb5\xfa\x9bZC\xf9\x17\xe0\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00'
-
-
-def test_net_flow_v1_pack(): pass
-
-
 def test_net_flow_v1_unpack():
+    from binascii import unhexlify
+    __sample_v1 = unhexlify(
+        '00010018677a613c4200fc1c24930870ac012057c0a863f70a0002010003000a0000000100000228677a372c677a372c5c1b0050ac01112c10000'
+        '0000004001bac011853ac18d9aac0a832020003001900000001000005dc677a377c677a377cd8e30050ac01062c100000000004001bac011418ac'
+        '188dcdc0a832660003000700000001000005dc677a3790677a37908a81176fac0106361000000000040003ac0f2724ac01e51dc0a832060004001'
+        'b0000000100000228677a3a38677a3a38a3511236ac2906fd180000000004001bac011645ac23178ec0a832060003001b0000000100000228677a'
+        '3a4c677a3a4cc9ff0050ac1f0686020000000003001bac0d09ffac019995c0a832060004001b00000001000005dc677a3a58677a3a58ee390017a'
+        'c0106de1000000000040003ac0e4ad8ac01ae2fc0a832060004001b00000001000005dc677a3a68677a3a68b36e0015ac01068110000000000400'
+        '1bac012338ac01d92ac0a832060003001b00000001000005dc677a3a74677a3a7400008350ac2101ab100000000003001bac0a6037ac2a934ac0a'
+        '832060004001b00000001000005dc677a3a74677a3a7400000000ac0132a91000000000040007ac0a471fac01fd4ac0a832060004001b00000001'
+        '00000028677a3a88677a3a8821996987ac1e067e020000000003001bac0128c9ac0142c4c0a83202000300190000000100000028677a3a88677a3'
+        'a887d360050ac0106fe100000000004001bac0b08e8ac0146e2c0a832020004001900000001000005dc677a3a9c677a3a9c60696987ac01063b10'
+        '0000000004001bac011d24ac3cf0c3c0a832060003001b00000001000005dc677a3a9c677a3a9c46320014ac0106731800000000040003ac0b115'
+        '1ac01de06c0a832060004001b00000001000005dc677a3ab0677a3ab0ef231a2bac2906e9100000000004001bac0c52d9ac016fe8c0a832020004'
+        '001900000001000005dc677a3ac4677a3ac4136e006eac1906a81000000000030019ac013dddac017deec0a832660003000700000001000000286'
+        '77a3ac4677a3ac40000dcbbac0101d3100000000004001bac0f28d1ac01cca5c0a832060004001b00000001000005dc677a3ad8677a3ad8c57317'
+        '6fac1906231800000000030007ac0a855bc0a8636e0a0002010004000a00000001000005dc677a3ae4677a3ae4bf6c0050ac0106cf10000000000'
+        '40007ac01301fac182145c0a832660003000700000001000005dc677a3b00677a3b00119504bec0a806ea100000000003000aac0130b6ac1ef4aa'
+        'c0a832060003001b00000001000005dc677a3b34677a3b3488640017ac01061f100000000004001bac01235fac1eb009c0a832060003001b00000'
+        '001000005dc677a3b48677a3b4881530050ac20064e100000000003001bac0104d9ac019463c0a832060003001b0000000100000228677a3b5c67'
+        '7a3b5c55100050ac010650180000000004001bac013caeac2aac21c0a832060003001b00000001000000fa677a3b84677a3b840ce70050ac0111f'
+        'd100000000004001bac011f1fac17ed69c0a832020003001900000001000005dc677a3b98677a3b98ba170016ac01067c1000000000030007'
+    )
     nf = Netflow1(__sample_v1)
     assert len(nf.data) == 24
     # print repr(nfv1)
 
 
-def test_net_flow_v5_pack(): pass
-
-
 def test_net_flow_v5_unpack():
-    nf = Netflow5(__sample_v5)
+    from binascii import unhexlify
+    buf_nf5_header = unhexlify(
+        '0005001db5fac9d03a0b4142265677de9b73763100010000'
+    )
+
+    buf_nf5_records = list(map(unhexlify, (
+        'ac0a86a6ac01aaf7c0a83232027100690000000100000228b5fa8114b5fa811435320050000006000000000000000000',
+        'ac019144ac1443e4c0a83216006902710000000100000028b5fa9bbdb5fa9bbd005085d7000006000000000000000000',
+        'ac17e2d7ac018c56c0a832320271006900000001000005dcb5fa6fb8b5fa6fb876e8176f000006000000000000000000',
+        'ac0ef2e5ac0191b2c0a832320271006900000001000000fab5fa81eeb5fa81eed0eb0015000006000000000000000000',
+        'ac0a436aac29a7090a000201027100db0000000100000228b5fa8592b5fa85928cb00035000006000000000000000000',
+        'ac01963dac151aa8c0a832160069027100000001000005dcb5fa86e0b5fa86e0b4e700c2000006000000000000000000',
+        'ac0156d1ac018615c0a832320271006900000001000005dcb5fa7d3ab5fa7d3a5b510050000006000000000000000000',
+        'ac32f1b1ac2919ca0a000201027100db00000001000005dcb5fa83c3b5fa83c3162c0015000006000000000000000000',
+        'ac0c4134ac019a7ac0a832320271006900000001000005dcb5fa8da7b5fa8da717330015000006000000000000000000',
+        'ac1ed284ac29d8d20a000201027100db00000001000005dcb5fa8e97b5fa8e97372a176f000006000000000000000000',
+        'ac01854aac2011fcc0a83216006902710000000100000228b5fa8834b5fa8834f5dd008f000006000000000000000000',
+        'ac010480ac3c5b6e0a000201027100db00000001000005dcb5fa9d72b5fa9d7273240016000006000000000000000000',
+        'ac01b94aac22c9d7c0a83216006902710000000100000028b5fa9072b5fa90720f8d00c2000006000000000000000000',
+        'ac2aa310ac01b419c0a83232027100690000000100000028b5fa9203b5fa920370660015000006000000000000000000',
+        'ac01ab6fac1e7f69c0a832160069027100000001000005dcb5fa937fb5fa937f00500b98000006000000000000000000',
+        'ac0c0aeaac01a115c0a832320271006900000001000005dcb5fa79cfb5fa79cf5b3317e0000006000000000000000000',
+        'ac01bbb3ac29758c0a000201006900db00000001000000fab5fa9433b5fa943300501eca000006000000000000000000',
+        'ac0f4a60ac01ab94c0a83232027100690000000100000228b5fa875bb5fa875b9ad62fab000006000000000000000000',
+        'ac2a0f93ac01b8a3c0a83232027100690000000100000028b5fa89bbb5fa89bb6ee10050000006000000000000000000',
+        'ac0193a1ac16800cc0a83216006902710000000100000028b5fa8726b5fa872600000000000001000000000000000000',
+        'ac01835aac1f52cdc0a832160069027100000001000005dcb5fa900db5fa900df72a008a000006000000000000000000',
+        'ac0ce0adac01a856c0a832320271006900000001000005dcb5fa9cf6b5fa9cf6e57c1a2b000006000000000000000000',
+        'ac1ecc54ac3c78260a000201027100db00000001000005dcb5fa80eab5fa80ea0000000000002f000000000000000000',
+        'ac01bb18ac017c7ac0a832160069027100000001000000fab5fa8870b5fa887000500b7d000006000000000000000000',
+        'ac170e72ac018fddc0a83232027100690000000100000228b5fa89f7b5fa89f70df7008a000006000000000000000000',
+        'ac0abb04ac3cb0150a000201027100db00000001000005dcb5fa90a9b5fa90a99cd0008f000006000000000000000000',
+        'ac0a7a3fac2903c80a000201027100db00000001000005dcb5fa7565b5fa7565eea60050000006000000000000000000',
+        'ac01b505c0a8639f0a000201006900db00000001000005dcb5fa7bc7b5fa7bc7005086a9000006000000000000000000',
+        'ac32a51bac2930bf0a000201027100db00000001000000fab5fa9b5ab5fa9b5a43f917e0000006000000000000000000',
+    )))
+
+    buf_input = buf_nf5_header + b''.join(buf_nf5_records)
+    nf = Netflow5(buf_input)
+    assert nf.version == 5
+    assert nf.count == 29
+    assert nf.sys_uptime == 3053111760
+    assert nf.unix_sec == 973816130
+
+    assert len(nf) == len(buf_input)
+    assert bytes(nf) == buf_input
+
     assert len(nf.data) == 29
-    # print repr(nfv5)
-
-
-if __name__ == '__main__':
-    test_net_flow_v1_pack()
-    test_net_flow_v1_unpack()
-    test_net_flow_v5_pack()
-    test_net_flow_v5_unpack()
-    print('Tests Successful...')
+    for idx, record in enumerate(nf.data):
+        assert bytes(record) == buf_nf5_records[idx]
+        assert len(record) == 48
diff --git a/dpkt/ntp.py b/dpkt/ntp.py
index 332ad72..9bd0dfe 100644
--- a/dpkt/ntp.py
+++ b/dpkt/ntp.py
@@ -4,7 +4,6 @@
 from __future__ import print_function
 
 from . import dpkt
-from .decorators import deprecated
 
 # NTP v4
 
@@ -48,33 +47,17 @@
         ('receive_time', '8s', 0),
         ('transmit_time', '8s', 0)
     )
-
-    @property
-    def v(self):
-        return (self.flags >> 3) & 0x7
-
-    @v.setter
-    def v(self, v):
-        self.flags = (self.flags & ~0x38) | ((v & 0x7) << 3)
-
-    @property
-    def li(self):
-        return (self.flags >> 6) & 0x3
-
-    @li.setter
-    def li(self, li):
-        self.flags = (self.flags & ~0xc0) | ((li & 0x3) << 6)
-
-    @property
-    def mode(self):
-        return self.flags & 0x7
-
-    @mode.setter
-    def mode(self, mode):
-        self.flags = (self.flags & ~0x7) | (mode & 0x7)
+    __bit_fields__ = {
+        'flags': (
+            ('li', 2),    # leap indicator, 2 hi bits
+            ('v', 3),     # version, 3 bits
+            ('mode', 3),  # mode, 3 lo bits
+        )
+    }
 
 
-__s = b'\x24\x02\x04\xef\x00\x00\x00\x84\x00\x00\x33\x27\xc1\x02\x04\x02\xc8\x90\xec\x11\x22\xae\x07\xe5\xc8\x90\xf9\xd9\xc0\x7e\x8c\xcd\xc8\x90\xf9\xd9\xda\xc5\xb0\x78\xc8\x90\xf9\xd9\xda\xc6\x8a\x93'
+__s = (b'\x24\x02\x04\xef\x00\x00\x00\x84\x00\x00\x33\x27\xc1\x02\x04\x02\xc8\x90\xec\x11\x22\xae'
+       b'\x07\xe5\xc8\x90\xf9\xd9\xc0\x7e\x8c\xcd\xc8\x90\xf9\xd9\xda\xc5\xb0\x78\xc8\x90\xf9\xd9\xda\xc6\x8a\x93')
 
 
 def test_ntp_pack():
@@ -96,8 +79,3 @@
     assert (n.li == ALARM_CONDITION)
     assert (n.v == 3)
     assert (n.mode == CLIENT)
-
-if __name__ == '__main__':
-    test_ntp_pack()
-    test_ntp_unpack()
-    print('Tests Successful...')
diff --git a/dpkt/ospf.py b/dpkt/ospf.py
index 125c02e..f748234 100644
--- a/dpkt/ospf.py
+++ b/dpkt/ospf.py
@@ -28,10 +28,37 @@
         ('area', 'I', 0),
         ('sum', 'H', 0),
         ('atype', 'H', 0),
-        ('auth', '8s', '')
+        ('auth', '8s', b'')
     )
 
     def __bytes__(self):
         if not self.sum:
             self.sum = dpkt.in_cksum(dpkt.Packet.__bytes__(self))
         return dpkt.Packet.__bytes__(self)
+
+
+def test_creation():
+    ospf = OSPF()
+    assert ospf.v == 0
+    assert ospf.type == 0
+    assert ospf.len == 0
+    assert ospf.router == 0
+    assert ospf.area == 0
+    assert ospf.sum == 0
+    assert ospf.atype == 0
+    assert ospf.auth == b''
+
+    # sum is 0, so it will be recalculated
+    assert bytes(ospf) == b''.join([
+        b'\x00' * 12,
+        b'\xff\xff',
+        b'\x00' * 10
+    ])
+
+    ospf.sum = 0x1234
+    # sum is not 0, so it will be used
+    assert bytes(ospf) == b''.join([
+        b'\x00' * 12,
+        b'\x12\x34',
+        b'\x00' * 10
+    ])
diff --git a/dpkt/pcap.py b/dpkt/pcap.py
index c709c15..74b1793 100644
--- a/dpkt/pcap.py
+++ b/dpkt/pcap.py
@@ -9,6 +9,7 @@
 from decimal import Decimal
 
 from . import dpkt
+from .compat import intround
 
 TCPDUMP_MAGIC = 0xa1b2c3d4
 TCPDUMP_MAGIC_NANO = 0xa1b23c4d
@@ -127,6 +128,7 @@
 DLT_ZWAVE_R3 = 262
 DLT_WATTSTOPPER_DLM = 263
 DLT_ISO_14443 = 264
+DLT_LINUX_SLL2 = 276
 
 if sys.platform.find('openbsd') != -1:
     DLT_LOOP = 12
@@ -137,7 +139,7 @@
 
 dltoff = {DLT_NULL: 4, DLT_EN10MB: 14, DLT_IEEE802: 22, DLT_ARCNET: 6,
           DLT_SLIP: 16, DLT_PPP: 4, DLT_FDDI: 21, DLT_PFLOG: 48, DLT_PFSYNC: 4,
-          DLT_LOOP: 4, DLT_LINUX_SLL: 16}
+          DLT_LOOP: 4, DLT_LINUX_SLL: 16, DLT_LINUX_SLL2: 20}
 
 
 class PktHdr(dpkt.Packet):
@@ -170,7 +172,6 @@
         __hdr__: Header fields of pcap file header.
         TODO.
     """
-
     __hdr__ = (
         ('magic', 'I', TCPDUMP_MAGIC),
         ('v_major', 'H', PCAP_VERSION_MAJOR),
@@ -195,36 +196,70 @@
         __hdr__: Header fields of simple pcap dumpfile writer.
         TODO.
     """
+    __le = sys.byteorder == 'little'
 
     def __init__(self, fileobj, snaplen=1500, linktype=DLT_EN10MB, nano=False):
         if 'b' not in fileobj.mode:
             raise ValueError('PCAP file NOT in binary mode (wb)')
         self.__f = fileobj
         self._precision = 9 if nano else 6
+        self._precision_multiplier = 10**self._precision
+
         magic = TCPDUMP_MAGIC_NANO if nano else TCPDUMP_MAGIC
-        if sys.byteorder == 'little':
+        if self.__le:
             fh = LEFileHdr(snaplen=snaplen, linktype=linktype, magic=magic)
+            self._PktHdr = LEPktHdr()
         else:
             fh = FileHdr(snaplen=snaplen, linktype=linktype, magic=magic)
+            self._PktHdr = PktHdr()
+
+        self._pack_hdr = self._PktHdr._pack_hdr
         self.__f.write(bytes(fh))
 
     def writepkt(self, pkt, ts=None):
+        """Write single packet and optional timestamp to file.
+
+        Args:
+            pkt: `bytes` will be called on this and written to file.
+            ts (float): Timestamp in seconds. Defaults to current time.
+        """
         if ts is None:
             ts = time.time()
-        s = bytes(pkt)
-        n = len(s)
+
+        self.writepkt_time(bytes(pkt), ts)
+
+    def writepkt_time(self, pkt, ts):
+        """Write single packet and its timestamp to file.
+
+        Args:
+            pkt (bytes): Some `bytes` to write to the file
+            ts (float): Timestamp in seconds
+        """
+        n = len(pkt)
         sec = int(ts)
-        usec = int(round(ts % 1 * 10 ** self._precision))
-        if sys.byteorder == 'little':
-            ph = LEPktHdr(tv_sec=sec,
-                          tv_usec=usec,
-                          caplen=n, len=n)
-        else:
-            ph = PktHdr(tv_sec=sec,
-                        tv_usec=usec,
-                        caplen=n, len=n)
-        self.__f.write(bytes(ph))
-        self.__f.write(s)
+        usec = intround(ts % 1 * self._precision_multiplier)
+        ph = self._pack_hdr(sec, usec, n, n)
+        self.__f.write(ph + pkt)
+
+    def writepkts(self, pkts):
+        """Write an iterable of packets to file.
+
+        Timestamps should be in seconds.
+        Packets must be of type `bytes` as they will not be cast.
+
+        Args:
+            pkts: iterable containing (ts, pkt)
+        """
+        fd = self.__f
+        pack_hdr = self._pack_hdr
+        precision_multiplier = self._precision_multiplier
+
+        for ts, pkt in pkts:
+            n = len(pkt)
+            sec = int(ts)
+            usec = intround(ts % 1 * precision_multiplier)
+            ph = pack_hdr(sec, usec, n, n)
+            fd.write(ph + pkt)
 
     def close(self):
         self.__f.close()
@@ -239,7 +274,6 @@
         __hdr__: Header fields of simple pypcap-compatible pcap file reader.
         TODO.
     """
-
     def __init__(self, fileobj):
         self.name = getattr(fileobj, 'name', '<%s>' % fileobj.__class__.__name__)
         if 'b' not in fileobj.mode:
@@ -273,13 +307,14 @@
         return self.__fh.linktype
 
     def setfilter(self, value, optimize=1):
-        return NotImplementedError
+        raise NotImplementedError
 
     def readpkts(self):
         return list(self)
 
     def __next__(self):
         return next(self.__iter)
+    next = __next__  # Python 2 compat
 
     def dispatch(self, cnt, callback, *args):
         """Collect and process packets with a user callback.
@@ -321,6 +356,57 @@
             yield (hdr.tv_sec + (hdr.tv_usec / self._divisor), buf)
 
 
+class UniversalReader(object):
+    """
+    Universal pcap reader for the libpcap and pcapng file formats
+    """
+    def __new__(cls, fileobj):
+        try:
+            pcap = Reader(fileobj)
+        except ValueError as e1:
+            fileobj.seek(0)
+            try:
+                from . import pcapng
+                pcap = pcapng.Reader(fileobj)
+            except ValueError as e2:
+                raise ValueError('unknown pcap format; libpcap error: %s, pcapng error: %s' % (e1, e2))
+        return pcap
+
+
+################################################################################
+#                                    TESTS                                     #
+################################################################################
+
+class TryExceptException:
+    def __init__(self, exception_type, msg=''):
+        self.exception_type = exception_type
+        self.msg = msg
+
+    def __call__(self, f, *args, **kwargs):
+        def wrapper(*args, **kwargs):
+            try:
+                f()
+            except self.exception_type as e:
+                if self.msg:
+                    assert str(e) == self.msg
+            else:
+                raise Exception("There should have been an Exception raised")
+        return wrapper
+
+
+@TryExceptException(Exception, msg='There should have been an Exception raised')
+def test_TryExceptException():
+    """Check that we can catch a function which does not throw an exception when it is supposed to"""
+    @TryExceptException(NotImplementedError)
+    def fun():
+        pass
+
+    try:
+        fun()
+    except Exception as e:
+        raise e
+
+
 def test_pcap_endian():
     be = b'\xa1\xb2\xc3\xd4\x00\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x00\x00\x00\x01'
     le = b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\x00\x00\x00\x01\x00\x00\x00'
@@ -329,14 +415,20 @@
     assert (befh.linktype == lefh.linktype)
 
 
-def test_reader():
-    data = (  # full libpcap file with one packet
+class TestData():
+    pcap = (  # full libpcap file with one packet
         b'\xd4\xc3\xb2\xa1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x01\x00\x00\x00'
         b'\xb2\x67\x4a\x42\xae\x91\x07\x00\x46\x00\x00\x00\x46\x00\x00\x00\x00\xc0\x9f\x32\x41\x8c\x00\xe0'
         b'\x18\xb1\x0c\xad\x08\x00\x45\x00\x00\x38\x00\x00\x40\x00\x40\x11\x65\x47\xc0\xa8\xaa\x08\xc0\xa8'
         b'\xaa\x14\x80\x1b\x00\x35\x00\x24\x85\xed'
     )
 
+
+def test_reader():
+    import pytest
+
+    data = TestData().pcap
+
     # --- BytesIO tests ---
     from .compat import BytesIO
 
@@ -346,6 +438,10 @@
     assert reader.name == '<BytesIO>'
     _, buf1 = next(iter(reader))
     assert buf1 == data[FileHdr.__hdr_len__ + PktHdr.__hdr_len__:]
+    assert reader.datalink() == 1
+
+    with pytest.raises(NotImplementedError):
+        reader.setfilter(1, 2)
 
     # --- dispatch() tests ---
 
@@ -365,41 +461,191 @@
     assert reader.dispatch(1, lambda ts, pkt: None) == 1
     assert reader.dispatch(1, lambda ts, pkt: None) == 0
 
+    # test loop() over all packets
+    fobj.seek(0)
+    reader = Reader(fobj)
 
-def test_writer_precision():
-    data = b'foo'
+    class Count:
+        counter = 0
+
+        @classmethod
+        def inc(cls):
+            cls.counter += 1
+
+    reader.loop(lambda ts, pkt: Count.inc())
+    assert Count.counter == 1
+
+
+def test_reader_dloff():
+    from binascii import unhexlify
+    buf_filehdr = unhexlify(
+        'a1b2c3d4'    # TCPDUMP_MAGIC
+        '0001'        # v_major
+        '0002'        # v_minor
+        '00000000'    # thiszone
+        '00000000'    # sigfigs
+        '00000100'    # snaplen
+        '00000023'    # linktype (not known)
+    )
+
+    buf_pkthdr = unhexlify(
+        '00000003'  # tv_sec
+        '00000005'  # tv_usec
+        '00000004'  # caplen
+        '00000004'  # len
+    )
+
     from .compat import BytesIO
-
-    # default precision
-    fobj = BytesIO()
-    writer = Writer(fobj)
-    writer.writepkt(data, ts=1454725786.526401)
-    fobj.flush()
-    fobj.seek(0)
-
+    fobj = BytesIO(buf_filehdr + buf_pkthdr + b'\x11' * 4)
     reader = Reader(fobj)
-    ts, buf1 = next(iter(reader))
-    assert ts == 1454725786.526401
-    assert buf1 == b'foo'
 
-    # nano precision
-    from decimal import Decimal
+    # confirm that if the linktype is unknown, it defaults to 0
+    assert reader.dloff == 0
 
-    fobj = BytesIO()
-    writer = Writer(fobj, nano=True)
-    writer.writepkt(data, ts=Decimal('1454725786.010203045'))
-    fobj.flush()
-    fobj.seek(0)
-
-    reader = Reader(fobj)
-    ts, buf1 = next(iter(reader))
-    assert ts == Decimal('1454725786.010203045')
-    assert buf1 == b'foo'
+    assert next(reader) == (3.000005, b'\x11' * 4)
 
 
-if __name__ == '__main__':
-    test_pcap_endian()
-    test_reader()
-    test_writer_precision()
+@TryExceptException(ValueError, msg="invalid tcpdump header")
+def test_reader_badheader():
+    from .compat import BytesIO
+    fobj = BytesIO(b'\x00' * 24)
+    _ = Reader(fobj)  # noqa
 
-    print('Tests Successful...')
+
+def test_reader_fd():
+    data = TestData().pcap
+
+    import tempfile
+    with tempfile.TemporaryFile() as fd:
+        fd.write(data)
+        fd.seek(0)
+        reader = Reader(fd)
+        assert reader.fd == fd.fileno()
+        assert reader.fileno() == fd.fileno()
+
+
+class WriterTestWrap:
+    """
+    Decorate a writer test function with an instance of this class.
+
+    The test will be provided with a writer object, which it should write some pkts to.
+
+    After the test has run, the BytesIO object will be passed to a Reader,
+    which will compare each pkt to the return value of the test.
+    """
+    def __init__(self, *args, **kwargs):
+        self.args = args
+        self.kwargs = kwargs
+
+    def __call__(self, f, *args, **kwargs):
+        def wrapper(*args, **kwargs):
+            from .compat import BytesIO
+            for little_endian in [True, False]:
+                fobj = BytesIO()
+                _sysle = Writer._Writer__le
+                Writer._Writer__le = little_endian
+                f.__globals__['writer'] = Writer(fobj, **self.kwargs.get('writer', {}))
+                f.__globals__['fobj'] = fobj
+                pkts = f(*args, **kwargs)
+                fobj.flush()
+                fobj.seek(0)
+
+                assert pkts
+                for (ts_out, pkt_out), (ts_in, pkt_in) in zip(pkts, Reader(fobj).readpkts()):
+                    assert ts_out == ts_in
+                    assert pkt_out == pkt_in
+
+                # 'noqa' for flake8 to ignore these since writer was injected into globals
+                writer.close()  # noqa
+                Writer._Writer__le = _sysle
+        return wrapper
+
+
+@WriterTestWrap()
+def test_writer_precision_normal():
+    ts, pkt = 1454725786.526401, b'foo'
+    writer.writepkt(pkt, ts=ts)  # noqa
+    return [(ts, pkt)]
+
+
+@WriterTestWrap(writer={'nano': True})
+def test_writer_precision_nano():
+    ts, pkt = Decimal('1454725786.010203045'), b'foo'
+    writer.writepkt(pkt, ts=ts)  # noqa
+    return [(ts, pkt)]
+
+
+@WriterTestWrap(writer={'nano': False})
+def test_writer_precision_nano_fail():
+    """if writer is not set to nano, supplying this timestamp should be truncated"""
+    ts, pkt = (Decimal('1454725786.010203045'), b'foo')
+    writer.writepkt(pkt, ts=ts)  # noqa
+    return [(1454725786.010203, pkt)]
+
+
+@WriterTestWrap()
+def test_writepkt_no_time():
+    ts, pkt = 1454725786.526401, b'foooo'
+    _tmp = time.time
+    time.time = lambda: ts
+    writer.writepkt(pkt)  # noqa
+    time.time = _tmp
+    return [(ts, pkt)]
+
+
+@WriterTestWrap(writer={'snaplen': 10})
+def test_writepkt_snaplen():
+    ts, pkt = 1454725786.526401, b'foooo'
+    writer.writepkt(pkt, ts)  # noqa
+    return [(ts, pkt)]
+
+
+@WriterTestWrap()
+def test_writepkt_with_time():
+    ts, pkt = 1454725786.526401, b'foooo'
+    writer.writepkt(pkt, ts)  # noqa
+    return [(ts, pkt)]
+
+
+@WriterTestWrap()
+def test_writepkt_time():
+    ts, pkt = 1454725786.526401, b'foooo'
+    writer.writepkt_time(pkt, ts)  # noqa
+    return [(ts, pkt)]
+
+
+@WriterTestWrap()
+def test_writepkts():
+    """writing multiple packets from a list"""
+    pkts = [
+        (1454725786.526401, b"fooo"),
+        (1454725787.526401, b"barr"),
+        (3243204320.093211, b"grill"),
+        (1454725789.526401, b"lol"),
+    ]
+
+    writer.writepkts(pkts)  # noqa
+    return pkts
+
+
+def test_universal_reader():
+    import pytest
+    from .compat import BytesIO
+    from . import pcapng
+
+    # libpcap
+    data = TestData().pcap
+    fobj = BytesIO(data)
+    reader = UniversalReader(fobj)
+    assert isinstance(reader, Reader)
+
+    # pcapng
+    data = pcapng.define_testdata().valid_pcapng
+    fobj = BytesIO(data)
+    reader = UniversalReader(fobj)
+    assert isinstance(reader, pcapng.Reader)
+
+    # unknown
+    fobj = BytesIO(b'\x42' * 1000)
+    with pytest.raises(ValueError):
+        reader = UniversalReader(fobj)
diff --git a/dpkt/pcapng.py b/dpkt/pcapng.py
index c2ce63c..313cc47 100644
--- a/dpkt/pcapng.py
+++ b/dpkt/pcapng.py
@@ -11,7 +11,7 @@
 import sys
 
 from . import dpkt
-from .compat import BytesIO
+from .compat import BytesIO, intround
 
 BYTE_ORDER_MAGIC = 0x1A2B3C4D
 BYTE_ORDER_MAGIC_LE = 0x4D3C2B1A
@@ -97,19 +97,23 @@
     return struct_pack('%ss' % _align32b(len(s)), s)
 
 
+def _padded_tolen(s, tolen):
+    """Return bytes `s` padded with `tolen` zeroes to align to the 32-bit boundary"""
+    return struct_pack('%ss' % tolen, s)
+
+
 def _padlen(s):
     """Return size of padding required to align str `s` to the 32-bit boundary"""
     return _align32b(len(s)) - len(s)
 
 
 class _PcapngBlock(dpkt.Packet):
-
     """Base class for a pcapng block with Options"""
 
     __hdr__ = (
         ('type', 'I', 0),  # block type
         ('len', 'I', 12),  # block total length: total size of this block, in octets
-        #( body, variable size )
+        # ( body, variable size )
         ('_len', 'I', 12),  # dup of len
     )
 
@@ -152,10 +156,13 @@
 
     def __bytes__(self):
         opts_buf = self._do_pack_options()
-        self.len = self._len = self.__hdr_len__ + len(opts_buf)
 
-        hdr_buf = dpkt.Packet.pack_hdr(self)
-        return hdr_buf[:-4] + opts_buf + hdr_buf[-4:]
+        n = len(opts_buf) + self.__hdr_len__
+        self.len = n
+        self._len = n
+
+        hdr_buf = self._pack_hdr(self.type, n, n)
+        return b''.join([hdr_buf[:-4], opts_buf, hdr_buf[-4:]])
 
     def __len__(self):
         if not getattr(self, 'opts', None):
@@ -170,7 +177,6 @@
 
 
 class PcapngOption(dpkt.Packet):
-
     """A single Option"""
 
     __hdr__ = (
@@ -186,9 +192,7 @@
         if self.code == PCAPNG_OPT_COMMENT:
             self.text = self.data.decode('utf-8')
 
-
     def __bytes__(self):
-        #return dpkt.Packet.__bytes__(self)
         # encode comment
         if self.code == PCAPNG_OPT_COMMENT:
             text = getattr(self, 'text', self.data)
@@ -196,7 +200,8 @@
             self.data = text.encode('utf-8') if not isinstance(text, bytes) else text
 
         self.len = len(self.data)
-        return dpkt.Packet.pack_hdr(self) + _padded(self.data)
+        hdr = self._pack_hdr(self.code, self.len)
+        return hdr + _padded(self.data)
 
     def __len__(self):
         return self.__hdr_len__ + len(self.data) + _padlen(self.data)
@@ -213,7 +218,6 @@
 
 
 class SectionHeaderBlock(_PcapngBlock):
-
     """Section Header block"""
 
     __hdr__ = (
@@ -223,17 +227,34 @@
         ('v_major', 'H', PCAPNG_VERSION_MAJOR),
         ('v_minor', 'H', PCAPNG_VERSION_MINOR),
         ('sec_len', 'q', -1),  # section length, -1 = auto
-        #( options, variable size )
+        # ( options, variable size )
         ('_len', 'I', 28)
     )
 
+    def __bytes__(self):
+        opts_buf = self._do_pack_options()
+
+        n = len(opts_buf) + self.__hdr_len__
+        self.len = n
+        self._len = n
+
+        hdr_buf = self._pack_hdr(
+            self.type,
+            n,
+            self.bom,
+            self.v_major,
+            self.v_minor,
+            self.sec_len,
+            n,
+        )
+        return b''.join([hdr_buf[:-4], opts_buf, hdr_buf[-4:]])
+
 
 class SectionHeaderBlockLE(SectionHeaderBlock):
     __byte_order__ = '<'
 
 
 class InterfaceDescriptionBlock(_PcapngBlock):
-
     """Interface Description block"""
 
     __hdr__ = (
@@ -242,17 +263,33 @@
         ('linktype', 'H', DLT_EN10MB),
         ('_reserved', 'H', 0),
         ('snaplen', 'I', 1500),
-        #( options, variable size )
+        # ( options, variable size )
         ('_len', 'I', 20)
     )
 
+    def __bytes__(self):
+        opts_buf = self._do_pack_options()
+
+        n = len(opts_buf) + self.__hdr_len__
+        self.len = n
+        self._len = n
+
+        hdr_buf = self._pack_hdr(
+            self.type,
+            n,
+            self.linktype,
+            self._reserved,
+            self.snaplen,
+            n,
+        )
+        return b''.join([hdr_buf[:-4], opts_buf, hdr_buf[-4:]])
+
 
 class InterfaceDescriptionBlockLE(InterfaceDescriptionBlock):
     __byte_order__ = '<'
 
 
 class EnhancedPacketBlock(_PcapngBlock):
-
     """Enhanced Packet block"""
 
     __hdr__ = (
@@ -263,8 +300,8 @@
         ('ts_low', 'I', 0),  # timestamp low
         ('caplen', 'I', 0),  # captured len, size of pkt_data
         ('pkt_len', 'I', 0),  # actual packet len
-        #( pkt_data, variable size )
-        #( options, variable size )
+        # ( pkt_data, variable size )
+        # ( options, variable size )
         ('_len', 'I', 64)
     )
 
@@ -283,13 +320,29 @@
 
     def __bytes__(self):
         pkt_buf = self.pkt_data
-        self.caplen = self.pkt_len = len(pkt_buf)
+
+        pkt_len = len(pkt_buf)
+        self.caplen = pkt_len
+        self.pkt_len = pkt_len
 
         opts_buf = self._do_pack_options()
-        self.len = self._len = self.__hdr_len__ + _align32b(self.caplen) + len(opts_buf)
 
-        hdr_buf = dpkt.Packet.pack_hdr(self)
-        return hdr_buf[:-4] + _padded(pkt_buf) + opts_buf + hdr_buf[-4:]
+        n = self.__hdr_len__ + _align32b(self.caplen) + len(opts_buf)
+        self.len = n
+        self._len = n
+
+        hdr_buf = self._pack_hdr(
+            self.type,
+            n,
+            self.iface_id,
+            self.ts_high,
+            self.ts_low,
+            pkt_len,
+            pkt_len,
+            n
+        )
+
+        return b''.join([hdr_buf[:-4], _padded(pkt_buf), opts_buf, hdr_buf[-4:]])
 
     def __len__(self):
         opts_len = sum(len(o) for o in self.opts)
@@ -301,33 +354,41 @@
 
 
 class Writer(object):
-
     """Simple pcapng dumpfile writer."""
 
+    __le = sys.byteorder == 'little'
+
     def __init__(self, fileobj, snaplen=1500, linktype=DLT_EN10MB, shb=None, idb=None):
         """
         Create a pcapng dumpfile writer for the given fileobj.
 
         shb can be an instance of SectionHeaderBlock(LE)
-        idb can be an instance of InterfaceDescriptionBlock(LE)
+        idb can be an instance of InterfaceDescriptionBlock(LE) (or sequence of them)
         """
         self.__f = fileobj
-        self.__le = sys.byteorder == 'little'
 
         if shb:
             self._validate_block('shb', shb, SectionHeaderBlock)
         if idb:
-            self._validate_block('idb', idb, InterfaceDescriptionBlock)
+            try:
+                for idb_ in idb:
+                    self._validate_block('idb', idb_, InterfaceDescriptionBlock)
+            except (TypeError, ValueError):  # not iter or _validate_block failed
+                self._validate_block('idb', idb, InterfaceDescriptionBlock)
+                idb = [idb]
 
         if self.__le:
             shb = shb or SectionHeaderBlockLE()
-            idb = idb or InterfaceDescriptionBlockLE(snaplen=snaplen, linktype=linktype)
+            idb = idb or [InterfaceDescriptionBlockLE(snaplen=snaplen, linktype=linktype)]
+            self._kls = EnhancedPacketBlockLE
         else:
             shb = shb or SectionHeaderBlock()
-            idb = idb or InterfaceDescriptionBlock(snaplen=snaplen, linktype=linktype)
+            idb = idb or [InterfaceDescriptionBlock(snaplen=snaplen, linktype=linktype)]
+            self._kls = EnhancedPacketBlock
 
         self.__f.write(bytes(shb))
-        self.__f.write(bytes(idb))
+        for idb_ in idb:
+            self.__f.write(bytes(idb_))
 
     def _validate_block(self, arg_name, blk, expected_cls):
         """Check a user-defined block for correct type and endianness"""
@@ -345,18 +406,19 @@
 
     def writepkt(self, pkt, ts=None):
         """
-        Write a single packet with its timestamp.
+        Write a single packet with an optional timestamp.
 
-        pkt can be a buffer or an instance of EnhancedPacketBlock(LE)
-        ts is a Unix timestamp in seconds since Epoch (e.g. 1454725786.99)
+        Args:
+            pkt: buffer or instance of EnhancedPacketBlock(LE)
+            ts: Unix timestamp in seconds since Epoch (e.g. 1454725786.99)
         """
         if isinstance(pkt, EnhancedPacketBlock):
             self._validate_block('pkt', pkt, EnhancedPacketBlock)
 
             if ts is not None:  # ts as an argument gets precedence
-                ts = int(round(ts * 1e6))
+                ts = intround(ts * 1e6)
             elif pkt.ts_high == pkt.ts_low == 0:
-                ts = int(round(time() * 1e6))
+                ts = intround(time() * 1e6)
 
             if ts is not None:
                 pkt.ts_high = ts >> 32
@@ -368,21 +430,76 @@
         # pkt is a buffer - wrap it into an EPB
         if ts is None:
             ts = time()
-        ts = int(round(ts * 1e6))  # to int microseconds
+        self.writepkt_time(pkt, ts)
 
-        s = bytes(pkt)
+    def writepkt_time(self, pkt, ts):
+        """
+        Write a single packet with a mandatory timestamp.
+
+        Args:
+            pkt: a buffer
+            ts: Unix timestamp in seconds since Epoch (e.g. 1454725786.99)
+        """
+        ts = intround(ts * 1e6)  # to int microseconds
+
+        s = pkt
         n = len(s)
 
-        kls = EnhancedPacketBlockLE if self.__le else EnhancedPacketBlock
-        epb = kls(ts_high=ts >> 32, ts_low=ts & 0xffffffff, caplen=n, pkt_len=n, pkt_data=s)
+        epb = self._kls(
+            ts_high=ts >> 32,
+            ts_low=ts & 0xffffffff,
+            caplen=n,
+            pkt_len=n,
+            pkt_data=s
+        )
         self.__f.write(bytes(epb))
 
+    def writepkts(self, pkts):
+        """
+        Take an iterable of (ts, pkt), and write to file.
+        """
+        kls = self._kls()
+        ph = kls._pack_hdr
+        fd = self.__f
+
+        iface_id = kls.iface_id
+        pkt_type = kls.type
+
+        opts_buf = kls._do_pack_options()
+        opts_len = len(opts_buf)
+
+        hdr_len = kls.__hdr_len__
+        precalc_n = hdr_len + opts_len
+
+        for ts, pkt in pkts:
+            ts = intround(ts * 1e6)  # to int microseconds
+            pkt_len = len(pkt)
+            pkt_len_align = _align32b(pkt_len)
+
+            n = precalc_n + pkt_len_align
+            hdr_buf = ph(
+                pkt_type,
+                n,
+                iface_id,
+                ts >> 32,
+                ts & 0xffffffff,
+                pkt_len,
+                pkt_len,
+                n
+            )
+            buf = b''.join([
+                hdr_buf[:-4],
+                _padded_tolen(pkt, pkt_len_align),
+                opts_buf,
+                hdr_buf[-4:]
+            ])
+            fd.write(buf)
+
     def close(self):
         self.__f.close()
 
 
 class Reader(object):
-
     """Simple pypcap-compatible pcapng file reader."""
 
     def __init__(self, fileobj):
@@ -471,13 +588,14 @@
         return self.idb.linktype
 
     def setfilter(self, value, optimize=1):
-        return NotImplementedError
+        raise NotImplementedError
 
     def readpkts(self):
         return list(self)
 
-    def next(self):
+    def __next__(self):
         return next(self.__iter)
+    next = __next__  # Python 2 compat
 
     def dispatch(self, cnt, callback, *args):
         """Collect and process packets with a user callback.
@@ -530,7 +648,6 @@
 # TESTS #
 #########
 
-
 def test_shb():
     """Test SHB with options"""
     buf = (
@@ -565,6 +682,7 @@
     assert bytes(shb.opts[1]) == b'\x00\x00\x00\x00'
 
     # block packing
+    assert bytes(shb) == bytes(buf)
     assert str(shb) == str(buf)
     assert len(shb) == len(buf)
 
@@ -597,6 +715,7 @@
     assert bytes(idb.opts[1]) == b'\x00\x00\x00\x00'
 
     # block packing
+    assert bytes(idb) == bytes(buf)
     assert str(idb) == str(buf)
     assert len(idb) == len(buf)
 
@@ -635,6 +754,7 @@
     assert bytes(epb.opts[1]) == b'\x00\x00\x00\x00'
 
     # block packing
+    assert bytes(epb) == bytes(buf)
     assert str(epb) == str(buf)
     assert len(epb) == len(buf)
 
@@ -664,28 +784,345 @@
     fobj.close()
 
 
+def test_pcapng_header():
+    """Reading an empty file will fail as the header length is incorrect"""
+    fobj = BytesIO()
+
+    try:
+        Reader(fobj)
+    except Exception as e:
+        assert isinstance(e, ValueError)
+
+
+def define_testdata():
+    class TestData(object):
+        def __init__(self):
+            self.valid_shb_le = SectionHeaderBlockLE(opts=[
+                PcapngOptionLE(code=3, data=b'64-bit Windows 8.1, build 9600'),
+                PcapngOptionLE(code=4, data=b'Dumpcap 1.12.7 (v1.12.7-0-g7fc8978 from master-1.12)'),
+                PcapngOptionLE()
+            ])
+
+            self.valid_shb_be = SectionHeaderBlock(opts=[
+                PcapngOption(code=3, data=b'64-bit Windows 8.1, build 9600'),
+                PcapngOption(code=4, data=b'Dumpcap 1.12.7 (v1.12.7-0-g7fc8978 from master-1.12)'),
+                PcapngOption()
+            ])
+
+            self.valid_idb_le = InterfaceDescriptionBlockLE(snaplen=0x40000, opts=[
+                PcapngOptionLE(code=2, data=b'\\Device\\NPF_{3BBF21A7-91AE-4DDB-AB2C-C782999C22D5}'),
+                PcapngOptionLE(code=9, data=b'\x06'),
+                PcapngOptionLE(code=12, data=b'64-bit Windows 8.1, build 9600'),
+                PcapngOptionLE()
+            ])
+
+            self.valid_idb_be = InterfaceDescriptionBlock(snaplen=0x40000, opts=[
+                PcapngOption(code=2, data=b'\\Device\\NPF_{3BBF21A7-91AE-4DDB-AB2C-C782999C22D5}'),
+                PcapngOption(code=9, data=b'\x06'),
+                PcapngOption(code=12, data=b'64-bit Windows 8.1, build 9600'),
+                PcapngOption()
+            ])
+
+            self.valid_pcapng = (
+                b'\x0a\x0d\x0d\x0a\x7c\x00\x00\x00\x4d\x3c\x2b\x1a\x01\x00\x00'
+                b'\x00\xff\xff\xff\xff\xff\xff\xff\xff\x03\x00\x1e\x00\x36\x34'
+                b'\x2d\x62\x69\x74\x20\x57\x69\x6e\x64\x6f\x77\x73\x20\x38\x2e'
+                b'\x31\x2c\x20\x62\x75\x69\x6c\x64\x20\x39\x36\x30\x30\x00\x00'
+                b'\x04\x00\x34\x00\x44\x75\x6d\x70\x63\x61\x70\x20\x31\x2e\x31'
+                b'\x32\x2e\x37\x20\x28\x76\x31\x2e\x31\x32\x2e\x37\x2d\x30\x2d'
+                b'\x67\x37\x66\x63\x38\x39\x37\x38\x20\x66\x72\x6f\x6d\x20\x6d'
+                b'\x61\x73\x74\x65\x72\x2d\x31\x2e\x31\x32\x29\x00\x00\x00\x00'
+                b'\x7c\x00\x00\x00\x01\x00\x00\x00\x7c\x00\x00\x00\x01\x00\x00'
+                b'\x00\x00\x00\x04\x00\x02\x00\x32\x00\x5c\x44\x65\x76\x69\x63'
+                b'\x65\x5c\x4e\x50\x46\x5f\x7b\x33\x42\x42\x46\x32\x31\x41\x37'
+                b'\x2d\x39\x31\x41\x45\x2d\x34\x44\x44\x42\x2d\x41\x42\x32\x43'
+                b'\x2d\x43\x37\x38\x32\x39\x39\x39\x43\x32\x32\x44\x35\x7d\x00'
+                b'\x00\x09\x00\x01\x00\x06\x00\x00\x00\x0c\x00\x1e\x00\x36\x34'
+                b'\x2d\x62\x69\x74\x20\x57\x69\x6e\x64\x6f\x77\x73\x20\x38\x2e'
+                b'\x31\x2c\x20\x62\x75\x69\x6c\x64\x20\x39\x36\x30\x30\x00\x00'
+                b'\x00\x00\x00\x00\x7c\x00\x00\x00\x06\x00\x00\x00\x84\x00\x00'
+                b'\x00\x00\x00\x00\x00\x63\x20\x05\x00\xd6\xc4\xab\x0b\x4a\x00'
+                b'\x00\x00\x4a\x00\x00\x00\x08\x00\x27\x96\xcb\x7c\x52\x54\x00'
+                b'\x12\x35\x02\x08\x00\x45\x00\x00\x3c\xa4\x40\x00\x00\x1f\x01'
+                b'\x27\xa2\xc0\xa8\x03\x28\x0a\x00\x02\x0f\x00\x00\x56\xf0\x00'
+                b'\x01\x00\x6d\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c'
+                b'\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x41\x42\x43\x44'
+                b'\x45\x46\x47\x48\x49\x00\x00\x01\x00\x0f\x00\x64\x70\x6b\x74'
+                b'\x20\x69\x73\x20\x61\x77\x65\x73\x6f\x6d\x65\x00\x00\x00\x00'
+                b'\x00\x84\x00\x00\x00'
+            )
+            self.valid_pkts = [
+                (1442984653.210838,
+                 (b"\x08\x00'\x96\xcb|RT\x00\x125\x02\x08\x00E\x00\x00<\xa4@"
+                  b"\x00\x00\x1f\x01'\xa2\xc0\xa8\x03(\n\x00\x02\x0f\x00\x00V"
+                  b"\xf0\x00\x01\x00mABCDEFGHIJKLMNOPQRSTUVWABCDEFGHI"))
+            ]
+
+            self.valid_epb_be = EnhancedPacketBlock(opts=[
+                PcapngOption(code=1, text=b'dpkt is awesome'),
+                PcapngOption()
+            ], pkt_data=(
+                b'\x08\x00\x27\x96\xcb\x7c\x52\x54\x00\x12\x35\x02\x08\x00\x45'
+                b'\x00\x00\x3c\xa4\x40\x00\x00\x1f\x01\x27\xa2\xc0\xa8\x03\x28'
+                b'\x0a\x00\x02\x0f\x00\x00\x56\xf0\x00\x01\x00\x6d\x41\x42\x43'
+                b'\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52'
+                b'\x53\x54\x55\x56\x57\x41\x42\x43\x44\x45\x46\x47\x48\x49'
+            ))
+
+            self.valid_epb_le = EnhancedPacketBlockLE(opts=[
+                PcapngOptionLE(code=1, text=b'dpkt is awesome'),
+                PcapngOptionLE()
+            ], pkt_data=(
+                b'\x08\x00\x27\x96\xcb\x7c\x52\x54\x00\x12\x35\x02\x08\x00\x45'
+                b'\x00\x00\x3c\xa4\x40\x00\x00\x1f\x01\x27\xa2\xc0\xa8\x03\x28'
+                b'\x0a\x00\x02\x0f\x00\x00\x56\xf0\x00\x01\x00\x6d\x41\x42\x43'
+                b'\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52'
+                b'\x53\x54\x55\x56\x57\x41\x42\x43\x44\x45\x46\x47\x48\x49'
+            ))
+
+        @property
+        def shb_idb_epb_le(self):
+            return self.valid_shb_le, self.valid_idb_le, self.valid_epb_le
+
+        @property
+        def shb_idb_epb_be(self):
+            return self.valid_shb_be, self.valid_idb_be, self.valid_epb_be
+
+        @property
+        def shb_idb_epb(self):
+            return self.shb_idb_epb_le if sys.byteorder == 'little' else self.shb_idb_epb_be
+
+    return TestData()
+
+
+def pre_test(f):
+    def wrapper(*args, **kwargs):
+        fobj = BytesIO()
+        f.__globals__['fobj'] = fobj
+        ret = f(*args, **kwargs)
+        fobj.flush()
+        fobj.seek(0)
+
+        return ret
+    return wrapper
+
+
+class WriterTestWrap:
+    """
+    Decorate a writer test function with an instance of this class.
+
+    The test will be provided with a writer object, which it should write some pkts to.
+
+    After the test has run, the BytesIO object will be passed to a Reader,
+    which will compare each pkt to the return value of the test.
+    """
+    def __init__(self, *args, **kwargs):
+        self.args = args
+        self.kwargs = kwargs
+
+    def __call__(self, f, *args, **kwargs):
+        def wrapper(*args, **kwargs):
+            from .compat import BytesIO
+            for little_endian in [True, False]:
+                fobj = BytesIO()
+                _sysle = Writer._Writer__le
+                Writer._Writer__le = little_endian
+                f.__globals__['writer'] = Writer(fobj, **self.kwargs.get('writer', {}))
+                f.__globals__['fobj'] = fobj
+                pkts = f(*args, **kwargs)
+                fobj.flush()
+                fobj.seek(0)
+
+                assert pkts, "You must return the input data from the test"
+                for (ts_out, pkt_out), (ts_in, pkt_in) in zip(pkts, iter(Reader(fobj))):
+                    assert ts_out == ts_in
+                    assert pkt_out == pkt_in
+
+                # 'noqa' for flake8 to ignore these since writer and fobj were injected into globals
+                writer.close()  # noqa
+                Writer._Writer__le = _sysle
+                del f.__globals__['writer']
+                del f.__globals__['fobj']
+        return wrapper
+
+
+class PostTest:
+    def __init__(self, *args, **kwargs):
+        self.args = args
+        self.kwargs = kwargs
+
+    def __call__(self, f, *args, **kwargs):
+        def wrapper(*args, **kwargs):
+            ret = f(*args, **kwargs)
+            fobj = f.__globals__['fobj']
+            test_type = self.kwargs.get('test')
+            if test_type == 'assertion':
+                isexception = False
+                try:
+                    Reader(fobj)
+                except Exception as e:
+                    isexception = True
+                    assert isinstance(e, self.kwargs['type'])
+                    assert str(e) == self.kwargs['msg']
+                assert isexception, "No assertion raised!"
+
+            elif test_type == 'compare_property':
+                prop = self.kwargs['property']
+                reader = Reader(fobj)
+                assert bytes(ret) == bytes(getattr(reader, prop))
+            elif test_type == 'compare_method':
+                method = self.kwargs['method']
+                reader = Reader(fobj)
+                comp = getattr(reader, method)()
+                assert comp == ret
+            else:
+                raise Exception("No test type specified")
+        return wrapper
+
+
+@PostTest(test='assertion', type=ValueError, msg='invalid pcapng header: not a SHB')
+@pre_test
+def test_shb_header():
+    shb = define_testdata().valid_shb_le
+    shb.type = 123456666
+    fobj.write(bytes(shb))  # noqa
+
+
+@PostTest(test='assertion', type=ValueError, msg='unknown endianness')
+@pre_test
+def test_shb_bom():
+    shb = define_testdata().valid_shb_le
+    shb.bom = 12345666
+    fobj.write(bytes(shb))  # noqa
+
+
+@PostTest(test='assertion', type=ValueError, msg='unknown pcapng version 123.45')
+@pre_test
+def test_shb_version():
+    shb = define_testdata().valid_shb_le
+    shb.v_major = 123
+    shb.v_minor = 45
+    fobj.write(bytes(shb))  # noqa
+
+
+@PostTest(test='assertion', type=ValueError, msg='IDB not found')
+@pre_test
+def test_no_idb():
+    shb = define_testdata().valid_shb_le
+    fobj.write(bytes(shb)+b'aaaa')  # noqa
+
+
+@PostTest(test='compare_property', property='idb')
+@pre_test
+def test_idb_opt_offset():
+    """Test that the timestamp offset is correctly written and read"""
+    shb = define_testdata().valid_shb_le
+    idb = define_testdata().valid_idb_le
+    idb.opts.insert(0, PcapngOptionLE(
+        code=PCAPNG_OPT_IF_TSOFFSET,
+        data=struct_pack('<q', 123456666))
+    )
+    fobj.write(bytes(shb)+bytes(idb))  # noqa
+    return idb
+
+
+@PostTest(test='compare_property', property='dloff')
+@pre_test
+def test_idb_linktype():
+    """Test that if the idb.linktype is not in dloff, dloff is set to 0"""
+    shb = define_testdata().valid_shb_le
+    idb = define_testdata().valid_idb_le
+    idb.linktype = 3456
+    fobj.write(bytes(shb)+bytes(idb))  # noqa
+    return 0
+
+
+def test_repr():
+    """check the __repr__ method for Packet subclass.
+
+    The __repr__ method currently includes the b'' in the string. This means that python2 and python3 will differ.
+    """
+    real = repr(define_testdata().valid_shb_le)
+
+    python2 = (
+        "SectionHeaderBlockLE(opts=[PcapngOptionLE(code=3, data='64-bit Windows 8.1, build 9600'),"
+        " PcapngOptionLE(code=4, data='Dumpcap 1.12.7 (v1.12.7-0-g7fc8978 from master-1.12)'),"
+        " PcapngOptionLE(opt_endofopt)])")
+    python3 = (
+        "SectionHeaderBlockLE(opts=[PcapngOptionLE(code=3, data=b'64-bit Windows 8.1, build 9600'),"
+        " PcapngOptionLE(code=4, data=b'Dumpcap 1.12.7 (v1.12.7-0-g7fc8978 from master-1.12)'),"
+        " PcapngOptionLE(opt_endofopt)])")
+
+    assert real in [python2, python3]
+
+
+@pre_test
+def test_filter():
+    buf = define_testdata().valid_pcapng
+    fobj.write(buf)  # noqa
+    fobj.flush()  # noqa
+    fobj.seek(0)  # noqa
+    reader = Reader(fobj)  # noqa
+    try:
+        reader.setfilter(None, None)
+    except Exception as e:
+        assert isinstance(e, NotImplementedError)
+
+
+@PostTest(test='compare_method', method='readpkts')
+@pre_test
+def test_readpkts():
+    fobj.write(define_testdata().valid_pcapng)  # noqa
+    return define_testdata().valid_pkts
+
+
+@PostTest(test='compare_method', method='next')
+@pre_test
+def test_next():
+    fobj.write(define_testdata().valid_pcapng)  # noqa
+    return define_testdata().valid_pkts[0]
+
+
+@pre_test
+def test_dispatch():
+    fobj.write(define_testdata().valid_pcapng)  # noqa
+    fobj.flush()  # noqa
+    fobj.seek(0)  # noqa
+
+    def callback(timestamp, pkt, *args):
+        assert (timestamp, pkt) == define_testdata().valid_pkts[0]
+
+    reader = Reader(fobj)  # noqa
+    assert 1 == reader.dispatch(0, callback)
+
+
+@pre_test
+def test_loop():
+    fobj.write(define_testdata().valid_pcapng)  # noqa
+    fobj.flush()  # noqa
+    fobj.seek(0)  # noqa
+
+    def callback(timestamp, pkt, *args):
+        assert (timestamp, pkt) == define_testdata().valid_pkts[0]
+
+    reader = Reader(fobj)  # noqa
+    reader.loop(callback)
+
+
+def test_idb_opt_err():
+    """Test that options end with opt_endofopt"""
+    idb = define_testdata().valid_idb_le
+    del idb.opts[-1]
+    try:
+        bytes(idb)
+    except Exception as e:
+        assert isinstance(e, dpkt.PackError)
+        assert str(e) == 'options must end with opt_endofopt'
+
+
 def test_custom_read_write():
     """Test a full pcapng file with 1 ICMP packet"""
-    buf = (
-        b'\x0a\x0d\x0d\x0a\x7c\x00\x00\x00\x4d\x3c\x2b\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff'
-        b'\xff\xff\x03\x00\x1e\x00\x36\x34\x2d\x62\x69\x74\x20\x57\x69\x6e\x64\x6f\x77\x73\x20\x38'
-        b'\x2e\x31\x2c\x20\x62\x75\x69\x6c\x64\x20\x39\x36\x30\x30\x00\x00\x04\x00\x34\x00\x44\x75'
-        b'\x6d\x70\x63\x61\x70\x20\x31\x2e\x31\x32\x2e\x37\x20\x28\x76\x31\x2e\x31\x32\x2e\x37\x2d'
-        b'\x30\x2d\x67\x37\x66\x63\x38\x39\x37\x38\x20\x66\x72\x6f\x6d\x20\x6d\x61\x73\x74\x65\x72'
-        b'\x2d\x31\x2e\x31\x32\x29\x00\x00\x00\x00\x7c\x00\x00\x00\x01\x00\x00\x00\x7c\x00\x00\x00'
-        b'\x01\x00\x00\x00\x00\x00\x04\x00\x02\x00\x32\x00\x5c\x44\x65\x76\x69\x63\x65\x5c\x4e\x50'
-        b'\x46\x5f\x7b\x33\x42\x42\x46\x32\x31\x41\x37\x2d\x39\x31\x41\x45\x2d\x34\x44\x44\x42\x2d'
-        b'\x41\x42\x32\x43\x2d\x43\x37\x38\x32\x39\x39\x39\x43\x32\x32\x44\x35\x7d\x00\x00\x09\x00'
-        b'\x01\x00\x06\x00\x00\x00\x0c\x00\x1e\x00\x36\x34\x2d\x62\x69\x74\x20\x57\x69\x6e\x64\x6f'
-        b'\x77\x73\x20\x38\x2e\x31\x2c\x20\x62\x75\x69\x6c\x64\x20\x39\x36\x30\x30\x00\x00\x00\x00'
-        b'\x00\x00\x7c\x00\x00\x00\x06\x00\x00\x00\x84\x00\x00\x00\x00\x00\x00\x00\x63\x20\x05\x00'
-        b'\xd6\xc4\xab\x0b\x4a\x00\x00\x00\x4a\x00\x00\x00\x08\x00\x27\x96\xcb\x7c\x52\x54\x00\x12'
-        b'\x35\x02\x08\x00\x45\x00\x00\x3c\xa4\x40\x00\x00\x1f\x01\x27\xa2\xc0\xa8\x03\x28\x0a\x00'
-        b'\x02\x0f\x00\x00\x56\xf0\x00\x01\x00\x6d\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c'
-        b'\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x41\x42\x43\x44\x45\x46\x47\x48\x49\x00\x00'
-        b'\x01\x00\x0f\x00\x64\x70\x6b\x74\x20\x69\x73\x20\x61\x77\x65\x73\x6f\x6d\x65\x00\x00\x00'
-        b'\x00\x00\x84\x00\x00\x00')
-
+    buf = define_testdata().valid_pcapng
     fobj = BytesIO(buf)
 
     # test reading
@@ -705,30 +1142,14 @@
     fobj.close()
 
     # test pcapng customized writing
-    shb = SectionHeaderBlockLE(opts=[
-        PcapngOptionLE(code=3, data=b'64-bit Windows 8.1, build 9600'),
-        PcapngOptionLE(code=4, data=b'Dumpcap 1.12.7 (v1.12.7-0-g7fc8978 from master-1.12)'),
-        PcapngOptionLE()
-    ])
-    idb = InterfaceDescriptionBlockLE(snaplen=0x40000, opts=[
-        PcapngOptionLE(code=2, data=b'\\Device\\NPF_{3BBF21A7-91AE-4DDB-AB2C-C782999C22D5}'),
-        PcapngOptionLE(code=9, data=b'\x06'),
-        PcapngOptionLE(code=12, data=b'64-bit Windows 8.1, build 9600'),
-        PcapngOptionLE()
-    ])
-    epb = EnhancedPacketBlockLE(opts=[
-        PcapngOptionLE(code=1, text=b'dpkt is awesome'),
-        PcapngOptionLE()
-    ], pkt_data=(
-        b'\x08\x00\x27\x96\xcb\x7c\x52\x54\x00\x12\x35\x02\x08\x00\x45\x00\x00\x3c\xa4\x40\x00\x00'
-        b'\x1f\x01\x27\xa2\xc0\xa8\x03\x28\x0a\x00\x02\x0f\x00\x00\x56\xf0\x00\x01\x00\x6d\x41\x42'
-        b'\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x41'
-        b'\x42\x43\x44\x45\x46\x47\x48\x49'
-    ))
+    shb, idb, epb = define_testdata().shb_idb_epb
+
     fobj = BytesIO()
     writer = Writer(fobj, shb=shb, idb=idb)
     writer.writepkt(epb, ts=1442984653.210838)
-    assert fobj.getvalue() == buf
+    # .valid_pcapng buf was collected on a little endian system
+    if sys.byteorder == 'little':
+        assert fobj.getvalue() == buf
     fobj.close()
 
     # same with timestamps defined inside EPB
@@ -738,18 +1159,195 @@
     fobj = BytesIO()
     writer = Writer(fobj, shb=shb, idb=idb)
     writer.writepkt(epb)
-    assert fobj.getvalue() == buf
+    if sys.byteorder == 'little':
+        assert fobj.getvalue() == buf
     fobj.close()
 
 
-if __name__ == '__main__':
-    # TODO: big endian unit tests; could not find any examples..
+def test_multi_idb_writer():
+    """Test writing multiple interface description blocks into pcapng and read it"""
+    fobj = BytesIO()
+    shb, idb, epb = define_testdata().shb_idb_epb
 
-    test_shb()
-    test_idb()
-    test_epb()
-    test_simple_write_read()
-    test_custom_read_write()
-    repr(PcapngOptionLE())
+    writer = Writer(fobj, shb=shb, idb=[idb, idb])
+    writer.writepkt(epb)
+    fobj.flush()
+    fobj.seek(0)
 
-    print('Tests Successful...')
+    Reader(fobj)
+    fobj.close()
+
+
+@pre_test
+def test_writer_validate_instance():
+    """System endianness and shb endianness should match"""
+    shb = 10
+
+    try:
+        writer = Writer(fobj, shb=shb)  # noqa
+    except Exception as e:
+        assert isinstance(e, ValueError)
+        assert str(e) == 'shb: expecting class SectionHeaderBlock'
+
+
+@pre_test
+def test_writepkt_epb_ts():
+    """writepkt should assign ts_high/low for epb if they are 0"""
+    global time
+    shb, idb, epb = define_testdata().shb_idb_epb
+    writer = Writer(fobj, shb=shb, idb=idb)  # noqa
+    epb.ts_high = epb.ts_low = 0
+    ts = 1454725786.526401
+    _tmp = time
+
+    def time():
+        return ts
+    writer.writepkt(epb)
+    time = _tmp
+
+    ts_high, ts_low = 338704, 3183502017
+    assert epb.ts_high == ts_high
+    assert epb.ts_low == ts_low
+
+
+@pre_test
+def test_writer_validate_le():
+    """System endianness and shb endianness should match"""
+    shb = define_testdata().valid_shb_be
+    _sysle = Writer._Writer__le
+
+    Writer._Writer__le = True
+
+    try:
+        writer = Writer(fobj, shb=shb)  # noqa
+    except Exception as e:
+        assert isinstance(e, ValueError)
+        assert str(e) == 'shb: expecting class SectionHeaderBlockLE on a little-endian system'
+
+    Writer._Writer__le = _sysle
+
+
+@pre_test
+def test_writer_validate_be():
+    """System endianness and shb endianness should match"""
+    shb = define_testdata().valid_shb_le
+    _sysle = Writer._Writer__le
+
+    Writer._Writer__le = False
+
+    try:
+        writer = Writer(fobj, shb=shb)  # noqa
+    except Exception as e:
+        assert isinstance(e, ValueError)
+        assert str(e) == 'shb: expecting class SectionHeaderBlock on a big-endian system'
+
+    Writer._Writer__le = _sysle
+
+
+@WriterTestWrap()
+def test_writepkt_no_time():
+    global time
+    ts, pkt = 1454725786.526401, b'foooo'
+    _tmp = time
+
+    def time():
+        return ts
+    writer.writepkt(pkt)  # noqa
+    time = _tmp
+    return [(ts, pkt)]
+
+
+@WriterTestWrap(writer={'snaplen': 10})
+def test_writepkt_snaplen():
+    ts, pkt = 1454725786.526401, b'foooo' * 100
+    writer.writepkt(pkt, ts)  # noqa
+    return [(ts, pkt)]
+
+
+@WriterTestWrap()
+def test_writepkt_with_time():
+    ts, pkt = 1454725786.526401, b'foooo'
+    writer.writepkt(pkt, ts)  # noqa
+    return [(ts, pkt)]
+
+
+@WriterTestWrap()
+def test_writepkts():
+    """writing multiple packets from a list"""
+    pkts = [
+        (1454725786.526401, b"fooo"),
+        (1454725787.526401, b"barr"),
+        (3243204320.093211, b"grill"),
+        (1454725789.526401, b"lol"),
+    ]
+
+    writer.writepkts(pkts)  # noqa
+    return pkts
+
+
+def test_pcapng_block_pack():
+    assert bytes(_PcapngBlock())
+
+
+def test_pcapng_block_unpack():
+    block = _PcapngBlock()
+    buf = b'012345678901'
+    try:
+        block.unpack(buf)
+    except Exception as e:
+        assert isinstance(e, dpkt.NeedData)
+
+
+def test_epb_unpack():
+    """EnhancedPacketBlock can only unpack data >64 bytes, the length of their header"""
+    shb, idb, epb = define_testdata().shb_idb_epb
+    buf = b'quite-long-but-not-long-enough-at-least-32'
+    try:
+        epb.unpack(buf)
+    except Exception as e:
+        assert isinstance(e, dpkt.NeedData)
+
+
+def test_epb_unpack_length_mismatch():
+    """Force calculated len to be 0 when unpacking epb, this should fail when unpacking"""
+    shb, idb, epb = define_testdata().shb_idb_epb
+
+    unpackme = bytes(epb)
+    unpackme = unpackme[:-4] + b'\x00' * 4
+    try:
+        epb.unpack(unpackme)
+    except Exception as e:
+        assert isinstance(e, dpkt.UnpackError)
+        assert str(e) == 'length fields do not match'
+
+
+def test_pcapng_block_len_no_opts():
+    """_PcapngBlock should return its own header __len__ if it has no opts"""
+    block = _PcapngBlock()
+    assert len(block) == 12
+
+
+def test_reader_file_descriptor():
+    """Reader has .fd and .fileno() convenience members. Compare them to the actual fobj that was passed in"""
+    pcapng = define_testdata().valid_pcapng
+    import tempfile
+    with tempfile.TemporaryFile() as fobj:
+        fobj.write(pcapng)
+        fobj.seek(0)
+
+        reader = Reader(fobj)
+        assert reader.fd == fobj.fileno()
+        assert reader.fileno() == fobj.fileno()
+
+
+def test_posttest():
+    """Check that PostTest wrapper doesn't fail silently"""
+    @PostTest()
+    @pre_test
+    def fun():
+        pass
+
+    try:
+        fun()
+    except Exception as e:
+        assert str(e) == 'No test type specified'
diff --git a/dpkt/pim.py b/dpkt/pim.py
index 723e132..7bf6d03 100644
--- a/dpkt/pim.py
+++ b/dpkt/pim.py
@@ -4,7 +4,6 @@
 from __future__ import absolute_import
 
 from . import dpkt
-from .decorators import deprecated
 
 
 class PIM(dpkt.Packet):
@@ -19,37 +18,50 @@
 
     __hdr__ = (
         ('_v_type', 'B', 0x20),
-        ('rsvd', 'B', 0),
+        ('_rsvd', 'B', 0),
         ('sum', 'H', 0)
     )
-
-    @property
-    def v(self):
-        return self._v_type >> 4
-
-    @v.setter
-    def v(self, v):
-        self._v_type = (v << 4) | (self._v_type & 0xf)
-
-    @property
-    def type(self):
-        return self._v_type & 0xf
-
-    @type.setter
-    def type(self, type):
-        self._v_type = (self._v_type & 0xf0) | type
+    __bit_fields__ = {
+        '_v_type': (
+            ('v', 4),
+            ('type', 4),
+        )
+    }
 
     def __bytes__(self):
         if not self.sum:
             self.sum = dpkt.in_cksum(dpkt.Packet.__bytes__(self))
         return dpkt.Packet.__bytes__(self)
 
+
 def test_pim():
-    pimdata =  PIM(b'\x20\x00\x9f\xf4\x00\x01\x00\x02\x00\x69')
+    from binascii import unhexlify
+    buf = unhexlify(
+        '20'            # _v_type
+        '00'            # rsvd
+        'df93'          # sum
+
+        '000100020069'  # data
+    )
+    pimdata = PIM(buf)
+    assert bytes(pimdata) == buf
+    # force checksum recalculation
+    pimdata = PIM(buf)
+    pimdata.sum = 0
+    assert pimdata.sum == 0
+    assert bytes(pimdata) == buf
+
     assert pimdata.v == 2
     assert pimdata.type == 0
 
     # test setters
+    buf_modified = unhexlify(
+        '31'            # _v_type
+        '00'            # rsvd
+        'df93'          # sum
+
+        '000100020069'  # data
+    )
     pimdata.v = 3
     pimdata.type = 1
-    assert bytes(pimdata) == b'\x31\x00\x9f\xf4\x00\x01\x00\x02\x00\x69'
+    assert bytes(pimdata) == buf_modified
diff --git a/dpkt/pmap.py b/dpkt/pmap.py
index 03fe68a..c7aff5a 100644
--- a/dpkt/pmap.py
+++ b/dpkt/pmap.py
@@ -19,7 +19,7 @@
         __hdr__: Header fields of Pmap.
         TODO.
     """
-    
+
     __hdr__ = (
         ('prog', 'I', 0),
         ('vers', 'I', 0),
diff --git a/dpkt/ppp.py b/dpkt/ppp.py
index 2e7c0c2..0840026 100644
--- a/dpkt/ppp.py
+++ b/dpkt/ppp.py
@@ -111,6 +111,73 @@
     assert p.pack_hdr() == b"\xff\x03\xc0\x21"
 
 
-if __name__ == '__main__':
-    # Runs all the test associated with this class/file
-    test_ppp()
+def test_ppp_classmethods():
+    import pytest
+
+    class TestProto(dpkt.Packet):
+        pass
+
+    proto_number = 123
+
+    # asserting that this proto is not currently added
+    with pytest.raises(KeyError):
+        PPP.get_p(proto_number)
+
+    PPP.set_p(proto_number, TestProto)
+
+    assert PPP.get_p(proto_number) == TestProto
+
+    # we need to reset the class, or impact other tests
+    del PPP._protosw[proto_number]
+
+
+def test_unpacking_exceptions():
+    from dpkt import ip
+
+    from binascii import unhexlify
+    buf_ppp = unhexlify(
+        'ff'  # addr
+        '03'  # cntrl
+        '21'  # p (PPP_IP)
+    )
+    buf_ip = unhexlify(
+        '45'    # _v_hl
+        '00'    # tos
+        '0014'  # len
+        '0000'  # id
+        '0000'  # off
+        '80'    # ttl
+        '06'    # p
+        'd47e'  # sum
+        '11111111'  # src
+        '22222222'  # dst
+    )
+
+    buf = buf_ppp + buf_ip
+    ppp = PPP(buf)
+    assert hasattr(ppp, 'ip')
+    assert isinstance(ppp.data, ip.IP)
+    assert bytes(ppp) == buf
+
+
+def test_ppp_packing_error():
+    import pytest
+
+    # addr is a 1-byte field, so this will overflow when packing
+    ppp = PPP(p=257, addr=1234)
+    with pytest.raises(dpkt.PackError):
+        ppp.pack_hdr()
+
+
+def test_proto_loading():
+    # test that failure to load protocol handlers isn't catastrophic
+    standard_protos = PPP._protosw
+    # delete existing protos
+    PPP._protosw = {}
+    assert not PPP._protosw
+
+    # inject a new global variable to be picked up
+    globals()['PPP_NON_EXISTENT_PROTO'] = "FAIL"
+    _mod_init()
+    # we should get the same answer as if NON_EXISTENT_PROTO didn't exist
+    assert PPP._protosw == standard_protos
diff --git a/dpkt/pppoe.py b/dpkt/pppoe.py
index 107e564..82c3035 100644
--- a/dpkt/pppoe.py
+++ b/dpkt/pppoe.py
@@ -8,7 +8,6 @@
 
 from . import dpkt
 from . import ppp
-from .decorators import deprecated
 
 # RFC 2516 codes
 PPPoE_PADI = 0x09
@@ -35,22 +34,12 @@
         ('session', 'H', 0),
         ('len', 'H', 0)  # payload length
     )
-
-    @property
-    def v(self):
-        return self._v_type >> 4
-
-    @v.setter
-    def v(self, v):
-        self._v_type = (v << 4) | (self._v_type & 0xf)
-
-    @property
-    def type(self):
-        return self._v_type & 0xf
-
-    @type.setter
-    def type(self, t):
-        self._v_type = (self._v_type & 0xf0) | t
+    __bit_fields__ = {
+        '_v_type': (
+            ('v', 4),
+            ('type', 4),
+        )
+    }
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
@@ -155,4 +144,46 @@
     pytest.raises(dpkt.NeedData, PPP, b"\x00")
 
 
+def test_pppoe_properties():
+    pppoe = PPPoE()
+    assert pppoe.v == 1
+    pppoe.v = 7
+    assert pppoe.v == 7
+
+    assert pppoe.type == 1
+    pppoe.type = 5
+    assert pppoe.type == 5
+
+
+def test_pppoe_unpack_error():
+    from binascii import unhexlify
+    buf = unhexlify(
+        "11"    # v/type
+        "00"    # code
+        "0011"  # session
+        "0066"  # len
+
+        "00"    # data
+    )
+    # this initialization swallows the UnpackError raised
+    pppoe = PPPoE(buf)
+    # unparsed data is still available
+    assert pppoe.data == b'\x00'
+
+
+def test_ppp_pack_hdr():
+    import pytest
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '01'  # protocol, with compression bit set
+
+        'ff'  # incomplete data
+    )
+    ppp = PPP(buf)
+    ppp.p = 1234567
+    with pytest.raises(dpkt.PackError):
+        ppp.pack_hdr()
+
+
 # XXX - TODO TLVs, etc.
diff --git a/dpkt/qq.py b/dpkt/qq.py
index bab090e..b5a9d04 100644
--- a/dpkt/qq.py
+++ b/dpkt/qq.py
@@ -178,9 +178,9 @@
     0x00A8: "QQ_CMD_AUTHORIZE",
     0xFFFF: "QQ_CMD_UNKNOWN",
     0x0021: "_CMD_REQUEST_AGENT",
-    0x0022: "_CMD_REQUEST_FACE",
+    # 0x0022: "_CMD_REQUEST_FACE",   # FIXME - dup dict key
     0x0023: "_CMD_TRANSFER",
-    0x0026: "_CMD_REQUEST_BEGIN",
+    # 0x0026: "_CMD_REQUEST_BEGIN",  # FIXME - dup dict key
 }
 
 
diff --git a/dpkt/radiotap.py b/dpkt/radiotap.py
index 7b50043..64a783f 100644
--- a/dpkt/radiotap.py
+++ b/dpkt/radiotap.py
@@ -3,51 +3,31 @@
 from __future__ import print_function
 from __future__ import absolute_import
 
-import socket
-
 from . import dpkt
 from . import ieee80211
-from .decorators import deprecated
+from .compat import compat_ord
 
 # Ref: http://www.radiotap.org
 # Fields Ref: http://www.radiotap.org/defined-fields/all
 
 # Present flags
-_TSFT_MASK = 0x1000000
-_FLAGS_MASK = 0x2000000
-_RATE_MASK = 0x4000000
-_CHANNEL_MASK = 0x8000000
-_FHSS_MASK = 0x10000000
-_ANT_SIG_MASK = 0x20000000
-_ANT_NOISE_MASK = 0x40000000
-_LOCK_QUAL_MASK = 0x80000000
-_TX_ATTN_MASK = 0x10000
-_DB_TX_ATTN_MASK = 0x20000
-_DBM_TX_POWER_MASK = 0x40000
-_ANTENNA_MASK = 0x80000
-_DB_ANT_SIG_MASK = 0x100000
-_DB_ANT_NOISE_MASK = 0x200000
-_RX_FLAGS_MASK = 0x400000
-_CHANNELPLUS_MASK = 0x200
-_EXT_MASK = 0x1
-
-_TSFT_SHIFT = 24
-_FLAGS_SHIFT = 25
-_RATE_SHIFT = 26
-_CHANNEL_SHIFT = 27
-_FHSS_SHIFT = 28
-_ANT_SIG_SHIFT = 29
-_ANT_NOISE_SHIFT = 30
-_LOCK_QUAL_SHIFT = 31
-_TX_ATTN_SHIFT = 16
-_DB_TX_ATTN_SHIFT = 17
-_DBM_TX_POWER_SHIFT = 18
-_ANTENNA_SHIFT = 19
-_DB_ANT_SIG_SHIFT = 20
-_DB_ANT_NOISE_SHIFT = 21
-_RX_FLAGS_SHIFT = 22
-_CHANNELPLUS_SHIFT = 10
-_EXT_SHIFT = 0
+_TSFT_SHIFT = 0
+_FLAGS_SHIFT = 1
+_RATE_SHIFT = 2
+_CHANNEL_SHIFT = 3
+_FHSS_SHIFT = 4
+_ANT_SIG_SHIFT = 5
+_ANT_NOISE_SHIFT = 6
+_LOCK_QUAL_SHIFT = 7
+_TX_ATTN_SHIFT = 8
+_DB_TX_ATTN_SHIFT = 9
+_DBM_TX_POWER_SHIFT = 10
+_ANTENNA_SHIFT = 11
+_DB_ANT_SIG_SHIFT = 12
+_DB_ANT_NOISE_SHIFT = 13
+_RX_FLAGS_SHIFT = 14
+_CHANNELPLUS_SHIFT = 18
+_EXT_SHIFT = 31
 
 # Flags elements
 _FLAGS_SIZE = 2
@@ -76,7 +56,6 @@
 _QUARTER_RATE_SHIFT = 15
 
 # Flags offsets and masks
-_FCS_SHIFT = 4
 _FCS_MASK = 0x10
 
 
@@ -94,152 +73,167 @@
         ('version', 'B', 0),
         ('pad', 'B', 0),
         ('length', 'H', 0),
-        ('present_flags', 'I', 0)
     )
 
+    __byte_order__ = '<'
+
+    def _is_present(self, bit):
+        index = bit // 8
+        mask = 1 << (bit % 8)
+        return 1 if self.present_flags[index] & mask else 0
+
+    def _set_bit(self, bit, val):
+        # present_flags is a bytearray, this gets the element
+        index = bit // 8
+        # mask retrieves every bit except our one
+        mask = ~(1 << (bit % 8) & 0xff)
+        # retrieve all of the bits, then or in the val at the appropriate place
+        # as the mask does not return the value at `bit`, if `val` is zero, the bit remains zero
+        self.present_flags[index] = (self.present_flags[index] & mask) | (val << bit % 8)
+
     @property
     def tsft_present(self):
-        return (self.present_flags & _TSFT_MASK) >> _TSFT_SHIFT
+        return self._is_present(_TSFT_SHIFT)
 
     @tsft_present.setter
     def tsft_present(self, val):
-        self.present_flags |= val << _TSFT_SHIFT
+        self._set_bit(_TSFT_SHIFT, val)
 
     @property
     def flags_present(self):
-        return (self.present_flags & _FLAGS_MASK) >> _FLAGS_SHIFT
+        return self._is_present(_FLAGS_SHIFT)
 
     @flags_present.setter
     def flags_present(self, val):
-        self.present_flags |= val << _FLAGS_SHIFT
+        self._set_bit(_FLAGS_SHIFT, val)
 
     @property
     def rate_present(self):
-        return (self.present_flags & _RATE_MASK) >> _RATE_SHIFT
+        return self._is_present(_RATE_SHIFT)
 
     @rate_present.setter
     def rate_present(self, val):
-        self.present_flags |= val << _RATE_SHIFT
+        self._set_bit(_RATE_SHIFT, val)
 
     @property
     def channel_present(self):
-        return (self.present_flags & _CHANNEL_MASK) >> _CHANNEL_SHIFT
+        return self._is_present(_CHANNEL_SHIFT)
 
     @channel_present.setter
     def channel_present(self, val):
-        self.present_flags |= val << _CHANNEL_SHIFT
+        self._set_bit(_CHANNEL_SHIFT, val)
 
     @property
     def fhss_present(self):
-        return (self.present_flags & _FHSS_MASK) >> _FHSS_SHIFT
+        return self._is_present(_FHSS_SHIFT)
 
     @fhss_present.setter
     def fhss_present(self, val):
-        self.present_flags |= val << _FHSS_SHIFT
+        self._set_bit(_FHSS_SHIFT, val)
 
     @property
     def ant_sig_present(self):
-        return (self.present_flags & _ANT_SIG_MASK) >> _ANT_SIG_SHIFT
+        return self._is_present(_ANT_SIG_SHIFT)
 
     @ant_sig_present.setter
     def ant_sig_present(self, val):
-        self.present_flags |= val << _ANT_SIG_SHIFT
+        self._set_bit(_ANT_SIG_SHIFT, val)
 
     @property
     def ant_noise_present(self):
-        return (self.present_flags & _ANT_NOISE_MASK) >> _ANT_NOISE_SHIFT
+        return self._is_present(_ANT_NOISE_SHIFT)
 
     @ant_noise_present.setter
     def ant_noise_present(self, val):
-        self.present_flags |= val << _ANT_NOISE_SHIFT
+        self._set_bit(_ANT_NOISE_SHIFT, val)
 
     @property
     def lock_qual_present(self):
-        return (self.present_flags & _LOCK_QUAL_MASK) >> _LOCK_QUAL_SHIFT
+        return self._is_present(_LOCK_QUAL_SHIFT)
 
     @lock_qual_present.setter
     def lock_qual_present(self, val):
-        self.present_flags |= val << _LOCK_QUAL_SHIFT
+        self._set_bit(_LOCK_QUAL_SHIFT, val)
 
     @property
     def tx_attn_present(self):
-        return (self.present_flags & _TX_ATTN_MASK) >> _TX_ATTN_SHIFT
+        return self._is_present(_TX_ATTN_SHIFT)
 
     @tx_attn_present.setter
     def tx_attn_present(self, val):
-        self.present_flags |= val << _TX_ATTN_SHIFT
+        self._set_bit(_TX_ATTN_SHIFT, val)
 
     @property
     def db_tx_attn_present(self):
-        return (self.present_flags & _DB_TX_ATTN_MASK) >> _DB_TX_ATTN_SHIFT
+        return self._is_present(_DB_TX_ATTN_SHIFT)
 
     @db_tx_attn_present.setter
     def db_tx_attn_present(self, val):
-        self.present_flags |= val << _DB_TX_ATTN_SHIFT
+        self._set_bit(_DB_TX_ATTN_SHIFT, val)
 
     @property
     def dbm_tx_power_present(self):
-        return (self.present_flags & _DBM_TX_POWER_MASK) >> _DBM_TX_POWER_SHIFT
+        return self._is_present(_DBM_TX_POWER_SHIFT)
 
     @dbm_tx_power_present.setter
     def dbm_tx_power_present(self, val):
-        self.present_flags |= val << _DBM_TX_POWER_SHIFT
+        self._set_bit(_DBM_TX_POWER_SHIFT, val)
 
     @property
     def ant_present(self):
-        return (self.present_flags & _ANTENNA_MASK) >> _ANTENNA_SHIFT
+        return self._is_present(_ANTENNA_SHIFT)
 
     @ant_present.setter
     def ant_present(self, val):
-        self.present_flags |= val << _ANTENNA_SHIFT
+        self._set_bit(_ANTENNA_SHIFT, val)
 
     @property
     def db_ant_sig_present(self):
-        return (self.present_flags & _DB_ANT_SIG_MASK) >> _DB_ANT_SIG_SHIFT
+        return self._is_present(_DB_ANT_SIG_SHIFT)
 
     @db_ant_sig_present.setter
     def db_ant_sig_present(self, val):
-        self.present_flags |= val << _DB_ANT_SIG_SHIFT
+        self._set_bit(_DB_ANT_SIG_SHIFT, val)
 
     @property
     def db_ant_noise_present(self):
-        return (self.present_flags & _DB_ANT_NOISE_MASK) >> _DB_ANT_NOISE_SHIFT
+        return self._is_present(_DB_ANT_NOISE_SHIFT)
 
     @db_ant_noise_present.setter
     def db_ant_noise_present(self, val):
-        self.present_flags |= val << _DB_ANT_NOISE_SHIFT
+        self._set_bit(_DB_ANT_NOISE_SHIFT, val)
 
     @property
     def rx_flags_present(self):
-        return (self.present_flags & _RX_FLAGS_MASK) >> _RX_FLAGS_SHIFT
+        return self._is_present(_RX_FLAGS_SHIFT)
 
     @rx_flags_present.setter
     def rx_flags_present(self, val):
-        self.present_flags |= val << _RX_FLAGS_SHIFT
+        self._set_bit(_RX_FLAGS_SHIFT, val)
 
     @property
     def chanplus_present(self):
-        return (self.present_flags & _CHANNELPLUS_MASK) >> _CHANNELPLUS_SHIFT
+        return self._is_present(_CHANNELPLUS_SHIFT)
 
     @chanplus_present.setter
     def chanplus_present(self, val):
-        self.present_flags |= val << _CHANNELPLUS_SHIFT
-
-    @property
-    def ext_present(self):
-        return (self.present_flags & _EXT_MASK) >> _EXT_SHIFT
-
-    @ext_present.setter
-    def ext_present(self, val):
-        self.present_flags |= val << _EXT_SHIFT
+        self._set_bit(_CHANNELPLUS_SHIFT, val)
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
-        self.data = buf[socket.ntohs(self.length):]
+        self.data = buf[self.length:]
 
         self.fields = []
         buf = buf[self.__hdr_len__:]
 
+        self.present_flags = bytearray(buf[:4])
+        buf = buf[4:]
+        ext_bit = _EXT_SHIFT
+        while self._is_present(ext_bit):
+            self.present_flags += bytearray(buf[:4])
+            buf = buf[4:]
+            ext_bit += 32
+
         # decode each field into self.<name> (eg. self.tsft) as well as append it self.fields list
         field_decoder = [
             ('tsft', self.tsft_present, self.TSFT),
@@ -256,15 +250,25 @@
             ('ant', self.ant_present, self.Antenna),
             ('db_ant_sig', self.db_ant_sig_present, self.DbAntennaSignal),
             ('db_ant_noise', self.db_ant_noise_present, self.DbAntennaNoise),
-            ('rx_flags', self.rx_flags_present, self.RxFlags)
+            ('rx_flags', self.rx_flags_present, self.RxFlags),
+            ('chanplus', self.chanplus_present, self.ChannelPlus)
         ]
+
+        offset = self.__hdr_len__ + len(self.present_flags)
+
         for name, present_bit, parser in field_decoder:
             if present_bit:
+                ali = parser.__alignment__
+                if ali > 1 and offset % ali:
+                    padding = ali - offset % ali
+                    buf = buf[padding:]
+                    offset += padding
                 field = parser(buf)
                 field.data = b''
                 setattr(self, name, field)
                 self.fields.append(field)
                 buf = buf[len(field):]
+                offset += len(field)
 
         if len(self.data) > 0:
             if self.flags_present and self.flags.fcs:
@@ -272,97 +276,116 @@
             else:
                 self.data = ieee80211.IEEE80211(self.data)
 
-    class Antenna(dpkt.Packet):
+    class RadiotapField(dpkt.Packet):
+        __alignment__ = 1
+        __byte_order__ = '<'
+
+    class Antenna(RadiotapField):
         __hdr__ = (
             ('index', 'B', 0),
         )
 
-    class AntennaNoise(dpkt.Packet):
+    class AntennaNoise(RadiotapField):
         __hdr__ = (
-            ('db', 'B', 0),
+            ('db', 'b', 0),
         )
 
-    class AntennaSignal(dpkt.Packet):
+    class AntennaSignal(RadiotapField):
         __hdr__ = (
-            ('db', 'B', 0),
+            ('db', 'b', 0),
         )
 
-    class Channel(dpkt.Packet):
+    class Channel(RadiotapField):
+        __alignment__ = 2
         __hdr__ = (
             ('freq', 'H', 0),
             ('flags', 'H', 0),
         )
 
-    class FHSS(dpkt.Packet):
+    class FHSS(RadiotapField):
         __hdr__ = (
             ('set', 'B', 0),
             ('pattern', 'B', 0),
         )
 
-    class Flags(dpkt.Packet):
+    class Flags(RadiotapField):
         __hdr__ = (
             ('val', 'B', 0),
         )
 
         @property
-        def fcs(self): return (self.val & _FCS_MASK) >> _FCS_SHIFT
+        def fcs(self):
+            return (self.val & _FCS_MASK) >> _FCS_SHIFT
 
-        # TODO statement seems to have no effect
         @fcs.setter
-        def fcs(self, v): (v << _FCS_SHIFT) | (self.val & ~_FCS_MASK)
+        def fcs(self, v):
+            self.val = (v << _FCS_SHIFT) | (v & ~_FCS_MASK)
 
-
-    class LockQuality(dpkt.Packet):
+    class LockQuality(RadiotapField):
+        __alignment__ = 2
         __hdr__ = (
             ('val', 'H', 0),
         )
 
-    class RxFlags(dpkt.Packet):
+    class RxFlags(RadiotapField):
+        __alignment__ = 2
         __hdr__ = (
             ('val', 'H', 0),
         )
 
-    class Rate(dpkt.Packet):
+    class Rate(RadiotapField):
         __hdr__ = (
             ('val', 'B', 0),
         )
 
-    class TSFT(dpkt.Packet):
+    class TSFT(RadiotapField):
+        __alignment__ = 8
         __hdr__ = (
             ('usecs', 'Q', 0),
         )
 
-    class TxAttenuation(dpkt.Packet):
+    class TxAttenuation(RadiotapField):
+        __alignment__ = 2
         __hdr__ = (
             ('val', 'H', 0),
         )
 
-    class DbTxAttenuation(dpkt.Packet):
+    class DbTxAttenuation(RadiotapField):
+        __alignment__ = 2
         __hdr__ = (
             ('db', 'H', 0),
         )
 
-    class DbAntennaNoise(dpkt.Packet):
+    class DbAntennaNoise(RadiotapField):
         __hdr__ = (
             ('db', 'B', 0),
         )
 
-    class DbAntennaSignal(dpkt.Packet):
+    class DbAntennaSignal(RadiotapField):
         __hdr__ = (
             ('db', 'B', 0),
         )
 
-    class DbmTxPower(dpkt.Packet):
+    class DbmTxPower(RadiotapField):
         __hdr__ = (
             ('dbm', 'B', 0),
         )
 
+    class ChannelPlus(RadiotapField):
+        __alignment__ = 4
+        __hdr__ = (
+            ('flags', 'I', 0),
+            ('freq', 'H', 0),
+            ('channel', 'B', 0),
+            ('maxpower', 'B', 0),
+        )
 
-def test_Radiotap():
+
+def test_radiotap_1():
     s = b'\x00\x00\x00\x18\x6e\x48\x00\x00\x00\x02\x6c\x09\xa0\x00\xa8\x81\x02\x00\x00\x00\x00\x00\x00\x00'
     rad = Radiotap(s)
     assert(rad.version == 0)
-    assert(rad.present_flags == 0x6e480000)
+    assert(rad.present_flags == b'\x6e\x48\x00\x00')
     assert(rad.tsft_present == 0)
     assert(rad.flags_present == 1)
     assert(rad.rate_present == 1)
@@ -377,11 +400,39 @@
     assert(rad.db_ant_sig_present == 0)
     assert(rad.db_ant_noise_present == 0)
     assert(rad.rx_flags_present == 1)
-    assert(rad.channel.freq == 0x6c09)
-    assert(rad.channel.flags == 0xa000)
+    assert(rad.channel.freq == 0x096c)
+    assert(rad.channel.flags == 0xa0)
     assert(len(rad.fields) == 7)
 
 
+def test_radiotap_2():
+    s = (b'\x00\x00\x30\x00\x2f\x40\x00\xa0\x20\x08\x00\xa0\x20\x08\x00\xa0\x20\x08\x00\x00\x00\x00'
+         b'\x00\x00\x08\x84\xbd\xac\x28\x00\x00\x00\x10\x02\x85\x09\xa0\x00\xa5\x00\x00\x00\xa1\x00'
+         b'\x9f\x01\xa1\x02')
+    rad = Radiotap(s)
+    assert(rad.version == 0)
+    assert(rad.present_flags == b'\x2f\x40\x00\xa0\x20\x08\x00\xa0\x20\x08\x00\xa0\x20\x08\x00\x00')
+    assert(rad.tsft_present)
+    assert(rad.flags_present)
+    assert(rad.rate_present)
+    assert(rad.channel_present)
+    assert(not rad.fhss_present)
+    assert(rad.ant_sig_present)
+    assert(not rad.ant_noise_present)
+    assert(not rad.lock_qual_present)
+    assert(not rad.db_tx_attn_present)
+    assert(not rad.dbm_tx_power_present)
+    assert(not rad.ant_present)
+    assert(not rad.db_ant_sig_present)
+    assert(not rad.db_ant_noise_present)
+    assert(rad.rx_flags_present)
+    assert(rad.channel.freq == 2437)
+    assert(rad.channel.flags == 0x00a0)
+    assert(len(rad.fields) == 6)
+    assert(rad.flags_present)
+    assert(rad.flags.fcs)
+
+
 def test_fcs():
     s = b'\x00\x00\x1a\x00\x2f\x48\x00\x00\x34\x8f\x71\x09\x00\x00\x00\x00\x10\x0c\x85\x09\xc0\x00\xcc\x01\x00\x00'
     rt = Radiotap(s)
@@ -389,7 +440,69 @@
     assert(rt.flags.fcs == 1)
 
 
-if __name__ == '__main__':
-    test_Radiotap()
-    test_fcs()
-    print('Tests Successful...')
+def test_radiotap_3():  # xchannel aka channel plus field
+    s = (
+        b'\x00\x00\x20\x00\x67\x08\x04\x00\x84\x84\x66\x25\x00\x00\x00\x00\x22\x0c\xd6\xa0\x01\x00\x00\x00\x40'
+        b'\x01\x00\x00\x3c\x14\x24\x11\x08\x02\x00\x00\xff\xff\xff\xff\xff\xff\x06\x03\x7f\x07\xa0\x16\x00\x19'
+        b'\xe3\xd3\x53\x52\x00\x8e\xaa\xaa\x03\x00\x00\x00\x08\x06\x00\x01\x08\x00\x06\x04\x00\x01\x00\x19\xe3'
+        b'\xd3\x53\x52\xa9\xfe\xf7\x00\x00\x00\x00\x00\x00\x00\x4f\x67\x32\x38'
+    )
+    rt = Radiotap(s)
+    assert rt.ant_noise.db == -96
+    assert rt.ant_sig.db == -42
+    assert rt.ant.index == 1
+    assert rt.chanplus_present
+    assert rt.chanplus.flags == 0x140
+    assert rt.chanplus.freq == 5180
+    assert rt.chanplus.channel == 36
+    assert rt.chanplus.maxpower == 17
+    assert len(rt.fields) == 7
+    assert repr(rt.data).startswith('IEEE80211')
+
+
+def test_radiotap_properties():
+    from binascii import unhexlify
+    buf = unhexlify(
+        '00'
+        '00'
+        '0018'
+
+        '0000000000000000000000000000000000000000'
+    )
+    radiotap = Radiotap(buf)
+    property_keys = [
+        'tsft', 'flags', 'rate', 'channel', 'fhss', 'ant_sig', 'ant_noise',
+        'lock_qual', 'tx_attn', 'db_tx_attn', 'dbm_tx_power', 'ant',
+        'db_ant_sig', 'db_ant_noise', 'rx_flags', 'chanplus'
+    ]
+    for prop in [key + '_present' for key in property_keys]:
+        print(prop)
+        assert hasattr(radiotap, prop)
+        assert getattr(radiotap, prop) == 0
+
+        setattr(radiotap, prop, 1)
+        assert getattr(radiotap, prop) == 1
+
+        setattr(radiotap, prop, 0)
+        assert getattr(radiotap, prop) == 0
+
+
+def test_radiotap_unpack_fcs():
+    from binascii import unhexlify
+    buf = unhexlify(
+        '00'     # version
+        '00'     # pad
+        '1800'   # length
+
+        '6e48000011026c09a000a8810200000000000000'
+        'd40000000012f0b61ca4ffffffff'
+    )
+    radiotap = Radiotap(buf)
+    assert radiotap.data.fcs_present == 1
+
+
+def test_flags():
+    flags = Radiotap.Flags(b'\x00')
+    assert flags.fcs == 0
+    flags.fcs = 1
+    assert flags.fcs == 1
diff --git a/dpkt/radius.py b/dpkt/radius.py
index 80e5de8..e71aae7 100644
--- a/dpkt/radius.py
+++ b/dpkt/radius.py
@@ -19,7 +19,7 @@
         __hdr__: Header fields of RADIUS.
         TODO.
     """
-    
+
     __hdr__ = (
         ('code', 'B', 0),
         ('id', 'B', 0),
@@ -39,13 +39,14 @@
     attrs = []
     while buf:
         t = compat_ord(buf[0])
-        l = compat_ord(buf[1])
-        if l < 2:
+        l_ = compat_ord(buf[1])
+        if l_ < 2:
             break
-        d, buf = buf[2:l], buf[l:]
+        d, buf = buf[2:l_], buf[l_:]
         attrs.append((t, d))
     return attrs
 
+
 # Codes
 RADIUS_ACCESS_REQUEST = 1
 RADIUS_ACCESS_ACCEPT = 2
@@ -100,3 +101,68 @@
 RADIUS_NAS_PORT_TYPE = 61
 RADIUS_PORT_LIMIT = 62
 RADIUS_LOGIN_LAT_PORT = 63
+
+
+def test_parse_attrs():
+    from binascii import unhexlify
+    buf = unhexlify(
+        '01'        # type (RADIUS_USER_NAME)
+        '06'        # end of attribute value
+        '75736572'  # value ('user')
+
+        '00'
+        '00'
+    )
+
+    attrs = parse_attrs(buf)
+    assert len(attrs) == 1
+
+    type0, value0 = attrs[0]
+    assert type0 == RADIUS_USER_NAME
+    assert value0 == b'user'
+
+
+def test_parse_multiple_attrs():
+    from binascii import unhexlify
+    buf = unhexlify(
+        '01'                # type (RADIUS_USER_NAME)
+        '06'                # end of attribute value
+        '75736572'          # value ('user')
+
+        '02'                # type (RADIUS_USER_PASSWORD)
+        '0a'                # end of attribute value
+        '70617373776f7264'  # value ('password')
+    )
+
+    attrs = parse_attrs(buf)
+    assert len(attrs) == 2
+
+    type0, value0 = attrs[0]
+    assert type0 == RADIUS_USER_NAME
+    assert value0 == b'user'
+
+    type1, value1 = attrs[1]
+    assert type1 == RADIUS_USER_PASSWORD
+    assert value1 == b'password'
+
+
+def test_radius_unpacking():
+    from binascii import unhexlify
+    buf_attrs = unhexlify(
+        '01'                # type (RADIUS_USER_NAME)
+        '06'                # end of attribute value
+        '75736572'          # value ('user')
+    )
+    buf_radius_header = unhexlify(
+        '01'                # code
+        '34'                # id
+        '1234'              # len
+        '0123456789abcdef'  # auth
+        '0123456789abcdef'  # auth
+    )
+    buf = buf_radius_header + buf_attrs
+    radius = RADIUS(buf)
+    assert len(radius.attrs) == 1
+    name0, value0 = radius.attrs[0]
+    assert name0 == 1
+    assert value0 == b'user'
diff --git a/dpkt/rfb.py b/dpkt/rfb.py
index 934ef2e..756493c 100644
--- a/dpkt/rfb.py
+++ b/dpkt/rfb.py
@@ -32,7 +32,7 @@
         __hdr__: Header fields of RADIUS.
         TODO.
     """
-    
+
     __hdr__ = (
         ('type', 'B', 0),
     )
@@ -40,14 +40,14 @@
 
 class SetPixelFormat(dpkt.Packet):
     __hdr__ = (
-        ('pad', '3s', ''),
-        ('pixel_fmt', '16s', '')
+        ('pad', '3s', b''),
+        ('pixel_fmt', '16s', b'')
     )
 
 
 class SetEncodings(dpkt.Packet):
     __hdr__ = (
-        ('pad', '1s', ''),
+        ('pad', '1s', b''),
         ('num_encodings', 'H', 0)
     )
 
@@ -65,7 +65,7 @@
 class KeyEvent(dpkt.Packet):
     __hdr__ = (
         ('down_flag', 'B', 0),
-        ('pad', '2s', ''),
+        ('pad', '2s', b''),
         ('key', 'I', 0)
     )
 
@@ -80,14 +80,14 @@
 
 class FramebufferUpdate(dpkt.Packet):
     __hdr__ = (
-        ('pad', '1s', ''),
+        ('pad', '1s', b''),
         ('num_rects', 'H', 0)
     )
 
 
 class SetColourMapEntries(dpkt.Packet):
     __hdr__ = (
-        ('pad', '1s', ''),
+        ('pad', '1s', b''),
         ('first_colour', 'H', 0),
         ('num_colours', 'H', 0)
     )
@@ -95,6 +95,6 @@
 
 class CutText(dpkt.Packet):
     __hdr__ = (
-        ('pad', '3s', ''),
+        ('pad', '3s', b''),
         ('length', 'I', 0)
     )
diff --git a/dpkt/rip.py b/dpkt/rip.py
index 12a0b24..8966e8a 100644
--- a/dpkt/rip.py
+++ b/dpkt/rip.py
@@ -31,16 +31,16 @@
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
-        l = []
+        l_ = []
         self.auth = None
         while self.data:
             rte = RTE(self.data[:20])
             if rte.family == 0xFFFF:
                 self.auth = Auth(self.data[:20])
             else:
-                l.append(rte)
+                l_.append(rte)
             self.data = self.data[20:]
-        self.data = self.rtes = l
+        self.data = self.rtes = l_
 
     def __len__(self):
         n = self.__hdr_len__
@@ -75,26 +75,48 @@
     )
 
 
-__s = b'\x02\x02\x00\x00\x00\x02\x00\x00\x01\x02\x03\x00\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x00\xc0\xa8\x01\x08\xff\xff\xff\xfc\x00\x00\x00\x00\x00\x00\x00\x01'
+def test_creation_with_auth():
+    from binascii import unhexlify
 
+    buf_auth = unhexlify(
+        'ffff'              # rsvd
+        '0002'              # type
+        '0123456789abcdef'  # auth
+        '0123456789abcdef'  # auth
+    )
+    auth_direct = Auth(buf_auth)
+    assert bytes(auth_direct) == buf_auth
 
-def test_rtp_pack():
-    r = RIP(__s)
-    assert (__s == bytes(r))
+    buf_rte = unhexlify(
+        '0002'      # family
+        '0000'      # route_tag
+        '01020300'  # addr
+        'ffffff00'  # subnet
+        '00000000'  # next_hop
+        '00000001'  # metric
+    )
 
+    rte = RTE(buf_rte)
+    assert bytes(rte) == buf_rte
 
-def test_rtp_unpack():
-    r = RIP(__s)
-    assert (r.auth is None)
-    assert (len(r.rtes) == 2)
+    buf_rip = unhexlify(
+        '02'    # cmd
+        '02'    # v
+        '0000'  # rsvd
+    )
+    rip = RIP(buf_rip + buf_auth + buf_rte)
 
-    rte = r.rtes[1]
-    assert (rte.family == 2)
-    assert (rte.route_tag == 0)
-    assert (rte.metric == 1)
+    assert rip.auth
+    assert rip.auth.rsvd == 0xffff
+    assert rip.auth.type == 2
+    assert rip.auth.auth == unhexlify('0123456789abcdef') * 2
 
+    assert len(rip.rtes) == 1
 
-if __name__ == '__main__':
-    test_rtp_pack()
-    test_rtp_unpack()
-    print('Tests Successful...')
+    rte = rip.rtes[0]
+    assert rte.family == 2
+    assert rte.route_tag == 0
+    assert rte.metric == 1
+
+    assert bytes(rip) == buf_rip + buf_auth + buf_rte
+    assert len(rip) == len(buf_rip + buf_auth + buf_rte)
diff --git a/dpkt/rpc.py b/dpkt/rpc.py
index 8c46e68..6de5b5b 100644
--- a/dpkt/rpc.py
+++ b/dpkt/rpc.py
@@ -37,6 +37,8 @@
 class RPC(dpkt.Packet):
     """Remote Procedure Call.
 
+    RFC 5531: https://tools.ietf.org/html/rfc5531
+
     TODO: Longer class information....
 
     Attributes:
@@ -61,8 +63,7 @@
             return 8 + len(self.data)
 
         def __bytes__(self):
-            return self.pack_hdr() + struct.pack('>I', len(self.data)) + \
-                   bytes(self.data)
+            return self.pack_hdr() + struct.pack('>I', len(self.data)) + bytes(self.data)
 
     class Call(dpkt.Packet):
         __hdr__ = (
@@ -79,13 +80,13 @@
             self.data = self.data[len(self.cred) + len(self.verf):]
 
         def __len__(self):
-            return len(str(self))  # XXX
+            return len(bytes(self))  # XXX
 
         def __bytes__(self):
             return dpkt.Packet.__bytes__(self) + \
-                   bytes(getattr(self, 'cred', RPC.Auth())) + \
-                   bytes(getattr(self, 'verf', RPC.Auth())) + \
-                   bytes(self.data)
+                bytes(getattr(self, 'cred', RPC.Auth())) + \
+                bytes(getattr(self, 'verf', RPC.Auth())) + \
+                bytes(self.data)
 
     class Reply(dpkt.Packet):
         __hdr__ = (('stat', 'I', MSG_ACCEPTED), )
@@ -104,14 +105,16 @@
                     self.data = buf[12:]
 
             def __len__(self):
-                if self.stat == PROG_MISMATCH: n = 8
-                else: n = 0
+                if self.stat == PROG_MISMATCH:
+                    n = 8
+                else:
+                    n = 0
                 return len(self.verf) + 4 + n + len(self.data)
 
             def __bytes__(self):
                 if self.stat == PROG_MISMATCH:
-                    return bytes(self.verf) + struct.pack('>III', self.stat,
-                                                        self.low, self.high) + self.data
+                    return bytes(self.verf) + \
+                        struct.pack('>III', self.stat, self.low, self.high) + self.data
                 return bytes(self.verf) + dpkt.Packet.__bytes__(self)
 
         class Reject(dpkt.Packet):
@@ -127,9 +130,12 @@
                     self.data = self.data[4:]
 
             def __len__(self):
-                if self.stat == RPC_MISMATCH: n = 8
-                elif self.stat == AUTH_ERROR: n = 4
-                else: n = 0
+                if self.stat == RPC_MISMATCH:
+                    n = 8
+                elif self.stat == AUTH_ERROR:
+                    n = 4
+                else:
+                    n = 0
                 return 4 + n + len(self.data)
 
             def __bytes__(self):
@@ -143,7 +149,7 @@
             dpkt.Packet.unpack(self, buf)
             if self.stat == MSG_ACCEPTED:
                 self.data = self.accept = self.Accept(self.data)
-            elif self.status == MSG_DENIED:
+            elif self.stat == MSG_DENIED:
                 self.data = self.reject = self.Reject(self.data)
 
     def unpack(self, buf):
@@ -155,18 +161,177 @@
 
 
 def unpack_xdrlist(cls, buf):
-    l = []
+    l_ = []
     while buf:
         if buf.startswith(b'\x00\x00\x00\x01'):
             p = cls(buf[4:])
-            l.append(p)
+            l_.append(p)
             buf = p.data
         elif buf.startswith(b'\x00\x00\x00\x00'):
             break
         else:
             raise dpkt.UnpackError('invalid XDR list')
-    return l
+    return l_
 
 
 def pack_xdrlist(*args):
     return b'\x00\x00\x00\x01'.join(map(bytes, args)) + b'\x00\x00\x00\x00'
+
+
+def test_auth():
+    from binascii import unhexlify
+    auth1 = RPC.Auth()
+    assert auth1.flavor == AUTH_NONE
+    buf = unhexlify('0000000000000000')
+    assert bytes(auth1) == buf
+
+    auth2 = RPC.Auth(buf)
+    assert auth2.flavor == AUTH_NONE
+
+    assert len(auth2) == 8
+
+
+def test_call():
+    from binascii import unhexlify
+    call1 = RPC.Call()
+    assert call1.rpcvers == 2
+    assert call1.prog == 0
+    assert call1.vers == 0
+    assert call1.proc == 0
+
+    buf = unhexlify(
+        '0000000200000000000000000000000000000000000000000000000000000000'
+    )
+    assert bytes(call1) == buf
+
+    call2 = RPC.Call(buf)
+    assert call2.rpcvers == 2
+    assert call2.prog == 0
+    assert call2.vers == 0
+    assert call2.proc == 0
+
+    assert len(call2) == 32
+    assert bytes(call2) == buf
+
+
+def test_reply():
+    from binascii import unhexlify
+    reply1 = RPC.Reply()
+    assert reply1.stat == MSG_ACCEPTED
+    assert bytes(reply1) == b'\00' * 4
+
+    buf_accepted = unhexlify(
+        '00000000'          # MSG_ACCEPTED
+        '0000000000000000'  # Auth
+        '00000000'          # SUCCESS
+        '0000000000000000'  # Auth
+    )
+
+    reply_accepted = RPC.Reply(buf_accepted)
+    assert reply_accepted.stat == MSG_ACCEPTED
+    assert bytes(reply_accepted) == buf_accepted
+    assert len(reply_accepted) == 24
+
+    buf_denied = unhexlify(
+        '00000001'          # MSG_DENIED
+        '00000000'          # RPC_MISMATCH
+        '00000000'          # low
+        'FFFFFFFF'          # high
+        '0000000000000000'  # Auth
+    )
+    reply_denied = RPC.Reply(buf_denied)
+    assert reply_denied.stat == MSG_DENIED
+    assert bytes(reply_denied) == buf_denied
+    assert len(reply_denied) == 24
+
+
+def test_accept():
+    from binascii import unhexlify
+    accept1 = RPC.Reply.Accept()
+    assert accept1.stat == SUCCESS
+
+    buf_success = unhexlify(
+        '0000000000000000'  # Auth
+        '00000000'          # SUCCESS
+        '0000000000000000'  # Auth
+    )
+    accept_success = RPC.Reply.Accept(buf_success)
+    assert accept_success.stat == SUCCESS
+    assert len(accept_success) == 20
+    assert bytes(accept_success) == buf_success
+
+    buf_prog_mismatch = unhexlify(
+        '0000000000000000'  # Auth
+        '00000002'          # PROG_MISMATCH
+        '0000000000000000'  # Auth
+    )
+    accept_prog_mismatch = RPC.Reply.Accept(buf_prog_mismatch)
+    assert accept_prog_mismatch.stat == PROG_MISMATCH
+    assert len(accept_prog_mismatch) == 20
+    assert bytes(accept_prog_mismatch) == buf_prog_mismatch
+
+
+def test_reject():
+    from binascii import unhexlify
+    reject1 = RPC.Reply.Reject()
+    assert reject1.stat == AUTH_ERROR
+
+    buf_rpc_mismatch = unhexlify(
+        '00000000'          # RPC_MISMATCH
+        '00000000'          # low
+        'FFFFFFFF'          # high
+        '0000000000000000'  # Auth
+    )
+    reject2 = RPC.Reply.Reject(buf_rpc_mismatch)
+    assert bytes(reject2) == buf_rpc_mismatch
+    assert reject2.low == 0
+    assert reject2.high == 0xffffffff
+    assert len(reject2) == 20
+
+    buf_auth_error = unhexlify(
+        '00000001'          # AUTH_ERROR
+        '00000000'          # low
+        'FFFFFFFF'          # high
+        '0000000000000000'  # Auth
+    )
+    reject3 = RPC.Reply.Reject(buf_auth_error)
+    assert bytes(reject3) == buf_auth_error
+    assert len(reject3) == 20
+
+    buf_other = unhexlify(
+        '00000002'          # NOT IMPLEMENTED
+        '00000000'          # low
+        'FFFFFFFF'          # high
+        '0000000000000000'  # Auth
+    )
+    reject4 = RPC.Reply.Reject(buf_other)
+    assert bytes(reject4) == buf_other
+    assert len(reject4) == 20
+
+
+def test_rpc():
+    from binascii import unhexlify
+    rpc = RPC()
+    assert rpc.xid == 0
+    assert rpc.dir == CALL
+
+    buf_call = unhexlify(
+        '00000000'  # xid
+        '00000000'  # CALL
+
+        '0000000200000000000000000000000000000000000000000000000000000000'
+    )
+    rpc_call = RPC(buf_call)
+    assert bytes(rpc_call) == buf_call
+
+    buf_reply = unhexlify(
+        '00000000'  # xid
+        '00000001'  # REPLY
+
+        '00000000'          # MSG_ACCEPTED
+        '0000000000000000'  # Auth
+        '00000000'          # SUCCESS
+        '0000000000000000'  # Auth
+    )
+    rpc_reply = RPC(buf_reply)
+    assert bytes(rpc_reply) == buf_reply
diff --git a/dpkt/rtp.py b/dpkt/rtp.py
index e1446f8..efa37dd 100644
--- a/dpkt/rtp.py
+++ b/dpkt/rtp.py
@@ -4,7 +4,6 @@
 from __future__ import absolute_import
 
 from .dpkt import Packet
-from .decorators import deprecated
 
 # version 1100 0000 0000 0000 ! 0xC000  14
 # p       0010 0000 0000 0000 ! 0x2000  13
@@ -49,41 +48,52 @@
     csrc = b''
 
     @property
-    def version(self): return (self._type & _VERSION_MASK) >> _VERSION_SHIFT
+    def version(self):
+        return (self._type & _VERSION_MASK) >> _VERSION_SHIFT
 
     @version.setter
     def version(self, ver):
         self._type = (ver << _VERSION_SHIFT) | (self._type & ~_VERSION_MASK)
 
     @property
-    def p(self): return (self._type & _P_MASK) >> _P_SHIFT
+    def p(self):
+        return (self._type & _P_MASK) >> _P_SHIFT
 
     @p.setter
-    def p(self, p): self._type = (p << _P_SHIFT) | (self._type & ~_P_MASK)
+    def p(self, p):
+        self._type = (p << _P_SHIFT) | (self._type & ~_P_MASK)
 
     @property
-    def x(self): return (self._type & _X_MASK) >> _X_SHIFT
+    def x(self):
+        return (self._type & _X_MASK) >> _X_SHIFT
 
     @x.setter
-    def x(self, x): self._type = (x << _X_SHIFT) | (self._type & ~_X_MASK)
+    def x(self, x):
+        self._type = (x << _X_SHIFT) | (self._type & ~_X_MASK)
 
     @property
-    def cc(self): return (self._type & _CC_MASK) >> _CC_SHIFT
+    def cc(self):
+        return (self._type & _CC_MASK) >> _CC_SHIFT
 
     @cc.setter
-    def cc(self, cc): self._type = (cc << _CC_SHIFT) | (self._type & ~_CC_MASK)
+    def cc(self, cc):
+        self._type = (cc << _CC_SHIFT) | (self._type & ~_CC_MASK)
 
     @property
-    def m(self): return (self._type & _M_MASK) >> _M_SHIFT
+    def m(self):
+        return (self._type & _M_MASK) >> _M_SHIFT
 
     @m.setter
-    def m(self, m): self._type = (m << _M_SHIFT) | (self._type & ~_M_MASK)
+    def m(self, m):
+        self._type = (m << _M_SHIFT) | (self._type & ~_M_MASK)
 
     @property
-    def pt(self): return (self._type & _PT_MASK) >> _PT_SHIFT
+    def pt(self):
+        return (self._type & _PT_MASK) >> _PT_SHIFT
 
     @pt.setter
-    def pt(self, m): self._type = (m << _PT_SHIFT) | (self._type & ~_PT_MASK)
+    def pt(self, m):
+        self._type = (m << _PT_SHIFT) | (self._type & ~_PT_MASK)
 
     def __len__(self):
         return self.__hdr_len__ + len(self.csrc) + len(self.data)
@@ -96,8 +106,18 @@
         self.csrc = buf[self.__hdr_len__:self.__hdr_len__ + self.cc * 4]
         self.data = buf[self.__hdr_len__ + self.cc * 4:]
 
+
 def test_rtp():
-    rtp = RTP(b"\x80\x08\x4d\x01\x00\x01\x00\xe0\x34\x3f\xfa\x34\x53\x53\x53\x56\x53\x5d\x56\x57\xd5\xd6\xd1\xde\xdf\xd3\xd9\xda\xdf\xdc\xdf\xd8\xdd\xd4\xdd\xd9\xd1\xd6\xdc\xda\xde\xdd\xc7\xc1\xdf\xdf\xda\xdb\xdd\xdd\xc4\xd9\x55\x57\xd4\x50\x44\x44\x5b\x44\x4f\x4c\x47\x40\x4c\x47\x59\x5b\x58\x5d\x56\x56\x53\x56\xd5\xd5\x54\x55\xd6\xd6\xd4\xd1\xd1\xd0\xd1\xd5\xdd\xd6\x55\xd4\xd6\xd1\xd4\xd6\xd7\xd7\xd5\xd4\xd0\xd7\xd1\xd4\xd2\xdc\xd6\xdc\xdf\xdc\xdd\xd2\xde\xdc\xd0\xdd\xdc\xd0\xd6\xd6\xd6\x55\x54\x55\x57\x57\x56\x50\x50\x5c\x5c\x52\x5d\x5d\x5f\x5e\x5d\x5e\x52\x50\x52\x56\x54\x57\x55\x55\xd4\xd7\x55\xd5\x55\x55\x55\x55\x55\x54\x57\x54\x55\x55\xd5\xd5\xd7\xd6\xd7\xd1\xd1\xd3\xd2\xd3\xd2\xd2\xd3\xd3")
+    rtp = RTP(
+        b'\x80\x08\x4d\x01\x00\x01\x00\xe0\x34\x3f\xfa\x34\x53\x53\x53\x56\x53\x5d\x56\x57\xd5\xd6'
+        b'\xd1\xde\xdf\xd3\xd9\xda\xdf\xdc\xdf\xd8\xdd\xd4\xdd\xd9\xd1\xd6\xdc\xda\xde\xdd\xc7\xc1'
+        b'\xdf\xdf\xda\xdb\xdd\xdd\xc4\xd9\x55\x57\xd4\x50\x44\x44\x5b\x44\x4f\x4c\x47\x40\x4c\x47'
+        b'\x59\x5b\x58\x5d\x56\x56\x53\x56\xd5\xd5\x54\x55\xd6\xd6\xd4\xd1\xd1\xd0\xd1\xd5\xdd\xd6'
+        b'\x55\xd4\xd6\xd1\xd4\xd6\xd7\xd7\xd5\xd4\xd0\xd7\xd1\xd4\xd2\xdc\xd6\xdc\xdf\xdc\xdd\xd2'
+        b'\xde\xdc\xd0\xdd\xdc\xd0\xd6\xd6\xd6\x55\x54\x55\x57\x57\x56\x50\x50\x5c\x5c\x52\x5d\x5d'
+        b'\x5f\x5e\x5d\x5e\x52\x50\x52\x56\x54\x57\x55\x55\xd4\xd7\x55\xd5\x55\x55\x55\x55\x55\x54'
+        b'\x57\x54\x55\x55\xd5\xd5\xd7\xd6\xd7\xd1\xd1\xd3\xd2\xd3\xd2\xd2\xd3\xd3'
+    )
     assert (rtp.version == 2)
     assert (rtp.p == 0)
     assert (rtp.x == 0)
@@ -108,7 +128,16 @@
     assert (rtp.ts == 65760)
     assert (rtp.ssrc == 0x343ffa34)
     assert (len(rtp) == 172)
-    assert (bytes(rtp) == b"\x80\x08\x4d\x01\x00\x01\x00\xe0\x34\x3f\xfa\x34\x53\x53\x53\x56\x53\x5d\x56\x57\xd5\xd6\xd1\xde\xdf\xd3\xd9\xda\xdf\xdc\xdf\xd8\xdd\xd4\xdd\xd9\xd1\xd6\xdc\xda\xde\xdd\xc7\xc1\xdf\xdf\xda\xdb\xdd\xdd\xc4\xd9\x55\x57\xd4\x50\x44\x44\x5b\x44\x4f\x4c\x47\x40\x4c\x47\x59\x5b\x58\x5d\x56\x56\x53\x56\xd5\xd5\x54\x55\xd6\xd6\xd4\xd1\xd1\xd0\xd1\xd5\xdd\xd6\x55\xd4\xd6\xd1\xd4\xd6\xd7\xd7\xd5\xd4\xd0\xd7\xd1\xd4\xd2\xdc\xd6\xdc\xdf\xdc\xdd\xd2\xde\xdc\xd0\xdd\xdc\xd0\xd6\xd6\xd6\x55\x54\x55\x57\x57\x56\x50\x50\x5c\x5c\x52\x5d\x5d\x5f\x5e\x5d\x5e\x52\x50\x52\x56\x54\x57\x55\x55\xd4\xd7\x55\xd5\x55\x55\x55\x55\x55\x54\x57\x54\x55\x55\xd5\xd5\xd7\xd6\xd7\xd1\xd1\xd3\xd2\xd3\xd2\xd2\xd3\xd3")
+    assert (bytes(rtp) == (
+        b'\x80\x08\x4d\x01\x00\x01\x00\xe0\x34\x3f\xfa\x34\x53\x53\x53\x56\x53\x5d\x56\x57\xd5\xd6'
+        b'\xd1\xde\xdf\xd3\xd9\xda\xdf\xdc\xdf\xd8\xdd\xd4\xdd\xd9\xd1\xd6\xdc\xda\xde\xdd\xc7\xc1'
+        b'\xdf\xdf\xda\xdb\xdd\xdd\xc4\xd9\x55\x57\xd4\x50\x44\x44\x5b\x44\x4f\x4c\x47\x40\x4c\x47'
+        b'\x59\x5b\x58\x5d\x56\x56\x53\x56\xd5\xd5\x54\x55\xd6\xd6\xd4\xd1\xd1\xd0\xd1\xd5\xdd\xd6'
+        b'\x55\xd4\xd6\xd1\xd4\xd6\xd7\xd7\xd5\xd4\xd0\xd7\xd1\xd4\xd2\xdc\xd6\xdc\xdf\xdc\xdd\xd2'
+        b'\xde\xdc\xd0\xdd\xdc\xd0\xd6\xd6\xd6\x55\x54\x55\x57\x57\x56\x50\x50\x5c\x5c\x52\x5d\x5d'
+        b'\x5f\x5e\x5d\x5e\x52\x50\x52\x56\x54\x57\x55\x55\xd4\xd7\x55\xd5\x55\x55\x55\x55\x55\x54'
+        b'\x57\x54\x55\x55\xd5\xd5\xd7\xd6\xd7\xd1\xd1\xd3\xd2\xd3\xd2\xd2\xd3\xd3'
+    ))
 
     # the following tests RTP header setters
     rtp = RTP()
@@ -122,3 +151,16 @@
     assert (rtp.seq == 1234)
     assert (rtp.ts == 5678)
     assert (rtp.ssrc == 0xabcdef01)
+
+
+def test_rtp_properties():
+    from .compat import compat_izip
+
+    rtp = RTP()
+    properties = ['version', 'p', 'x', 'cc', 'm', 'pt']
+    defaults = [2, 0, 0, 0, 0, 0]
+    for prop, default in compat_izip(properties, defaults):
+        assert hasattr(rtp, prop)
+        assert getattr(rtp, prop) == default
+        setattr(rtp, prop, 1)
+        assert getattr(rtp, prop) == 1
diff --git a/dpkt/rx.py b/dpkt/rx.py
index ab5ce37..772db53 100644
--- a/dpkt/rx.py
+++ b/dpkt/rx.py
@@ -39,7 +39,7 @@
         __hdr__: Header fields of Rx.
         TODO.
     """
-    
+
     __hdr__ = (
         ('epoch', 'I', 0),
         ('cid', 'I', 0),
diff --git a/dpkt/sccp.py b/dpkt/sccp.py
index 4b7b814..5515747 100644
--- a/dpkt/sccp.py
+++ b/dpkt/sccp.py
@@ -37,15 +37,15 @@
 class CallInfo(dpkt.Packet):
     __byte_order__ = '<'
     __hdr__ = (
-        ('calling_party_name', '40s', ''),
-        ('calling_party', '24s', ''),
-        ('called_party_name', '40s', ''),
-        ('called_party', '24s', ''),
+        ('calling_party_name', '40s', b''),
+        ('calling_party', '24s', b''),
+        ('called_party_name', '40s', b''),
+        ('called_party', '24s', b''),
         ('line_instance', 'I', 0),
         ('call_id', 'I', 0),
         ('call_type', 'I', 0),
-        ('orig_called_party_name', '40s', ''),
-        ('orig_called_party', '24s', '')
+        ('orig_called_party_name', '40s', b''),
+        ('orig_called_party', '24s', b'')
     )
 
 
@@ -78,7 +78,7 @@
     __byte_order__ = '<'
     __hdr__ = (
         ('msg_timeout', 'I', 0),
-        ('display_msg', '32s', ''),
+        ('display_msg', '32s', b''),
         ('line_instance', 'I', 1),
         ('call_id', 'I', 0)
     )
@@ -87,7 +87,7 @@
 class DisplayText(dpkt.Packet):
     __byte_order__ = '<'
     __hdr__ = (
-        ('display_msg', '36s', ''),
+        ('display_msg', '36s', b''),
     )
 
 
@@ -114,7 +114,7 @@
     __byte_order__ = '<'
     __hdr__ = (
         ('channel_status', 'I', 0),
-        ('ip', '4s', ''),
+        ('ip', '4s', b''),
         ('port', 'I', 0),
         ('passthruparty_id', 'I', 0),
     )
@@ -151,7 +151,8 @@
     __hdr__ = (
         ('conference_id', 'I', 0),
         ('passthruparty_id', 'I', 0),
-        ('remote_ip', '4s', ''),
+        ('ipv4_or_ipv6', 'I', 0),
+        ('remote_ip', '16s', b''),
         ('remote_port', 'I', 0),
         ('ms_packet', 'I', 0),
         ('payload_capability', 'I', 4),  # 4: G.711 u-law 64k
@@ -159,6 +160,7 @@
         ('silence_suppression', 'I', 0),
         ('max_frames_per_pkt', 'I', 1),
         ('g723_bitrate', 'I', 0),
+        ('call_reference', 'I', 0)
     )
 
 
@@ -186,13 +188,13 @@
         __hdr__: Header fields of SCCP.
         TODO.
     """
-    
+
     __byte_order__ = '<'
     __hdr__ = (
         ('len', 'I', 0),
         ('rsvd', 'I', 0),
         ('msgid', 'I', 0),
-        ('msg', '0s', ''),
+        ('msg', '0s', b''),
     )
     _msgsw = {
         KEYPAD_BUTTON: KeypadButton,
@@ -222,3 +224,43 @@
             setattr(self, p.__class__.__name__.lower(), p)
         except (KeyError, dpkt.UnpackError):
             pass
+
+
+def test_sccp():
+    import pytest
+    from binascii import unhexlify
+    buf = unhexlify(
+        '08000000'  # len
+        '00000000'  # rsvd
+        '03000000'  # msgid (KEYPAD_BUTTON)
+
+        'abcdef01'  # msg
+        '23456789'  # daat
+    )
+    sccp = SCCP(buf)
+    assert sccp.msg == b'\xab\xcd\xef\x01'
+    assert sccp.data == b'\x23\x45\x67\x89'
+    assert isinstance(sccp.keypadbutton, KeypadButton)
+
+    # len is too long for data, raises NeedData
+    buf = unhexlify(
+        '88880000'  # len
+        '00000000'  # rsvd
+        '00000003'  # msgid (KEYPAD_BUTTON)
+
+        'abcdef01'  # msg
+    )
+    with pytest.raises(dpkt.NeedData):
+        SCCP(buf)
+
+    # msgid is invalid, raises KeyError on _msgsw (silently caught)
+    buf = unhexlify(
+        '08000000'  # len
+        '00000000'  # rsvd
+        '00000003'  # msgid (invalid)
+
+        'abcdef01'  # msg
+    )
+    sccp = SCCP(buf)
+    assert sccp.msg == b'\xab\xcd\xef\x01'
+    assert sccp.data == b''
diff --git a/dpkt/sctp.py b/dpkt/sctp.py
index 81f9ee9..81a4a45 100644
--- a/dpkt/sctp.py
+++ b/dpkt/sctp.py
@@ -47,24 +47,27 @@
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
-        l = []
+        l_ = []
         while self.data:
             chunk = Chunk(self.data)
-            l.append(chunk)
+            l_.append(chunk)
+            if len(chunk) == 0:
+                self.data = b''
+                break
             self.data = self.data[len(chunk):]
-        self.data = self.chunks = l
+        self.chunks = l_
 
     def __len__(self):
-        return self.__hdr_len__ + sum(map(len, self.data))
+        return self.__hdr_len__ + sum(len(x) for x in self.chunks)
 
     def __bytes__(self):
-        l = [bytes(x) for x in self.data]
+        l_ = [bytes(x) for x in self.chunks]
         if self.sum == 0:
             s = crc32c.add(0xffffffff, self.pack_hdr())
-            for x in l:
+            for x in l_:
                 s = crc32c.add(s, x)
             self.sum = crc32c.done(s)
-        return self.pack_hdr() + b''.join(l)
+        return self.pack_hdr() + b''.join(l_)
 
 
 class Chunk(dpkt.Packet):
@@ -76,10 +79,28 @@
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
+
         self.data = self.data[:self.len - self.__hdr_len__]
+        self.padding = b''  # optional padding for DATA chunks
+
+        # SCTP DATA Chunked is padded, 4-bytes aligned
+        if self.type == DATA and self.len % 4:
+            plen = 4 - self.len % 4  # padded length
+            if plen:
+                pos = self.__hdr_len__ + len(self.data)  # end of data in buf
+                self.padding = buf[pos:pos + plen]
+
+    def __len__(self):
+        return self.len + len(self.padding)
+
+    def __bytes__(self):
+        return self.pack_hdr() + bytes(self.data) + self.padding
 
 
-__s = b'\x80\x44\x00\x50\x00\x00\x00\x00\x30\xba\xef\x54\x01\x00\x00\x3c\x3b\xb9\x9c\x46\x00\x01\xa0\x00\x00\x0a\xff\xff\x2b\x2d\x7e\xb2\x00\x05\x00\x08\x9b\xe6\x18\x9b\x00\x05\x00\x08\x9b\xe6\x18\x9c\x00\x0c\x00\x06\x00\x05\x00\x00\x80\x00\x00\x04\xc0\x00\x00\x04\xc0\x06\x00\x08\x00\x00\x00\x00'
+__s = (b'\x80\x44\x00\x50\x00\x00\x00\x00\x30\xba\xef\x54\x01\x00\x00\x3c\x3b\xb9\x9c\x46\x00\x01'
+       b'\xa0\x00\x00\x0a\xff\xff\x2b\x2d\x7e\xb2\x00\x05\x00\x08\x9b\xe6\x18\x9b\x00\x05\x00\x08'
+       b'\x9b\xe6\x18\x9c\x00\x0c\x00\x06\x00\x05\x00\x00\x80\x00\x00\x04\xc0\x00\x00\x04\xc0\x06'
+       b'\x00\x08\x00\x00\x00\x00')
 
 
 def test_sctp_pack():
@@ -100,7 +121,64 @@
     assert (chunk.len == 60)
 
 
-if __name__ == '__main__':
-    test_sctp_pack()
-    test_sctp_unpack()
-    print('Tests Successful...')
+def test_sctp_data_chunk():  # https://github.com/kbandla/dpkt/issues/499
+    # packet 5 from 'sctp-www.cap' downloaded from https://wiki.wireshark.org/SampleCaptures
+    # chunk len == 419 so requires padding to a 4-byte boundary
+    d = (b'\x80\x44\x00\x50\xd2\x6a\xc1\xe5\x70\xe5\x5b\x4c\x00\x03\x01\xa3\x2b\x2d\x7e\xb2\x00\x00'
+         b'\x00\x00\x00\x00\x00\x00\x47\x45\x54\x20\x2f\x20\x48\x54\x54\x50\x2f\x31\x2e\x31\x0d\x0a'
+         b'\x48\x6f\x73\x74\x3a\x20\x32\x30\x33\x2e\x32\x35\x35\x2e\x32\x35\x32\x2e\x31\x39\x34\x0d'
+         b'\x0a\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74\x3a\x20\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x35'
+         b'\x2e\x30\x20\x28\x58\x31\x31\x3b\x20\x55\x3b\x20\x4c\x69\x6e\x75\x78\x20\x69\x36\x38\x36'
+         b'\x3b\x20\x6b\x6f\x2d\x4b\x52\x3b\x20\x72\x76\x3a\x31\x2e\x37\x2e\x31\x32\x29\x20\x47\x65'
+         b'\x63\x6b\x6f\x2f\x32\x30\x30\x35\x31\x30\x30\x37\x20\x44\x65\x62\x69\x61\x6e\x2f\x31\x2e'
+         b'\x37\x2e\x31\x32\x2d\x31\x0d\x0a\x41\x63\x63\x65\x70\x74\x3a\x20\x74\x65\x78\x74\x2f\x78'
+         b'\x6d\x6c\x2c\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x78\x6d\x6c\x2c\x61\x70\x70'
+         b'\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x78\x68\x74\x6d\x6c\x2b\x78\x6d\x6c\x2c\x74\x65\x78'
+         b'\x74\x2f\x68\x74\x6d\x6c\x3b\x71\x3d\x30\x2e\x39\x2c\x74\x65\x78\x74\x2f\x70\x6c\x61\x69'
+         b'\x6e\x3b\x71\x3d\x30\x2e\x38\x2c\x69\x6d\x61\x67\x65\x2f\x70\x6e\x67\x2c\x2a\x2f\x2a\x3b'
+         b'\x71\x3d\x30\x2e\x35\x0d\x0a\x41\x63\x63\x65\x70\x74\x2d\x4c\x61\x6e\x67\x75\x61\x67\x65'
+         b'\x3a\x20\x6b\x6f\x2c\x65\x6e\x2d\x75\x73\x3b\x71\x3d\x30\x2e\x37\x2c\x65\x6e\x3b\x71\x3d'
+         b'\x30\x2e\x33\x0d\x0a\x41\x63\x63\x65\x70\x74\x2d\x45\x6e\x63\x6f\x64\x69\x6e\x67\x3a\x20'
+         b'\x67\x7a\x69\x70\x2c\x64\x65\x66\x6c\x61\x74\x65\x0d\x0a\x41\x63\x63\x65\x70\x74\x2d\x43'
+         b'\x68\x61\x72\x73\x65\x74\x3a\x20\x45\x55\x43\x2d\x4b\x52\x2c\x75\x74\x66\x2d\x38\x3b\x71'
+         b'\x3d\x30\x2e\x37\x2c\x2a\x3b\x71\x3d\x30\x2e\x37\x0d\x0a\x4b\x65\x65\x70\x2d\x41\x6c\x69'
+         b'\x76\x65\x3a\x20\x33\x30\x30\x0d\x0a\x43\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x3a\x20\x6b'
+         b'\x65\x65\x70\x2d\x61\x6c\x69\x76\x65\x0d\x0a\x0d\x0a\x00')  # <-- ends with \x00 padding
+
+    sctp = SCTP(d)
+    assert sctp.chunks
+    assert len(sctp.chunks) == 1
+
+    ch = sctp.chunks[0]
+    assert ch.type == DATA
+    assert ch.len == 419
+    assert len(ch) == 420  # 419 +1 byte padding
+    assert ch.data[-14:] == b'keep-alive\r\n\r\n'  # no padding byte at the end
+
+    # no remaining sctp data
+    assert sctp.data == b''
+
+    # test packing of the padded chunk
+    assert bytes(ch) == d[SCTP.__hdr_len__:]
+  
+
+
+def test_malformed_sctp_data_chunk():  
+    # packet 7964 from '4.pcap' downloaded from https://research.unsw.edu.au/projects/unsw-nb15-dataset
+    d = (b'\x27\x0f\xe1\xc3\xc2\x73\x4d\x32\x4f\x54\x27\x8c' #header
+         b'\x0b\x00\x00\x04' #chunk 0, COOKIE_ACK chunk
+         b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') #chunk 1, malformed DATA chunk, size labeled as 0
+
+
+    sctp = SCTP(d)
+    assert sctp.chunks
+    assert len(sctp.chunks) == 2
+
+    ch = sctp.chunks[1]
+    assert ch.type == DATA
+    assert ch.len == 0
+    assert len(ch) == 0 
+    assert ch.data == b'\x00\x00'
+    
+    # no remaining sctp data
+    assert sctp.data == b''
diff --git a/dpkt/sip.py b/dpkt/sip.py
index 470f27a..6d6a92b 100644
--- a/dpkt/sip.py
+++ b/dpkt/sip.py
@@ -15,7 +15,7 @@
         __hdr__: Header fields of SIP request.
         TODO.
     """
-    
+
     __hdr_defaults__ = {
         'method': 'INVITE',
         'uri': 'sip:user@example.com',
@@ -39,7 +39,7 @@
         __hdr__: Header fields of SIP response.
         TODO.
     """
-    
+
     __hdr_defaults__ = {
         'version': '2.0',
         'status': '200',
@@ -47,4 +47,3 @@
         'headers': {'To': '', 'From': '', 'Call-ID': '', 'CSeq': '', 'Contact': ''}
     }
     __proto = 'SIP'
-
diff --git a/dpkt/sll.py b/dpkt/sll.py
index 5294e36..ce316ec 100644
--- a/dpkt/sll.py
+++ b/dpkt/sll.py
@@ -17,12 +17,12 @@
         __hdr__: Header fields of SLL.
         TODO.
     """
-    
+
     __hdr__ = (
         ('type', 'H', 0),  # 0: to us, 1: bcast, 2: mcast, 3: other, 4: from us
         ('hrd', 'H', arp.ARP_HRD_ETH),
         ('hlen', 'H', 6),  # hardware address length
-        ('hdr', '8s', ''),  # first 8 bytes of link-layer header
+        ('hdr', '8s', b''),  # first 8 bytes of link-layer header
         ('ethtype', 'H', ethernet.ETH_TYPE_IP),
     )
     _typesw = ethernet.Ethernet._typesw
@@ -35,8 +35,10 @@
         except (KeyError, dpkt.UnpackError):
             pass
 
+
 def test_sll():
-    slldata = b'\x00\x00\x00\x01\x00\x06\x00\x0b\xdb\x52\x0e\x08\xf6\x7f\x08\x00\x45\x00\x00\x34\xcc\x6c\x40\x00\x40\x06\x74\x08\x82\xd9\xfa\x8e\x82\xd9\xfa\x0d'
+    slldata = (b'\x00\x00\x00\x01\x00\x06\x00\x0b\xdb\x52\x0e\x08\xf6\x7f\x08\x00\x45\x00\x00\x34'
+               b'\xcc\x6c\x40\x00\x40\x06\x74\x08\x82\xd9\xfa\x8e\x82\xd9\xfa\x0d')
     slltest = SLL(slldata)
     assert slltest.type == 0
     assert slltest.hrd == 1
@@ -45,5 +47,6 @@
     assert slltest.ethtype == 0x0800
 
     # give invalid ethtype of 0x1234 to make sure error is caught
-    slldata2 = b'\x00\x00\x00\x01\x00\x06\x00\x0b\xdb\x52\x0e\x08\xf6\x7f\x12\x34\x45\x00\x00\x34\xcc\x6c\x40\x00\x40\x06\x74\x08\x82\xd9\xfa\x8e\x82\xd9\xfa\x0d'
+    slldata2 = (b'\x00\x00\x00\x01\x00\x06\x00\x0b\xdb\x52\x0e\x08\xf6\x7f\x12\x34\x45\x00\x00\x34'
+                b'\xcc\x6c\x40\x00\x40\x06\x74\x08\x82\xd9\xfa\x8e\x82\xd9\xfa\x0d')
     slltest = SLL(slldata2)
diff --git a/dpkt/sll2.py b/dpkt/sll2.py
new file mode 100644
index 0000000..da2199f
--- /dev/null
+++ b/dpkt/sll2.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+"""Linux libpcap "cooked v2" capture encapsulation."""
+from __future__ import absolute_import
+
+from . import arp
+from . import dpkt
+from . import ethernet
+
+
+class SLL2(dpkt.Packet):
+    """Linux libpcap "cooked v2" capture encapsulation.
+
+    See https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL2.html
+
+    Attributes:
+        __hdr__: Header fields of SLLv2.
+    """
+
+    __hdr__ = (
+        ('ethtype', 'H', ethernet.ETH_TYPE_IP),
+        ('mbz', 'H', 0),  # reserved
+        ('intindex', 'i', 0),  # the 1-based index of the interface on which the packet was observed
+        ('hrd', 'H', arp.ARP_HRD_ETH),
+        ('type', 'B', 0),  # 0: to us, 1: bcast, 2: mcast, 3: other, 4: from us
+        ('hlen', 'B', 6),  # hardware address length
+        ('hdr', '8s', b''),  # first 8 bytes of link-layer header
+    )
+    _typesw = ethernet.Ethernet._typesw
+
+    def unpack(self, buf):
+        dpkt.Packet.unpack(self, buf)
+        try:
+            self.data = self._typesw[self.ethtype](self.data)
+            setattr(self, self.data.__class__.__name__.lower(), self.data)
+        except (KeyError, dpkt.UnpackError):
+            pass
+
+
+def test_sll2():
+    sll2data = (b'\x08\x00\x00\x00\x00\x00\x00\x03\x00\x01\x00\x06\x00\x0b\xdb\x52\x0e\x08\xf6\x7f'
+                b'\x45\x00\x00\x34\xcc\x6c\x40\x00\x40\x06\x74\x08\x82\xd9\xfa\x8e\x82\xd9\xfa\x0d')
+    sll2test = SLL2(sll2data)
+    assert sll2test.type == 0
+    assert sll2test.mbz == 0
+    assert sll2test.intindex == 3
+    assert sll2test.hrd == 1
+    assert sll2test.hlen == 6
+    assert sll2test.hdr == b'\x00\x0b\xdb\x52\x0e\x08\xf6\x7f'
+    assert sll2test.ethtype == 0x0800
+
+    # give invalid ethtype of 0x1234 to make sure error is handled
+    sll2data2 = (b'\x12\x34\x00\x00\x00\x00\x00\x03\x00\x01\x00\x06\x00\x0b\xdb\x52\x0e\x08\xf6\x7f'
+                b'\x45\x00\x00\x34\xcc\x6c\x40\x00\x40\x06\x74\x08\x82\xd9\xfa\x8e\x82\xd9\xfa\x0d')
+    sll2test2 = SLL2(sll2data2)
diff --git a/dpkt/smb.py b/dpkt/smb.py
index 831ec31..48f44c0 100644
--- a/dpkt/smb.py
+++ b/dpkt/smb.py
@@ -34,7 +34,7 @@
 
 
 class SMB(dpkt.Packet):
-    """Server Message Block.
+    r"""Server Message Block.
 
     TODO: Longer class information....
 
@@ -82,11 +82,13 @@
 
 
 def test_smb():
-    buf = b'\xffSMB\xa0\x00\x00\x00\x00\x08\x03\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xfa\x7a\x00\x08\x53\x02'
+    buf = (b'\xffSMB\xa0\x00\x00\x00\x00\x08\x03\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+           b'\x00\x00\x08\xfa\x7a\x00\x08\x53\x02')
     smb = SMB(buf)
 
     assert smb.flags == SMB_FLAGS_CASE_INSENSITIVE
-    assert smb.flags2 == SMB_FLAGS2_UNICODE | SMB_FLAGS2_NT_STATUS | SMB_FLAGS2_EXTENDED_SECURITY | SMB_FLAGS2_EXTENDED_ATTRIBUTES | SMB_FLAGS2_LONG_NAMES
+    assert smb.flags2 == (SMB_FLAGS2_UNICODE | SMB_FLAGS2_NT_STATUS |
+                          SMB_FLAGS2_EXTENDED_SECURITY | SMB_FLAGS2_EXTENDED_ATTRIBUTES | SMB_FLAGS2_LONG_NAMES)
     assert smb.pid == 31482
     assert smb.uid == 2048
     assert smb.mid == 595
@@ -95,9 +97,5 @@
     smb = SMB()
     smb.pid = 0x00081020
     smb.uid = 0x800
-    assert str(smb) == str(b'\xffSMB\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x10\x00\x08\x00\x00')
-
-
-if __name__ == '__main__':
-    test_smb()
-    print('Tests Successful...')
+    assert str(smb) == str(b'\xffSMB\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00'
+                           b'\x00\x00\x00\x00\x00\x00\x00\x20\x10\x00\x08\x00\x00')
diff --git a/dpkt/snoop.py b/dpkt/snoop.py
index 983e152..7d648d8 100644
--- a/dpkt/snoop.py
+++ b/dpkt/snoop.py
@@ -4,8 +4,10 @@
 from __future__ import absolute_import
 
 import time
+from abc import abstractmethod
 
 from . import dpkt
+from .compat import intround
 
 # RFC 1761
 
@@ -39,11 +41,33 @@
 
     __byte_order__ = '!'
     __hdr__ = (
+        # 32-bit unsigned integer representing the length in octets of the
+        # captured packet as received via a network.
         ('orig_len', 'I', 0),
+        # 32-bit unsigned integer representing the length of the Packet Data
+        # field.  This is the number of octets of the captured packet that are
+        # included in this packet record.  If the received packet was
+        # truncated, the Included Length field will be less than the Original
+        # Length field.
         ('incl_len', 'I', 0),
+        # 32-bit unsigned integer representing the total length of this packet
+        # record in octets.  This includes the 24 octets of descriptive
+        # information, the length of the Packet Data field, and the length of
+        # the Pad field.
         ('rec_len', 'I', 0),
+        # 32-bit unsigned integer representing the number of packets that were
+        # lost by the system that created the packet file between the first
+        # packet record in the file and this one.  Packets may be lost because
+        # of insufficient resources in the capturing system, or for other
+        # reasons.  Note: some implementations lack the ability to count
+        # dropped packets.  Those implementations may set the cumulative drops
+        # value to zero.
         ('cum_drops', 'I', 0),
+        # 32-bit unsigned integer representing the time, in seconds since
+        # January 1, 1970, when the packet arrived.
         ('ts_sec', 'I', 0),
+        # 32-bit unsigned integer representing microsecond resolution of packet
+        # arrival time.
         ('ts_usec', 'I', 0),
     )
 
@@ -66,7 +90,38 @@
     )
 
 
-class Writer(object):
+class FileWriter(object):
+    def __init__(self, fileobj):
+        self._f = fileobj
+        self.write = self._f.write
+
+    def close(self):
+        self._f.close()
+
+    def writepkt(self, pkt, ts=None):
+        """Write single packet and optional timestamp to file.
+
+        Args:
+            pkt: `bytes` will be called on this and written to file.
+            ts (float): Timestamp in seconds. Defaults to current time.
+       """
+        if ts is None:
+            ts = time.time()
+
+        self.writepkt_time(bytes(pkt), ts)
+
+    @abstractmethod
+    def writepkt_time(self, pkt, ts):
+        """Write single packet and its timestamp to file.
+
+        Args:
+            pkt (bytes): Some `bytes` to write to the file
+            ts (float): Timestamp in seconds
+        """
+        pass
+
+
+class Writer(FileWriter):
     """Simple snoop dumpfile writer.
 
     TODO: Longer class information....
@@ -74,30 +129,130 @@
     Attributes:
         TODO.
     """
+    precision_multiplier = 1000000
 
     def __init__(self, fileobj, linktype=SDL_ETHER):
-        self.__f = fileobj
+        super(Writer, self).__init__(fileobj)
         fh = FileHdr(linktype=linktype)
-        self.__f.write(str(fh))
 
-    def writepkt(self, pkt, ts=None):
-        if ts is None:
-            ts = time.time()
-        s = str(pkt)
-        n = len(s)
-        pad_len = 4 - n % 4 if n % 4 else 0
-        ph = PktHdr(orig_len=n, incl_len=n,
-                    rec_len=PktHdr.__hdr_len__ + n + pad_len,
-                    ts_sec=int(ts),
-                    ts_usec=int((int(ts) - float(ts)) * 1000000.0))
-        self.__f.write(str(ph))
-        self.__f.write(s + '\0' * pad_len)
+        self._PktHdr = PktHdr()
+        self._pack_hdr = self._PktHdr._pack_hdr
 
-    def close(self):
-        self.__f.close()
+        self.write(bytes(fh))
+
+    def writepkt_time(self, pkt, ts):
+        """Write single packet and its timestamp to file.
+
+        Args:
+            pkt (bytes): Some `bytes` to write to the file
+            ts (float): Timestamp in seconds
+       """
+        pkt_len = len(pkt)
+        pad_len = (4 - pkt_len) & 3
+
+        pkt_header = self._pack_hdr(
+            pkt_len,
+            pkt_len,
+            PktHdr.__hdr_len__ + pkt_len + pad_len,
+            0,
+            int(ts),
+            intround(ts % 1 * self.precision_multiplier),
+        )
+        self.write(pkt_header + pkt + b'\x00' * pad_len)
+
+    def writepkts(self, pkts):
+        """Write an iterable of packets to file.
+
+        Timestamps should be in seconds.
+        Packets must be of type `bytes` as they will not be cast.
+
+        Args:
+            pkts: iterable containing (ts, pkt)
+        """
+        # take local references to these variables so we don't need to
+        # dereference every time in the loop
+        write = self.write
+        pack_hdr = self._pack_hdr
+
+        for ts, pkt in pkts:
+            pkt_len = len(pkt)
+            pad_len = (4 - pkt_len) & 3
+
+            pkt_header = pack_hdr(
+                pkt_len,
+                pkt_len,
+                PktHdr.__hdr_len__ + pkt_len + pad_len,
+                0,
+                int(ts),
+                intround(ts % 1 * self.precision_multiplier),
+            )
+            write(pkt_header + pkt + b'\x00' * pad_len)
 
 
-class Reader(object):
+class FileReader(object):
+    def __init__(self, fileobj):
+        self.name = getattr(fileobj, 'name', '<%s>' % fileobj.__class__.__name__)
+        self._f = fileobj
+        self.filter = ''
+
+    @property
+    def fd(self):
+        return self._f.fileno()
+
+    def fileno(self):
+        return self.fd
+
+    def setfilter(self, value, optimize=1):
+        raise NotImplementedError
+
+    def readpkts(self):
+        return list(self)
+
+    def dispatch(self, cnt, callback, *args):
+        """Collect and process packets with a user callback.
+
+        Return the number of packets processed, or 0 for a savefile.
+
+        Arguments:
+
+        cnt      -- number of packets to process;
+                    or 0 to process all packets until EOF
+        callback -- function with (timestamp, pkt, *args) prototype
+        *args    -- optional arguments passed to callback on execution
+       """
+        processed = 0
+        if cnt > 0:
+            for _ in range(cnt):
+                try:
+                    ts, pkt = next(self)
+                except StopIteration:
+                    break
+                callback(ts, pkt, *args)
+                processed += 1
+        else:
+            for ts, pkt in self:
+                callback(ts, pkt, *args)
+                processed += 1
+        return processed
+
+    def loop(self, callback, *args):
+        """
+        Convenience method which will apply the callback to all packets.
+
+        Returns the number of packets processed.
+
+        Arguments:
+
+        callback -- function with (timestamp, pkt, *args) prototype
+        *args    -- optional arguments passed to callback on execution
+        """
+        return self.dispatch(0, callback, *args)
+
+    def __iter__(self):
+        return self
+
+
+class Reader(FileReader):
     """Simple pypcap-compatible snoop file reader.
 
     TODO: Longer class information....
@@ -107,46 +262,294 @@
     """
 
     def __init__(self, fileobj):
-        self.name = fileobj.name
-        self.fd = fileobj.fileno()
-        self.__f = fileobj
-        buf = self.__f.read(FileHdr.__hdr_len__)
-        self.__fh = FileHdr(buf)
-        self.__ph = PktHdr
-        if self.__fh.magic != SNOOP_MAGIC:
-            raise ValueError('invalid snoop header')
-        self.dloff = dltoff[self.__fh.linktype]
-        self.filter = ''
+        super(Reader, self).__init__(fileobj)
 
-    def fileno(self):
-        return self.fd
+        buf = self._f.read(FileHdr.__hdr_len__)
+        self._fh = FileHdr(buf)
+        self._ph = PktHdr
+
+        if self._fh.magic != SNOOP_MAGIC:
+            raise ValueError('invalid snoop header')
+
+        self.dloff = dltoff[self._fh.linktype]
 
     def datalink(self):
-        return self.__fh.linktype
+        return self._fh.linktype
 
-    def setfilter(self, value, optimize=1):
-        return NotImplementedError
+    def __next__(self):
+        buf = self._f.read(self._ph.__hdr_len__)
+        if not buf:
+            raise StopIteration
 
-    def readpkts(self):
-        return list(self)
+        hdr = self._ph(buf)
+        buf = self._f.read(hdr.rec_len - self._ph.__hdr_len__)
+        return (hdr.ts_sec + (hdr.ts_usec / 1000000.0), buf[:hdr.incl_len])
+    next = __next__
 
-    def dispatch(self, cnt, callback, *args):
-        if cnt > 0:
-            for i in range(cnt):
-                ts, pkt = next(self)
-                callback(ts, pkt, *args)
-        else:
-            for ts, pkt in self:
-                callback(ts, pkt, *args)
 
-    def loop(self, callback, *args):
-        self.dispatch(0, callback, *args)
+def test_snoop_pkt_header():
+    from binascii import unhexlify
 
-    def __iter__(self):
-        self.__f.seek(FileHdr.__hdr_len__)
-        while 1:
-            buf = self.__f.read(PktHdr.__hdr_len__)
-            if not buf: break
-            hdr = self.__ph(buf)
-            buf = self.__f.read(hdr.rec_len - PktHdr.__hdr_len__)
-            yield (hdr.ts_sec + (hdr.ts_usec / 1000000.0), buf[:hdr.incl_len])
+    buf = unhexlify(
+        '000000010000000200000003000000040000000500000006'
+    )
+
+    pkt = PktHdr(buf)
+    assert pkt.orig_len == 1
+    assert pkt.incl_len == 2
+    assert pkt.rec_len == 3
+    assert pkt.cum_drops == 4
+    assert pkt.ts_sec == 5
+    assert pkt.ts_usec == 6
+    assert bytes(pkt) == buf
+
+
+def test_snoop_file_header():
+    from binascii import unhexlify
+
+    buf = unhexlify(
+        '000000000000000b000000160000014d'
+    )
+    hdr = FileHdr(buf)
+    assert hdr.magic == 11
+    assert hdr.v == 22
+    assert hdr.linktype == 333
+
+
+class TestSnoopWriter(object):
+    @classmethod
+    def setup_class(cls):
+        from .compat import BytesIO
+        from binascii import unhexlify
+
+        cls.fobj = BytesIO()
+        # write the file header only
+        cls.writer = Writer(cls.fobj)
+
+        cls.file_header = unhexlify(
+            '736e6f6f700000000000000200000004'
+        )
+
+        cls.pkt = unhexlify(
+            '000000010000000200000003000000040000000500000006'
+        )
+
+        cls.pkt_and_header = unhexlify(
+            '00000018'  # orig_len
+            '00000018'  # incl_len
+            '00000030'  # rec_len
+            '00000000'  # cum_drops
+            '00000000'  # ts_sec
+            '00000000'  # ts_usec
+
+            # data
+            '000000010000000200000003000000040000000500000006'
+        )
+
+    def test_snoop_file_writer_filehdr(self):
+        # jump to the start and read the file header
+        self.fobj.seek(0)
+        buf = self.fobj.read()
+        assert buf == self.file_header
+
+    def test_writepkt(self):
+        loc = self.fobj.tell()
+        self.writer.writepkt(self.pkt)
+
+        # jump back to just before the writing of the packet
+        self.fobj.seek(loc)
+        # read the packet back in
+        buf = self.fobj.read()
+        # compare everything except the timestamp
+        assert buf[:16] == self.pkt_and_header[:16]
+        assert buf[24:] == self.pkt_and_header[24:]
+
+    def test_writepkt_time(self):
+        loc = self.fobj.tell()
+        self.writer.writepkt_time(self.pkt, 0)
+        self.fobj.seek(loc)
+        # read the packet we just wrote
+        buf = self.fobj.read()
+        assert buf == self.pkt_and_header
+
+    def test_writepkts(self):
+        loc = self.fobj.tell()
+        self.writer.writepkts([
+            (0, self.pkt),
+            (1, self.pkt),
+            (2, self.pkt),
+        ])
+        self.fobj.seek(loc)
+        buf = self.fobj.read()
+
+        pkt_len = len(self.pkt_and_header)
+        # chunk up the file and check each packet
+        for idx in range(0, 3):
+            pkt = buf[idx * pkt_len:(idx + 1) * pkt_len]
+
+            assert pkt[:16] == self.pkt_and_header[:16]
+            assert pkt[16:20] == dpkt.struct.pack('>I', idx)
+            assert pkt[20:] == self.pkt_and_header[20:]
+
+    def test_snoop_writer_close(self):
+        assert not self.fobj.closed
+
+        # check that the underlying file object is closed
+        self.writer.close()
+        assert self.fobj.closed
+
+
+class TestSnoopReader(object):
+    @classmethod
+    def setup_class(cls):
+        from binascii import unhexlify
+
+        cls.header = unhexlify(
+            '736e6f6f700000000000000200000004'
+        )
+
+        cls.pkt_header = unhexlify(
+            '00000018'  # orig_len
+            '00000018'  # incl_len
+            '00000030'  # rec_len
+            '00000000'  # cum_drops
+            '00000000'  # ts_sec
+            '00000000'  # ts_usec
+        )
+
+        cls.pkt_bytes = unhexlify(
+            # data
+            '000000010000000200000003000000040000000500000006'
+        )
+
+    def setup_method(self):
+        from .compat import BytesIO
+
+        self.fobj = BytesIO(
+            self.header + self.pkt_header + self.pkt_bytes
+        )
+        self.reader = Reader(self.fobj)
+
+    def test_open(self):
+        assert self.reader.dloff == 14
+        assert self.reader.datalink() == SDL_ETHER
+
+    def test_invalid_magic(self):
+        import pytest
+
+        self.fobj.seek(0)
+        self.fobj.write(b'\x00' * 4)
+        self.fobj.seek(0)
+
+        with pytest.raises(ValueError, match='invalid snoop header'):
+            Reader(self.fobj)
+
+    def test_read_pkt(self):
+        ts, pkt = next(self.reader)
+        assert ts == 0
+        assert pkt == self.pkt_bytes
+
+    def test_readpkts(self):
+        pkts = self.reader.readpkts()
+        assert len(pkts) == 1
+        ts, buf = pkts[0]
+        assert ts == 0
+        assert buf == self.pkt_bytes
+
+
+class TestFileWriter(object):
+    def setup_method(self):
+        from .compat import BytesIO
+
+        self.fobj = BytesIO()
+        self.writer = FileWriter(self.fobj)
+
+    def test_write(self):
+        buf = b'\x01' * 10
+        self.writer.write(buf)
+        self.fobj.seek(0)
+        assert self.fobj.read() == buf
+
+    def test_close(self):
+        assert not self.fobj.closed
+        self.writer.close()
+        assert self.fobj.closed
+
+
+class TestFileReader(object):
+    """
+    Testing for the FileReader superclass which Reader inherits from.
+    """
+    pkts = [
+        (0, b'000001'),
+        (1, b'000002'),
+        (2, b'000003'),
+    ]
+
+    class SampleReader(FileReader):
+        """
+        Very simple class which returns index as timestamp, and
+        unparsed buffer as packet
+        """
+        def __init__(self, fobj):
+            super(TestFileReader.SampleReader, self).__init__(fobj)
+
+            self._iter = iter(TestFileReader.pkts)
+
+        def __next__(self):
+            return next(self._iter)
+        next = __next__
+
+    def setup_method(self):
+        import tempfile
+
+        self.fd = tempfile.TemporaryFile()
+        self.reader = self.SampleReader(self.fd)
+
+    def test_attributes(self):
+        import pytest
+
+        assert self.reader.name == self.fd.name
+        assert self.reader.fd == self.fd.fileno()
+        assert self.reader.fileno() == self.fd.fileno()
+        assert self.reader.filter == ''
+
+        with pytest.raises(NotImplementedError):
+            self.reader.setfilter(1, 2)
+
+    def test_readpkts_list(self):
+        pkts = self.reader.readpkts()
+        print(len(pkts))
+        for idx, (ts, buf) in enumerate(pkts):
+            assert ts == idx
+            assert buf == self.pkts[idx][1]
+
+    def test_readpkts_iter(self):
+        for idx, (ts, buf) in enumerate(self.reader):
+            assert ts == idx
+            assert buf == self.pkts[idx][1]
+
+    def test_dispatch_all(self):
+        assert self.reader.dispatch(0, lambda ts, pkt: None) == 3
+
+    def test_dispatch_some(self):
+        assert self.reader.dispatch(2, lambda ts, pkt: None) == 2
+
+    def test_dispatch_termination(self):
+        assert self.reader.dispatch(20, lambda ts, pkt: None) == 3
+
+    def test_loop(self):
+        class Count:
+            counter = 0
+
+            @classmethod
+            def inc(cls):
+                cls.counter += 1
+
+        assert self.reader.loop(lambda ts, pkt: Count.inc()) == 3
+        assert Count.counter == 3
+
+    def test_next(self):
+        ts, buf = next(self.reader)
+        assert ts == 0
+        assert buf == self.pkts[0][1]
diff --git a/dpkt/ssl.py b/dpkt/ssl.py
index 6ea6b7a..dda44c0 100644
--- a/dpkt/ssl.py
+++ b/dpkt/ssl.py
@@ -9,6 +9,8 @@
 
 from . import dpkt
 from . import ssl_ciphersuites
+from .compat import compat_ord
+from .utils import deprecation_warning
 
 #
 # Note from April 2011: cde...@gmail.com added code that parses SSL3/TLS messages more in depth.
@@ -16,22 +18,36 @@
 # Jul 2012: afleenor@google.com modified and extended SSL support further.
 #
 
+
 # SSL 2.0 is deprecated in RFC 6176
 class SSL2(dpkt.Packet):
     __hdr__ = (
         ('len', 'H', 0),
-        ('msg', 's', ''),
-        ('pad', 's', ''),
     )
 
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
+        # In SSL, all data sent is encapsulated in a record, an object which is
+        # composed of a header and some non-zero amount of data. Each record header
+        # contains a two or three byte length code. If the most significant bit is
+        # set in the first byte of the record length code then the record has
+        # no padding and the total header length will be 2 bytes, otherwise the
+        # record has padding and the total header length will be 3 bytes. The
+        # record header is transmitted before the data portion of the record.
         if self.len & 0x8000:
             n = self.len = self.len & 0x7FFF
             self.msg, self.data = self.data[:n], self.data[n:]
         else:
+            # Note that in the long header case (3 bytes total), the second most
+            # significant bit in the first byte has special meaning. When zero,
+            # the record being sent is a data record. When one, the record
+            # being sent is a security escape (there are currently no examples
+            # of security escapes; this is reserved for future versions of the
+            # protocol). In either case, the length code describes how much
+            # data is in the record.
             n = self.len = self.len & 0x3FFF
-            padlen = ord(self.data[0])
+            padlen = compat_ord(self.data[0])
+
             self.msg = self.data[1:1 + n]
             self.pad = self.data[1 + n:1 + n + padlen]
             self.data = self.data[1 + n + padlen:]
@@ -50,15 +66,15 @@
         self.records = []
         dpkt.Packet.__init__(self, *args, **kwargs)
 
-
     def unpack(self, buf):
         dpkt.Packet.unpack(self, buf)
         pointer = 0
         while len(self.data[pointer:]) > 0:
-            end = pointer + 5 + struct.unpack("!H", buf[pointer+3:pointer+5])[0]
+            end = pointer + 5 + struct.unpack("!H", buf[pointer + 3:pointer + 5])[0]
             self.records.append(TLSRecord(buf[pointer:end]))
             pointer = end
-            self.data = self.data[pointer:]
+        self.data = self.data[pointer:]
+
 
 
 # SSLv3/TLS versions
@@ -192,11 +208,12 @@
 
     pointer = 2
     while pointer < extensions_length:
-        ext_type = struct.unpack('!H', buf[pointer:pointer+2])[0]
+        ext_type = struct.unpack('!H', buf[pointer:pointer + 2])[0]
         pointer += 2
         ext_data, parsed = parse_variable_array(buf[pointer:], 2)
         extensions.append((ext_type, ext_data))
         pointer += parsed
+
     return extensions
 
 
@@ -205,7 +222,6 @@
 
 
 class TLSRecord(dpkt.Packet):
-
     """
     SSLv3 or TLSv1+ packet.
 
@@ -245,20 +261,16 @@
 
 
 class TLSChangeCipherSpec(dpkt.Packet):
-
     """
     ChangeCipherSpec message is just a single byte with value 1
     """
-
     __hdr__ = (('type', 'B', 1),)
 
 
 class TLSAppData(str):
-
     """
     As far as TLSRecord is concerned, AppData is just an opaque blob.
     """
-
     pass
 
 
@@ -283,18 +295,21 @@
         dpkt.Packet.unpack(self, buf)
         # now session, cipher suites, extensions are in self.data
         self.session_id, pointer = parse_variable_array(self.data, 1)
-        # print 'pointer',pointer
+
         # handle ciphersuites
         ciphersuites, parsed = parse_variable_array(self.data[pointer:], 2)
         pointer += parsed
-        self.num_ciphersuites = len(ciphersuites) / 2
+        num_ciphersuites = int(len(ciphersuites) / 2)
+        self.ciphersuites = [
+            ssl_ciphersuites.BY_CODE.get(code, ssl_ciphersuites.get_unknown_ciphersuite(code))
+            for code in struct.unpack('!' + num_ciphersuites * 'H', ciphersuites)]
         # check len(ciphersuites) % 2 == 0 ?
+
         # compression methods
-        compression_methods, parsed = parse_variable_array(
-            self.data[pointer:], 1)
+        compression_methods, parsed = parse_variable_array(self.data[pointer:], 1)
         pointer += parsed
-        self.num_compression_methods = parsed - 1
-        self.compression_methods = map(ord, compression_methods)
+        self.compression_methods = struct.unpack('{0}B'.format(len(compression_methods)), compression_methods)
+
         # Parse extensions if present
         if len(self.data[pointer:]) >= 6:
             self.extensions = parse_extensions(self.data[pointer:])
@@ -310,34 +325,52 @@
         try:
             dpkt.Packet.unpack(self, buf)
             self.session_id, pointer = parse_variable_array(self.data, 1)
+
             # single cipher suite
-            self.cipher_suite = struct.unpack('!H', self.data[pointer:pointer + 2])[0]
+            code = struct.unpack('!H', self.data[pointer:pointer + 2])[0]
+            self.ciphersuite = \
+                ssl_ciphersuites.BY_CODE.get(code, ssl_ciphersuites.get_unknown_ciphersuite(code))
             pointer += 2
+
             # single compression method
-            self.compression = struct.unpack('!B', self.data[pointer:pointer + 1])[0]
+            self.compression_method = struct.unpack('!B', self.data[pointer:pointer + 1])[0]
             pointer += 1
+
             # Parse extensions if present
             if len(self.data[pointer:]) >= 6:
                 self.extensions = parse_extensions(self.data[pointer:])
+
         except struct.error:
             # probably data too short
             raise dpkt.NeedData
 
+    # XXX - legacy, deprecated
+    # for whatever reason these attributes were named differently than their sister attributes in TLSClientHello
+    @property
+    def cipher_suite(self):
+        deprecation_warning("TLSServerHello.cipher_suite is deprecated and renamed to .ciphersuite")
+        return self.ciphersuite
+
+    @property
+    def compression(self):
+        deprecation_warning("TLSServerHello.compression is deprecated and renamed to .compression_method")
+        return self.compression_method
+
 
 class TLSCertificate(dpkt.Packet):
     __hdr__ = tuple()
 
     def unpack(self, buf):
-       try:
-           dpkt.Packet.unpack(self, buf)
-           all_certs, all_certs_len = parse_variable_array(self.data, 3)
-           self.certificates = []
-           pointer = 3
-           while pointer < all_certs_len:
-               cert, parsed = parse_variable_array(self.data[pointer:], 3)
-               self.certificates.append((cert))
-               pointer += parsed
-       except struct.error:
+        try:
+            dpkt.Packet.unpack(self, buf)
+            all_certs, all_certs_len = parse_variable_array(self.data, 3)
+            self.certificates = []
+            pointer = 3
+            while pointer < all_certs_len:
+                cert, parsed = parse_variable_array(self.data[pointer:], 3)
+                self.certificates.append((cert))
+                pointer += parsed
+        except struct.error:
             raise dpkt.NeedData
 
 
@@ -345,6 +378,7 @@
     __hdr__ = tuple()
 
 
+TLSNewSessionTicket = TLSUnknownHandshake