Merge commit '48f6b61' (version v0.8) into import

Change-Id: Icb233d0c82a2fb3c2ee0cb8e0a3cf93360f22613
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c6b54c9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+*.class
+*.jar
+/target
+.classpath
+.project
+.settings
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..6fb1cdb
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,18 @@
+language: java
+
+sudo: false
+
+env:
+  global:
+    secure: "bEwUUf3355VeqQZYTO0wsZKogquTUNwPQIwKWyHTGGaEVayTwfF2sMEzWojgDPA7E6EbWmSKSm0RKFpkpu5nWr7NvnaoJa/+BpRL/69Y5qB29qVNrCyWM2ZLaoEcTxrqeLKm1ma8TA7IOLAQnxEMZYar/siV9ycDOptp2ZzUoIM="
+
+addons:
+  coverity_scan:
+    project:
+      name: "c-rack/cbor-java"
+      description: "Build submitted via Travis CI"
+    notification_email: constantin@rack.li
+    build_command_prepend: "mvn clean"
+    build_command: "mvn -DskipTests=true compile"
+    branch_pattern: master
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e06d208
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ab05a6a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,98 @@
+cbor-java
+=========
+
+A Java 7 implementation of [RFC 7049](http://tools.ietf.org/html/rfc7049): Concise Binary Object Representation ([CBOR](http://cbor.io/))
+
+
+[![Build Status](https://travis-ci.org/c-rack/cbor-java.svg?branch=master)](https://travis-ci.org/c-rack/cbor-java)
+[![Coverage Status](https://coveralls.io/repos/c-rack/cbor-java/badge.svg?branch=master&service=github)](https://coveralls.io/github/c-rack/cbor-java?branch=master)
+[![Coverity Scan Build Status](https://scan.coverity.com/projects/1218/badge.svg)](https://scan.coverity.com/projects/1218)
+[![Dependency Status](https://www.versioneye.com/user/projects/555e2fb6634daa30fb000ea0/badge.svg?style=flat)](https://www.versioneye.com/user/projects/555e2fb6634daa30fb000ea0)
+
+
+## Features
+
+* Encodes and decodes **all examples** described in RFC 7049
+* Provides a **fluent interface builder** for CBOR messages
+* Supports semantic tags
+* Supports 64-bit integer values
+* Passes all [CPD](http://c-rack.github.io/cbor-java/cpd.html), [PMD](http://c-rack.github.io/cbor-java/pmd.html) and [FindBugs](http://c-rack.github.io/cbor-java/findbugs.html) tests
+
+## Documentation
+
+* [Documentation](http://c-rack.github.io/cbor-java/)
+* [JavaDoc](http://c-rack.github.io/cbor-java/apidocs/index.html)
+
+## Maven Dependency
+
+Add this to the dependencies section of your pom.xml file:
+
+```xml
+<dependency>
+    <groupId>co.nstant.in</groupId>
+    <artifactId>cbor</artifactId>
+    <version>0.8</version>
+</dependency>
+```
+
+## Usage
+
+### Encoding Example
+
+```java
+ByteArrayOutputStream baos = new ByteArrayOutputStream();
+new CborEncoder(baos).encode(new CborBuilder()
+    .add("text")                // add string
+    .add(1234)                  // add integer
+    .add(new byte[] { 0x10 })   // add byte array
+    .addArray()                 // add array
+        .add(1)
+        .add("text")
+        .end()
+    .build());
+byte[] encodedBytes = baos.toByteArray();
+```
+
+### Decoding Example
+
+```java
+ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
+List<DataItem> dataItems = new CborDecoder(bais).decode();
+for(DataItem dataItem : dataItems) {
+    // process data item
+}
+```
+
+### Streaming Decoding Example
+
+```java
+ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
+new CborDecoder(bais).decode(new DataItemListener() {
+
+    @Override
+    public void onDataItem(DataItem dataItem) {
+        // process data item
+    }
+
+});
+```
+
+## Contribution Process
+
+This project uses the [C4 process](https://rfc.zeromq.org/spec:42/C4/) for all code changes.
+
+## License
+
+    Copyright 2013-2017 Constantin Rack
+ 
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+ 
+        http://www.apache.org/licenses/LICENSE-2.0
+ 
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..40aee1c
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,260 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>co.nstant.in</groupId>
+	<artifactId>cbor</artifactId>
+	<version>0.8</version>
+	<name>CBOR for Java</name>
+	<description>Java implementation of RFC 7049: Concise Binary Object Representation (CBOR)</description>
+	<url>https://github.com/c-rack/cbor-java</url>
+	<licenses>
+		<license>
+			<name>The Apache Software License, Version 2.0</name>
+			<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+			<distribution>repo</distribution>
+			<comments>A business-friendly OSS license</comments>
+		</license>
+	</licenses>
+	<prerequisites>
+		<maven>3.0.1</maven>
+	</prerequisites>
+	<scm>
+		<url>https://github.com/c-rack/cbor-java</url>
+	</scm>
+    <distributionManagement>
+        <snapshotRepository>
+            <id>ossrh</id>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+        </snapshotRepository>
+        <repository>
+            <id>ossrh</id>
+            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+        </repository>
+    </distributionManagement>    
+	<properties>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+		<github.global.server>github</github.global.server>
+	</properties>
+	<developers>
+		<developer>
+			<id>crack</id>
+			<name>Constantin Rack</name>
+			<email>constantin@rack.li</email>
+			<url>http://co.nstant.in/</url>
+			<roles>
+				<role>developer</role>
+			</roles>
+			<timezone>Europe/Berlin</timezone>
+		</developer>
+		<developer>
+			<id>marandus</id>
+			<name>Thomas Rix</name>
+			<email>rix@decoit.de</email>
+			<url>http://www.decoit.de/</url>
+			<roles>
+				<role>developer</role>
+			</roles>
+			<timezone>Europe/Berlin</timezone>
+		</developer>
+	</developers>
+	<build>
+		<plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-gpg-plugin</artifactId>
+                <version>1.5</version>
+                <executions>
+                    <execution>
+                        <id>sign-artifacts</id>
+                        <phase>verify</phase>
+                        <goals>
+                            <goal>sign</goal>
+                        </goals>
+                    </execution>
+                </executions>
+           </plugin>
+			<plugin>
+				<groupId>org.eluder.coveralls</groupId>
+				<artifactId>coveralls-maven-plugin</artifactId>
+				<version>4.3.0</version>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>3.7.0</version>
+				<configuration>
+					<source>1.7</source>
+					<target>1.7</target>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<version>3.0.1</version>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-javadoc-plugin</artifactId>
+				<version>2.10.4</version>
+				<executions>
+					<execution>
+						<id>attach-javadocs</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-site-plugin</artifactId>
+				<version>3.6</version>
+				<configuration>
+					<locales>en</locales>
+				</configuration>
+				<dependencies>
+					<dependency>
+						<groupId>lt.velykis.maven.skins</groupId>
+						<artifactId>reflow-velocity-tools</artifactId>
+						<version>1.1.1</version>
+					</dependency>
+					<!-- Reflow skin requires Velocity >= 1.7 -->
+					<dependency>
+						<groupId>org.apache.velocity</groupId>
+						<artifactId>velocity</artifactId>
+						<version>1.7</version>
+					</dependency>
+				</dependencies>
+			</plugin>
+			<plugin>
+				<groupId>com.github.github</groupId>
+				<artifactId>site-maven-plugin</artifactId>
+				<version>0.12</version>
+				<configuration>
+					<message>Creating site for ${project.version}</message>
+				</configuration>
+				<executions>
+					<execution>
+						<goals>
+							<goal>site</goal>
+						</goals>
+						<phase>site</phase>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.jacoco</groupId>
+				<artifactId>jacoco-maven-plugin</artifactId>
+				<version>0.7.9</version>
+				<executions>
+					<execution>
+						<id>prepare-agent</id>
+						<goals>
+							<goal>prepare-agent</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-pmd-plugin</artifactId>
+				<version>3.8</version>
+				<configuration>
+					<skipEmptyReport>false</skipEmptyReport>
+				</configuration>
+			</plugin>
+            <plugin>
+                <groupId>org.sonatype.plugins</groupId>
+                <artifactId>nexus-staging-maven-plugin</artifactId>
+                <version>1.6.7</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <serverId>ossrh</serverId>
+                    <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+                    <autoReleaseAfterClose>true</autoReleaseAfterClose>
+                </configuration>
+            </plugin>
+		</plugins>
+	</build>
+	<reporting>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jxr-plugin</artifactId>
+				<version>2.5</version>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-surefire-report-plugin</artifactId>
+				<version>2.20.1</version>
+			</plugin>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>findbugs-maven-plugin</artifactId>
+				<version>3.0.5</version>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-pmd-plugin</artifactId>
+				<version>3.8</version>
+				<configuration>
+					<skipEmptyReport>false</skipEmptyReport>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>jdepend-maven-plugin</artifactId>
+				<version>2.0</version>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-project-info-reports-plugin</artifactId>
+				<version>2.9</version>
+				<reportSets>
+					<reportSet>
+						<reports><!-- select reports -->
+							<report>index</report>
+						</reports>
+					</reportSet>
+				</reportSets>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-javadoc-plugin</artifactId>
+				<version>2.10.4</version>
+				<reportSets>
+					<reportSet><!-- by default, id = "default" -->
+						<reports><!-- select non-aggregate reports -->
+							<report>javadoc</report>
+							<report>test-javadoc</report>
+						</reports>
+					</reportSet>
+					<reportSet><!-- aggregate reportSet, to define in poms having modules -->
+						<id>aggregate</id>
+						<inherited>false</inherited><!-- don't run aggregate in child modules -->
+						<reports>
+							<report>aggregate</report>
+						</reports>
+					</reportSet>
+				</reportSets>
+			</plugin>
+		</plugins>
+	</reporting>
+	<dependencies>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.12</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+</project>
diff --git a/src/main/java/co/nstant/in/cbor/CborBuilder.java b/src/main/java/co/nstant/in/cbor/CborBuilder.java
new file mode 100644
index 0000000..8dc1ef9
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/CborBuilder.java
@@ -0,0 +1,129 @@
+package co.nstant.in.cbor;
+
+import java.math.BigInteger;
+import java.util.LinkedList;
+import java.util.List;
+
+import co.nstant.in.cbor.builder.AbstractBuilder;
+import co.nstant.in.cbor.builder.ArrayBuilder;
+import co.nstant.in.cbor.builder.ByteStringBuilder;
+import co.nstant.in.cbor.builder.MapBuilder;
+import co.nstant.in.cbor.builder.UnicodeStringBuilder;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Map;
+import co.nstant.in.cbor.model.UnicodeString;
+
+public class CborBuilder extends AbstractBuilder<CborBuilder> {
+
+    private final List<DataItem> dataItems = new LinkedList<>();
+
+    public CborBuilder() {
+        super(null);
+    }
+
+    public CborBuilder reset() {
+        dataItems.clear();
+        return this;
+    }
+
+    public List<DataItem> build() {
+        return dataItems;
+    }
+
+    public CborBuilder add(DataItem dataItem) {
+        dataItems.add(dataItem);
+        return this;
+    }
+
+    public CborBuilder add(long value) {
+        add(convert(value));
+        return this;
+    }
+
+    public CborBuilder add(BigInteger value) {
+        add(convert(value));
+        return this;
+    }
+
+    public CborBuilder add(boolean value) {
+        add(convert(value));
+        return this;
+    }
+
+    public CborBuilder add(float value) {
+        add(convert(value));
+        return this;
+    }
+
+    public CborBuilder add(double value) {
+        add(convert(value));
+        return this;
+    }
+
+    public CborBuilder add(byte[] bytes) {
+        add(convert(bytes));
+        return this;
+    }
+
+    public ByteStringBuilder<CborBuilder> startByteString() {
+        return startByteString(null);
+    }
+
+    public ByteStringBuilder<CborBuilder> startByteString(byte[] bytes) {
+        add(new ByteString(bytes).setChunked(true));
+        return new ByteStringBuilder<CborBuilder>(this);
+    }
+
+    public CborBuilder add(String string) {
+        add(convert(string));
+        return this;
+    }
+
+    public UnicodeStringBuilder<CborBuilder> startString() {
+        return startString(null);
+    }
+
+    public UnicodeStringBuilder<CborBuilder> startString(String string) {
+        add(new UnicodeString(string).setChunked(true));
+        return new UnicodeStringBuilder<CborBuilder>(this);
+    }
+
+    public CborBuilder addTag(long value) {
+        add(tag(value));
+        return this;
+    }
+
+    public ArrayBuilder<CborBuilder> startArray() {
+        Array array = new Array();
+        array.setChunked(true);
+        add(array);
+        return new ArrayBuilder<CborBuilder>(this, array);
+    }
+
+    public ArrayBuilder<CborBuilder> addArray() {
+        Array array = new Array();
+        add(array);
+        return new ArrayBuilder<CborBuilder>(this, array);
+    }
+
+    public MapBuilder<CborBuilder> addMap() {
+        Map map = new Map();
+        add(map);
+        return new MapBuilder<CborBuilder>(this, map);
+    }
+
+    public MapBuilder<CborBuilder> startMap() {
+        Map map = new Map();
+        map.setChunked(true);
+        add(map);
+        return new MapBuilder<CborBuilder>(this, map);
+    }
+
+    @Override
+    protected void addChunk(DataItem dataItem) {
+        add(dataItem);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/CborDecoder.java b/src/main/java/co/nstant/in/cbor/CborDecoder.java
new file mode 100644
index 0000000..d726101
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/CborDecoder.java
@@ -0,0 +1,288 @@
+package co.nstant.in.cbor;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+import co.nstant.in.cbor.decoder.ArrayDecoder;
+import co.nstant.in.cbor.decoder.ByteStringDecoder;
+import co.nstant.in.cbor.decoder.MapDecoder;
+import co.nstant.in.cbor.decoder.NegativeIntegerDecoder;
+import co.nstant.in.cbor.decoder.SpecialDecoder;
+import co.nstant.in.cbor.decoder.TagDecoder;
+import co.nstant.in.cbor.decoder.UnicodeStringDecoder;
+import co.nstant.in.cbor.decoder.UnsignedIntegerDecoder;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.LanguageTaggedString;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.Number;
+import co.nstant.in.cbor.model.RationalNumber;
+import co.nstant.in.cbor.model.Tag;
+import co.nstant.in.cbor.model.UnicodeString;
+
+/**
+ * Decoder for the CBOR format based.
+ */
+public class CborDecoder {
+
+    private final InputStream inputStream;
+    private final UnsignedIntegerDecoder unsignedIntegerDecoder;
+    private final NegativeIntegerDecoder negativeIntegerDecoder;
+    private final ByteStringDecoder byteStringDecoder;
+    private final UnicodeStringDecoder unicodeStringDecoder;
+    private final ArrayDecoder arrayDecoder;
+    private final MapDecoder mapDecoder;
+    private final TagDecoder tagDecoder;
+    private final SpecialDecoder specialDecoder;
+
+    private boolean autoDecodeInfinitiveArrays = true;
+    private boolean autoDecodeInfinitiveMaps = true;
+    private boolean autoDecodeInfinitiveByteStrings = true;
+    private boolean autoDecodeInfinitiveUnicodeStrings = true;
+    private boolean autoDecodeRationalNumbers = true;
+    private boolean autoDecodeLanguageTaggedStrings = true;
+    private boolean rejectDuplicateKeys = false;
+
+    /**
+     * Initialize a new decoder which reads the binary encoded data from an
+     * {@link InputStream}.
+     */
+    public CborDecoder(InputStream inputStream) {
+        Objects.requireNonNull(inputStream);
+        this.inputStream = inputStream;
+        unsignedIntegerDecoder = new UnsignedIntegerDecoder(this, inputStream);
+        negativeIntegerDecoder = new NegativeIntegerDecoder(this, inputStream);
+        byteStringDecoder = new ByteStringDecoder(this, inputStream);
+        unicodeStringDecoder = new UnicodeStringDecoder(this, inputStream);
+        arrayDecoder = new ArrayDecoder(this, inputStream);
+        mapDecoder = new MapDecoder(this, inputStream);
+        tagDecoder = new TagDecoder(this, inputStream);
+        specialDecoder = new SpecialDecoder(this, inputStream);
+    }
+
+    /**
+     * Convenience method to decode a byte array directly.
+     *
+     * @param bytes
+     *            the CBOR encoded data
+     * @return a list of {@link DataItem}s
+     * @throws CborException
+     *             if decoding failed
+     */
+    public static List<DataItem> decode(byte[] bytes) throws CborException {
+        return new CborDecoder(new ByteArrayInputStream(bytes)).decode();
+    }
+
+    /**
+     * Decode the {@link InputStream} to a list of {@link DataItem}s.
+     *
+     * @return the list of {@link DataItem}s
+     * @throws CborException
+     *             if decoding failed
+     */
+    public List<DataItem> decode() throws CborException {
+        List<DataItem> dataItems = new LinkedList<>();
+        DataItem dataItem;
+        while ((dataItem = decodeNext()) != null) {
+            dataItems.add(dataItem);
+        }
+        return dataItems;
+    }
+
+    /**
+     * Streaming decoding of an input stream. On each decoded DataItem, the
+     * callback listener is invoked.
+     *
+     * @param dataItemListener
+     *            the callback listener
+     * @throws CborException
+     *             if decoding failed
+     */
+    public void decode(DataItemListener dataItemListener) throws CborException {
+        Objects.requireNonNull(dataItemListener);
+        DataItem dataItem = decodeNext();
+        while (dataItem != null) {
+            dataItemListener.onDataItem(dataItem);
+            dataItem = decodeNext();
+        }
+    }
+
+    /**
+     * Decodes exactly one DataItem from the input stream.
+     *
+     * @return a {@link DataItem} or null if end of stream has reached.
+     * @throws CborException
+     *             if decoding failed
+     */
+    public DataItem decodeNext() throws CborException {
+        int symbol;
+        try {
+            symbol = inputStream.read();
+        } catch (IOException ioException) {
+            throw new CborException(ioException);
+        }
+        if (symbol == -1) {
+            return null;
+        }
+        switch (MajorType.ofByte(symbol)) {
+        case ARRAY:
+            return arrayDecoder.decode(symbol);
+        case BYTE_STRING:
+            return byteStringDecoder.decode(symbol);
+        case MAP:
+            return mapDecoder.decode(symbol);
+        case NEGATIVE_INTEGER:
+            return negativeIntegerDecoder.decode(symbol);
+        case UNICODE_STRING:
+            return unicodeStringDecoder.decode(symbol);
+        case UNSIGNED_INTEGER:
+            return unsignedIntegerDecoder.decode(symbol);
+        case SPECIAL:
+            return specialDecoder.decode(symbol);
+        case TAG:
+            Tag tag = tagDecoder.decode(symbol);
+            DataItem next = decodeNext();
+            if (next == null) {
+                throw new CborException("Unexpected end of stream: tag without following data item.");
+            } else {
+                if (autoDecodeRationalNumbers && tag.getValue() == 30) {
+                    return decodeRationalNumber(next);
+                } else if (autoDecodeLanguageTaggedStrings && tag.getValue() == 38) {
+                    return decodeLanguageTaggedString(next);
+                } else {
+                    DataItem itemToTag = next;
+                    while (itemToTag.hasTag())
+                        itemToTag = itemToTag.getTag();
+                    itemToTag.setTag(tag);
+                    return next;
+                }
+            }
+        case INVALID:
+        default:
+            throw new CborException("Not implemented major type " + symbol);
+        }
+    }
+
+    private DataItem decodeLanguageTaggedString(DataItem dataItem) throws CborException {
+        if (!(dataItem instanceof Array)) {
+            throw new CborException("Error decoding LanguageTaggedString: not an array");
+        }
+
+        Array array = (Array) dataItem;
+
+        if (array.getDataItems().size() != 2) {
+            throw new CborException("Error decoding LanguageTaggedString: array size is not 2");
+        }
+
+        DataItem languageDataItem = array.getDataItems().get(0);
+
+        if (!(languageDataItem instanceof UnicodeString)) {
+            throw new CborException("Error decoding LanguageTaggedString: first data item is not an UnicodeString");
+        }
+
+        DataItem stringDataItem = array.getDataItems().get(1);
+
+        if (!(stringDataItem instanceof UnicodeString)) {
+            throw new CborException("Error decoding LanguageTaggedString: second data item is not an UnicodeString");
+        }
+
+        UnicodeString language = (UnicodeString) languageDataItem;
+        UnicodeString string = (UnicodeString) stringDataItem;
+
+        return new LanguageTaggedString(language, string);
+    }
+
+    private DataItem decodeRationalNumber(DataItem dataItem) throws CborException {
+        if (!(dataItem instanceof Array)) {
+            throw new CborException("Error decoding RationalNumber: not an array");
+        }
+
+        Array array = (Array) dataItem;
+
+        if (array.getDataItems().size() != 2) {
+            throw new CborException("Error decoding RationalNumber: array size is not 2");
+        }
+
+        DataItem numeratorDataItem = array.getDataItems().get(0);
+
+        if (!(numeratorDataItem instanceof Number)) {
+            throw new CborException("Error decoding RationalNumber: first data item is not a number");
+        }
+
+        DataItem denominatorDataItem = array.getDataItems().get(1);
+
+        if (!(denominatorDataItem instanceof Number)) {
+            throw new CborException("Error decoding RationalNumber: second data item is not a number");
+        }
+
+        Number numerator = (Number) numeratorDataItem;
+        Number denominator = (Number) denominatorDataItem;
+
+        return new RationalNumber(numerator, denominator);
+    }
+
+    public boolean isAutoDecodeInfinitiveArrays() {
+        return autoDecodeInfinitiveArrays;
+    }
+
+    public void setAutoDecodeInfinitiveArrays(boolean autoDecodeInfinitiveArrays) {
+        this.autoDecodeInfinitiveArrays = autoDecodeInfinitiveArrays;
+    }
+
+    public boolean isAutoDecodeInfinitiveMaps() {
+        return autoDecodeInfinitiveMaps;
+    }
+
+    public void setAutoDecodeInfinitiveMaps(boolean autoDecodeInfinitiveMaps) {
+        this.autoDecodeInfinitiveMaps = autoDecodeInfinitiveMaps;
+    }
+
+    public boolean isAutoDecodeInfinitiveByteStrings() {
+        return autoDecodeInfinitiveByteStrings;
+    }
+
+    public void setAutoDecodeInfinitiveByteStrings(
+        boolean autoDecodeInfinitiveByteStrings) {
+        this.autoDecodeInfinitiveByteStrings = autoDecodeInfinitiveByteStrings;
+    }
+
+    public boolean isAutoDecodeInfinitiveUnicodeStrings() {
+        return autoDecodeInfinitiveUnicodeStrings;
+    }
+
+    public void setAutoDecodeInfinitiveUnicodeStrings(
+        boolean autoDecodeInfinitiveUnicodeStrings) {
+        this.autoDecodeInfinitiveUnicodeStrings = autoDecodeInfinitiveUnicodeStrings;
+    }
+
+    public boolean isAutoDecodeRationalNumbers() {
+        return autoDecodeRationalNumbers;
+    }
+
+    public void setAutoDecodeRationalNumbers(
+        boolean autoDecodeRationalNumbers) {
+        this.autoDecodeRationalNumbers = autoDecodeRationalNumbers;
+    }
+
+    public boolean isAutoDecodeLanguageTaggedStrings() {
+        return autoDecodeLanguageTaggedStrings;
+    }
+
+    public void setAutoDecodeLanguageTaggedStrings(
+        boolean autoDecodeLanguageTaggedStrings) {
+        this.autoDecodeLanguageTaggedStrings = autoDecodeLanguageTaggedStrings;
+    }
+
+    public boolean isRejectDuplicateKeys() {
+        return rejectDuplicateKeys;
+    }
+
+    public void setRejectDuplicateKeys(boolean rejectDuplicateKeys) {
+        this.rejectDuplicateKeys = rejectDuplicateKeys;
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/CborEncoder.java b/src/main/java/co/nstant/in/cbor/CborEncoder.java
new file mode 100644
index 0000000..06d5387
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/CborEncoder.java
@@ -0,0 +1,121 @@
+package co.nstant.in.cbor;
+
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Objects;
+
+import co.nstant.in.cbor.encoder.ArrayEncoder;
+import co.nstant.in.cbor.encoder.ByteStringEncoder;
+import co.nstant.in.cbor.encoder.MapEncoder;
+import co.nstant.in.cbor.encoder.NegativeIntegerEncoder;
+import co.nstant.in.cbor.encoder.SpecialEncoder;
+import co.nstant.in.cbor.encoder.TagEncoder;
+import co.nstant.in.cbor.encoder.UnicodeStringEncoder;
+import co.nstant.in.cbor.encoder.UnsignedIntegerEncoder;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Map;
+import co.nstant.in.cbor.model.NegativeInteger;
+import co.nstant.in.cbor.model.SimpleValue;
+import co.nstant.in.cbor.model.Special;
+import co.nstant.in.cbor.model.Tag;
+import co.nstant.in.cbor.model.UnicodeString;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+/**
+ * Encoder for the CBOR format based.
+ */
+public class CborEncoder {
+
+    private final UnsignedIntegerEncoder unsignedIntegerEncoder;
+    private final NegativeIntegerEncoder negativeIntegerEncoder;
+    private final ByteStringEncoder byteStringEncoder;
+    private final UnicodeStringEncoder unicodeStringEncoder;
+    private final ArrayEncoder arrayEncoder;
+    private final MapEncoder mapEncoder;
+    private final TagEncoder tagEncoder;
+    private final SpecialEncoder specialEncoder;
+
+    /**
+     * Initialize a new encoder which writes the binary encoded data to an
+     * {@link OutputStream}.
+     */
+    public CborEncoder(OutputStream outputStream) {
+        Objects.requireNonNull(outputStream);
+        unsignedIntegerEncoder = new UnsignedIntegerEncoder(this, outputStream);
+        negativeIntegerEncoder = new NegativeIntegerEncoder(this, outputStream);
+        byteStringEncoder = new ByteStringEncoder(this, outputStream);
+        unicodeStringEncoder = new UnicodeStringEncoder(this, outputStream);
+        arrayEncoder = new ArrayEncoder(this, outputStream);
+        mapEncoder = new MapEncoder(this, outputStream);
+        tagEncoder = new TagEncoder(this, outputStream);
+        specialEncoder = new SpecialEncoder(this, outputStream);
+    }
+
+    /**
+     * Encode a list of {@link DataItem}s, also known as a stream.
+     *
+     * @param dataItems
+     *            a list of {@link DataItem}s
+     * @throws CborException
+     *             if the {@link DataItem}s could not be encoded or there was an
+     *             problem with the {@link OutputStream}.
+     */
+    public void encode(List<DataItem> dataItems) throws CborException {
+        for (DataItem dataItem : dataItems) {
+            encode(dataItem);
+        }
+    }
+
+    /**
+     * Encode a single {@link DataItem}.
+     *
+     * @param dataItem
+     *            the {@link DataItem} to encode. If null, encoder encodes a
+     *            {@link SimpleValue} NULL value.
+     * @throws CborException
+     *             if {@link DataItem} could not be encoded or there was an
+     *             problem with the {@link OutputStream}.
+     */
+    public void encode(DataItem dataItem) throws CborException {
+        if (dataItem == null) {
+            dataItem = SimpleValue.NULL;
+        }
+
+        if (dataItem.hasTag()) {
+            Tag tagDi = dataItem.getTag();
+            tagEncoder.encode(tagDi);
+        }
+
+        switch (dataItem.getMajorType()) {
+        case UNSIGNED_INTEGER:
+            unsignedIntegerEncoder.encode((UnsignedInteger) dataItem);
+            break;
+        case NEGATIVE_INTEGER:
+            negativeIntegerEncoder.encode((NegativeInteger) dataItem);
+            break;
+        case BYTE_STRING:
+            byteStringEncoder.encode((ByteString) dataItem);
+            break;
+        case UNICODE_STRING:
+            unicodeStringEncoder.encode((UnicodeString) dataItem);
+            break;
+        case ARRAY:
+            arrayEncoder.encode((Array) dataItem);
+            break;
+        case MAP:
+            mapEncoder.encode((Map) dataItem);
+            break;
+        case SPECIAL:
+            specialEncoder.encode((Special) dataItem);
+            break;
+        case TAG:
+            tagEncoder.encode((Tag) dataItem);
+            break;
+        default:
+            throw new CborException("Unknown major type");
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/CborException.java b/src/main/java/co/nstant/in/cbor/CborException.java
new file mode 100644
index 0000000..d4708c9
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/CborException.java
@@ -0,0 +1,19 @@
+package co.nstant.in.cbor;
+
+public class CborException extends Exception {
+
+	private static final long serialVersionUID = 8839905301881841410L;
+
+	public CborException(String message) {
+		super(message);
+	}
+
+	public CborException(Throwable cause) {
+		super(cause);
+	}
+
+	public CborException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/DataItemListener.java b/src/main/java/co/nstant/in/cbor/DataItemListener.java
new file mode 100644
index 0000000..2044954
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/DataItemListener.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor;
+
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * Callback interface for a streaming {@link CborDecoder}.
+ */
+public interface DataItemListener {
+
+	/**
+	 * Gets called on every decoded {@link DataItem}.
+	 */
+	void onDataItem(DataItem dataItem);
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/builder/AbstractBuilder.java b/src/main/java/co/nstant/in/cbor/builder/AbstractBuilder.java
new file mode 100644
index 0000000..2b227b5
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/builder/AbstractBuilder.java
@@ -0,0 +1,113 @@
+package co.nstant.in.cbor.builder;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.decoder.HalfPrecisionFloatDecoder;
+import co.nstant.in.cbor.encoder.HalfPrecisionFloatEncoder;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.DoublePrecisionFloat;
+import co.nstant.in.cbor.model.HalfPrecisionFloat;
+import co.nstant.in.cbor.model.NegativeInteger;
+import co.nstant.in.cbor.model.SimpleValue;
+import co.nstant.in.cbor.model.SinglePrecisionFloat;
+import co.nstant.in.cbor.model.Tag;
+import co.nstant.in.cbor.model.UnicodeString;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+public abstract class AbstractBuilder<T> {
+
+    private final T parent;
+
+    public AbstractBuilder(T parent) {
+        this.parent = parent;
+    }
+
+    protected T getParent() {
+        return parent;
+    }
+
+    protected void addChunk(DataItem dataItem) {
+        throw new IllegalStateException();
+    }
+
+    protected DataItem convert(long value) {
+        if (value >= 0) {
+            return new UnsignedInteger(value);
+        } else {
+            return new NegativeInteger(value);
+        }
+    }
+
+    protected DataItem convert(BigInteger value) {
+        if (value.signum() == -1) {
+            return new NegativeInteger(value);
+        } else {
+            return new UnsignedInteger(value);
+        }
+    }
+
+    protected DataItem convert(boolean value) {
+        if (value) {
+            return SimpleValue.TRUE;
+        } else {
+            return SimpleValue.FALSE;
+        }
+    }
+
+    protected DataItem convert(byte[] bytes) {
+        return new ByteString(bytes);
+    }
+
+    protected DataItem convert(String string) {
+        return new UnicodeString(string);
+    }
+
+    protected DataItem convert(float value) {
+        if (isHalfPrecisionEnough(value)) {
+            return new HalfPrecisionFloat(value);
+        } else {
+            return new SinglePrecisionFloat(value);
+        }
+    }
+
+    protected DataItem convert(double value) {
+        return new DoublePrecisionFloat(value);
+    }
+
+    protected Tag tag(long value) {
+        return new Tag(value);
+    }
+
+    private boolean isHalfPrecisionEnough(float value) {
+        try {
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+            HalfPrecisionFloatEncoder encoder = getHalfPrecisionFloatEncoder(outputStream);
+            encoder.encode(new HalfPrecisionFloat(value));
+            byte[] bytes = outputStream.toByteArray();
+            ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+            HalfPrecisionFloatDecoder decoder = getHalfPrecisionFloatDecoder(inputStream);
+            if (inputStream.read() == -1) { // to skip type byte
+                throw new CborException("unexpected end of stream");
+            }
+            HalfPrecisionFloat halfPrecisionFloat = decoder.decode(0);
+            return value == halfPrecisionFloat.getValue();
+        } catch (CborException cborException) {
+            return false;
+        }
+    }
+
+    protected HalfPrecisionFloatEncoder getHalfPrecisionFloatEncoder(OutputStream outputStream) {
+        return new HalfPrecisionFloatEncoder(null, outputStream);
+    }
+
+    protected HalfPrecisionFloatDecoder getHalfPrecisionFloatDecoder(InputStream inputStream) {
+        return new HalfPrecisionFloatDecoder(null, inputStream);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/builder/ArrayBuilder.java b/src/main/java/co/nstant/in/cbor/builder/ArrayBuilder.java
new file mode 100644
index 0000000..274f929
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/builder/ArrayBuilder.java
@@ -0,0 +1,86 @@
+package co.nstant.in.cbor.builder;
+
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Map;
+import co.nstant.in.cbor.model.SimpleValue;
+
+public class ArrayBuilder<T extends AbstractBuilder<?>> extends
+                AbstractBuilder<T> {
+
+    private final Array array;
+
+    public ArrayBuilder(T parent, Array array) {
+        super(parent);
+        this.array = array;
+    }
+
+    public ArrayBuilder<T> add(DataItem dataItem) {
+        array.add(dataItem);
+        return this;
+    }
+
+    public ArrayBuilder<T> add(long value) {
+        add(convert(value));
+        return this;
+    }
+
+    public ArrayBuilder<T> add(boolean value) {
+        add(convert(value));
+        return this;
+    }
+
+    public ArrayBuilder<T> add(float value) {
+        add(convert(value));
+        return this;
+    }
+
+    public ArrayBuilder<T> add(double value) {
+        add(convert(value));
+        return this;
+    }
+
+    public ArrayBuilder<T> add(byte[] bytes) {
+        add(convert(bytes));
+        return this;
+    }
+
+    public ArrayBuilder<T> add(String string) {
+        add(convert(string));
+        return this;
+    }
+
+    public ArrayBuilder<ArrayBuilder<T>> addArray() {
+        Array nestedArray = new Array();
+        add(nestedArray);
+        return new ArrayBuilder<ArrayBuilder<T>>(this, nestedArray);
+    }
+
+    public ArrayBuilder<ArrayBuilder<T>> startArray() {
+        Array nestedArray = new Array();
+        nestedArray.setChunked(true);
+        add(nestedArray);
+        return new ArrayBuilder<ArrayBuilder<T>>(this, nestedArray);
+    }
+
+    public MapBuilder<ArrayBuilder<T>> addMap() {
+        Map nestedMap = new Map();
+        add(nestedMap);
+        return new MapBuilder<ArrayBuilder<T>>(this, nestedMap);
+    }
+
+    public MapBuilder<ArrayBuilder<T>> startMap() {
+        Map nestedMap = new Map();
+        nestedMap.setChunked(true);
+        add(nestedMap);
+        return new MapBuilder<ArrayBuilder<T>>(this, nestedMap);
+    }
+
+    public T end() {
+        if (array.isChunked()) {
+            add(SimpleValue.BREAK);
+        }
+        return getParent();
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/builder/ByteStringBuilder.java b/src/main/java/co/nstant/in/cbor/builder/ByteStringBuilder.java
new file mode 100644
index 0000000..2f302b5
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/builder/ByteStringBuilder.java
@@ -0,0 +1,22 @@
+package co.nstant.in.cbor.builder;
+
+import co.nstant.in.cbor.model.SimpleValue;
+
+public class ByteStringBuilder<T extends AbstractBuilder<?>> extends
+                AbstractBuilder<T> {
+
+    public ByteStringBuilder(T parent) {
+        super(parent);
+    }
+
+    public ByteStringBuilder<T> add(byte[] bytes) {
+        getParent().addChunk(convert(bytes));
+        return this;
+    }
+
+    public T end() {
+        getParent().addChunk(SimpleValue.BREAK);
+        return getParent();
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/builder/MapBuilder.java b/src/main/java/co/nstant/in/cbor/builder/MapBuilder.java
new file mode 100644
index 0000000..55e4303
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/builder/MapBuilder.java
@@ -0,0 +1,155 @@
+package co.nstant.in.cbor.builder;
+
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Map;
+
+public class MapBuilder<T extends AbstractBuilder<?>> extends
+                AbstractBuilder<T> {
+
+    private final Map map;
+
+    public MapBuilder(T parent, Map map) {
+        super(parent);
+        this.map = map;
+    }
+
+    public MapBuilder<T> put(DataItem key, DataItem value) {
+        map.put(key, value);
+        return this;
+    }
+
+    public MapBuilder<T> put(long key, long value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(long key, boolean value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(long key, float value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(long key, double value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(long key, byte[] value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(long key, String value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(String key, long value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(String key, boolean value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(String key, float value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(String key, double value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(String key, byte[] value) {
+        map.put(convert(key), convert(value));
+        return this;
+    }
+
+    public MapBuilder<T> put(String key, String value) {
+        put(convert(key), convert(value));
+        return this;
+    }
+
+    public ArrayBuilder<MapBuilder<T>> putArray(DataItem key) {
+        Array array = new Array();
+        put(key, array);
+        return new ArrayBuilder<>(this, array);
+    }
+
+    public ArrayBuilder<MapBuilder<T>> putArray(long key) {
+        Array array = new Array();
+        put(convert(key), array);
+        return new ArrayBuilder<>(this, array);
+    }
+
+    public ArrayBuilder<MapBuilder<T>> putArray(String key) {
+        Array array = new Array();
+        put(convert(key), array);
+        return new ArrayBuilder<>(this, array);
+    }
+
+    public ArrayBuilder<MapBuilder<T>> startArray(DataItem key) {
+        Array array = new Array();
+        array.setChunked(true);
+        put(key, array);
+        return new ArrayBuilder<>(this, array);
+    }
+
+    public ArrayBuilder<MapBuilder<T>> startArray(long key) {
+        return startArray(convert(key));
+    }
+
+    public ArrayBuilder<MapBuilder<T>> startArray(String key) {
+        Array array = new Array();
+        array.setChunked(true);
+        put(convert(key), array);
+        return new ArrayBuilder<>(this, array);
+    }
+
+    public MapBuilder<MapBuilder<T>> putMap(DataItem key) {
+        Map nestedMap = new Map();
+        put(key, nestedMap);
+        return new MapBuilder<>(this, nestedMap);
+    }
+
+    public MapBuilder<MapBuilder<T>> putMap(long key) {
+        Map nestedMap = new Map();
+        put(convert(key), nestedMap);
+        return new MapBuilder<>(this, nestedMap);
+    }
+
+    public MapBuilder<MapBuilder<T>> putMap(String key) {
+        Map nestedMap = new Map();
+        put(convert(key), nestedMap);
+        return new MapBuilder<>(this, nestedMap);
+    }
+
+    public MapBuilder<MapBuilder<T>> startMap(DataItem key) {
+        Map nestedMap = new Map();
+        nestedMap.setChunked(true);
+        put(key, nestedMap);
+        return new MapBuilder<>(this, nestedMap);
+    }
+
+    public MapBuilder<MapBuilder<T>> startMap(long key) {
+        return startMap(convert(key));
+    }
+
+    public MapBuilder<MapBuilder<T>> startMap(String key) {
+        return startMap(convert(key));
+    }
+
+    public T end() {
+        return getParent();
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/builder/UnicodeStringBuilder.java b/src/main/java/co/nstant/in/cbor/builder/UnicodeStringBuilder.java
new file mode 100644
index 0000000..0dd1e24
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/builder/UnicodeStringBuilder.java
@@ -0,0 +1,22 @@
+package co.nstant.in.cbor.builder;
+
+import co.nstant.in.cbor.model.SimpleValue;
+
+public class UnicodeStringBuilder<T extends AbstractBuilder<?>> extends
+                AbstractBuilder<T> {
+
+    public UnicodeStringBuilder(T parent) {
+        super(parent);
+    }
+
+    public UnicodeStringBuilder<T> add(String string) {
+        getParent().addChunk(convert(string));
+        return this;
+    }
+
+    public T end() {
+        getParent().addChunk(SimpleValue.BREAK);
+        return getParent();
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/AbstractDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/AbstractDecoder.java
new file mode 100644
index 0000000..30d46c9
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/AbstractDecoder.java
@@ -0,0 +1,119 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.AdditionalInformation;
+
+public abstract class AbstractDecoder<T> {
+
+    protected static final int INFINITY = -1;
+
+    protected final InputStream inputStream;
+    protected final CborDecoder decoder;
+
+    public AbstractDecoder(CborDecoder decoder, InputStream inputStream) {
+        this.decoder = decoder;
+        this.inputStream = inputStream;
+    }
+
+    public abstract T decode(int initialByte) throws CborException;
+
+    protected int nextSymbol() throws CborException {
+        try {
+            int symbol = inputStream.read();
+            if (symbol == -1) {
+                throw new IOException("Unexpected end of stream");
+            }
+            return symbol;
+        } catch (IOException ioException) {
+            throw new CborException(ioException);
+        }
+    }
+
+    protected long getLength(int initialByte) throws CborException {
+        switch (AdditionalInformation.ofByte(initialByte)) {
+        case DIRECT:
+            return initialByte & 31;
+        case ONE_BYTE:
+            return nextSymbol();
+        case TWO_BYTES:
+            long twoByteValue = 0;
+            twoByteValue |= nextSymbol() << 8;
+            twoByteValue |= nextSymbol() << 0;
+            return twoByteValue;
+        case FOUR_BYTES:
+            long fourByteValue = 0L;
+            fourByteValue |= (long) nextSymbol() << 24;
+            fourByteValue |= (long) nextSymbol() << 16;
+            fourByteValue |= (long) nextSymbol() << 8;
+            fourByteValue |= (long) nextSymbol() << 0;
+            return fourByteValue;
+        case EIGHT_BYTES:
+            long eightByteValue = 0;
+            eightByteValue |= (long) nextSymbol() << 56;
+            eightByteValue |= (long) nextSymbol() << 48;
+            eightByteValue |= (long) nextSymbol() << 40;
+            eightByteValue |= (long) nextSymbol() << 32;
+            eightByteValue |= (long) nextSymbol() << 24;
+            eightByteValue |= (long) nextSymbol() << 16;
+            eightByteValue |= (long) nextSymbol() << 8;
+            eightByteValue |= (long) nextSymbol() << 0;
+            return eightByteValue;
+        case INDEFINITE:
+            return INFINITY;
+        case RESERVED:
+        default:
+            throw new CborException("Reserved additional information");
+        }
+    }
+
+    protected BigInteger getLengthAsBigInteger(int initialByte)
+                    throws CborException {
+        switch (AdditionalInformation.ofByte(initialByte)) {
+        case DIRECT:
+            return BigInteger.valueOf(initialByte & 31);
+        case ONE_BYTE:
+            return BigInteger.valueOf(nextSymbol());
+        case TWO_BYTES:
+            long twoByteValue = 0;
+            twoByteValue |= nextSymbol() << 8;
+            twoByteValue |= nextSymbol() << 0;
+            return BigInteger.valueOf(twoByteValue);
+        case FOUR_BYTES:
+            long fourByteValue = 0L;
+            fourByteValue |= (long) nextSymbol() << 24;
+            fourByteValue |= (long) nextSymbol() << 16;
+            fourByteValue |= (long) nextSymbol() << 8;
+            fourByteValue |= (long) nextSymbol() << 0;
+            return BigInteger.valueOf(fourByteValue);
+        case EIGHT_BYTES:
+            BigInteger eightByteValue = BigInteger.ZERO;
+            eightByteValue = eightByteValue.or(BigInteger.valueOf(nextSymbol())
+                            .shiftLeft(56));
+            eightByteValue = eightByteValue.or(BigInteger.valueOf(nextSymbol())
+                            .shiftLeft(48));
+            eightByteValue = eightByteValue.or(BigInteger.valueOf(nextSymbol())
+                            .shiftLeft(40));
+            eightByteValue = eightByteValue.or(BigInteger.valueOf(nextSymbol())
+                            .shiftLeft(32));
+            eightByteValue = eightByteValue.or(BigInteger.valueOf(nextSymbol())
+                            .shiftLeft(24));
+            eightByteValue = eightByteValue.or(BigInteger.valueOf(nextSymbol())
+                            .shiftLeft(16));
+            eightByteValue = eightByteValue.or(BigInteger.valueOf(nextSymbol())
+                            .shiftLeft(8));
+            eightByteValue = eightByteValue.or(BigInteger.valueOf(nextSymbol())
+                            .shiftLeft(0));
+            return eightByteValue;
+        case INDEFINITE:
+            return BigInteger.valueOf(INFINITY);
+        case RESERVED:
+        default:
+            throw new CborException("Reserved additional information");
+        }
+    }
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/ArrayDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/ArrayDecoder.java
new file mode 100644
index 0000000..344be10
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/ArrayDecoder.java
@@ -0,0 +1,59 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.InputStream;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Special;
+
+public class ArrayDecoder extends AbstractDecoder<Array> {
+
+    public ArrayDecoder(CborDecoder decoder, InputStream inputStream) {
+        super(decoder, inputStream);
+    }
+
+    @Override
+    public Array decode(int initialByte) throws CborException {
+        long length = getLength(initialByte);
+        if (length == INFINITY) {
+            return decodeInfinitiveLength();
+        } else {
+            return decodeFixedLength(length);
+        }
+    }
+
+    private Array decodeInfinitiveLength() throws CborException {
+        Array array = new Array();
+        array.setChunked(true);
+        if (decoder.isAutoDecodeInfinitiveArrays()) {
+            DataItem dataItem;
+            for (;;) {
+                dataItem = decoder.decodeNext();
+                if (dataItem == null) {
+                    throw new CborException("Unexpected end of stream");
+                }
+                if (Special.BREAK.equals(dataItem)) {
+                    array.add(Special.BREAK);
+                    break;
+                }
+                array.add(dataItem);
+            }
+        }
+        return array;
+    }
+
+    private Array decodeFixedLength(long length) throws CborException {
+        Array array = new Array();
+        for (long i = 0; i < length; i++) {
+            DataItem dataItem = decoder.decodeNext();
+            if (dataItem == null) {
+                throw new CborException("Unexpected end of stream");
+            }
+            array.add(dataItem);
+        }
+        return array;
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/ByteStringDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/ByteStringDecoder.java
new file mode 100644
index 0000000..99d6fe8
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/ByteStringDecoder.java
@@ -0,0 +1,67 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.Special;
+
+public class ByteStringDecoder extends AbstractDecoder<ByteString> {
+
+    public ByteStringDecoder(CborDecoder decoder, InputStream inputStream) {
+        super(decoder, inputStream);
+    }
+
+    @Override
+    public ByteString decode(int initialByte) throws CborException {
+        long length = getLength(initialByte);
+        if (length == INFINITY) {
+            if (decoder.isAutoDecodeInfinitiveByteStrings()) {
+                return decodeInfinitiveLength();
+            } else {
+                ByteString byteString = new ByteString(null);
+                byteString.setChunked(true);
+                return byteString;
+            }
+        } else {
+            return decodeFixedLength(length);
+        }
+    }
+
+    private ByteString decodeInfinitiveLength() throws CborException {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        for (;;) {
+            DataItem dataItem = decoder.decodeNext();
+            if (dataItem == null) {
+                throw new CborException("Unexpected end of stream");
+            }
+            MajorType majorType = dataItem.getMajorType();
+            if (Special.BREAK.equals(dataItem)) {
+                break;
+            } else if (majorType == MajorType.BYTE_STRING) {
+                ByteString byteString = (ByteString) dataItem;
+                byte[] byteArray = byteString.getBytes();
+                if (byteArray != null) {
+                    bytes.write(byteArray, 0, byteArray.length);
+                }
+            } else {
+                throw new CborException("Unexpected major type "
+                        + majorType);
+            }
+        }
+        return new ByteString(bytes.toByteArray());
+    }
+
+    private ByteString decodeFixedLength(long length) throws CborException {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream((int) length);
+        for (long i = 0; i < length; i++) {
+            bytes.write(nextSymbol());
+        }
+        return new ByteString(bytes.toByteArray());
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/DoublePrecisionFloatDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/DoublePrecisionFloatDecoder.java
new file mode 100644
index 0000000..fe92f88
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/DoublePrecisionFloatDecoder.java
@@ -0,0 +1,38 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.InputStream;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DoublePrecisionFloat;
+
+public class DoublePrecisionFloatDecoder extends
+                AbstractDecoder<DoublePrecisionFloat> {
+
+    public DoublePrecisionFloatDecoder(CborDecoder decoder,
+                    InputStream inputStream) {
+        super(decoder, inputStream);
+    }
+
+    @Override
+    public DoublePrecisionFloat decode(int initialByte) throws CborException {
+        long bits = 0;
+        bits |= nextSymbol() & 0xFF;
+        bits <<= 8;
+        bits |= nextSymbol() & 0xFF;
+        bits <<= 8;
+        bits |= nextSymbol() & 0xFF;
+        bits <<= 8;
+        bits |= nextSymbol() & 0xFF;
+        bits <<= 8;
+        bits |= nextSymbol() & 0xFF;
+        bits <<= 8;
+        bits |= nextSymbol() & 0xFF;
+        bits <<= 8;
+        bits |= nextSymbol() & 0xFF;
+        bits <<= 8;
+        bits |= nextSymbol() & 0xFF;
+        return new DoublePrecisionFloat(Double.longBitsToDouble(bits));
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/HalfPrecisionFloatDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/HalfPrecisionFloatDecoder.java
new file mode 100644
index 0000000..ec3cc2d
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/HalfPrecisionFloatDecoder.java
@@ -0,0 +1,43 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.InputStream;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.HalfPrecisionFloat;
+
+public class HalfPrecisionFloatDecoder extends
+                AbstractDecoder<HalfPrecisionFloat> {
+
+    public HalfPrecisionFloatDecoder(CborDecoder decoder,
+                    InputStream inputStream) {
+        super(decoder, inputStream);
+    }
+
+    @Override
+    public HalfPrecisionFloat decode(int initialByte) throws CborException {
+        int bits = nextSymbol() << 8 | nextSymbol();
+        return new HalfPrecisionFloat(toFloat(bits));
+    }
+
+    /**
+     * @see http://stackoverflow.com/a/5684578
+     */
+    private static float toFloat(int bits) {
+        int s = (bits & 0x8000) >> 15;
+        int e = (bits & 0x7C00) >> 10;
+        int f = bits & 0x03FF;
+
+        if (e == 0) {
+            return (float) ((s != 0 ? -1 : 1) * Math.pow(2, -14) * (f / Math
+                            .pow(2, 10)));
+        } else if (e == 0x1F) {
+            return f != 0 ? Float.NaN
+                            : (s != 0 ? -1 : 1) * Float.POSITIVE_INFINITY;
+        }
+
+        return (float) ((s != 0 ? -1 : 1) * Math.pow(2, e - 15) * (1 + f / Math
+                        .pow(2, 10)));
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/MapDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/MapDecoder.java
new file mode 100644
index 0000000..f30023f
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/MapDecoder.java
@@ -0,0 +1,65 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.InputStream;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Map;
+import co.nstant.in.cbor.model.Special;
+
+public class MapDecoder extends AbstractDecoder<Map> {
+
+    public MapDecoder(CborDecoder decoder, InputStream inputStream) {
+        super(decoder, inputStream);
+    }
+
+    @Override
+    public Map decode(int initialByte) throws CborException {
+        long length = getLength(initialByte);
+        if (length == INFINITY) {
+            return decodeInfinitiveLength();
+        } else {
+            return decodeFixedLength(length);
+        }
+    }
+
+    private Map decodeInfinitiveLength() throws CborException {
+        Map map = new Map();
+        map.setChunked(true);
+        if (decoder.isAutoDecodeInfinitiveMaps()) {
+            for (;;) {
+                DataItem key = decoder.decodeNext();
+                if (Special.BREAK.equals(key)) {
+                    break;
+                }
+                DataItem value = decoder.decodeNext();
+                if (key == null || value == null) {
+                    throw new CborException("Unexpected end of stream");
+                }
+                if (decoder.isRejectDuplicateKeys() && map.get(key) != null) {
+                    throw new CborException("Duplicate key found in map");
+                }
+                map.put(key, value);
+            }
+        }
+        return map;
+    }
+
+    private Map decodeFixedLength(long length) throws CborException {
+        Map map = new Map((int) length);
+        for (long i = 0; i < length; i++) {
+            DataItem key = decoder.decodeNext();
+            DataItem value = decoder.decodeNext();
+            if (key == null || value == null) {
+                throw new CborException("Unexpected end of stream");
+            }
+            if (decoder.isRejectDuplicateKeys() && map.get(key) != null) {
+                throw new CborException("Duplicate key found in map");
+            }
+            map.put(key, value);
+        }
+        return map;
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/NegativeIntegerDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/NegativeIntegerDecoder.java
new file mode 100644
index 0000000..72ebd6a
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/NegativeIntegerDecoder.java
@@ -0,0 +1,23 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.InputStream;
+import java.math.BigInteger;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.NegativeInteger;
+
+public class NegativeIntegerDecoder extends AbstractDecoder<NegativeInteger> {
+
+	private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
+
+	public NegativeIntegerDecoder(CborDecoder decoder, InputStream inputStream) {
+		super(decoder, inputStream);
+	}
+
+	@Override
+	public NegativeInteger decode(int initialByte) throws CborException {
+		return new NegativeInteger(MINUS_ONE.subtract(getLengthAsBigInteger(initialByte)));
+	}
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/SinglePrecisionFloatDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/SinglePrecisionFloatDecoder.java
new file mode 100644
index 0000000..a87d984
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/SinglePrecisionFloatDecoder.java
@@ -0,0 +1,28 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.InputStream;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.SinglePrecisionFloat;
+
+public class SinglePrecisionFloatDecoder extends AbstractDecoder<SinglePrecisionFloat> {
+
+	public SinglePrecisionFloatDecoder(CborDecoder decoder, InputStream inputStream) {
+		super(decoder, inputStream);
+	}
+
+	@Override
+	public SinglePrecisionFloat decode(int initialByte) throws CborException {
+		int bits = 0;
+		bits |= nextSymbol() & 0xFF;
+		bits <<= 8;
+		bits |= nextSymbol() & 0xFF;
+		bits <<= 8;
+		bits |= nextSymbol() & 0xFF;
+		bits <<= 8;
+		bits |= nextSymbol() & 0xFF;
+		return new SinglePrecisionFloat(Float.intBitsToFloat(bits));
+	}
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/SpecialDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/SpecialDecoder.java
new file mode 100644
index 0000000..bab71e7
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/SpecialDecoder.java
@@ -0,0 +1,60 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.InputStream;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.SimpleValue;
+import co.nstant.in.cbor.model.SimpleValueType;
+import co.nstant.in.cbor.model.Special;
+import co.nstant.in.cbor.model.SpecialType;
+
+public class SpecialDecoder extends AbstractDecoder<Special> {
+
+    private final HalfPrecisionFloatDecoder halfPrecisionFloatDecoder;
+    private final SinglePrecisionFloatDecoder singlePrecisionFloatDecoder;
+    private final DoublePrecisionFloatDecoder doublePrecisionFloatDecoder;
+
+    public SpecialDecoder(CborDecoder decoder, InputStream inputStream) {
+        super(decoder, inputStream);
+        this.halfPrecisionFloatDecoder = new HalfPrecisionFloatDecoder(decoder, inputStream);
+        this.singlePrecisionFloatDecoder = new SinglePrecisionFloatDecoder(decoder, inputStream);
+        this.doublePrecisionFloatDecoder = new DoublePrecisionFloatDecoder(decoder, inputStream);
+    }
+
+    @Override
+    public Special decode(int initialByte) throws CborException {
+        switch (SpecialType.ofByte(initialByte)) {
+        case BREAK:
+            return Special.BREAK;
+        case SIMPLE_VALUE:
+            switch (SimpleValueType.ofByte(initialByte)) {
+            case FALSE:
+                return SimpleValue.FALSE;
+            case TRUE:
+                return SimpleValue.TRUE;
+            case NULL:
+                return SimpleValue.NULL;
+            case UNDEFINED:
+                return SimpleValue.UNDEFINED;
+            case UNALLOCATED:
+                return new SimpleValue(initialByte & 31);
+            case RESERVED:
+            default:
+                throw new CborException("Not implemented");
+            }
+        case IEEE_754_HALF_PRECISION_FLOAT:
+            return halfPrecisionFloatDecoder.decode(initialByte);
+        case IEEE_754_SINGLE_PRECISION_FLOAT:
+            return singlePrecisionFloatDecoder.decode(initialByte);
+        case IEEE_754_DOUBLE_PRECISION_FLOAT:
+            return doublePrecisionFloatDecoder.decode(initialByte);
+        case SIMPLE_VALUE_NEXT_BYTE:
+            return new SimpleValue(nextSymbol());
+        case UNALLOCATED:
+        default:
+            throw new CborException("Not implemented");
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/TagDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/TagDecoder.java
new file mode 100644
index 0000000..f8cd86e
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/TagDecoder.java
@@ -0,0 +1,20 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.InputStream;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Tag;
+
+public class TagDecoder extends AbstractDecoder<Tag> {
+
+    public TagDecoder(CborDecoder decoder, InputStream inputStream) {
+        super(decoder, inputStream);
+    }
+
+    @Override
+    public Tag decode(int initialByte) throws CborException {
+        return new Tag(getLength(initialByte));
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/UnicodeStringDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/UnicodeStringDecoder.java
new file mode 100644
index 0000000..18bb8ba
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/UnicodeStringDecoder.java
@@ -0,0 +1,65 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.Special;
+import co.nstant.in.cbor.model.UnicodeString;
+
+public class UnicodeStringDecoder extends AbstractDecoder<UnicodeString> {
+
+    public UnicodeStringDecoder(CborDecoder decoder, InputStream inputStream) {
+        super(decoder, inputStream);
+    }
+
+    @Override
+    public UnicodeString decode(int initialByte) throws CborException {
+        long length = getLength(initialByte);
+        if (length == INFINITY) {
+            if (decoder.isAutoDecodeInfinitiveUnicodeStrings()) {
+                return decodeInfinitiveLength();
+            } else {
+                UnicodeString unicodeString = new UnicodeString(null);
+                unicodeString.setChunked(true);
+                return unicodeString;
+            }
+        } else {
+            return decodeFixedLength(length);
+        }
+    }
+
+    private UnicodeString decodeInfinitiveLength() throws CborException {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+        for (;;) {
+            DataItem dataItem = decoder.decodeNext();
+            if (dataItem == null) {
+                throw new CborException("Unexpected end of stream");
+            }
+            MajorType majorType = dataItem.getMajorType();
+            if (Special.BREAK.equals(dataItem)) {
+                break;
+            } else if (majorType == MajorType.UNICODE_STRING) {
+                UnicodeString unicodeString = (UnicodeString) dataItem;
+                byte[] byteArray = unicodeString.toString().getBytes(StandardCharsets.UTF_8);
+                bytes.write(byteArray, 0, byteArray.length);
+            } else {
+                throw new CborException("Unexpected major type " + majorType);
+            }
+        }
+        return new UnicodeString(new String(bytes.toByteArray(), StandardCharsets.UTF_8));
+    }
+
+    private UnicodeString decodeFixedLength(long length) throws CborException {
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream((int) length);
+        for (long i = 0; i < length; i++) {
+            bytes.write(nextSymbol());
+        }
+        return new UnicodeString(new String(bytes.toByteArray(), StandardCharsets.UTF_8));
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/decoder/UnsignedIntegerDecoder.java b/src/main/java/co/nstant/in/cbor/decoder/UnsignedIntegerDecoder.java
new file mode 100644
index 0000000..922cf60
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/decoder/UnsignedIntegerDecoder.java
@@ -0,0 +1,20 @@
+package co.nstant.in.cbor.decoder;
+
+import java.io.InputStream;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+public class UnsignedIntegerDecoder extends AbstractDecoder<UnsignedInteger> {
+
+    public UnsignedIntegerDecoder(CborDecoder decoder, InputStream inputStream) {
+        super(decoder, inputStream);
+    }
+
+    @Override
+    public UnsignedInteger decode(int initialByte) throws CborException {
+        return new UnsignedInteger(getLengthAsBigInteger(initialByte));
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/AbstractEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/AbstractEncoder.java
new file mode 100644
index 0000000..9a2ad13
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/AbstractEncoder.java
@@ -0,0 +1,131 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.AdditionalInformation;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.Tag;
+
+public abstract class AbstractEncoder<T> {
+
+	private final OutputStream outputStream;
+	protected final CborEncoder encoder;
+
+	public AbstractEncoder(CborEncoder encoder, OutputStream outputStream) {
+		this.encoder = encoder;
+		this.outputStream = outputStream;
+	}
+
+	public abstract void encode(T dataItem) throws CborException;
+
+	protected void encodeTypeChunked(MajorType majorType) throws CborException {
+		int symbol = majorType.getValue() << 5;
+		symbol |= AdditionalInformation.INDEFINITE.getValue();
+		try {
+			outputStream.write(symbol);
+		} catch (IOException ioException) {
+			throw new CborException(ioException);
+		}
+	}
+
+	protected void encodeTypeAndLength(MajorType majorType, long length) throws CborException {
+		int symbol = majorType.getValue() << 5;
+		if (length <= 23L) {
+			write((int) (symbol | length));
+		} else if (length <= 255L) {
+			symbol |= AdditionalInformation.ONE_BYTE.getValue();
+			write(symbol);
+			write((int) length);
+		} else if (length <= 65535L) {
+			symbol |= AdditionalInformation.TWO_BYTES.getValue();
+			write(symbol);
+			write((int) (length >> 8));
+			write((int) (length & 0xFF));
+		} else if (length <= 4294967295L) {
+			symbol |= AdditionalInformation.FOUR_BYTES.getValue();
+			write(symbol);
+			write((int) ((length >> 24) & 0xFF));
+			write((int) ((length >> 16) & 0xFF));
+			write((int) ((length >> 8) & 0xFF));
+			write((int) (length & 0xFF));
+		} else {
+			symbol |= AdditionalInformation.EIGHT_BYTES.getValue();
+			write(symbol);
+			write((int) ((length >> 56) & 0xFF));
+			write((int) ((length >> 48) & 0xFF));
+			write((int) ((length >> 40) & 0xFF));
+			write((int) ((length >> 32) & 0xFF));
+			write((int) ((length >> 24) & 0xFF));
+			write((int) ((length >> 16) & 0xFF));
+			write((int) ((length >> 8) & 0xFF));
+			write((int) (length & 0xFF));
+		}
+	}
+
+	protected void encodeTypeAndLength(MajorType majorType, BigInteger length) throws CborException {
+		boolean negative = majorType == MajorType.NEGATIVE_INTEGER;
+		int symbol = majorType.getValue() << 5;
+		if (length.compareTo(BigInteger.valueOf(24)) == -1) {
+			write(symbol | length.intValue());
+		} else if (length.compareTo(BigInteger.valueOf(256)) == -1) {
+			symbol |= AdditionalInformation.ONE_BYTE.getValue();
+			write(symbol);
+			write(length.intValue());
+		} else if (length.compareTo(BigInteger.valueOf(65536L)) == -1) {
+			symbol |= AdditionalInformation.TWO_BYTES.getValue();
+			write(symbol);
+			long twoByteValue = length.longValue();
+			write((int) (twoByteValue >> 8));
+			write((int) (twoByteValue & 0xFF));
+		} else if (length.compareTo(BigInteger.valueOf(4294967296L)) == -1) {
+			symbol |= AdditionalInformation.FOUR_BYTES.getValue();
+			write(symbol);
+			long fourByteValue = length.longValue();
+			write((int) ((fourByteValue >> 24) & 0xFF));
+			write((int) ((fourByteValue >> 16) & 0xFF));
+			write((int) ((fourByteValue >> 8) & 0xFF));
+			write((int) (fourByteValue & 0xFF));
+		} else if (length.compareTo(new BigInteger("18446744073709551616")) == -1) {
+			symbol |= AdditionalInformation.EIGHT_BYTES.getValue();
+			write(symbol);
+			BigInteger mask = BigInteger.valueOf(0xFF);
+			write(length.shiftRight(56).and(mask).intValue());
+			write(length.shiftRight(48).and(mask).intValue());
+			write(length.shiftRight(40).and(mask).intValue());
+			write(length.shiftRight(32).and(mask).intValue());
+			write(length.shiftRight(24).and(mask).intValue());
+			write(length.shiftRight(16).and(mask).intValue());
+			write(length.shiftRight(8).and(mask).intValue());
+			write(length.and(mask).intValue());
+		} else {
+			if (negative) {
+				encoder.encode(new Tag(3));
+			} else {
+				encoder.encode(new Tag(2));
+			}
+			encoder.encode(new ByteString(length.toByteArray()));
+		}
+	}
+
+	protected void write(int b) throws CborException {
+		try {
+			outputStream.write(b);
+		} catch (IOException ioException) {
+			throw new CborException(ioException);
+		}
+	}
+
+	protected void write(byte[] bytes) throws CborException {
+		try {
+			outputStream.write(bytes);
+		} catch (IOException ioException) {
+			throw new CborException(ioException);
+		}
+	}
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/ArrayEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/ArrayEncoder.java
new file mode 100644
index 0000000..ae0ddbe
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/ArrayEncoder.java
@@ -0,0 +1,31 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.OutputStream;
+import java.util.List;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.MajorType;
+
+public class ArrayEncoder extends AbstractEncoder<Array> {
+
+    public ArrayEncoder(CborEncoder encoder, OutputStream outputStream) {
+        super(encoder, outputStream);
+    }
+
+    @Override
+    public void encode(Array array) throws CborException {
+        List<DataItem> dataItems = array.getDataItems();
+        if (array.isChunked()) {
+            encodeTypeChunked(MajorType.ARRAY);
+        } else {
+            encodeTypeAndLength(MajorType.ARRAY, dataItems.size());
+        }
+        for (DataItem dataItem : dataItems) {
+            encoder.encode(dataItem);
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/ByteStringEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/ByteStringEncoder.java
new file mode 100644
index 0000000..6c1ca19
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/ByteStringEncoder.java
@@ -0,0 +1,33 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.OutputStream;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.SimpleValue;
+
+public class ByteStringEncoder extends AbstractEncoder<ByteString> {
+
+    public ByteStringEncoder(CborEncoder encoder, OutputStream outputStream) {
+        super(encoder, outputStream);
+    }
+
+    @Override
+    public void encode(ByteString byteString) throws CborException {
+        byte[] bytes = byteString.getBytes();
+        if (byteString.isChunked()) {
+            encodeTypeChunked(MajorType.BYTE_STRING);
+            if (bytes != null) {
+                encode(new ByteString(bytes));
+            }
+        } else if (bytes == null) {
+            encoder.encode(SimpleValue.NULL);
+        } else {
+            encodeTypeAndLength(MajorType.BYTE_STRING, bytes.length);
+            write(bytes);
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/DoublePrecisionFloatEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/DoublePrecisionFloatEncoder.java
new file mode 100644
index 0000000..5e09369
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/DoublePrecisionFloatEncoder.java
@@ -0,0 +1,29 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.OutputStream;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DoublePrecisionFloat;
+
+public class DoublePrecisionFloatEncoder extends AbstractEncoder<DoublePrecisionFloat> {
+
+	public DoublePrecisionFloatEncoder(CborEncoder encoder, OutputStream outputStream) {
+		super(encoder, outputStream);
+	}
+
+	@Override
+	public void encode(DoublePrecisionFloat dataItem) throws CborException {
+		write((7 << 5) | 27);
+		long bits = Double.doubleToRawLongBits(dataItem.getValue());
+		write((int) ((bits >> 56) & 0xFF));
+		write((int) ((bits >> 48) & 0xFF));
+		write((int) ((bits >> 40) & 0xFF));
+		write((int) ((bits >> 32) & 0xFF));
+		write((int) ((bits >> 24) & 0xFF));
+		write((int) ((bits >> 16) & 0xFF));
+		write((int) ((bits >> 8) & 0xFF));
+		write((int) ((bits >> 0) & 0xFF));
+	}
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/HalfPrecisionFloatEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/HalfPrecisionFloatEncoder.java
new file mode 100644
index 0000000..13ec12f
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/HalfPrecisionFloatEncoder.java
@@ -0,0 +1,58 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.OutputStream;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.HalfPrecisionFloat;
+
+public class HalfPrecisionFloatEncoder extends AbstractEncoder<HalfPrecisionFloat> {
+
+	public HalfPrecisionFloatEncoder(CborEncoder encoder, OutputStream outputStream) {
+		super(encoder, outputStream);
+	}
+
+	@Override
+	public void encode(HalfPrecisionFloat dataItem) throws CborException {
+		write((7 << 5) | 25);
+		int bits = fromFloat(dataItem.getValue());
+		write((bits >> 8) & 0xFF);
+		write((bits >> 0) & 0xFF);
+	}
+
+	/**
+	 * @see <a href="http://stackoverflow.com/a/6162687">Stack Overflow</a>
+	 */
+
+	// returns all higher 16 bits as 0 for all results
+	public static int fromFloat(float fval) {
+		int fbits = Float.floatToIntBits(fval);
+		int sign = fbits >>> 16 & 0x8000; // sign only
+		int val = 0x1000 + fbits & 0x7fffffff; // rounded value
+
+		if (val >= 0x47800000) // might be or become NaN/Inf
+		{ // avoid Inf due to rounding
+			if ((fbits & 0x7fffffff) >= 0x47800000) { // is or must become
+														// NaN/Inf
+				if (val < 0x7f800000) {// was value but too large
+					return sign | 0x7c00; // make it +/-Inf
+				}
+				return sign | 0x7c00 | // remains +/-Inf or NaN
+						(fbits & 0x007fffff) >>> 13; // keep NaN (and
+														// Inf) bits
+			}
+			return sign | 0x7bff; // unrounded not quite Inf
+		}
+		if (val >= 0x38800000) { // remains normalized value
+			return sign | val - 0x38000000 >>> 13; // exp - 127 + 15
+		}
+		if (val < 0x33000000) { // too small for subnormal
+			return sign; // becomes +/-0
+		}
+		val = (fbits & 0x7fffffff) >>> 23; // tmp exp for subnormal calc
+		return sign | ((fbits & 0x7fffff | 0x800000) // add subnormal bit
+				+ (0x800000 >>> val - 102) // round depending on cut off
+		>>> 126 - val); // div by 2^(1-(exp-127+15)) and >> 13 | exp=0
+	}
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/MapEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/MapEncoder.java
new file mode 100644
index 0000000..bbf79be
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/MapEncoder.java
@@ -0,0 +1,100 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.TreeMap;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.Map;
+import co.nstant.in.cbor.model.SimpleValue;
+
+public class MapEncoder extends AbstractEncoder<Map> {
+
+	public MapEncoder(CborEncoder encoder, OutputStream outputStream) {
+		super(encoder, outputStream);
+	}
+
+	@Override
+	public void encode(Map map) throws CborException {
+		Collection<DataItem> keys = map.getKeys();
+
+		if (map.isChunked()) {
+			encodeTypeChunked(MajorType.MAP);
+		} else {
+			encodeTypeAndLength(MajorType.MAP, keys.size());
+		}
+
+		if (keys.isEmpty()) {
+			return;
+		}
+
+		if (map.isChunked()) {
+			for (DataItem key : keys) {
+				encoder.encode(key);
+				encoder.encode(map.get(key));
+			}
+			encoder.encode(SimpleValue.BREAK);
+			return;
+		}
+
+		/**
+		 * The keys in every map must be sorted lowest value to highest. Sorting
+		 * is performed on the bytes of the representation of the key data items
+		 * without paying attention to the 3/5 bit splitting for major types.
+		 * (Note that this rule allows maps that have keys of different types,
+		 * even though that is probably a bad practice that could lead to errors
+		 * in some canonicalization implementations.) The sorting rules are:
+		 * 
+		 * If two keys have different lengths, the shorter one sorts earlier;
+		 * 
+		 * If two keys have the same length, the one with the lower value in
+		 * (byte-wise) lexical order sorts earlier.
+		 */
+
+		TreeMap<byte[], byte[]> sortedMap = new TreeMap<>(new Comparator<byte[]>() {
+
+			@Override
+			public int compare(byte[] o1, byte[] o2) {
+				if (o1.length < o2.length) {
+					return -1;
+				}
+				if (o1.length > o2.length) {
+					return 1;
+				}
+				for (int i = 0; i < o1.length; i++) {
+					if (o1[i] < o2[i]) {
+						return -1;
+					}
+					if (o1[i] > o2[i]) {
+						return 1;
+					}
+				}
+				return 0;
+			}
+
+		});
+		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+		CborEncoder e = new CborEncoder(byteArrayOutputStream);
+		for (DataItem key : keys) {
+			// Key
+			e.encode(key);
+			byte[] keyBytes = byteArrayOutputStream.toByteArray();
+			byteArrayOutputStream.reset();
+			// Value
+			e.encode(map.get(key));
+			byte[] valueBytes = byteArrayOutputStream.toByteArray();
+			byteArrayOutputStream.reset();
+			sortedMap.put(keyBytes, valueBytes);
+		}
+		for (java.util.Map.Entry<byte[], byte[]> entry : sortedMap.entrySet()) {
+			write(entry.getKey());
+			write(entry.getValue());
+		}
+	}
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/NegativeIntegerEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/NegativeIntegerEncoder.java
new file mode 100644
index 0000000..b078fee
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/NegativeIntegerEncoder.java
@@ -0,0 +1,24 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.OutputStream;
+import java.math.BigInteger;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.NegativeInteger;
+
+public class NegativeIntegerEncoder extends AbstractEncoder<NegativeInteger> {
+
+	private static final BigInteger MINUS_ONE = BigInteger.valueOf(-1);
+
+	public NegativeIntegerEncoder(CborEncoder encoder, OutputStream outputStream) {
+		super(encoder, outputStream);
+	}
+
+	@Override
+	public void encode(NegativeInteger dataItem) throws CborException {
+		encodeTypeAndLength(MajorType.NEGATIVE_INTEGER, MINUS_ONE.subtract(dataItem.getValue()).abs());
+	}
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/SinglePrecisionFloatEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/SinglePrecisionFloatEncoder.java
new file mode 100644
index 0000000..820562e
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/SinglePrecisionFloatEncoder.java
@@ -0,0 +1,25 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.OutputStream;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.SinglePrecisionFloat;
+
+public class SinglePrecisionFloatEncoder extends AbstractEncoder<SinglePrecisionFloat> {
+
+	public SinglePrecisionFloatEncoder(CborEncoder encoder, OutputStream outputStream) {
+		super(encoder, outputStream);
+	}
+
+	@Override
+	public void encode(SinglePrecisionFloat dataItem) throws CborException {
+		write((7 << 5) | 26);
+		int bits = Float.floatToRawIntBits(dataItem.getValue());
+		write((bits >> 24) & 0xFF);
+		write((bits >> 16) & 0xFF);
+		write((bits >> 8) & 0xFF);
+		write((bits >> 0) & 0xFF);
+	}
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/SpecialEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/SpecialEncoder.java
new file mode 100644
index 0000000..b00eb99
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/SpecialEncoder.java
@@ -0,0 +1,81 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.OutputStream;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DoublePrecisionFloat;
+import co.nstant.in.cbor.model.HalfPrecisionFloat;
+import co.nstant.in.cbor.model.SimpleValue;
+import co.nstant.in.cbor.model.SimpleValueType;
+import co.nstant.in.cbor.model.SinglePrecisionFloat;
+import co.nstant.in.cbor.model.Special;
+
+public class SpecialEncoder extends AbstractEncoder<Special> {
+
+    private final HalfPrecisionFloatEncoder halfPrecisionFloatEncoder;
+    private final SinglePrecisionFloatEncoder singlePrecisionFloatEncoder;
+    private final DoublePrecisionFloatEncoder doublePrecisionFloatEncoder;
+
+    public SpecialEncoder(CborEncoder encoder, OutputStream outputStream) {
+        super(encoder, outputStream);
+        halfPrecisionFloatEncoder = new HalfPrecisionFloatEncoder(encoder, outputStream);
+        singlePrecisionFloatEncoder = new SinglePrecisionFloatEncoder(encoder, outputStream);
+        doublePrecisionFloatEncoder = new DoublePrecisionFloatEncoder(encoder, outputStream);
+    }
+
+    @Override
+    public void encode(Special dataItem) throws CborException {
+        switch (dataItem.getSpecialType()) {
+        case BREAK:
+            write((7 << 5) | 31);
+            break;
+        case SIMPLE_VALUE:
+            SimpleValue simpleValue = (SimpleValue) dataItem;
+            switch (simpleValue.getSimpleValueType()) {
+            case FALSE:
+            case NULL:
+            case TRUE:
+            case UNDEFINED:
+                SimpleValueType type = simpleValue.getSimpleValueType();
+                write((7 << 5) | type.getValue());
+                break;
+            case UNALLOCATED:
+                write((7 << 5) | simpleValue.getValue());
+                break;
+            case RESERVED:
+                break;
+            }
+            break;
+        case UNALLOCATED:
+            throw new CborException("Unallocated special type");
+        case IEEE_754_HALF_PRECISION_FLOAT:
+            if (!(dataItem instanceof HalfPrecisionFloat)) {
+                throw new CborException("Wrong data item type");
+            }
+            halfPrecisionFloatEncoder.encode((HalfPrecisionFloat) dataItem);
+            break;
+        case IEEE_754_SINGLE_PRECISION_FLOAT:
+            if (!(dataItem instanceof SinglePrecisionFloat)) {
+                throw new CborException("Wrong data item type");
+            }
+            singlePrecisionFloatEncoder.encode((SinglePrecisionFloat) dataItem);
+            break;
+        case IEEE_754_DOUBLE_PRECISION_FLOAT:
+            if (!(dataItem instanceof DoublePrecisionFloat)) {
+                throw new CborException("Wrong data item type");
+            }
+            doublePrecisionFloatEncoder.encode((DoublePrecisionFloat) dataItem);
+            break;
+        case SIMPLE_VALUE_NEXT_BYTE:
+            if (!(dataItem instanceof SimpleValue)) {
+                throw new CborException("Wrong data item type");
+            }
+            SimpleValue simpleValueNextByte = (SimpleValue) dataItem;
+            write((7 << 5) | 24);
+            write(simpleValueNextByte.getValue());
+            break;
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/TagEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/TagEncoder.java
new file mode 100644
index 0000000..be9948e
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/TagEncoder.java
@@ -0,0 +1,21 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.OutputStream;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.Tag;
+
+public class TagEncoder extends AbstractEncoder<Tag> {
+
+    public TagEncoder(CborEncoder encoder, OutputStream outputStream) {
+        super(encoder, outputStream);
+    }
+
+    @Override
+    public void encode(Tag tag) throws CborException {
+        encodeTypeAndLength(MajorType.TAG, tag.getValue());
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/UnicodeStringEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/UnicodeStringEncoder.java
new file mode 100644
index 0000000..ef769cb
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/UnicodeStringEncoder.java
@@ -0,0 +1,36 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.SimpleValue;
+import co.nstant.in.cbor.model.UnicodeString;
+
+public class UnicodeStringEncoder extends AbstractEncoder<UnicodeString> {
+
+    public UnicodeStringEncoder(CborEncoder encoder, OutputStream outputStream) {
+        super(encoder, outputStream);
+    }
+
+    @Override
+    public void encode(UnicodeString dataItem) throws CborException {
+        String string = dataItem.getString();
+        if (dataItem.isChunked()) {
+            encodeTypeChunked(MajorType.UNICODE_STRING);
+            if (string != null) {
+                encode(new UnicodeString(string));
+            }
+        } else if (string == null) {
+            encoder.encode(SimpleValue.NULL);
+        } else {
+            byte[] bytes;
+            bytes = string.getBytes(StandardCharsets.UTF_8);
+            encodeTypeAndLength(MajorType.UNICODE_STRING, bytes.length);
+            write(bytes);
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/encoder/UnsignedIntegerEncoder.java b/src/main/java/co/nstant/in/cbor/encoder/UnsignedIntegerEncoder.java
new file mode 100644
index 0000000..c7d08ef
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/encoder/UnsignedIntegerEncoder.java
@@ -0,0 +1,21 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.OutputStream;
+
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+public class UnsignedIntegerEncoder extends AbstractEncoder<UnsignedInteger> {
+
+    public UnsignedIntegerEncoder(CborEncoder encoder, OutputStream outputStream) {
+        super(encoder, outputStream);
+    }
+
+    @Override
+    public void encode(UnsignedInteger dataItem) throws CborException {
+        encodeTypeAndLength(MajorType.UNSIGNED_INTEGER, dataItem.getValue());
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/AbstractFloat.java b/src/main/java/co/nstant/in/cbor/model/AbstractFloat.java
new file mode 100644
index 0000000..5766e9a
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/AbstractFloat.java
@@ -0,0 +1,32 @@
+package co.nstant.in.cbor.model;
+
+import java.util.Objects;
+
+public class AbstractFloat extends Special {
+
+    private final float value;
+
+    public AbstractFloat(SpecialType specialType, float value) {
+        super(specialType);
+        this.value = value;
+    }
+
+    public float getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof AbstractFloat) {
+            AbstractFloat other = (AbstractFloat) object;
+            return super.equals(object) && value == other.value;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ Objects.hashCode(value);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/AdditionalInformation.java b/src/main/java/co/nstant/in/cbor/model/AdditionalInformation.java
new file mode 100644
index 0000000..23a014d
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/AdditionalInformation.java
@@ -0,0 +1,55 @@
+package co.nstant.in.cbor.model;
+
+/**
+ * The initial byte of each data item contains both information about the major
+ * type (the high-order 3 bits) and additional information (the low-order 5
+ * bits). When the value of the additional information is less than 24, it is
+ * directly used as a small unsigned integer. When it is 24 to 27, the
+ * additional bytes for a variable-length integer immediately follow; the values
+ * 24 to 27 of the additional information specify that its length is a 1-, 2-,
+ * 4- or 8-byte unsigned integer, respectively. Additional information value 31
+ * is used for indefinite length items, described in Section 2.2. Additional
+ * information values 28 to 30 are reserved for future expansion.
+ */
+public enum AdditionalInformation {
+
+    DIRECT(0), // 0-23
+    ONE_BYTE(24), // 24
+    TWO_BYTES(25), // 25
+    FOUR_BYTES(26), // 26
+    EIGHT_BYTES(27), // 27
+    RESERVED(28), // 28-30
+    INDEFINITE(31); // 31
+
+    private final int value;
+
+    private AdditionalInformation(int value) {
+        this.value = value;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public static AdditionalInformation ofByte(int b) {
+        switch (b & 31) {
+        case 24:
+            return ONE_BYTE;
+        case 25:
+            return TWO_BYTES;
+        case 26:
+            return FOUR_BYTES;
+        case 27:
+            return EIGHT_BYTES;
+        case 28:
+        case 29:
+        case 30:
+            return RESERVED;
+        case 31:
+            return INDEFINITE;
+        default:
+            return DIRECT;
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/Array.java b/src/main/java/co/nstant/in/cbor/model/Array.java
new file mode 100644
index 0000000..2c36861
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/Array.java
@@ -0,0 +1,49 @@
+package co.nstant.in.cbor.model;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class Array extends ChunkableDataItem {
+
+    private final ArrayList<DataItem> objects;
+
+    public Array() {
+        super(MajorType.ARRAY);
+        objects = new ArrayList<>();
+    }
+
+    public Array add(DataItem object) {
+        objects.add(object);
+        return this;
+    }
+
+    public List<DataItem> getDataItems() {
+        return objects;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof Array) {
+            Array other = (Array) object;
+            return super.equals(object) && objects.equals(other.objects);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ objects.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder("[");
+        if (isChunked()) {
+            stringBuilder.append("_ ");
+        }
+        stringBuilder.append(Arrays.toString(objects.toArray()).substring(1));
+        return stringBuilder.toString();
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/ByteString.java b/src/main/java/co/nstant/in/cbor/model/ByteString.java
new file mode 100644
index 0000000..be7b598
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/ByteString.java
@@ -0,0 +1,40 @@
+package co.nstant.in.cbor.model;
+
+import java.util.Arrays;
+
+public class ByteString extends ChunkableDataItem {
+
+    private final byte[] bytes;
+
+    public ByteString(byte[] bytes) {
+        super(MajorType.BYTE_STRING);
+        if (bytes == null) {
+            this.bytes = null;
+        } else {
+            this.bytes = bytes;
+        }
+    }
+
+    public byte[] getBytes() {
+        if (bytes == null) {
+            return null;
+        } else {
+            return bytes;
+        }
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof ByteString) {
+            ByteString other = (ByteString) object;
+            return super.equals(object) && Arrays.equals(bytes, other.bytes);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ Arrays.hashCode(bytes);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/ChunkableDataItem.java b/src/main/java/co/nstant/in/cbor/model/ChunkableDataItem.java
new file mode 100644
index 0000000..cd01de9
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/ChunkableDataItem.java
@@ -0,0 +1,36 @@
+package co.nstant.in.cbor.model;
+
+import java.util.Objects;
+
+class ChunkableDataItem extends DataItem {
+
+    private boolean chunked = false;
+
+    protected ChunkableDataItem(MajorType majorType) {
+        super(majorType);
+    }
+
+    public boolean isChunked() {
+        return chunked;
+    }
+
+    public ChunkableDataItem setChunked(boolean chunked) {
+        this.chunked = chunked;
+        return this;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof ChunkableDataItem) {
+            ChunkableDataItem other = (ChunkableDataItem) object;
+            return super.equals(object) && chunked == other.chunked;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ Objects.hashCode(chunked);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/DataItem.java b/src/main/java/co/nstant/in/cbor/model/DataItem.java
new file mode 100644
index 0000000..4cecf76
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/DataItem.java
@@ -0,0 +1,69 @@
+package co.nstant.in.cbor.model;
+
+import java.util.Objects;
+
+public class DataItem {
+
+    private final MajorType majorType;
+    private Tag tag;
+
+    protected DataItem(MajorType majorType) {
+        this.majorType = majorType;
+        Objects.requireNonNull(majorType, "majorType is null");
+    }
+
+    public MajorType getMajorType() {
+        return majorType;
+    }
+
+    public void setTag(int tag) {
+        if (tag < 0) {
+            throw new IllegalArgumentException("tag number must be 0 or greater");
+        }
+
+        this.tag = new Tag(tag);
+    }
+
+    public void setTag(Tag tag) {
+        Objects.requireNonNull(tag, "tag is null");
+        this.tag = tag;
+    }
+
+    public void removeTag() {
+        tag = null;
+    }
+
+    public Tag getTag() {
+        return tag;
+    }
+
+    public boolean hasTag() {
+        return tag != null;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof DataItem) {
+            DataItem other = (DataItem) object;
+            if (tag != null) {
+                return tag.equals(other.tag) && majorType == other.majorType;
+            } else {
+                return other.tag == null && majorType == other.majorType;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(majorType, tag);
+    }
+
+    protected void assertTrue(boolean condition, String message) {
+        if (!condition) {
+            throw new IllegalArgumentException(message);
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/DoublePrecisionFloat.java b/src/main/java/co/nstant/in/cbor/model/DoublePrecisionFloat.java
new file mode 100644
index 0000000..3a6da94
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/DoublePrecisionFloat.java
@@ -0,0 +1,37 @@
+package co.nstant.in.cbor.model;
+
+import java.util.Objects;
+
+public class DoublePrecisionFloat extends Special {
+
+    private final double value;
+
+    public DoublePrecisionFloat(double value) {
+        super(SpecialType.IEEE_754_DOUBLE_PRECISION_FLOAT);
+        this.value = value;
+    }
+
+    public double getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof DoublePrecisionFloat) {
+            DoublePrecisionFloat other = (DoublePrecisionFloat) object;
+            return super.equals(object) && value == other.value;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ Objects.hashCode(value);
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(value);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/HalfPrecisionFloat.java b/src/main/java/co/nstant/in/cbor/model/HalfPrecisionFloat.java
new file mode 100644
index 0000000..ca33d62
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/HalfPrecisionFloat.java
@@ -0,0 +1,9 @@
+package co.nstant.in.cbor.model;
+
+public class HalfPrecisionFloat extends AbstractFloat {
+
+    public HalfPrecisionFloat(float value) {
+        super(SpecialType.IEEE_754_HALF_PRECISION_FLOAT, value);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/LanguageTaggedString.java b/src/main/java/co/nstant/in/cbor/model/LanguageTaggedString.java
new file mode 100644
index 0000000..f54667d
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/LanguageTaggedString.java
@@ -0,0 +1,28 @@
+package co.nstant.in.cbor.model;
+
+import java.util.Objects;
+
+/**
+ * See https://peteroupc.github.io/CBOR/langtags.html
+ */
+public class LanguageTaggedString extends Array {
+
+    public LanguageTaggedString(String language, String string) {
+        this(new UnicodeString(language), new UnicodeString(string));
+    }
+
+    public LanguageTaggedString(UnicodeString language, UnicodeString string) {
+        setTag(38);
+        add(Objects.requireNonNull(language));
+        add(Objects.requireNonNull(string));
+    }
+
+    public UnicodeString getLanguage() {
+        return (UnicodeString) getDataItems().get(0);
+    }
+
+    public UnicodeString getString() {
+        return (UnicodeString) getDataItems().get(1);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/MajorType.java b/src/main/java/co/nstant/in/cbor/model/MajorType.java
new file mode 100644
index 0000000..e970b45
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/MajorType.java
@@ -0,0 +1,126 @@
+package co.nstant.in.cbor.model;
+
+public enum MajorType {
+
+    INVALID(-1),
+
+    /**
+     * Major type 0: an unsigned integer. The 5-bit additional information is
+     * either the integer itself (for additional information values 0 through
+     * 23), or the length of additional data. Additional information 24 means
+     * the value is represented in an additional uint8_t, 25 means a uint16_t,
+     * 26 means a uint32_t, and 27 means a uint64_t. For example, the integer 10
+     * is denoted as the one byte 0b000_01010 (major type 0, additional
+     * information 10). The integer 500 would be 0b000_11001 (major type 0,
+     * additional information 25) followed by the two bytes 0x01f4, which is 500
+     * in decimal.
+     */
+    UNSIGNED_INTEGER(0),
+
+    /**
+     * Major type 1: a negative integer. The encoding follows the rules for
+     * unsigned integers (major type 0), except that the value is then -1 minus
+     * the encoded unsigned integer. For example, the integer -500 would be
+     * 0b001_11001 (major type 1, additional information 25) followed by the two
+     * bytes 0x01f3, which is 499 in decimal.
+     */
+    NEGATIVE_INTEGER(1),
+
+    /**
+     * Major type 2: a byte string. The string's length in bytes is represented
+     * following the rules for positive integers (major type 0). For example, a
+     * byte string whose length is 5 would have an initial byte of 0b010_00101
+     * (major type 2, additional information 5 for the length), followed by 5
+     * bytes of binary content. A byte string whose length is 500 would have 3
+     * initial bytes of 0b010_11001 (major type 2, additional information 25 to
+     * indicate a two-byte length) followed by the two bytes 0x01f4 for a length
+     * of 500, followed by 500 bytes of binary content.
+     */
+    BYTE_STRING(2),
+
+    /**
+     * Major type 3: string of Unicode characters that is encoded as UTF-8
+     * [RFC3629]. The format of this type is identical to that of byte strings
+     * (major type 2), that is, as with major type 2, the length gives the
+     * number of bytes. This type is provided for systems that need to interpret
+     * or display human-readable text. In contrast to formats such as JSON, the
+     * Unicode characters in this type are never escaped. Thus, a newline
+     * character (U+000A) is always represented in a string as the byte 0x0a,
+     * and never as the bytes 0x5c6e (the characters "\" and "n") or as
+     * 0x5c7530303061 (the characters "\", "u", "0", "0", "0", and "a").
+     */
+    UNICODE_STRING(3),
+
+    /**
+     * Major type 4: an array of data items. Arrays are also called lists,
+     * sequences, or tuples. The array's length follows the rules for byte
+     * strings (major type 2), except that the length denotes the number of data
+     * items, not the length in bytes that the array takes up. Items in an array
+     * do not need to all be of the same type. For example, an array that
+     * contains 10 items of any type would have an initial byte of 0b100_01010
+     * (major type of 4, additional information of 10 for the length) followed
+     * by the 10 remaining items.
+     */
+    ARRAY(4),
+
+    /**
+     * Major type 5: a map of pairs of data items. Maps are also called tables,
+     * dictionaries, hashes, or objects (in JSON). A map is comprised of pairs
+     * of data items, the even-numbered ones serving as keys and the following
+     * odd-numbered ones serving as values for the key that comes immediately
+     * before it. The map's length follows the rules for byte strings (major
+     * type 2), except that the length denotes the number of pairs, not the
+     * length in bytes that the map takes up. For example, a map that contains 9
+     * pairs would have an initial byte of 0b101_01001 (major type of 5,
+     * additional information of 9 for the number of pairs) followed by the 18
+     * remaining items. The first item is the first key, the second item is the
+     * first value, the third item is the second key, and so on.
+     */
+    MAP(5),
+
+    /**
+     * Major type 6: optional semantic tagging of other major types. See Section
+     * 2.4.
+     */
+    TAG(6),
+
+    /**
+     * Major type 7: floating point numbers and simple data types that need no
+     * content, as well as the "break" stop code. See Section 2.3.
+     */
+    SPECIAL(7);
+
+    private final int value;
+
+    private MajorType(int value) {
+        this.value = value;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public static MajorType ofByte(int b) {
+        switch (b >> 5) {
+        case 0:
+            return UNSIGNED_INTEGER;
+        case 1:
+            return NEGATIVE_INTEGER;
+        case 2:
+            return BYTE_STRING;
+        case 3:
+            return UNICODE_STRING;
+        case 4:
+            return ARRAY;
+        case 5:
+            return MAP;
+        case 6:
+            return TAG;
+        case 7:
+            return SPECIAL;
+        default:
+            return INVALID;
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/Map.java b/src/main/java/co/nstant/in/cbor/model/Map.java
new file mode 100644
index 0000000..153ae0d
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/Map.java
@@ -0,0 +1,79 @@
+package co.nstant.in.cbor.model;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+public class Map extends ChunkableDataItem {
+
+    private final HashMap<DataItem, DataItem> map;
+    private final List<DataItem> keys = new LinkedList<>();
+
+    public Map() {
+        super(MajorType.MAP);
+        map = new HashMap<>();
+    }
+
+    public Map(int initialCapacity) {
+        super(MajorType.MAP);
+        map = new HashMap<>(initialCapacity);
+    }
+
+    public Map put(DataItem key, DataItem value) {
+        if (map.put(key, value) == null) {
+            keys.add(key);
+        }
+        return this;
+    }
+
+    public DataItem get(DataItem key) {
+        return map.get(key);
+    }
+
+    public DataItem remove(DataItem key) {
+        keys.remove(key);
+        return map.remove(key);
+    }
+
+    public Collection<DataItem> getKeys() {
+        return keys;
+    }
+
+    public Collection<DataItem> getValues() {
+        return map.values();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof Map) {
+            Map other = (Map) object;
+            return super.equals(object) && map.equals(other.map);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ map.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder();
+        if (isChunked()) {
+            stringBuilder.append("{_ ");
+        } else {
+            stringBuilder.append("{ ");
+        }
+        for (DataItem key : keys) {
+            stringBuilder.append(key).append(": ").append(map.get(key)).append(", ");
+        }
+        if (stringBuilder.toString().endsWith(", ")) {
+            stringBuilder.setLength(stringBuilder.length() - 2);
+        }
+        stringBuilder.append(" }");
+        return stringBuilder.toString();
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/NegativeInteger.java b/src/main/java/co/nstant/in/cbor/model/NegativeInteger.java
new file mode 100644
index 0000000..12f320f
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/NegativeInteger.java
@@ -0,0 +1,16 @@
+package co.nstant.in.cbor.model;
+
+import java.math.BigInteger;
+
+public class NegativeInteger extends Number {
+
+    public NegativeInteger(long value) {
+        this(BigInteger.valueOf(value));
+        assertTrue(value < 0L, "value " + value + " is not < 0");
+    }
+
+    public NegativeInteger(BigInteger value) {
+        super(MajorType.NEGATIVE_INTEGER, value);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/Number.java b/src/main/java/co/nstant/in/cbor/model/Number.java
new file mode 100644
index 0000000..a018852
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/Number.java
@@ -0,0 +1,38 @@
+package co.nstant.in.cbor.model;
+
+import java.math.BigInteger;
+import java.util.Objects;
+
+public abstract class Number extends DataItem {
+
+    private final BigInteger value;
+
+    protected Number(MajorType majorType, BigInteger value) {
+        super(majorType);
+        this.value = Objects.requireNonNull(value);
+    }
+
+    public BigInteger getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof Number) {
+            Number other = (Number) object;
+            return super.equals(object) && value.equals(other.value);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ value.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return value.toString();
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/RationalNumber.java b/src/main/java/co/nstant/in/cbor/model/RationalNumber.java
new file mode 100644
index 0000000..0ddd937
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/RationalNumber.java
@@ -0,0 +1,36 @@
+package co.nstant.in.cbor.model;
+
+import java.math.BigInteger;
+
+import co.nstant.in.cbor.CborException;
+
+/**
+ * Rational Numbers: http://peteroupc.github.io/CBOR/rational.html
+ */
+
+public class RationalNumber extends Array {
+
+    public RationalNumber(Number numerator, Number denominator) throws CborException {
+        setTag(30);
+        if (numerator == null) {
+            throw new CborException("Numerator is null");
+        }
+        if (denominator == null) {
+            throw new CborException("Denominator is null");
+        }
+        if (denominator.getValue().equals(BigInteger.ZERO)) {
+            throw new CborException("Denominator is zero");
+        }
+        add(numerator);
+        add(denominator);
+    }
+
+    public Number getNumerator() {
+        return (Number) getDataItems().get(0);
+    }
+
+    public Number getDenominator() {
+        return (Number) getDataItems().get(1);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/SimpleValue.java b/src/main/java/co/nstant/in/cbor/model/SimpleValue.java
new file mode 100644
index 0000000..1a02fc9
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/SimpleValue.java
@@ -0,0 +1,58 @@
+package co.nstant.in.cbor.model;
+
+import java.util.Objects;
+
+public class SimpleValue extends Special {
+
+    private final SimpleValueType simpleValueType;
+
+    public static final SimpleValue FALSE = new SimpleValue(
+            SimpleValueType.FALSE);
+    public static final SimpleValue TRUE = new SimpleValue(SimpleValueType.TRUE);
+    public static final SimpleValue NULL = new SimpleValue(SimpleValueType.NULL);
+    public static final SimpleValue UNDEFINED = new SimpleValue(
+            SimpleValueType.UNDEFINED);
+
+    private final int value;
+
+    public SimpleValue(SimpleValueType simpleValueType) {
+        super(SpecialType.SIMPLE_VALUE);
+        this.value = simpleValueType.getValue();
+        this.simpleValueType = simpleValueType;
+    }
+
+    public SimpleValue(int value) {
+        super(value <= 23 ? SpecialType.SIMPLE_VALUE
+                : SpecialType.SIMPLE_VALUE_NEXT_BYTE);
+        this.value = value;
+        this.simpleValueType = SimpleValueType.ofByte(value);
+    }
+
+    public SimpleValueType getSimpleValueType() {
+        return simpleValueType;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof SimpleValue) {
+            SimpleValue other = (SimpleValue) object;
+            return super.equals(object) && value == other.value;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ Objects.hashCode(value);
+    }
+
+    @Override
+    public String toString() {
+        return simpleValueType.toString();
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/SimpleValueType.java b/src/main/java/co/nstant/in/cbor/model/SimpleValueType.java
new file mode 100644
index 0000000..f450abe
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/SimpleValueType.java
@@ -0,0 +1,46 @@
+package co.nstant.in.cbor.model;
+
+public enum SimpleValueType {
+
+    FALSE(20),
+    TRUE(21),
+    NULL(22),
+    UNDEFINED(23),
+    RESERVED(0),
+    UNALLOCATED(0);
+
+    private final int value;
+
+    private SimpleValueType(int value) {
+        this.value = value;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public static SimpleValueType ofByte(int b) {
+        switch (b & 31) {
+        case 20:
+            return FALSE;
+        case 21:
+            return TRUE;
+        case 22:
+            return NULL;
+        case 23:
+            return UNDEFINED;
+        case 24:
+        case 25:
+        case 26:
+        case 27:
+        case 28:
+        case 29:
+        case 30:
+        case 31:
+            return RESERVED;
+        default:
+            return UNALLOCATED;
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/SinglePrecisionFloat.java b/src/main/java/co/nstant/in/cbor/model/SinglePrecisionFloat.java
new file mode 100644
index 0000000..d28b016
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/SinglePrecisionFloat.java
@@ -0,0 +1,9 @@
+package co.nstant.in.cbor.model;
+
+public class SinglePrecisionFloat extends AbstractFloat {
+
+    public SinglePrecisionFloat(float value) {
+        super(SpecialType.IEEE_754_SINGLE_PRECISION_FLOAT, value);
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/Special.java b/src/main/java/co/nstant/in/cbor/model/Special.java
new file mode 100644
index 0000000..365950b
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/Special.java
@@ -0,0 +1,39 @@
+package co.nstant.in.cbor.model;
+
+import java.util.Objects;
+
+public class Special extends DataItem {
+
+    public static final Special BREAK = new Special(SpecialType.BREAK);
+
+    private final SpecialType specialType;
+
+    protected Special(SpecialType specialType) {
+        super(MajorType.SPECIAL);
+        this.specialType = Objects.requireNonNull(specialType);
+    }
+
+    public SpecialType getSpecialType() {
+        return specialType;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof Special) {
+            Special other = (Special) object;
+            return super.equals(object) && specialType == other.specialType;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ Objects.hashCode(specialType);
+    }
+
+    @Override
+    public String toString() {
+        return specialType.name();
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/SpecialType.java b/src/main/java/co/nstant/in/cbor/model/SpecialType.java
new file mode 100644
index 0000000..9c99537
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/SpecialType.java
@@ -0,0 +1,34 @@
+package co.nstant.in.cbor.model;
+
+public enum SpecialType {
+
+    SIMPLE_VALUE,
+    SIMPLE_VALUE_NEXT_BYTE,
+    IEEE_754_HALF_PRECISION_FLOAT,
+    IEEE_754_SINGLE_PRECISION_FLOAT,
+    IEEE_754_DOUBLE_PRECISION_FLOAT,
+    UNALLOCATED,
+    BREAK;
+
+    public static SpecialType ofByte(int b) {
+        switch (b & 31) {
+        case 24:
+            return SIMPLE_VALUE_NEXT_BYTE;
+        case 25:
+            return IEEE_754_HALF_PRECISION_FLOAT;
+        case 26:
+            return IEEE_754_SINGLE_PRECISION_FLOAT;
+        case 27:
+            return IEEE_754_DOUBLE_PRECISION_FLOAT;
+        case 28:
+        case 29:
+        case 30:
+            return UNALLOCATED;
+        case 31:
+            return BREAK;
+        default:
+            return SIMPLE_VALUE;
+        }
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/Tag.java b/src/main/java/co/nstant/in/cbor/model/Tag.java
new file mode 100644
index 0000000..507d81a
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/Tag.java
@@ -0,0 +1,37 @@
+package co.nstant.in.cbor.model;
+
+import java.util.Objects;
+
+public class Tag extends DataItem {
+
+    private final long value;
+
+    public Tag(long value) {
+        super(MajorType.TAG);
+        this.value = value;
+    }
+
+    public long getValue() {
+        return value;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof Tag) {
+            Tag other = (Tag) object;
+            return super.equals(object) && value == other.value;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode() ^ Objects.hashCode(value);
+    }
+
+    @Override
+    public String toString() {
+        return "Tag("+value+")";
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/UnicodeString.java b/src/main/java/co/nstant/in/cbor/model/UnicodeString.java
new file mode 100644
index 0000000..b2bf0bf
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/UnicodeString.java
@@ -0,0 +1,50 @@
+package co.nstant.in.cbor.model;
+
+public class UnicodeString extends ChunkableDataItem {
+
+    private final String string;
+
+    public UnicodeString(String string) {
+        super(MajorType.UNICODE_STRING);
+        this.string = string;
+    }
+
+    @Override
+    public String toString() {
+        if (string == null) {
+            return "null";
+        } else {
+            return string;
+        }
+    }
+
+    public String getString() {
+        return string;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof UnicodeString && super.equals(object)) {
+            UnicodeString other = (UnicodeString) object;
+            if (string == null) {
+                return other.string == null;
+            } else {
+                return string.equals(other.string);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 0;
+
+        if (string != null) {
+            hash = super.hashCode();
+            hash += string.hashCode();
+        }
+
+        return hash;
+    }
+
+}
diff --git a/src/main/java/co/nstant/in/cbor/model/UnsignedInteger.java b/src/main/java/co/nstant/in/cbor/model/UnsignedInteger.java
new file mode 100644
index 0000000..e547760
--- /dev/null
+++ b/src/main/java/co/nstant/in/cbor/model/UnsignedInteger.java
@@ -0,0 +1,16 @@
+package co.nstant.in.cbor.model;
+
+import java.math.BigInteger;
+
+public class UnsignedInteger extends Number {
+
+	public UnsignedInteger(long value) {
+		this(BigInteger.valueOf(value));
+		assertTrue(value >= 0L, "value " + value + " is not >= 0");
+	}
+
+	public UnsignedInteger(BigInteger value) {
+		super(MajorType.UNSIGNED_INTEGER, value);
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/CborBuilderTest.java b/src/test/java/co/nstant/in/cbor/CborBuilderTest.java
new file mode 100644
index 0000000..1c58e6e
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/CborBuilderTest.java
@@ -0,0 +1,34 @@
+package co.nstant.in.cbor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Tag;
+
+public class CborBuilderTest {
+
+    @Test
+    public void shouldResetDataItems() {
+        CborBuilder builder = new CborBuilder();
+        builder.add(true);
+        builder.add(1.0f);
+        assertEquals(2, builder.build().size());
+        builder.reset();
+        assertEquals(0, builder.build().size());
+    }
+
+    @Test
+    public void shouldAddTag() {
+        CborBuilder builder = new CborBuilder();
+        List<DataItem> dataItems = builder.addTag(1234).build();
+        assertEquals(1, dataItems.size());
+        assertTrue(dataItems.get(0) instanceof Tag);
+        assertEquals(1234, ((Tag) dataItems.get(0)).getValue());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/CborEncoderTest.java b/src/test/java/co/nstant/in/cbor/CborEncoderTest.java
new file mode 100644
index 0000000..060a90e
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/CborEncoderTest.java
@@ -0,0 +1,33 @@
+package co.nstant.in.cbor;
+
+import java.io.ByteArrayOutputStream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.MajorType;
+
+public class CborEncoderTest {
+
+    private class InvalidDataItem extends DataItem {
+
+        public InvalidDataItem() {
+            super(MajorType.INVALID);
+        }
+
+    }
+
+    private CborEncoder encoder;
+
+    @Before
+    public void setup() {
+        encoder = new CborEncoder(new ByteArrayOutputStream());
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldNotEncodeInvalidMajorType() throws CborException {
+        encoder.encode(new InvalidDataItem());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/CborExceptionTest.java b/src/test/java/co/nstant/in/cbor/CborExceptionTest.java
new file mode 100644
index 0000000..1ae974b
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/CborExceptionTest.java
@@ -0,0 +1,29 @@
+package co.nstant.in.cbor;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CborExceptionTest {
+
+    @Test
+    public void shouldHaveMessage() {
+        CborException cborException = new CborException("message");
+        Assert.assertEquals("message", cborException.getMessage());
+    }
+
+    @Test
+    public void shouldHaveThrowable() {
+        Throwable throwable = new Throwable();
+        CborException cborException = new CborException(throwable);
+        Assert.assertEquals(throwable, cborException.getCause());
+    }
+
+    @Test
+    public void shouldHaveMessageAndThrowable() {
+        Throwable throwable = new Throwable();
+        CborException cborException = new CborException("message", throwable);
+        Assert.assertEquals("message", cborException.getMessage());
+        Assert.assertEquals(throwable, cborException.getCause());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/DatemItemListenerTest.java b/src/test/java/co/nstant/in/cbor/DatemItemListenerTest.java
new file mode 100644
index 0000000..daec312
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/DatemItemListenerTest.java
@@ -0,0 +1,35 @@
+package co.nstant.in.cbor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+public class DatemItemListenerTest {
+
+    @Test
+    public void shouldDecodeZero() throws CborException {
+    	final int[] dataItems = new int[1];
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        new CborEncoder(outputStream).encode(new CborBuilder().add(1234).build());
+        new CborDecoder(new ByteArrayInputStream(outputStream.toByteArray())).decode(new DataItemListener() {
+
+            @Override
+            public void onDataItem(DataItem dataItem) {
+                synchronized (dataItems) {
+                	++dataItems[0];	
+				}
+                assertTrue(dataItem instanceof UnsignedInteger);
+            }
+
+        });
+        assertEquals(1, dataItems[0]);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/EncoderDecoderTest.java b/src/test/java/co/nstant/in/cbor/EncoderDecoderTest.java
new file mode 100644
index 0000000..bbac324
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/EncoderDecoderTest.java
@@ -0,0 +1,45 @@
+package co.nstant.in.cbor;
+
+import java.io.ByteArrayOutputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.NegativeInteger;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+public class EncoderDecoderTest {
+
+	@Test
+	public void test() throws CborException {
+		UnsignedInteger a = new UnsignedInteger(1);
+		NegativeInteger x = new NegativeInteger(-2);
+		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteArrayOutputStream);
+		encoder.encode(a);
+		encoder.encode(x);
+		byte[] bytes = byteArrayOutputStream.toByteArray();
+		DataItem object = CborDecoder.decode(bytes).get(0);
+		Assert.assertEquals(a, object);
+	}
+
+	@Test
+	public void testTagging() throws CborException {
+		UnsignedInteger a = new UnsignedInteger(1);
+		NegativeInteger x = new NegativeInteger(-2);
+
+		a.setTag(1);
+
+		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteArrayOutputStream);
+		encoder.encode(a);
+		encoder.encode(x);
+		byte[] bytes = byteArrayOutputStream.toByteArray();
+		DataItem object = CborDecoder.decode(bytes).get(0);
+		Assert.assertEquals(a, object);
+		Assert.assertTrue(object.hasTag());
+		Assert.assertEquals(1L, object.getTag().getValue());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/builder/AbstractBuilderTest.java b/src/test/java/co/nstant/in/cbor/builder/AbstractBuilderTest.java
new file mode 100644
index 0000000..01a4194
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/builder/AbstractBuilderTest.java
@@ -0,0 +1,75 @@
+package co.nstant.in.cbor.builder;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.OutputStream;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.encoder.HalfPrecisionFloatEncoder;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.HalfPrecisionFloat;
+import co.nstant.in.cbor.model.SinglePrecisionFloat;
+
+public class AbstractBuilderTest {
+
+    private class TestBuilder extends AbstractBuilder<Object> {
+
+        private boolean encoderThrowsException = false;
+
+        public TestBuilder() {
+            super(null);
+        }
+
+        public DataItem testConvert(float value) {
+            return convert(value);
+        }
+
+        public void testAddChunk() {
+            addChunk(null);
+        }
+
+        public void setEncoderThrowsException() {
+            encoderThrowsException = true;
+        }
+
+        @Override
+        protected HalfPrecisionFloatEncoder getHalfPrecisionFloatEncoder(OutputStream outputStream) {
+            if (encoderThrowsException) {
+                return new HalfPrecisionFloatEncoder(null, outputStream) {
+                    @Override
+                    public void encode(HalfPrecisionFloat dataItem) throws CborException {
+                        throw new CborException("test");
+                    }
+                };
+            } else {
+                return super.getHalfPrecisionFloatEncoder(outputStream);
+            }
+        }
+
+    }
+
+    @Test
+    public void shouldCallIsHalfPrecisionEnough() {
+        TestBuilder builder = new TestBuilder();
+        assertTrue(builder.testConvert(0.0f) instanceof HalfPrecisionFloat);
+        assertTrue(0.0f == ((HalfPrecisionFloat) builder.testConvert(0.0f)).getValue());
+        assertTrue(builder.testConvert(0.3f) instanceof SinglePrecisionFloat);
+        assertTrue(0.3f == ((SinglePrecisionFloat) builder.testConvert(0.3f)).getValue());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void shouldThrowExceptionOnAddChunk() {
+        new TestBuilder().testAddChunk();
+    }
+
+    @Test
+    public void IsHalfPrecisionEnoughShallReturnFalseOnException() {
+        TestBuilder builder = new TestBuilder();
+        builder.setEncoderThrowsException();
+        assertTrue(builder.testConvert(0.0f) instanceof SinglePrecisionFloat);
+        assertTrue(0.0f == ((SinglePrecisionFloat) builder.testConvert(0.0f)).getValue());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/builder/ArrayBuilderTest.java b/src/test/java/co/nstant/in/cbor/builder/ArrayBuilderTest.java
new file mode 100644
index 0000000..9604f78
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/builder/ArrayBuilderTest.java
@@ -0,0 +1,78 @@
+package co.nstant.in.cbor.builder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.DoublePrecisionFloat;
+import co.nstant.in.cbor.model.HalfPrecisionFloat;
+import co.nstant.in.cbor.model.SimpleValue;
+
+public class ArrayBuilderTest {
+
+    @Test
+    public void shouldAddBoolean() {
+        CborBuilder builder = new CborBuilder();
+        List<DataItem> dataItems = builder.addArray()
+            .add(true)
+            .add(false)
+            .end()
+            .build();
+        assertEquals(1, dataItems.size());
+        assertTrue(dataItems.get(0) instanceof Array);
+        Array array = (Array) dataItems.get(0);
+        assertEquals(2, array.getDataItems().size());
+        assertTrue(array.getDataItems().get(0) instanceof SimpleValue);
+        assertTrue(array.getDataItems().get(1) instanceof SimpleValue);
+    }
+
+    @Test
+    public void shouldAddFloat() {
+        CborBuilder builder = new CborBuilder();
+        List<DataItem> dataItems = builder.addArray()
+            .add(1.0f)
+            .end()
+            .build();
+        assertEquals(1, dataItems.size());
+        assertTrue(dataItems.get(0) instanceof Array);
+        Array array = (Array) dataItems.get(0);
+        assertEquals(1, array.getDataItems().size());
+        assertTrue(array.getDataItems().get(0) instanceof HalfPrecisionFloat);
+    }
+
+    @Test
+    public void shouldAddDouble() {
+        CborBuilder builder = new CborBuilder();
+        List<DataItem> dataItems = builder.addArray()
+            .add(1.0d)
+            .end()
+            .build();
+        assertEquals(1, dataItems.size());
+        assertTrue(dataItems.get(0) instanceof Array);
+        Array array = (Array) dataItems.get(0);
+        assertEquals(1, array.getDataItems().size());
+        assertTrue(array.getDataItems().get(0) instanceof DoublePrecisionFloat);
+    }
+
+    @Test
+    public void shouldAddByteArray() {
+        CborBuilder builder = new CborBuilder();
+        List<DataItem> dataItems = builder.addArray()
+            .add(new byte[] { 0x0 })
+            .end()
+            .build();
+        assertEquals(1, dataItems.size());
+        assertTrue(dataItems.get(0) instanceof Array);
+        Array array = (Array) dataItems.get(0);
+        assertEquals(1, array.getDataItems().size());
+        assertTrue(array.getDataItems().get(0) instanceof ByteString);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/builder/MapBuilderTest.java b/src/test/java/co/nstant/in/cbor/builder/MapBuilderTest.java
new file mode 100644
index 0000000..d4c3407
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/builder/MapBuilderTest.java
@@ -0,0 +1,84 @@
+package co.nstant.in.cbor.builder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Map;
+import co.nstant.in.cbor.model.UnicodeString;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+public class MapBuilderTest {
+
+    @Test
+    public void testMapBuilder() {
+        CborBuilder builder = new CborBuilder();
+        List<DataItem> dataItems = builder.addMap()
+            .put(new UnicodeString("key"), new UnicodeString("value"))
+            .put(1, true)
+            .put(2, "value".getBytes())
+            .put(3, 1.0d)
+            .put(4, 1.0f)
+            .put(5, 1L)
+            .put(6, "value")
+            .put("7", true)
+            .put("8", "value".getBytes())
+            .put("9", 1.0d)
+            .put("10", 1.0f)
+            .put("11", 1L)
+            .put("12", "value")
+            .putMap(13)
+            .end()
+            .putMap("14")
+            .end()
+            .putMap(new UnsignedInteger(15))
+            .end()
+            .putArray(16)
+            .end()
+            .putArray("17")
+            .end()
+            .putArray(new UnsignedInteger(18))
+            .end()
+            .end()
+            .startMap()
+            .startArray(1).end()
+            .startArray(new UnsignedInteger(2)).end()
+            .end()
+            .build();
+        assertEquals(2, dataItems.size());
+        assertTrue(dataItems.get(0) instanceof Map);
+        Map map = (Map) dataItems.get(0);
+        assertEquals(19, map.getKeys().size());
+    }
+
+    @Test
+    public void startMapInMap() {
+        CborBuilder builder = new CborBuilder();
+        List<DataItem> dataItems = builder.addMap()
+                .startMap(new ByteString(new byte[] {0x01}))
+                    .put(1, 2)
+                    .end()
+                .startMap(1)
+                    .end()
+                .startMap("asdf")
+                    .end()
+                .end().build();
+        Map rootMap = (Map)dataItems.get(0);
+        DataItem keys[] = new DataItem[3];
+        rootMap.getKeys().toArray(keys);
+        assertEquals(keys[0], new ByteString(new byte[] {0x01}));
+        assertEquals(keys[1], new UnsignedInteger(1));
+        assertEquals(keys[2], new UnicodeString("asdf"));
+
+        assertTrue(rootMap.get(keys[0]) instanceof Map);
+        assertTrue(rootMap.get(keys[1]) instanceof Map);
+        assertTrue(rootMap.get(keys[2]) instanceof Map);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/decoder/AbstractDecoderTest.java b/src/test/java/co/nstant/in/cbor/decoder/AbstractDecoderTest.java
new file mode 100644
index 0000000..4a13f5d
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/decoder/AbstractDecoderTest.java
@@ -0,0 +1,105 @@
+package co.nstant.in.cbor.decoder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.math.BigInteger;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.UnicodeString;
+
+public class AbstractDecoderTest {
+
+    private class TestableAbstractDecoder extends AbstractDecoder<UnicodeString> {
+
+        public TestableAbstractDecoder(InputStream inputStream) {
+            super(null, inputStream);
+        }
+
+        public void callNextSymbol() throws CborException {
+            nextSymbol();
+        }
+
+        public long callGetLength(int initialByte) throws CborException {
+            return getLength(initialByte);
+        }
+
+        public BigInteger callGetLengthAsBigInteger(int initialByte) throws CborException {
+            return getLengthAsBigInteger(initialByte);
+        }
+
+        @Override
+        public UnicodeString decode(int initialByte) throws CborException {
+            return null;
+        }
+
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowExceptionOnUnexpectedEndOfStream() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] {});
+        new TestableAbstractDecoder(inputStream).callNextSymbol();
+        fail();
+    }
+
+    @Test
+    public void shouldDecodeInfinityLength() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] {});
+        TestableAbstractDecoder decoder = new TestableAbstractDecoder(inputStream);
+        assertEquals(BigInteger.valueOf(-1), decoder.callGetLengthAsBigInteger(31));
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowExceptionOnReserved1() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] {});
+        new TestableAbstractDecoder(inputStream).callGetLengthAsBigInteger(28);
+        fail();
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowExceptionOnReserved2() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] {});
+        new TestableAbstractDecoder(inputStream).callGetLengthAsBigInteger(29);
+        fail();
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowExceptionOnReserved3() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] {});
+        new TestableAbstractDecoder(inputStream).callGetLengthAsBigInteger(30);
+        fail();
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowExceptionOnReserved4() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] {});
+        new TestableAbstractDecoder(inputStream).callGetLength(28);
+        fail();
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowExceptionOnReserved5() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] {});
+        new TestableAbstractDecoder(inputStream).callGetLength(29);
+        fail();
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowExceptionOnReserved6() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] {});
+        new TestableAbstractDecoder(inputStream).callGetLength(30);
+        fail();
+    }
+
+    @Test
+    public void shouldDecodeEightBytesLengthToLong() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 });
+        long value = new TestableAbstractDecoder(inputStream).callGetLength(27);
+        assertEquals(72340172838076673L, value);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/decoder/ArrayDecoderTest.java b/src/test/java/co/nstant/in/cbor/decoder/ArrayDecoderTest.java
new file mode 100644
index 0000000..8daa962
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/decoder/ArrayDecoderTest.java
@@ -0,0 +1,20 @@
+package co.nstant.in.cbor.decoder;

+

+import org.junit.Test;

+

+import co.nstant.in.cbor.CborDecoder;

+import co.nstant.in.cbor.CborException;

+

+public class ArrayDecoderTest {

+    @Test(expected = CborException.class)

+    public void shouldThrowOnIncompleteArray() throws CborException {

+        byte[] bytes = new byte[] {(byte) 0x82, 0x01 };

+        CborDecoder.decode(bytes);

+    }

+

+    @Test(expected = CborException.class)

+    public void shouldThrowInIncompleteIndefiniteLengthArray() throws CborException {

+        byte[] bytes = new byte[] {(byte) 0x9f, 0x01, 0x02};

+        CborDecoder.decode(bytes);

+    }

+}

diff --git a/src/test/java/co/nstant/in/cbor/decoder/ByteStringDecoderTest.java b/src/test/java/co/nstant/in/cbor/decoder/ByteStringDecoderTest.java
new file mode 100644
index 0000000..ffac44f
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/decoder/ByteStringDecoderTest.java
@@ -0,0 +1,76 @@
+package co.nstant.in.cbor.decoder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+public class ByteStringDecoderTest {
+
+    @Test
+    public void shouldDecodeChunkedByteString() throws CborException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(new CborBuilder()
+            .startByteString()
+            .add(new byte[] { '\0' })
+            .add(new byte[] { 0x10 })
+            .add(new byte[] { 0x13 })
+            .end()
+            .build());
+        byte[] encodedBytes = baos.toByteArray();
+        ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
+        CborDecoder decoder = new CborDecoder(bais);
+        List<DataItem> dataItems = decoder.decode();
+        assertNotNull(dataItems);
+        assertEquals(1, dataItems.size());
+    }
+
+    @Test
+    public void shouldDecodeByteString1K() throws CborException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(new CborBuilder().add(new byte[1024]).build());
+        byte[] encodedBytes = baos.toByteArray();
+        ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
+        CborDecoder decoder = new CborDecoder(bais);
+        List<DataItem> dataItems = decoder.decode();
+        assertNotNull(dataItems);
+        assertEquals(1, dataItems.size());
+    }
+
+    @Test
+    public void shouldDecodeByteString1M() throws CborException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(new CborBuilder().add(new byte[1024 * 1024]).build());
+        byte[] encodedBytes = baos.toByteArray();
+        ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
+        CborDecoder decoder = new CborDecoder(bais);
+        List<DataItem> dataItems = decoder.decode();
+        assertNotNull(dataItems);
+        assertEquals(1, dataItems.size());
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowOnIncompleteByteString() throws CborException {
+        byte[] bytes = new byte[] {0x42, 0x20};
+        CborDecoder.decode(bytes);
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldTrowOnMissingBreak() throws CborException {
+        byte[] bytes = new byte[] {0x5f, 0x41, 0x20};
+        CborDecoder.decode(bytes);
+    }
+}
diff --git a/src/test/java/co/nstant/in/cbor/decoder/CborDecoderTest.java b/src/test/java/co/nstant/in/cbor/decoder/CborDecoderTest.java
new file mode 100644
index 0000000..acf0313
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/decoder/CborDecoderTest.java
@@ -0,0 +1,179 @@
+package co.nstant.in.cbor.decoder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.RationalNumber;
+import co.nstant.in.cbor.model.Tag;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+public class CborDecoderTest {
+
+    @Test(expected = CborException.class)
+    public void shouldThrowCborException() throws CborException {
+        CborDecoder cborDecoder = new CborDecoder(new InputStream() {
+
+            @Override
+            public int read() throws IOException {
+                throw new IOException();
+            }
+
+        });
+        cborDecoder.decodeNext();
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowCborException2() throws CborException {
+        CborDecoder cborDecoder = new CborDecoder(new InputStream() {
+
+            @Override
+            public int read() throws IOException {
+                return (8 << 5); // invalid major type
+            }
+
+        });
+        cborDecoder.decodeNext();
+    }
+
+    @Test
+    public void shouldSetAutoDecodeInfinitiveMaps() {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] { 0, 1, 2 });
+        CborDecoder cborDecoder = new CborDecoder(inputStream);
+        assertTrue(cborDecoder.isAutoDecodeInfinitiveMaps());
+        cborDecoder.setAutoDecodeInfinitiveMaps(false);
+        assertFalse(cborDecoder.isAutoDecodeInfinitiveMaps());
+    }
+
+    @Test
+    public void shouldSetAutoDecodeRationalNumbers() {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] { 0, 1, 2 });
+        CborDecoder cborDecoder = new CborDecoder(inputStream);
+        assertTrue(cborDecoder.isAutoDecodeRationalNumbers());
+        cborDecoder.setAutoDecodeRationalNumbers(false);
+        assertFalse(cborDecoder.isAutoDecodeRationalNumbers());
+    }
+
+    @Test
+    public void shouldSetAutoDecodeLanguageTaggedStrings() {
+        InputStream inputStream = new ByteArrayInputStream(new byte[] { 0, 1, 2 });
+        CborDecoder cborDecoder = new CborDecoder(inputStream);
+        assertTrue(cborDecoder.isAutoDecodeLanguageTaggedStrings());
+        cborDecoder.setAutoDecodeLanguageTaggedStrings(false);
+        assertFalse(cborDecoder.isAutoDecodeLanguageTaggedStrings());
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowOnRationalNumberDecode1() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(30)
+            .add(true)
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        decoder.decode();
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowOnRationalNumberDecode2() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(30)
+            .addArray().add(true).end()
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        decoder.decode();
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowOnRationalNumberDecode3() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(30)
+            .addArray().add(true).add(true).end()
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        decoder.decode();
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowOnRationalNumberDecode4() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(30)
+            .addArray().add(1).add(true).end()
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        decoder.decode();
+    }
+
+    @Test
+    public void shouldDecodeRationalNumber() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(30)
+            .addArray().add(1).add(2).end()
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        assertEquals(new RationalNumber(new UnsignedInteger(1), new UnsignedInteger(2)), decoder.decodeNext());
+    }
+
+    @Test
+    public void shouldDecodeTaggedTags() throws CborException {
+        DataItem decoded = CborDecoder.decode(new byte[] {(byte) 0xC1, (byte) 0xC2, 0x02}).get(0);
+
+        Tag outer = new Tag(1);
+        Tag inner = new Tag(2);
+        UnsignedInteger expected = new UnsignedInteger(2);
+        inner.setTag(outer);
+        expected.setTag(inner);
+
+        assertEquals(expected, decoded);
+    }
+
+    @Test
+    public void shouldDecodeTaggedRationalNumber() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(1)
+            .addTag(30)
+            .addArray().add(1).add(2).end()
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+
+        RationalNumber expected = new RationalNumber(new UnsignedInteger(1), new UnsignedInteger(2));
+        expected.getTag().setTag(new Tag(1));
+        assertEquals(expected, decoder.decodeNext());
+    }
+}
diff --git a/src/test/java/co/nstant/in/cbor/decoder/LanguageTaggedStringDecoderTest.java b/src/test/java/co/nstant/in/cbor/decoder/LanguageTaggedStringDecoderTest.java
new file mode 100644
index 0000000..5523f60
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/decoder/LanguageTaggedStringDecoderTest.java
@@ -0,0 +1,104 @@
+package co.nstant.in.cbor.decoder;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.LanguageTaggedString;
+import co.nstant.in.cbor.model.UnicodeString;
+
+public class LanguageTaggedStringDecoderTest {
+
+    // Unexpected end of stream, tag without data item
+    @Test(expected = CborException.class)
+    public void shouldThrowException() throws CborException {
+        List<DataItem> items = new CborBuilder().addTag(38).build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        decoder.decode();
+    }
+
+    @Test(expected = CborException.class)
+    public void testExceptionOnNotAnArray() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(38)
+            .add(true)
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        decoder.decode();
+    }
+
+    @Test(expected = CborException.class)
+    public void testExceptionOnNot2ElementArray() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(38)
+            .addArray().add(true).end()
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        decoder.decode();
+    }
+
+    @Test(expected = CborException.class)
+    public void testExceptionOnNotFirstElementIsString() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(38)
+            .addArray().add(true).add(true).end()
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        decoder.decode();
+    }
+
+    @Test(expected = CborException.class)
+    public void testExceptionOnNotSecondElementIsString() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(38)
+            .addArray().add("en").add(true).end()
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        decoder.decode();
+    }
+
+    @Test
+    public void testDecoding() throws CborException {
+        List<DataItem> items = new CborBuilder()
+            .addTag(38)
+            .addArray().add("en").add("string").end()
+            .build();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(items);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        CborDecoder decoder = new CborDecoder(bais);
+        DataItem item = decoder.decodeNext();
+        assertEquals(new LanguageTaggedString(new UnicodeString("en"), new UnicodeString("string")), item);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/decoder/MapDecoderTest.java b/src/test/java/co/nstant/in/cbor/decoder/MapDecoderTest.java
new file mode 100644
index 0000000..480625a
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/decoder/MapDecoderTest.java
@@ -0,0 +1,62 @@
+package co.nstant.in.cbor.decoder;

+

+import static org.junit.Assert.assertEquals;

+

+import java.io.ByteArrayInputStream;

+import java.util.List;

+

+import org.junit.Test;

+

+import co.nstant.in.cbor.CborDecoder;

+import co.nstant.in.cbor.CborException;

+import co.nstant.in.cbor.model.DataItem;

+import co.nstant.in.cbor.model.Map;

+import co.nstant.in.cbor.model.UnsignedInteger;

+

+public class MapDecoderTest {

+    @Test(expected = CborException.class)

+    public void shouldThrowOnMissingKeyInMap() throws CborException {

+        byte[] bytes = new byte[] {(byte) 0xa2, 0x01, 0x02};

+        CborDecoder.decode(bytes);

+    }

+

+    @Test(expected = CborException.class)

+    public void shouldThrowOnMissingValueInMap() throws CborException {

+        byte[] bytes = new byte[] {(byte) 0xa2, 0x01, 0x02, 0x03};

+        CborDecoder.decode(bytes);

+    }

+

+    @Test(expected = CborException.class)

+    public void shouldThrowOnIncompleteIndefiniteLengthMap() throws CborException {

+        byte[] bytes = new byte[] {(byte) 0xbf, 0x61, 0x01};

+        CborDecoder.decode(bytes);

+    }

+

+    @Test

+    public void shouldUseLastOfDuplicateKeysByDefault() throws CborException {

+        byte[] bytes = new byte[] {(byte)0xa2, 0x01, 0x01, 0x01, 0x02};

+        List<DataItem> decoded = CborDecoder.decode(bytes);

+        Map map = (Map)decoded.get(0);

+        assertEquals(map.getKeys().size(), 1);

+        assertEquals(map.get(new UnsignedInteger(1)), new UnsignedInteger(2));

+    }

+

+    @Test(expected = CborException.class)

+    public void shouldThrowOnDuplicateKeyIfEnabled() throws CborException {

+        byte[] bytes = new byte[] {(byte)0xa2, 0x01, 0x01, 0x01, 0x02};

+        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

+        CborDecoder decoder = new CborDecoder(bais);

+        decoder.setRejectDuplicateKeys(true);

+        decoder.decode();

+    }

+

+    @Test(expected = CborException.class)

+    public void shouldThrowInDuplicateKeyInIndefiniteLengthMapIfEnabled()

+            throws CborException {

+        byte[] bytes = new byte[] {(byte)0xbf, 0x01, 0x01, 0x01, 0x02, (byte) 0xff};

+        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

+        CborDecoder decoder = new CborDecoder(bais);

+        decoder.setRejectDuplicateKeys(true);

+        decoder.decode();

+    }

+}

diff --git a/src/test/java/co/nstant/in/cbor/decoder/SimpleValueDecoderTest.java b/src/test/java/co/nstant/in/cbor/decoder/SimpleValueDecoderTest.java
new file mode 100644
index 0000000..2c45973
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/decoder/SimpleValueDecoderTest.java
@@ -0,0 +1,61 @@
+package co.nstant.in.cbor.decoder;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+import co.nstant.in.cbor.model.Special;
+
+public class SimpleValueDecoderTest {
+
+    @Test
+    public void shouldDecodeBoolean() throws CborException {
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteArrayOutputStream);
+        encoder.encode(SimpleValue.TRUE);
+        encoder.encode(SimpleValue.FALSE);
+        byte[] encodedBytes = byteArrayOutputStream.toByteArray();
+        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(encodedBytes);
+        List<DataItem> dataItems = new CborDecoder(byteArrayInputStream).decode();
+        int result = 0;
+        int position = 1;
+        for (DataItem dataItem : dataItems) {
+            position++;
+            switch (dataItem.getMajorType()) {
+            case SPECIAL:
+                Special special = (Special) dataItem;
+                switch (special.getSpecialType()) {
+                case SIMPLE_VALUE:
+                    SimpleValue simpleValue = (SimpleValue) special;
+                    switch (simpleValue.getSimpleValueType()) {
+                    case FALSE:
+                        result += position * 2;
+                        break;
+                    case TRUE:
+                        result += position * 3;
+                        break;
+                    default:
+                        break;
+                    }
+                    break;
+                default:
+                    break;
+                }
+                break;
+            default:
+                break;
+            }
+        }
+        assertEquals(12, result);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/decoder/SpecialDecoderTest.java b/src/test/java/co/nstant/in/cbor/decoder/SpecialDecoderTest.java
new file mode 100644
index 0000000..0ea2d50
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/decoder/SpecialDecoderTest.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.decoder;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborException;
+
+public class SpecialDecoderTest {
+
+    @Test(expected = CborException.class)
+    public void shouldThrowExceptionOnUnallocated() throws CborException {
+        SpecialDecoder decoder = new SpecialDecoder(null, null);
+        decoder.decode(28);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/decoder/UnicodeStringDecoderTest.java b/src/test/java/co/nstant/in/cbor/decoder/UnicodeStringDecoderTest.java
new file mode 100644
index 0000000..b2fb038
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/decoder/UnicodeStringDecoderTest.java
@@ -0,0 +1,50 @@
+package co.nstant.in.cbor.decoder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+public class UnicodeStringDecoderTest {
+
+    @Test
+    public void shouldDecodeChunkedUnicodeString() throws CborException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(baos);
+        encoder.encode(new CborBuilder()
+            .startString()
+            .add("foo")
+            .add("bar")
+            .end()
+            .build());
+        byte[] encodedBytes = baos.toByteArray();
+        ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
+        CborDecoder decoder = new CborDecoder(bais);
+        List<DataItem> dataItems = decoder.decode();
+        assertNotNull(dataItems);
+        assertEquals(1, dataItems.size());
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowOnIncompleteString() throws CborException {
+        byte[] bytes = new byte[] {0x62, 0x61};
+        CborDecoder.decode(bytes);
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowOnMissingBreak() throws CborException {
+        byte[] bytes = new byte[] {0x7f, 0x61, 0x61};
+        CborDecoder.decode(bytes);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/decoder/UnsignedIntegerDecoderTest.java b/src/test/java/co/nstant/in/cbor/decoder/UnsignedIntegerDecoderTest.java
new file mode 100644
index 0000000..058b9a5
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/decoder/UnsignedIntegerDecoderTest.java
@@ -0,0 +1,43 @@
+package co.nstant.in.cbor.decoder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+public class UnsignedIntegerDecoderTest {
+
+    @Test
+    public void shouldDecodeBigNumbers() throws CborException {
+        BigInteger value = BigInteger.ONE;
+        for (int i = 1; i < 64; i++) {
+            value = value.shiftLeft(1);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            CborEncoder encoder = new CborEncoder(baos);
+            encoder.encode(new CborBuilder().add(value).build());
+            byte[] encodedBytes = baos.toByteArray();
+            ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
+            CborDecoder decoder = new CborDecoder(bais);
+            List<DataItem> dataItems = decoder.decode();
+            assertNotNull(dataItems);
+            assertEquals(1, dataItems.size());
+            DataItem dataItem = dataItems.get(0);
+            assertTrue(dataItem instanceof UnsignedInteger);
+            assertEquals(value, ((UnsignedInteger) dataItem).getValue());
+        }
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/encoder/AbstractEncoderTest.java b/src/test/java/co/nstant/in/cbor/encoder/AbstractEncoderTest.java
new file mode 100644
index 0000000..7d73883
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/encoder/AbstractEncoderTest.java
@@ -0,0 +1,79 @@
+package co.nstant.in.cbor.encoder;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.MajorType;
+
+public class AbstractEncoderTest {
+
+    private class TestEncoder extends AbstractEncoder<Object> {
+
+        public TestEncoder(OutputStream outputStream) {
+            super(null, outputStream);
+        }
+
+        @Override
+        public void encode(Object dataItem) throws CborException {
+        }
+
+        public void doEncodeTypeAndLength(long length) throws CborException {
+            encodeTypeAndLength(MajorType.ARRAY, length);
+        }
+
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowCborExceptionOnUnderlyingIoException() throws CborException {
+        final int[] counter = new int[1];
+        new CborEncoder(new OutputStream() {
+
+            @Override
+            public synchronized void write(int b) throws IOException {
+                if (++counter[0] == 3) {
+                    throw new IOException();
+                }
+            }
+
+        }).encode(new CborBuilder().startString().add("string").end().build());
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowCborExceptionOnUnderlyingIoException2() throws CborException {
+        new CborEncoder(new OutputStream() {
+
+            @Override
+            public synchronized void write(int b) throws IOException {
+                throw new IOException();
+            }
+
+        }).encode(new CborBuilder().startArray().add(1).end().build());
+    }
+
+    @Test
+    public void shallEncode32bit() throws CborException {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        TestEncoder encoder = new TestEncoder(outputStream);
+        encoder.doEncodeTypeAndLength(4294967296L);
+        byte[] bytes = outputStream.toByteArray();
+        assertEquals(9, bytes.length);
+        assertEquals((byte) 0x9B, bytes[0]);
+        assertEquals((byte) 0x00, bytes[1]);
+        assertEquals((byte) 0x00, bytes[2]);
+        assertEquals((byte) 0x00, bytes[3]);
+        assertEquals((byte) 0x01, bytes[4]);
+        assertEquals((byte) 0x00, bytes[5]);
+        assertEquals((byte) 0x00, bytes[6]);
+        assertEquals((byte) 0x00, bytes[7]);
+        assertEquals((byte) 0x00, bytes[8]);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/encoder/ByteStringEncoderTest.java b/src/test/java/co/nstant/in/cbor/encoder/ByteStringEncoderTest.java
new file mode 100644
index 0000000..97c13ee
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/encoder/ByteStringEncoderTest.java
@@ -0,0 +1,37 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+
+public class ByteStringEncoderTest {
+
+    @Test
+    public void shouldEncodeNullString() throws CborException {
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        List<DataItem> dataItems = new CborBuilder()
+            .add((ByteString) null)
+            .build();
+        new CborEncoder(byteArrayOutputStream).encode(dataItems);
+    }
+
+    @Test
+    public void shouldEncodeChunkedString() throws CborException {
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        List<DataItem> dataItems = new CborBuilder()
+            .add(new ByteString(null))
+            .add(new ByteString("test".getBytes()))
+            .startByteString("test".getBytes())
+            .end()
+            .build();
+        new CborEncoder(byteArrayOutputStream).encode(dataItems);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/encoder/MapEncoderTest.java b/src/test/java/co/nstant/in/cbor/encoder/MapEncoderTest.java
new file mode 100644
index 0000000..fb11cfd
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/encoder/MapEncoderTest.java
@@ -0,0 +1,31 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+public class MapEncoderTest {
+
+    @Test
+    public void shouldEncodeMap() throws CborException {
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        List<DataItem> dataItems = new CborBuilder()
+            .addMap()
+            .put(1, true)
+            .put(".", true)
+            .put(3, true)
+            .put("..", true)
+            .put(2, true)
+            .put("...", true)
+            .end()
+            .build();
+        new CborEncoder(byteArrayOutputStream).encode(dataItems);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/encoder/RationalNumberEncoderTest.java b/src/test/java/co/nstant/in/cbor/encoder/RationalNumberEncoderTest.java
new file mode 100644
index 0000000..bf3cde5
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/encoder/RationalNumberEncoderTest.java
@@ -0,0 +1,55 @@
+package co.nstant.in.cbor.encoder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.RationalNumber;
+import co.nstant.in.cbor.model.Tag;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+public class RationalNumberEncoderTest {
+
+    private static final UnsignedInteger ONE = new UnsignedInteger(1);
+    private static final UnsignedInteger TWO = new UnsignedInteger(2);
+
+    private ByteArrayOutputStream outputStream;
+    private CborEncoder encoder;
+
+    @Before
+    public void setup() {
+        outputStream = new ByteArrayOutputStream();
+        encoder = new CborEncoder(outputStream);
+    }
+
+    @Test
+    public void shouldEncode() throws CborException {
+        encoder.encode(new RationalNumber(ONE, TWO));
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+        CborDecoder decoder = new CborDecoder(inputStream);
+
+        Array expected = new Array();
+        expected.setTag(11);
+        expected.add(ONE);
+        expected.add(TWO);
+
+        Object object = decoder.decodeNext();
+        assertTrue(object instanceof Array);
+        Array decoded = (Array) object;
+
+        assertEquals(new Tag(30), decoded.getTag());
+        assertEquals(2, decoded.getDataItems().size());
+        assertEquals(ONE, decoded.getDataItems().get(0));
+        assertEquals(TWO, decoded.getDataItems().get(1));
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/encoder/SpecialEncoderTest.java b/src/test/java/co/nstant/in/cbor/encoder/SpecialEncoderTest.java
new file mode 100644
index 0000000..1789890
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/encoder/SpecialEncoderTest.java
@@ -0,0 +1,52 @@
+package co.nstant.in.cbor.encoder;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Special;
+import co.nstant.in.cbor.model.SpecialType;
+
+public class SpecialEncoderTest {
+
+    private class Mock extends Special {
+
+        public Mock(SpecialType specialType) {
+            super(specialType);
+        }
+
+    }
+
+    private SpecialEncoder encoder;
+
+    @Before
+    public void setup() {
+        encoder = new SpecialEncoder(null, null);
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldNotEncodeReserved() throws CborException {
+        encoder.encode(new Mock(SpecialType.UNALLOCATED));
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldVerifyImplementation1() throws CborException {
+        encoder.encode(new Mock(SpecialType.IEEE_754_DOUBLE_PRECISION_FLOAT));
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldVerifyImplementation2() throws CborException {
+        encoder.encode(new Mock(SpecialType.IEEE_754_HALF_PRECISION_FLOAT));
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldVerifyImplementation3() throws CborException {
+        encoder.encode(new Mock(SpecialType.IEEE_754_SINGLE_PRECISION_FLOAT));
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldVerifyImplementation4() throws CborException {
+        encoder.encode(new Mock(SpecialType.SIMPLE_VALUE_NEXT_BYTE));
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/encoder/UnicodeStringEncoderTest.java b/src/test/java/co/nstant/in/cbor/encoder/UnicodeStringEncoderTest.java
new file mode 100644
index 0000000..d1e2dd0
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/encoder/UnicodeStringEncoderTest.java
@@ -0,0 +1,34 @@
+package co.nstant.in.cbor.encoder;
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+public class UnicodeStringEncoderTest {
+
+    @Test
+    public void shouldEncodeNullString() throws CborException {
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        List<DataItem> dataItems = new CborBuilder()
+            .add((String) null)
+            .build();
+        new CborEncoder(byteArrayOutputStream).encode(dataItems);
+    }
+
+    @Test
+    public void shouldEncodeChunkedString() throws CborException {
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        List<DataItem> dataItems = new CborBuilder()
+            .startString("test")
+            .end()
+            .build();
+        new CborEncoder(byteArrayOutputStream).encode(dataItems);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example01Test.java b/src/test/java/co/nstant/in/cbor/examples/Example01Test.java
new file mode 100644
index 0000000..82747ef
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example01Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 0 -> 0x00
+ */
+public class Example01Test extends AbstractNumberTest {
+
+    public Example01Test() {
+        super(0, new byte[] { 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example02Test.java b/src/test/java/co/nstant/in/cbor/examples/Example02Test.java
new file mode 100644
index 0000000..4c0bd2f
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example02Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 0 -> 0x00
+ */
+public class Example02Test extends AbstractNumberTest {
+
+    public Example02Test() {
+        super(1, new byte[] { 0x01 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example03Test.java b/src/test/java/co/nstant/in/cbor/examples/Example03Test.java
new file mode 100644
index 0000000..a5029e0
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example03Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 10 -> 0x0a
+ */
+public class Example03Test extends AbstractNumberTest {
+
+    public Example03Test() {
+        super(10, new byte[] { 0x0a });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example04Test.java b/src/test/java/co/nstant/in/cbor/examples/Example04Test.java
new file mode 100644
index 0000000..bd46947
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example04Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 23 -> 0x17
+ */
+public class Example04Test extends AbstractNumberTest {
+
+    public Example04Test() {
+        super(23, new byte[] { 0x17 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example05Test.java b/src/test/java/co/nstant/in/cbor/examples/Example05Test.java
new file mode 100644
index 0000000..e4b3fcf
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example05Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 24 -> 0x1818
+ */
+public class Example05Test extends AbstractNumberTest {
+
+    public Example05Test() {
+        super(24, new byte[] { 0x18, 0x18 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example06Test.java b/src/test/java/co/nstant/in/cbor/examples/Example06Test.java
new file mode 100644
index 0000000..807082b
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example06Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 25 -> 0x1819
+ */
+public class Example06Test extends AbstractNumberTest {
+
+    public Example06Test() {
+        super(25, new byte[] { 0x18, 0x19 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example07Test.java b/src/test/java/co/nstant/in/cbor/examples/Example07Test.java
new file mode 100644
index 0000000..418745c
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example07Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 100 -> 0x1864
+ */
+public class Example07Test extends AbstractNumberTest {
+
+    public Example07Test() {
+        super(100, new byte[] { 0x18, 0x64 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example08Test.java b/src/test/java/co/nstant/in/cbor/examples/Example08Test.java
new file mode 100644
index 0000000..22a992d
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example08Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 1000 -> 0x1903e8
+ */
+public class Example08Test extends AbstractNumberTest {
+
+    public Example08Test() {
+        super(1000, new byte[] { 0x19, 0x03, (byte) 0xe8 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example09Test.java b/src/test/java/co/nstant/in/cbor/examples/Example09Test.java
new file mode 100644
index 0000000..4b6cfef
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example09Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 1000000 -> 0x1a000f4240
+ */
+public class Example09Test extends AbstractNumberTest {
+
+    public Example09Test() {
+        super(1000000, new byte[] { 0x1a, 0x00, 0x0f, 0x42, 0x40 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example10Test.java b/src/test/java/co/nstant/in/cbor/examples/Example10Test.java
new file mode 100644
index 0000000..1e11890
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example10Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 1000000000000 -> 0x1b 00 00 00 e8 d4 a5 10 00
+ */
+public class Example10Test extends AbstractNumberTest {
+
+    public Example10Test() {
+        super(1000000000000L, new byte[] { 0x1b, 0x00, 0x00, 0x00,
+                        (byte) 0xe8, (byte) 0xd4, (byte) 0xa5, 0x10, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example11Test.java b/src/test/java/co/nstant/in/cbor/examples/Example11Test.java
new file mode 100644
index 0000000..ec5240c
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example11Test.java
@@ -0,0 +1,19 @@
+package co.nstant.in.cbor.examples;
+
+import java.math.BigInteger;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * 18446744073709551615 -> 0x1bffffffffffffffff
+ */
+public class Example11Test extends AbstractNumberTest {
+
+    public Example11Test() {
+        super(new BigInteger("18446744073709551615"),
+                        new byte[] { 0x1b, (byte) 0xff, (byte) 0xff,
+                                        (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                                        (byte) 0xff, (byte) 0xff, (byte) 0xff });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example12Test.java b/src/test/java/co/nstant/in/cbor/examples/Example12Test.java
new file mode 100644
index 0000000..1f86e85
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example12Test.java
@@ -0,0 +1,52 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Tag;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+/**
+ * 18446744073709551616 -> 0xc249010000000000000000
+ */
+public class Example12Test {
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteArrayOutputStream);
+		encoder.encode(new UnsignedInteger(new BigInteger(
+				"18446744073709551616")));
+		Assert.assertArrayEquals(new byte[] { (byte) 0xc2, 0x49, 0x01, 0x00,
+				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+				byteArrayOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
+				new byte[] { (byte) 0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00,
+						0x00, 0x00, 0x00, 0x00 });
+		CborDecoder decoder = new CborDecoder(byteArrayInputStream);
+		DataItem b = decoder.decodeNext();
+
+		Assert.assertTrue(b.hasTag());
+		Tag tag = b.getTag();
+		Assert.assertEquals(2, tag.getValue());
+
+		Assert.assertTrue(b instanceof ByteString);
+		ByteString byteString = (ByteString) b;
+		Assert.assertArrayEquals(new byte[] { (byte) 0x01, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x00, 0x00 }, byteString.getBytes());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example13Test.java b/src/test/java/co/nstant/in/cbor/examples/Example13Test.java
new file mode 100644
index 0000000..6d172b9
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example13Test.java
@@ -0,0 +1,19 @@
+package co.nstant.in.cbor.examples;
+
+import java.math.BigInteger;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * -18446744073709551616 -> 0x3bffffffffffffffff
+ */
+public class Example13Test extends AbstractNumberTest {
+
+    public Example13Test() {
+        super(new BigInteger("-18446744073709551616"),
+                        new byte[] { (byte) 0x3b, (byte) 0xff, (byte) 0xff,
+                                        (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                                        (byte) 0xff, (byte) 0xff, (byte) 0xff });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example14Test.java b/src/test/java/co/nstant/in/cbor/examples/Example14Test.java
new file mode 100644
index 0000000..8cca685
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example14Test.java
@@ -0,0 +1,52 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.NegativeInteger;
+import co.nstant.in.cbor.model.Tag;
+
+/**
+ * -18446744073709551617 -> 0xc349010000000000000000
+ */
+public class Example14Test {
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteArrayOutputStream);
+		encoder.encode(new NegativeInteger(new BigInteger(
+				"-18446744073709551617")));
+		Assert.assertArrayEquals(new byte[] { (byte) 0xc3, 0x49, 0x01, 0x00,
+				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+				byteArrayOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(
+				new byte[] { (byte) 0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00,
+						0x00, 0x00, 0x00, 0x00 });
+		CborDecoder decoder = new CborDecoder(byteArrayInputStream);
+		DataItem b = decoder.decodeNext();
+
+		Assert.assertTrue(b.hasTag());
+		Tag tag = b.getTag();
+		Assert.assertEquals(3, tag.getValue());
+
+		Assert.assertTrue(b instanceof ByteString);
+		ByteString byteString = (ByteString) b;
+		Assert.assertArrayEquals(new byte[] { (byte) 0x01, 0x00, 0x00, 0x00,
+				0x00, 0x00, 0x00, 0x00, 0x00 }, byteString.getBytes());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example15Test.java b/src/test/java/co/nstant/in/cbor/examples/Example15Test.java
new file mode 100644
index 0000000..4a8ad1f
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example15Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * -1 -> 0x20
+ */
+public class Example15Test extends AbstractNumberTest {
+
+    public Example15Test() {
+        super(-1, new byte[] { 0x20 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example16Test.java b/src/test/java/co/nstant/in/cbor/examples/Example16Test.java
new file mode 100644
index 0000000..e3d63bc
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example16Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * -10 -> 0x29
+ */
+public class Example16Test extends AbstractNumberTest {
+
+    public Example16Test() {
+        super(-10, new byte[] { 0x29 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example17Test.java b/src/test/java/co/nstant/in/cbor/examples/Example17Test.java
new file mode 100644
index 0000000..f27607f
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example17Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * -100 -> 0x3863
+ */
+public class Example17Test extends AbstractNumberTest {
+
+    public Example17Test() {
+        super(-100, new byte[] { 0x38, 0x63 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example18Test.java b/src/test/java/co/nstant/in/cbor/examples/Example18Test.java
new file mode 100644
index 0000000..ff7cec0
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example18Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractNumberTest;
+
+/**
+ * -1000 -> 0x3903e7
+ */
+public class Example18Test extends AbstractNumberTest {
+
+    public Example18Test() {
+        super(-1000, new byte[] { 0x39, 0x03, (byte) 0xe7 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example19Test.java b/src/test/java/co/nstant/in/cbor/examples/Example19Test.java
new file mode 100644
index 0000000..d47b10a
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example19Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * 0.0 -> 0xf90000
+ */
+public class Example19Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example19Test() {
+        super(0.0f, new byte[] { (byte) 0xf9, 0x00, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example20Test.java b/src/test/java/co/nstant/in/cbor/examples/Example20Test.java
new file mode 100644
index 0000000..b20c298
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example20Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * Example 20: -0.0 -> 0xf98000
+ */
+public class Example20Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example20Test() {
+        super(-0.0f, new byte[] { (byte) 0xf9, (byte) 0x80, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example21Test.java b/src/test/java/co/nstant/in/cbor/examples/Example21Test.java
new file mode 100644
index 0000000..9d29b66
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example21Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * Example 21: 1.0 -> 0xf93c00
+ */
+public class Example21Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example21Test() {
+        super(1.0f, new byte[] { (byte) 0xf9, 0x3c, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example22Test.java b/src/test/java/co/nstant/in/cbor/examples/Example22Test.java
new file mode 100644
index 0000000..e79df39
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example22Test.java
@@ -0,0 +1,16 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractDoublePrecisionFloatTest;
+
+/**
+ * Example 22: 1.1 -> 0xfb 3f f1 99 99 99 99 99 9a
+ */
+public class Example22Test extends AbstractDoublePrecisionFloatTest {
+
+    public Example22Test() {
+        super(1.1d, new byte[] { (byte) 0xfb, 0x3f, (byte) 0xf1, (byte) 0x99,
+                        (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x99,
+                        (byte) 0x9a });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example23Test.java b/src/test/java/co/nstant/in/cbor/examples/Example23Test.java
new file mode 100644
index 0000000..e970b83
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example23Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * 1.5 -> 0xf93e00
+ */
+public class Example23Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example23Test() {
+        super(1.5f, new byte[] { (byte) 0xf9, 0x3e, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example24Test.java b/src/test/java/co/nstant/in/cbor/examples/Example24Test.java
new file mode 100644
index 0000000..b798523
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example24Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * 65504.0 -> 0xf97bff
+ */
+public class Example24Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example24Test() {
+        super(65504.0f, new byte[] { (byte) 0xf9, 0x7b, (byte) 0xff });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example25Test.java b/src/test/java/co/nstant/in/cbor/examples/Example25Test.java
new file mode 100644
index 0000000..f8ca008
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example25Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractSinglePrecisionFloatTest;
+
+/**
+ * 100000.0 -> 0xfa47c35000
+ */
+public class Example25Test extends AbstractSinglePrecisionFloatTest {
+
+    public Example25Test() {
+        super(100000.0f, new byte[] { (byte) 0xfa, 0x47, (byte) 0xc3, 0x50,
+                        0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example26Test.java b/src/test/java/co/nstant/in/cbor/examples/Example26Test.java
new file mode 100644
index 0000000..d3e5125
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example26Test.java
@@ -0,0 +1,16 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractSinglePrecisionFloatTest;
+
+/**
+ * 3.4028234663852886e+38 -> 0xfa 7f 7f ff ff
+ */
+public class Example26Test extends AbstractSinglePrecisionFloatTest {
+
+    public Example26Test() {
+        super(Float.parseFloat("3.4028234663852886e+38"), new byte[] {
+                        (byte) 0xfa, 0x7f, (byte) 0x7f, (byte) 0xff,
+                        (byte) 0xff });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example27Test.java b/src/test/java/co/nstant/in/cbor/examples/Example27Test.java
new file mode 100644
index 0000000..79f722a
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example27Test.java
@@ -0,0 +1,16 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractDoublePrecisionFloatTest;
+
+/**
+ * 1.0e+300 -> 0xfb 7e 37 e4 3c 88 00 75 9c
+ */
+public class Example27Test extends AbstractDoublePrecisionFloatTest {
+
+    public Example27Test() {
+        super(Double.parseDouble("1.0e+300"), new byte[] {
+                        (byte) 0xfb, 0x7e, 0x37, (byte) 0xe4, 0x3c,
+                        (byte) 0x88, 0x00, 0x75, (byte) 0x9c });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example28Test.java b/src/test/java/co/nstant/in/cbor/examples/Example28Test.java
new file mode 100644
index 0000000..b15ebbc
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example28Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * 5.960464477539063e-08 -> 0xf90001
+ */
+public class Example28Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example28Test() {
+        super(Float.parseFloat("5.960464477539063e-08"), new byte[] {
+                        (byte) 0xf9, 0x00, 0x01 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example29Test.java b/src/test/java/co/nstant/in/cbor/examples/Example29Test.java
new file mode 100644
index 0000000..2def77e
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example29Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * 6.103515625e-05 -> 0xf90400
+ */
+public class Example29Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example29Test() {
+        super(Float.parseFloat("6.103515625e-05"), new byte[] {
+                        (byte) 0xf9, 0x04, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example30Test.java b/src/test/java/co/nstant/in/cbor/examples/Example30Test.java
new file mode 100644
index 0000000..8f8db91
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example30Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * -4,0 -> 0xf9c400
+ */
+public class Example30Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example30Test() {
+        super(Float.parseFloat("-4.0"), new byte[] {
+                        (byte) 0xf9, (byte) 0xc4, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example31Test.java b/src/test/java/co/nstant/in/cbor/examples/Example31Test.java
new file mode 100644
index 0000000..e2d4391
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example31Test.java
@@ -0,0 +1,16 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractDoublePrecisionFloatTest;
+
+/**
+ * -4.1 -> 0xfb c0 10 66 66 66 66 66 66
+ */
+public class Example31Test extends AbstractDoublePrecisionFloatTest {
+
+    public Example31Test() {
+        super(Double.parseDouble("-4.1"), new byte[] {
+                        (byte) 0xfb, (byte) 0xc0, 0x10, 0x66, 0x66, 0x66, 0x66,
+                        0x66, 0x66 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example32Test.java b/src/test/java/co/nstant/in/cbor/examples/Example32Test.java
new file mode 100644
index 0000000..d5315e6
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example32Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * Infinity -> 0xf97c00
+ */
+public class Example32Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example32Test() {
+        super(Float.POSITIVE_INFINITY, new byte[] {
+                        (byte) 0xf9, 0x7c, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example33Test.java b/src/test/java/co/nstant/in/cbor/examples/Example33Test.java
new file mode 100644
index 0000000..5118f62
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example33Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * NaN -> 0xf97e00
+ */
+public class Example33Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example33Test() {
+        super(Float.NaN, new byte[] {
+                        (byte) 0xf9, 0x7e, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example34Test.java b/src/test/java/co/nstant/in/cbor/examples/Example34Test.java
new file mode 100644
index 0000000..38c4583
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example34Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractHalfPrecisionFloatTest;
+
+/**
+ * -Infinity -> 0xf9fc00
+ */
+public class Example34Test extends AbstractHalfPrecisionFloatTest {
+
+    public Example34Test() {
+        super(Float.NEGATIVE_INFINITY, new byte[] {
+                        (byte) 0xf9, (byte) 0xfc, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example35Test.java b/src/test/java/co/nstant/in/cbor/examples/Example35Test.java
new file mode 100644
index 0000000..a709a6b
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example35Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractSinglePrecisionFloatTest;
+
+/**
+ * Infinity -> 0xfa 7f 80 00 00
+ */
+public class Example35Test extends AbstractSinglePrecisionFloatTest {
+
+    public Example35Test() {
+        super(Float.POSITIVE_INFINITY, new byte[] {
+                        (byte) 0xfa, 0x7f, (byte) 0x80, 0x00, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example36Test.java b/src/test/java/co/nstant/in/cbor/examples/Example36Test.java
new file mode 100644
index 0000000..1e056fe
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example36Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractSinglePrecisionFloatTest;
+
+/**
+ * NaN -> 0xfa7fc00000
+ */
+public class Example36Test extends AbstractSinglePrecisionFloatTest {
+
+    public Example36Test() {
+        super(Float.NaN, new byte[] {
+                        (byte) 0xfa, 0x7f, (byte) 0xc0, 0x00, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example37Test.java b/src/test/java/co/nstant/in/cbor/examples/Example37Test.java
new file mode 100644
index 0000000..b5d0a47
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example37Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractSinglePrecisionFloatTest;
+
+/**
+ * -Infinity -> 0xfa ff 80 00 00
+ */
+public class Example37Test extends AbstractSinglePrecisionFloatTest {
+
+    public Example37Test() {
+        super(Float.NEGATIVE_INFINITY, new byte[] {
+                        (byte) 0xfa, (byte) 0xff, (byte) 0x80, 0x00, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example38Test.java b/src/test/java/co/nstant/in/cbor/examples/Example38Test.java
new file mode 100644
index 0000000..671b7d7
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example38Test.java
@@ -0,0 +1,16 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractDoublePrecisionFloatTest;
+
+/**
+ * Infinity -> 0xfb 7f f0 00 00 00 00 00 00
+ */
+public class Example38Test extends AbstractDoublePrecisionFloatTest {
+
+    public Example38Test() {
+        super(Double.POSITIVE_INFINITY, new byte[] {
+                        (byte) 0xfb, 0x7f, (byte) 0xf0, 0x00, 0x00, 0x00, 0x00,
+                        0x00, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example39Test.java b/src/test/java/co/nstant/in/cbor/examples/Example39Test.java
new file mode 100644
index 0000000..501a231
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example39Test.java
@@ -0,0 +1,16 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractDoublePrecisionFloatTest;
+
+/**
+ * NaN -> 0xfb 7f f8 00 00 00 00 00 00
+ */
+public class Example39Test extends AbstractDoublePrecisionFloatTest {
+
+    public Example39Test() {
+        super(Double.NaN, new byte[] {
+                        (byte) 0xfb, 0x7f, (byte) 0xf8, 0x00, 0x00, 0x00, 0x00,
+                        0x00, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example40Test.java b/src/test/java/co/nstant/in/cbor/examples/Example40Test.java
new file mode 100644
index 0000000..cb633ea
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example40Test.java
@@ -0,0 +1,16 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractDoublePrecisionFloatTest;
+
+/**
+ * -Infinity -> 0xfb ff f0 00 00 00 00 00 00
+ */
+public class Example40Test extends AbstractDoublePrecisionFloatTest {
+
+    public Example40Test() {
+        super(Double.NEGATIVE_INFINITY, new byte[] {
+                        (byte) 0xfb, (byte) 0xff, (byte) 0xf0, 0x00, 0x00,
+                        0x00, 0x00, 0x00, 0x00 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example41Test.java b/src/test/java/co/nstant/in/cbor/examples/Example41Test.java
new file mode 100644
index 0000000..f74daaa
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example41Test.java
@@ -0,0 +1,41 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+
+/**
+ * false -> 0xf4
+ */
+public class Example41Test {
+
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xf4 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(SimpleValue.FALSE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof SimpleValue);
+        SimpleValue simpleValue = (SimpleValue) dataItem;
+        Assert.assertEquals(SimpleValue.FALSE, simpleValue);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example42Test.java b/src/test/java/co/nstant/in/cbor/examples/Example42Test.java
new file mode 100644
index 0000000..4f2d99f
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example42Test.java
@@ -0,0 +1,41 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+
+/**
+ * true -> 0xf5
+ */
+public class Example42Test {
+
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xf5 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(SimpleValue.TRUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof SimpleValue);
+        SimpleValue simpleValue = (SimpleValue) dataItem;
+        Assert.assertEquals(SimpleValue.TRUE, simpleValue);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example43Test.java b/src/test/java/co/nstant/in/cbor/examples/Example43Test.java
new file mode 100644
index 0000000..e59e5c3
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example43Test.java
@@ -0,0 +1,41 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+
+/**
+ * nil -> 0xf6
+ */
+public class Example43Test {
+
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xf6 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(SimpleValue.NULL);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof SimpleValue);
+        SimpleValue simpleValue = (SimpleValue) dataItem;
+        Assert.assertEquals(SimpleValue.NULL, simpleValue);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example44Test.java b/src/test/java/co/nstant/in/cbor/examples/Example44Test.java
new file mode 100644
index 0000000..f5a23ab
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example44Test.java
@@ -0,0 +1,41 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+
+/**
+ * undefined -> 0xf7
+ */
+public class Example44Test {
+
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xf7 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(SimpleValue.UNDEFINED);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof SimpleValue);
+        SimpleValue simpleValue = (SimpleValue) dataItem;
+        Assert.assertEquals(SimpleValue.UNDEFINED, simpleValue);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example45Test.java b/src/test/java/co/nstant/in/cbor/examples/Example45Test.java
new file mode 100644
index 0000000..be5d75b
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example45Test.java
@@ -0,0 +1,42 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+
+/**
+ * simple(16) -> 0xf0
+ */
+public class Example45Test {
+
+    private static final SimpleValue VALUE = new SimpleValue(16);
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xf0 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof SimpleValue);
+        SimpleValue simpleValue = (SimpleValue) dataItem;
+        Assert.assertEquals(VALUE, simpleValue);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example46Test.java b/src/test/java/co/nstant/in/cbor/examples/Example46Test.java
new file mode 100644
index 0000000..146c3f2
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example46Test.java
@@ -0,0 +1,42 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+
+/**
+ * simple(24) -> 0xf818
+ */
+public class Example46Test {
+
+    private static final SimpleValue VALUE = new SimpleValue(24);
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xf8, 0x18 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof SimpleValue);
+        SimpleValue simpleValue = (SimpleValue) dataItem;
+        Assert.assertEquals(VALUE, simpleValue);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example47Test.java b/src/test/java/co/nstant/in/cbor/examples/Example47Test.java
new file mode 100644
index 0000000..2c3ca8d
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example47Test.java
@@ -0,0 +1,43 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+
+/**
+ * simple(255) -> 0xf8ff
+ */
+public class Example47Test {
+
+    private static final SimpleValue VALUE = new SimpleValue(255);
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xf8,
+                    (byte) 0xff };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof SimpleValue);
+        SimpleValue simpleValue = (SimpleValue) dataItem;
+        Assert.assertEquals(VALUE, simpleValue);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example48Test.java b/src/test/java/co/nstant/in/cbor/examples/Example48Test.java
new file mode 100644
index 0000000..878bfaf
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example48Test.java
@@ -0,0 +1,52 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.UnicodeString;
+
+/**
+ * 0("2013-03-21T20:04:00Z") -> 0xc074323031332d30332d32315432303a30343a30305a
+ */
+public class Example48Test {
+
+	private final List<DataItem> VALUE;
+
+	private final byte[] ENCODED_VALUE = new byte[] { (byte) 0xc0, 0x74,
+			0x32, 0x30, 0x31, 0x33, 0x2d, 0x30, 0x33, 0x2d, 0x32, 0x31, 0x54,
+			0x32, 0x30, 0x3a, 0x30, 0x34, 0x3a, 0x30, 0x30, 0x5a };
+
+	public Example48Test() {
+		DataItem di = new UnicodeString("2013-03-21T20:04:00Z");
+		di.setTag(0);
+
+		VALUE = new CborBuilder().add(di).build();
+	}
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example49Test.java b/src/test/java/co/nstant/in/cbor/examples/Example49Test.java
new file mode 100644
index 0000000..1978935
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example49Test.java
@@ -0,0 +1,51 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+/**
+ * 1(1363896240) -> 0xc11a514b67b0
+ */
+public class Example49Test {
+
+	private final List<DataItem> VALUE;
+
+	private final byte[] ENCODED_VALUE = new byte[] { (byte) 0xc1, 0x1a,
+			0x51, 0x4b, 0x67, (byte) 0xb0 };
+
+	public Example49Test() {
+		DataItem di = new UnsignedInteger(1363896240);
+		di.setTag(1);
+
+		VALUE = new CborBuilder().add(di).build();
+	}
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example50Test.java b/src/test/java/co/nstant/in/cbor/examples/Example50Test.java
new file mode 100644
index 0000000..daa67b3
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example50Test.java
@@ -0,0 +1,52 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.DoublePrecisionFloat;
+
+/**
+ * 1(1363896240.5) -> 0xc1fb41d452d9ec200000
+ */
+public class Example50Test {
+
+	private final List<DataItem> VALUE;
+
+	private final byte[] ENCODED_VALUE = new byte[] { (byte) 0xc1,
+			(byte) 0xfb, 0x41, (byte) 0xd4, 0x52, (byte) 0xd9, (byte) 0xec,
+			0x20, 0x00, 0x00 };
+
+	public Example50Test() {
+		DataItem di = new DoublePrecisionFloat(1363896240.5);
+		di.setTag(1);
+
+		VALUE = new CborBuilder().add(di).build();
+	}
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example51Test.java b/src/test/java/co/nstant/in/cbor/examples/Example51Test.java
new file mode 100644
index 0000000..7784881
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example51Test.java
@@ -0,0 +1,52 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * 23(h'01020304') -> 0xd74401020304
+ */
+public class Example51Test {
+
+	private final List<DataItem> VALUE;
+
+	private final byte[] ENCODED_VALUE = new byte[] { (byte) 0xd7, 0x44,
+			0x01, 0x02, 0x03, 0x04 };
+
+	public Example51Test() {
+		DataItem di = new ByteString(new byte[] { 0x01, 0x02, 0x03, 0x04 });
+		di.setTag(23);
+
+		VALUE = new CborBuilder().add(di).build();
+	}
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example52Test.java b/src/test/java/co/nstant/in/cbor/examples/Example52Test.java
new file mode 100644
index 0000000..cd78367
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example52Test.java
@@ -0,0 +1,51 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * 24(h'6449455446') -> 0xd818456449455446
+ */
+public class Example52Test {
+
+	private final List<DataItem> VALUE;
+
+	private final byte[] ENCODED_VALUE = new byte[] { (byte) 0xd8, 0x18,
+			0x45, 0x64, 0x49, 0x45, 0x54, 0x46 };
+
+	public Example52Test() {
+		DataItem di = new ByteString(new byte[] { 0x64, 0x49, 0x45, 0x54, 0x46 });
+		di.setTag(24);
+
+		VALUE = new CborBuilder().add(di).build();
+	}
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example53Test.java b/src/test/java/co/nstant/in/cbor/examples/Example53Test.java
new file mode 100644
index 0000000..dd0de16
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example53Test.java
@@ -0,0 +1,54 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.UnicodeString;
+
+/**
+ * 32("http://www.example.com") ->
+ * 0xd82076687474703a2f2f7777772e6578616d706c652e636f6d
+ */
+public class Example53Test {
+
+	private final List<DataItem> VALUE;
+
+	private final byte[] ENCODED_VALUE = new byte[] { (byte) 0xd8, 0x20,
+			0x76, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77,
+			0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f,
+			0x6d };
+
+	public Example53Test() {
+		DataItem di = new UnicodeString("http://www.example.com");
+		di.setTag(32);
+
+		VALUE = new CborBuilder().add(di).build();
+	}
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example54Test.java b/src/test/java/co/nstant/in/cbor/examples/Example54Test.java
new file mode 100644
index 0000000..026a699
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example54Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractByteStringTest;
+
+/**
+ * h'' -> 0x40
+ */
+public class Example54Test extends AbstractByteStringTest {
+
+    public Example54Test() {
+        super(new byte[] {}, new byte[] { 0x40 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example55Test.java b/src/test/java/co/nstant/in/cbor/examples/Example55Test.java
new file mode 100644
index 0000000..5191dbc
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example55Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractByteStringTest;
+
+/**
+ * h'01020304' -> 0x4401020304
+ */
+public class Example55Test extends AbstractByteStringTest {
+
+    public Example55Test() {
+        super(new byte[] { 0x01, 0x02, 0x03, 0x04 },
+                        new byte[] { 0x44, 0x01, 0x02, 0x03, 0x04 });
+    }
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example56Test.java b/src/test/java/co/nstant/in/cbor/examples/Example56Test.java
new file mode 100644
index 0000000..49680c6
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example56Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractStringTest;
+
+
+/**
+ * "" -> 0x60
+ */
+public class Example56Test extends AbstractStringTest {
+
+    public Example56Test() {
+        super("", new byte[] { 0x60 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example57Test.java b/src/test/java/co/nstant/in/cbor/examples/Example57Test.java
new file mode 100644
index 0000000..eb1f866
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example57Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractStringTest;
+
+/**
+ * "a" -> 0x6161
+ */
+public class Example57Test extends AbstractStringTest {
+
+    public Example57Test() {
+        super("a", new byte[] { 0x61, 0x61 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example58Test.java b/src/test/java/co/nstant/in/cbor/examples/Example58Test.java
new file mode 100644
index 0000000..53b3555
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example58Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractStringTest;
+
+/**
+ * "IETF" -> 0x64 49 45 54 46
+ */
+public class Example58Test extends AbstractStringTest {
+
+    public Example58Test() {
+        super("IETF", new byte[] { 0x64, 0x49, 0x45, 0x54, 0x46 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example59Test.java b/src/test/java/co/nstant/in/cbor/examples/Example59Test.java
new file mode 100644
index 0000000..c2afbac
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example59Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractStringTest;
+
+/**
+ * "\"\\" -> 0x62225c
+ */
+public class Example59Test extends AbstractStringTest {
+
+    public Example59Test() {
+        super("\"\\", new byte[] { 0x62, 0x22, 0x5c });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example60Test.java b/src/test/java/co/nstant/in/cbor/examples/Example60Test.java
new file mode 100644
index 0000000..89f1fd4
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example60Test.java
@@ -0,0 +1,14 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractStringTest;
+
+/**
+ * "\u00fc" -> 0x62c3bc
+ */
+public class Example60Test extends AbstractStringTest {
+
+    public Example60Test() {
+        super("\u00fc", new byte[] { 0x62, (byte) 0xc3, (byte) 0xbc });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example61Test.java b/src/test/java/co/nstant/in/cbor/examples/Example61Test.java
new file mode 100644
index 0000000..4b6cc91
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example61Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractStringTest;
+
+/**
+ * "\u6c34" -> 0x63e6b0b4
+ */
+public class Example61Test extends AbstractStringTest {
+
+    public Example61Test() {
+        super("\u6c34", new byte[] { 0x63, (byte) 0xe6, (byte) 0xb0,
+                        (byte) 0xb4 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example62Test.java b/src/test/java/co/nstant/in/cbor/examples/Example62Test.java
new file mode 100644
index 0000000..8711b0c
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example62Test.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.examples;
+
+import co.nstant.in.cbor.model.AbstractStringTest;
+
+/**
+ * "\ud800\udd51" -> 0x64f0908591
+ */
+public class Example62Test extends AbstractStringTest {
+
+    public Example62Test() {
+        super("\ud800\udd51", new byte[] { 0x64, (byte) 0xf0, (byte) 0x90,
+                        (byte) 0x85, (byte) 0x91 });
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example63Test.java b/src/test/java/co/nstant/in/cbor/examples/Example63Test.java
new file mode 100644
index 0000000..24b9330
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example63Test.java
@@ -0,0 +1,42 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * [] -> 0x80
+ */
+public class Example63Test {
+
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0x80 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(new CborBuilder().addArray().end().build());
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof Array);
+        Array array = (Array) dataItem;
+        Assert.assertTrue(array.getDataItems().isEmpty());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example64Test.java b/src/test/java/co/nstant/in/cbor/examples/Example64Test.java
new file mode 100644
index 0000000..8ddffb0
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example64Test.java
@@ -0,0 +1,51 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Number;
+
+/**
+ * [1, 2, 3] -> 0x83010203
+ */
+public class Example64Test {
+
+	private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0x83, 0x01,
+			0x02, 0x03 };
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(new CborBuilder().addArray().add(1).add(2).add(3).end()
+				.build());
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		DataItem dataItem = decoder.decodeNext();
+		Assert.assertTrue(dataItem instanceof Array);
+		Array array = (Array) dataItem;
+		Assert.assertEquals(3, array.getDataItems().size());
+		Assert.assertEquals(1, ((Number) array.getDataItems().get(0))
+				.getValue().intValue());
+		Assert.assertEquals(2, ((Number) array.getDataItems().get(1))
+				.getValue().intValue());
+		Assert.assertEquals(3, ((Number) array.getDataItems().get(2))
+				.getValue().intValue());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example65Test.java b/src/test/java/co/nstant/in/cbor/examples/Example65Test.java
new file mode 100644
index 0000000..6c6aff6
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example65Test.java
@@ -0,0 +1,89 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Number;
+
+/**
+ * [1, [2, 3], [4, 5]] -> 0x83 01 82 02 03 82 04 05
+ */
+public class Example65Test {
+
+	private static final List<DataItem> VALUE = new CborBuilder().addArray()
+			.add(1).addArray().add(2).add(3).end().addArray().add(4).add(5)
+			.end().end().build();
+
+	private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0x83, 0x01,
+			(byte) 0x82, 0x02, 0x03, (byte) 0x82, 0x04, 0x05 };
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		DataItem dataItem = decoder.decodeNext();
+		Assert.assertTrue(dataItem instanceof Array);
+		Array array = (Array) dataItem;
+		Assert.assertEquals(3, array.getDataItems().size());
+
+		DataItem dataItem1 = array.getDataItems().get(0);
+		DataItem dataItem2 = array.getDataItems().get(1);
+		DataItem dataItem3 = array.getDataItems().get(2);
+
+		Assert.assertTrue(dataItem1 instanceof Number);
+		Assert.assertTrue(dataItem2 instanceof Array);
+		Assert.assertTrue(dataItem3 instanceof Array);
+
+		Number number = (Number) dataItem1;
+		Array array1 = (Array) dataItem2;
+		Array array2 = (Array) dataItem3;
+
+		Assert.assertEquals(1, number.getValue().intValue());
+		Assert.assertEquals(2, array1.getDataItems().size());
+		Assert.assertEquals(2, array2.getDataItems().size());
+
+		DataItem array1item1 = array1.getDataItems().get(0);
+		DataItem array1item2 = array1.getDataItems().get(1);
+
+		Assert.assertTrue(array1item1 instanceof Number);
+		Assert.assertTrue(array1item2 instanceof Number);
+
+		Number array1number1 = (Number) array1item1;
+		Number array1number2 = (Number) array1item2;
+
+		Assert.assertEquals(2, array1number1.getValue().intValue());
+		Assert.assertEquals(3, array1number2.getValue().intValue());
+
+		DataItem array2item1 = array2.getDataItems().get(0);
+		DataItem array2item2 = array2.getDataItems().get(1);
+
+		Assert.assertTrue(array2item1 instanceof Number);
+		Assert.assertTrue(array2item2 instanceof Number);
+
+		Number array2number1 = (Number) array2item1;
+		Number array2number2 = (Number) array2item2;
+
+		Assert.assertEquals(4, array2number1.getValue().intValue());
+		Assert.assertEquals(5, array2number2.getValue().intValue());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example66Test.java b/src/test/java/co/nstant/in/cbor/examples/Example66Test.java
new file mode 100644
index 0000000..32f9a6d
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example66Test.java
@@ -0,0 +1,55 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * [1, 2, 3, 4, 5, 6,7, 8, 9, 10, 11, 12,13, 14, 15, 16, 17,18, 19, 20, 21, 22,
+ * 23, 24, 25] -> 0x98 190102030405060708090a0b0c0d0e0f101112131415161718181819
+ */
+public class Example66Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .addArray()
+                    .add(1).add(2).add(3).add(4).add(5)
+                    .add(6).add(7).add(8).add(9).add(10)
+                    .add(11).add(12).add(13).add(14).add(15)
+                    .add(16).add(17).add(18).add(19).add(20)
+                    .add(21).add(22).add(23).add(24).add(25)
+                    .end()
+                    .build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0x98,
+                    0x19, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+                    0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+                    0x14, 0x15, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19
+    };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example67Test.java b/src/test/java/co/nstant/in/cbor/examples/Example67Test.java
new file mode 100644
index 0000000..c4091bf
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example67Test.java
@@ -0,0 +1,43 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * {} -> 0xa0
+ */
+public class Example67Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .addMap().end().build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xa0 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example68Test.java b/src/test/java/co/nstant/in/cbor/examples/Example68Test.java
new file mode 100644
index 0000000..0bca2c4
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example68Test.java
@@ -0,0 +1,44 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * {1: 2, 3: 4} -> 0xa201020304
+ */
+public class Example68Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .addMap().put(1, 2).put(3, 4).end().build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] {
+                    (byte) 0xa2, 0x01, 0x02, 0x03, 0x04 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example69Test.java b/src/test/java/co/nstant/in/cbor/examples/Example69Test.java
new file mode 100644
index 0000000..ba78f42
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example69Test.java
@@ -0,0 +1,46 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * {"a": 1, "b": [2,3]} -> 0xa26161016162820203
+ */
+public class Example69Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .addMap().put("a", 1).putArray("b").add(2).add(3).end()
+                    .end().build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] {
+                    (byte) 0xa2, 0x61, 0x61, 0x01, 0x61, 0x62, (byte) 0x82,
+                    0x02, 0x03 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example70Test.java b/src/test/java/co/nstant/in/cbor/examples/Example70Test.java
new file mode 100644
index 0000000..6703ffe
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example70Test.java
@@ -0,0 +1,50 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * ["a", {"b": "c"}] -> 0x826161a161626163
+ */
+public class Example70Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .addArray()
+                    .add("a")
+                    .addMap()
+                    .put("b", "c")
+                    .end()
+                    .end()
+                    .build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0x82, 0x61,
+                    0x61, (byte) 0xa1, 0x61, 0x62, 0x61, 0x63 };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example71Test.java b/src/test/java/co/nstant/in/cbor/examples/Example71Test.java
new file mode 100644
index 0000000..11d5edf
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example71Test.java
@@ -0,0 +1,54 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * {"a": "A", "b": "B", "c": "C", "d": "D","e": "E"} ->
+ * 0xa56161614161626142616361436164614461656145
+ */
+public class Example71Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .addMap()
+                    .put("a", "A")
+                    .put("b", "B")
+                    .put("c", "C")
+                    .put("d", "D")
+                    .put("e", "E")
+                    .end()
+                    .build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xa5, 0x61,
+                    0x61, 0x61, 0x41, 0x61, 0x62, 0x61, 0x42, 0x61, 0x63, 0x61,
+                    0x43, 0x61, 0x64, 0x61, 0x44, 0x61, 0x65, 0x61, 0x45
+    };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example72Test.java b/src/test/java/co/nstant/in/cbor/examples/Example72Test.java
new file mode 100644
index 0000000..374b317
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example72Test.java
@@ -0,0 +1,50 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * (_ h'0102', h'030405') -> 0x5f42010243030405ff
+ */
+public class Example72Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .startByteString()
+                    .add(new byte[] { 0x01, 0x02 })
+                    .add(new byte[] { 0x03, 0x04, 0x05 })
+                    .end()
+                    .build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] {
+                    0x5f, 0x42, 0x01, 0x02, 0x43, 0x03, 0x04, 0x05, (byte) 0xff
+    };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        decoder.setAutoDecodeInfinitiveByteStrings(false);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example73Test.java b/src/test/java/co/nstant/in/cbor/examples/Example73Test.java
new file mode 100644
index 0000000..ebb12dc
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example73Test.java
@@ -0,0 +1,51 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * (_ "strea", "ming") -> 0x7f657374726561646d696e67ff
+ */
+public class Example73Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .startString()
+                    .add("strea")
+                    .add("ming")
+                    .end()
+                    .build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] {
+                    0x7f, 0x65, 0x73, 0x74, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x69,
+                    0x6e, 0x67, (byte) 0xff
+    };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        decoder.setAutoDecodeInfinitiveUnicodeStrings(false);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example74Test.java b/src/test/java/co/nstant/in/cbor/examples/Example74Test.java
new file mode 100644
index 0000000..e3c22d5
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example74Test.java
@@ -0,0 +1,49 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+
+/**
+ * [_ ] -> 0x9fff
+ */
+public class Example74Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .add(new Array().setChunked(true))
+                    .add(SimpleValue.BREAK)
+                    .build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] {
+                    (byte) 0x9f, (byte) 0xff };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        decoder.setAutoDecodeInfinitiveArrays(false);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example75Test.java b/src/test/java/co/nstant/in/cbor/examples/Example75Test.java
new file mode 100644
index 0000000..ede4fce
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example75Test.java
@@ -0,0 +1,56 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+
+/**
+ * [_ 1, [2, 3], [_ 4, 5]] -> 0x9f018202039f0405ffff
+ */
+public class Example75Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .add(new Array().setChunked(true))
+                    .add(1)
+                    .addArray().add(2).add(3).end()
+                    .add(new Array().setChunked(true))
+                    .add(4)
+                    .add(5)
+                    .add(SimpleValue.BREAK)
+                    .add(SimpleValue.BREAK)
+                    .build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] {
+                    (byte) 0x9f, 0x01, (byte) 0x82, 0x02, 0x03, (byte) 0x9f,
+                    0x04, 0x05, (byte) 0xff, (byte) 0xff };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        decoder.setAutoDecodeInfinitiveArrays(false);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example76Test.java b/src/test/java/co/nstant/in/cbor/examples/Example76Test.java
new file mode 100644
index 0000000..5a0c017
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example76Test.java
@@ -0,0 +1,53 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SimpleValue;
+
+/**
+ * [_ 1, [2, 3], [4,5]] -> 0x9f01820203820405ff
+ */
+public class Example76Test {
+
+    private static final List<DataItem> VALUE = new CborBuilder()
+                    .add(new Array().setChunked(true))
+                    .add(1)
+                    .addArray().add(2).add(3).end()
+                    .addArray().add(4).add(5).end()
+                    .add(SimpleValue.BREAK)
+                    .build();
+
+    private static final byte[] ENCODED_VALUE = new byte[] {
+                    (byte) 0x9f, 0x01, (byte) 0x82, 0x02, 0x03, (byte) 0x82,
+                    0x04, 0x05, (byte) 0xff };
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(VALUE);
+        Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        decoder.setAutoDecodeInfinitiveArrays(false);
+        List<DataItem> dataItems = decoder.decode();
+        Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example77Test.java b/src/test/java/co/nstant/in/cbor/examples/Example77Test.java
new file mode 100644
index 0000000..199cf57
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example77Test.java
@@ -0,0 +1,45 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * [1, [2, 3], [_ 4,5]] -> 0x83 01 82 02 03 9f 04 05 ff
+ */
+public class Example77Test {
+
+	private static final List<DataItem> VALUE = new CborBuilder().addArray()
+			.add(1).addArray().add(2).add(3).end().startArray().add(4).add(5)
+			.end().end().build();
+
+	private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0x83, 0x01,
+			(byte) 0x82, 0x02, 0x03, (byte) 0x9f, 0x04, 0x05, (byte) 0xff };
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example78Test.java b/src/test/java/co/nstant/in/cbor/examples/Example78Test.java
new file mode 100644
index 0000000..d785fb4
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example78Test.java
@@ -0,0 +1,45 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * [1, [_ 2, 3], [4,5]] -> 0x83019f0203ff820405
+ */
+public class Example78Test {
+
+	private static final List<DataItem> VALUE = new CborBuilder().addArray()
+			.add(1).startArray().add(2).add(3).end().addArray().add(4).add(5)
+			.end().end().build();
+
+	private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0x83, 0x01,
+			(byte) 0x9f, 0x02, 0x03, (byte) 0xff, (byte) 0x82, 0x04, 0x05 };
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example79Test.java b/src/test/java/co/nstant/in/cbor/examples/Example79Test.java
new file mode 100644
index 0000000..bb881be
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example79Test.java
@@ -0,0 +1,51 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * [_ 1, 2, 3, 4, 5, 6,7, 8, 9, 10, 11, 12,13, 14, 15, 16, 17,18, 19, 20, 21,
+ * 22,23, 24, 25] ->
+ * 0x9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff
+ */
+public class Example79Test {
+
+	private static final List<DataItem> VALUE = new CborBuilder().startArray()
+			.add(1).add(2).add(3).add(4).add(5).add(6).add(7).add(8).add(9)
+			.add(10).add(11).add(12).add(13).add(14).add(15).add(16).add(17)
+			.add(18).add(19).add(20).add(21).add(22).add(23).add(24).add(25)
+			.end().build();
+
+	private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0x9f, 0x01,
+			0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
+			0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+			0x18, 0x18, 0x18, 0x19, (byte) 0xff };
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example80Test.java b/src/test/java/co/nstant/in/cbor/examples/Example80Test.java
new file mode 100644
index 0000000..9d7c011
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example80Test.java
@@ -0,0 +1,46 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * {_ "a": 1, "b": [_ 2, 3]} -> 0xbf61610161629f0203ffff
+ */
+public class Example80Test {
+
+	private static final List<DataItem> VALUE = new CborBuilder().startMap()
+			.put("a", 1).put("b", 2).startArray("b").add(2).add(3).end().end()
+			.build();
+
+	private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xbf, 0x61,
+			0x61, 0x01, 0x61, 0x62, (byte) 0x9f, 0x02, 0x03, (byte) 0xff,
+			(byte) 0xff };
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example81Test.java b/src/test/java/co/nstant/in/cbor/examples/Example81Test.java
new file mode 100644
index 0000000..c6b0b16
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example81Test.java
@@ -0,0 +1,44 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * ["a", {_ "b": "c"}] -> 0x826161bf61626163ff
+ */
+public class Example81Test {
+
+	private static final List<DataItem> VALUE = new CborBuilder().addArray()
+			.add("a").startMap().put("b", "c").end().end().build();
+
+	private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0x82, 0x61,
+			0x61, (byte) 0xbf, 0x61, 0x62, 0x61, 0x63, (byte) 0xff };
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/examples/Example82Test.java b/src/test/java/co/nstant/in/cbor/examples/Example82Test.java
new file mode 100644
index 0000000..2998438
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/examples/Example82Test.java
@@ -0,0 +1,45 @@
+package co.nstant.in.cbor.examples;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * {_ "Fun": true, "Amt": -2} -> 0xbf6346756ef563416d7421ff
+ */
+public class Example82Test {
+
+	private static final List<DataItem> VALUE = new CborBuilder().startMap()
+			.put("Fun", true).put("Amt", -2).end().build();
+
+	private static final byte[] ENCODED_VALUE = new byte[] { (byte) 0xbf, 0x63,
+			0x46, 0x75, 0x6e, (byte) 0xf5, 0x63, 0x41, 0x6d, 0x74, 0x21,
+			(byte) 0xff };
+
+	@Test
+	public void shouldEncode() throws CborException {
+		ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteOutputStream);
+		encoder.encode(VALUE);
+		Assert.assertArrayEquals(ENCODED_VALUE, byteOutputStream.toByteArray());
+	}
+
+	@Test
+	public void shouldDecode() throws CborException {
+		InputStream inputStream = new ByteArrayInputStream(ENCODED_VALUE);
+		CborDecoder decoder = new CborDecoder(inputStream);
+		List<DataItem> dataItems = decoder.decode();
+		Assert.assertArrayEquals(VALUE.toArray(), dataItems.toArray());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/AbstractByteStringTest.java b/src/test/java/co/nstant/in/cbor/model/AbstractByteStringTest.java
new file mode 100644
index 0000000..5cf1044
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/AbstractByteStringTest.java
@@ -0,0 +1,44 @@
+package co.nstant.in.cbor.model;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+
+public abstract class AbstractByteStringTest {
+
+    private final byte[] value;
+    private final byte[] encodedValue;
+
+    public AbstractByteStringTest(byte[] value, byte[] encodedValue) {
+        this.value = value;
+        this.encodedValue = encodedValue;
+    }
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(new ByteString(value));
+        Assert.assertArrayEquals(encodedValue, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(encodedValue);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof ByteString);
+        ByteString byteString = (ByteString) dataItem;
+        Assert.assertArrayEquals(value, byteString.getBytes());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/AbstractDataItemTest.java b/src/test/java/co/nstant/in/cbor/model/AbstractDataItemTest.java
new file mode 100644
index 0000000..a2f76b2
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/AbstractDataItemTest.java
@@ -0,0 +1,24 @@
+package co.nstant.in.cbor.model;
+
+import java.io.ByteArrayOutputStream;
+
+import org.junit.Assert;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+
+public abstract class AbstractDataItemTest {
+
+	protected void shouldEncodeAndDecode(String message, DataItem dataItem)
+			throws CborException {
+		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+		CborEncoder encoder = new CborEncoder(byteArrayOutputStream);
+		encoder.encode(dataItem);
+		byte[] bytes = byteArrayOutputStream.toByteArray();
+		DataItem object = CborDecoder.decode(bytes).get(0);
+		Assert.assertEquals(message, dataItem, object);
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/AbstractDoublePrecisionFloatTest.java b/src/test/java/co/nstant/in/cbor/model/AbstractDoublePrecisionFloatTest.java
new file mode 100644
index 0000000..b851539
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/AbstractDoublePrecisionFloatTest.java
@@ -0,0 +1,47 @@
+package co.nstant.in.cbor.model;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.DoublePrecisionFloat;
+
+public abstract class AbstractDoublePrecisionFloatTest {
+
+    private final double value;
+    private final byte[] encodedValue;
+
+    public AbstractDoublePrecisionFloatTest(double value, byte[] encodedValue) {
+        this.value = value;
+        this.encodedValue = encodedValue;
+    }
+
+    @Test
+    public void shouldEncode() throws CborException {
+        List<DataItem> dataItems = new CborBuilder().add(value).build();
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(dataItems.get(0));
+        Assert.assertArrayEquals(encodedValue, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(encodedValue);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof DoublePrecisionFloat);
+        DoublePrecisionFloat doublePrecisionFloat = (DoublePrecisionFloat) dataItem;
+        Assert.assertEquals(value, doublePrecisionFloat.getValue(), 0);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/AbstractFloatTest.java b/src/test/java/co/nstant/in/cbor/model/AbstractFloatTest.java
new file mode 100644
index 0000000..873b9f3
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/AbstractFloatTest.java
@@ -0,0 +1,30 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.util.Objects;
+
+import org.junit.Test;
+
+public class AbstractFloatTest {
+
+    @Test
+    public void testEquals() {
+        AbstractFloat a = new AbstractFloat(SpecialType.IEEE_754_SINGLE_PRECISION_FLOAT, 0.0f);
+        AbstractFloat b = new AbstractFloat(SpecialType.IEEE_754_SINGLE_PRECISION_FLOAT, 0.3f);
+        assertEquals(a, a);
+        assertEquals(b, b);
+        assertFalse(a.equals(b));
+        assertFalse(a.equals(null));
+        assertFalse(a.equals("test"));
+    }
+
+    @Test
+    public void testHashcode() {
+        Special superClass = new Special(SpecialType.IEEE_754_SINGLE_PRECISION_FLOAT);
+        AbstractFloat f = new AbstractFloat(SpecialType.IEEE_754_SINGLE_PRECISION_FLOAT, 0.0f);
+        assertEquals(superClass.hashCode() ^ Objects.hashCode(0.0f), f.hashCode());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/AbstractHalfPrecisionFloatTest.java b/src/test/java/co/nstant/in/cbor/model/AbstractHalfPrecisionFloatTest.java
new file mode 100644
index 0000000..e7fd1a2
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/AbstractHalfPrecisionFloatTest.java
@@ -0,0 +1,44 @@
+package co.nstant.in.cbor.model;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.HalfPrecisionFloat;
+
+public abstract class AbstractHalfPrecisionFloatTest {
+
+    private final float value;
+    private final byte[] encodedValue;
+
+    public AbstractHalfPrecisionFloatTest(float value, byte[] encodedValue) {
+        this.value = value;
+        this.encodedValue = encodedValue;
+    }
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(new HalfPrecisionFloat(value));
+        Assert.assertArrayEquals(encodedValue, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(encodedValue);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof HalfPrecisionFloat);
+        HalfPrecisionFloat halfPrecisionFloat = (HalfPrecisionFloat) dataItem;
+        Assert.assertEquals(value, halfPrecisionFloat.getValue(), 0);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/AbstractNumberTest.java b/src/test/java/co/nstant/in/cbor/model/AbstractNumberTest.java
new file mode 100644
index 0000000..a277497
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/AbstractNumberTest.java
@@ -0,0 +1,53 @@
+package co.nstant.in.cbor.model;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.Number;
+
+public abstract class AbstractNumberTest {
+
+    private final BigInteger value;
+    private final byte[] encodedValue;
+
+    public AbstractNumberTest(long value, byte[] encodedValue) {
+        this.value = BigInteger.valueOf(value);
+        this.encodedValue = encodedValue;
+    }
+
+    public AbstractNumberTest(BigInteger value, byte[] encodedValue) {
+        this.value = value;
+        this.encodedValue = encodedValue;
+    }
+
+    @Test
+    public void shouldEncode() throws CborException {
+        List<DataItem> dataItems = new CborBuilder().add(value).build();
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(dataItems.get(0));
+        Assert.assertArrayEquals(encodedValue, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(encodedValue);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof Number);
+        Number number = (Number) dataItem;
+        Assert.assertEquals(value, number.getValue());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/AbstractSinglePrecisionFloatTest.java b/src/test/java/co/nstant/in/cbor/model/AbstractSinglePrecisionFloatTest.java
new file mode 100644
index 0000000..30b6ec3
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/AbstractSinglePrecisionFloatTest.java
@@ -0,0 +1,44 @@
+package co.nstant.in.cbor.model;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.SinglePrecisionFloat;
+
+public abstract class AbstractSinglePrecisionFloatTest {
+
+    private final float value;
+    private final byte[] encodedValue;
+
+    public AbstractSinglePrecisionFloatTest(float value, byte[] encodedValue) {
+        this.value = value;
+        this.encodedValue = encodedValue;
+    }
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(new SinglePrecisionFloat(value));
+        Assert.assertArrayEquals(encodedValue, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(encodedValue);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof SinglePrecisionFloat);
+        SinglePrecisionFloat singlePrecisionFloat = (SinglePrecisionFloat) dataItem;
+        Assert.assertEquals(value, singlePrecisionFloat.getValue(), 0);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/AbstractStringTest.java b/src/test/java/co/nstant/in/cbor/model/AbstractStringTest.java
new file mode 100644
index 0000000..77b416c
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/AbstractStringTest.java
@@ -0,0 +1,44 @@
+package co.nstant.in.cbor.model;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.UnicodeString;
+
+public abstract class AbstractStringTest {
+
+    private final String value;
+    private final byte[] encodedValue;
+
+    public AbstractStringTest(String value, byte[] encodedValue) {
+        this.value = value;
+        this.encodedValue = encodedValue;
+    }
+
+    @Test
+    public void shouldEncode() throws CborException {
+        ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+        CborEncoder encoder = new CborEncoder(byteOutputStream);
+        encoder.encode(new UnicodeString(value));
+        Assert.assertArrayEquals(encodedValue, byteOutputStream.toByteArray());
+    }
+
+    @Test
+    public void shouldDecode() throws CborException {
+        InputStream inputStream = new ByteArrayInputStream(encodedValue);
+        CborDecoder decoder = new CborDecoder(inputStream);
+        DataItem dataItem = decoder.decodeNext();
+        Assert.assertTrue(dataItem instanceof UnicodeString);
+        UnicodeString unicodeString = (UnicodeString) dataItem;
+        Assert.assertEquals(value, unicodeString.toString());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/AdditionalInformationTest.java b/src/test/java/co/nstant/in/cbor/model/AdditionalInformationTest.java
new file mode 100644
index 0000000..465926b
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/AdditionalInformationTest.java
@@ -0,0 +1,21 @@
+package co.nstant.in.cbor.model;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AdditionalInformationTest {
+
+    /**
+     * Additional information values 28 to 30 are reserved for future expansion.
+     */
+    @Test
+    public void shouldHandleReserved28() {
+        Assert.assertEquals(AdditionalInformation.RESERVED,
+                        AdditionalInformation.ofByte(28));
+        Assert.assertEquals(AdditionalInformation.RESERVED,
+                        AdditionalInformation.ofByte(29));
+        Assert.assertEquals(AdditionalInformation.RESERVED,
+                        AdditionalInformation.ofByte(30));
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/ArrayTest.java b/src/test/java/co/nstant/in/cbor/model/ArrayTest.java
new file mode 100644
index 0000000..179958e
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/ArrayTest.java
@@ -0,0 +1,36 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class ArrayTest {
+
+    @Test
+    public void testEquals() {
+        Array array = new Array();
+        assertEquals(array, array);
+        assertNotEquals(array, null);
+    }
+
+    @Test
+    public void testHashcode() {
+        Array array1 = new Array().add(new UnicodeString("string"));
+        Array array2 = new Array().add(new UnicodeString("string"));
+        assertEquals(array1.hashCode(), array2.hashCode());
+    }
+
+    @Test
+    public void testToString() {
+        Array array = new Array().add(new UnicodeString("a"));
+        assertEquals("[a]", array.toString());
+        array.setChunked(true);
+        assertEquals("[_ a]", array.toString());
+        array.add(new UnicodeString("b"));
+        assertEquals("[_ a, b]", array.toString());
+        array.setChunked(false);
+        assertEquals("[a, b]", array.toString());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/ByteStringTest.java b/src/test/java/co/nstant/in/cbor/model/ByteStringTest.java
new file mode 100644
index 0000000..7911f3d
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/ByteStringTest.java
@@ -0,0 +1,55 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborException;
+
+public class ByteStringTest extends AbstractDataItemTest {
+
+    @Test
+    public void testByteString() throws CborException {
+        shouldEncodeAndDecode("1-byte array", new ByteString(
+            new byte[] { (byte) 0x00 }));
+    }
+
+    @Test
+    public void testUnicodeString() throws CborException {
+        shouldEncodeAndDecode("string", new UnicodeString("hello world"));
+    }
+
+    @Test
+    public void shouldEquals() {
+        byte[] bytes = "string".getBytes();
+        ByteString byteString = new ByteString(bytes);
+        assertTrue(byteString.equals(byteString));
+    }
+
+    @Test
+    public void shouldNotEquals() {
+        byte[] bytes = "string".getBytes();
+        ByteString byteString = new ByteString(bytes);
+        assertFalse(byteString.equals(new Object()));
+    }
+
+    @Test
+    public void shouldHashcode() {
+        ChunkableDataItem superClass = new ChunkableDataItem(MajorType.BYTE_STRING);
+        byte[] bytes = "string".getBytes();
+        ByteString byteString = new ByteString(bytes);
+        assertEquals(byteString.hashCode(), superClass.hashCode() ^ Arrays.hashCode(bytes));
+    }
+
+    @Test
+    public void shouldNotClone() {
+        byte[] bytes = "see issue #18".getBytes();
+        ByteString byteString = new ByteString(bytes);
+        assertEquals(byteString.getBytes(), bytes);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/ChunkableDataItemTest.java b/src/test/java/co/nstant/in/cbor/model/ChunkableDataItemTest.java
new file mode 100644
index 0000000..0db1264
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/ChunkableDataItemTest.java
@@ -0,0 +1,29 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import org.junit.Test;
+
+public class ChunkableDataItemTest {
+
+    private class TestDataItem extends ChunkableDataItem {
+
+        protected TestDataItem() {
+            super(MajorType.INVALID);
+        }
+
+    }
+
+    @Test
+    public void testEquals() {
+        TestDataItem item1 = new TestDataItem();
+        TestDataItem item2 = new TestDataItem();
+        assertEquals(item1, item2);
+        item1.setChunked(true);
+        item2.setChunked(false);
+        assertFalse(item1.equals(item2));
+        assertFalse(item1.equals(null));
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/DataItemTest.java b/src/test/java/co/nstant/in/cbor/model/DataItemTest.java
new file mode 100644
index 0000000..fc7f344
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/DataItemTest.java
@@ -0,0 +1,90 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class DataItemTest {
+
+    private class TestDataItem extends DataItem {
+
+        protected TestDataItem() {
+            super(MajorType.INVALID);
+        }
+
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void shouldThrowNullPointerException() {
+        new DataItem(null);
+    }
+
+    @Test
+    public void testSetTag_Long() {
+        DataItem di = new DataItem(MajorType.UNSIGNED_INTEGER);
+        di.setTag(1);
+        assertNotNull(di.getTag());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetTag_Long_negative() {
+        DataItem di = new DataItem(MajorType.UNSIGNED_INTEGER);
+        di.setTag(-1);
+    }
+
+    @Test
+    public void testSetTag_Tag() {
+        DataItem di = new DataItem(MajorType.UNSIGNED_INTEGER);
+        di.setTag(new Tag(1));
+        assertNotNull(di.getTag());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testSetTag_Tag_null() {
+        DataItem di = new DataItem(MajorType.UNSIGNED_INTEGER);
+        di.setTag(null);
+    }
+
+    @Test
+    public void testGetTag() {
+        DataItem di = new DataItem(MajorType.UNSIGNED_INTEGER);
+        di.setTag(new Tag(1));
+
+        Tag t = di.getTag();
+        assertEquals(1L, t.getValue());
+    }
+
+    @Test
+    public void testHasTag() {
+        DataItem di = new DataItem(MajorType.UNSIGNED_INTEGER);
+        assertFalse(di.hasTag());
+
+        di.setTag(new Tag(1));
+        assertTrue(di.hasTag());
+    }
+
+    @Test
+    public void testRemoveTag() {
+        DataItem di = new DataItem(MajorType.UNSIGNED_INTEGER);
+        di.setTag(new Tag(1));
+        assertNotNull(di.getTag());
+
+        di.removeTag();
+        assertNull(di.getTag());
+    }
+
+    @Test
+    public void testEquals() {
+        DataItem a = new TestDataItem();
+        DataItem b = new TestDataItem();
+        assertEquals(a, b);
+        a.setTag(1);
+        assertFalse(a.equals(b));
+        assertFalse(a.equals(null));
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/DoublePrecisionFloatTest.java b/src/test/java/co/nstant/in/cbor/model/DoublePrecisionFloatTest.java
new file mode 100644
index 0000000..1cb20fa
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/DoublePrecisionFloatTest.java
@@ -0,0 +1,41 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.util.Objects;
+
+import org.junit.Test;
+
+public class DoublePrecisionFloatTest {
+
+    @Test
+    public void testEquals() {
+        DoublePrecisionFloat a = new DoublePrecisionFloat(1.234);
+        DoublePrecisionFloat b = new DoublePrecisionFloat(0.333);
+        assertEquals(a, a);
+        assertEquals(b, b);
+    }
+
+    @Test
+    public void testNotEquals() {
+        DoublePrecisionFloat doublePrecisionFloat = new DoublePrecisionFloat(1.234);
+        assertFalse(doublePrecisionFloat.equals(null));
+        assertFalse(doublePrecisionFloat.equals(new DoublePrecisionFloat(1.2345)));
+    }
+
+    @Test
+    public void testHashcode() {
+        Special superClass = new Special(SpecialType.IEEE_754_DOUBLE_PRECISION_FLOAT);
+        double value = 1.234;
+        DoublePrecisionFloat doublePrecisionFloat = new DoublePrecisionFloat(value);
+        assertEquals(superClass.hashCode() ^ Objects.hashCode(value), doublePrecisionFloat.hashCode());
+    }
+
+    @Test
+    public void testToString() {
+        DoublePrecisionFloat doublePrecisionFloat = new DoublePrecisionFloat(1.234);
+        assertEquals("1.234", doublePrecisionFloat.toString());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/LanguageTaggedStringTest.java b/src/test/java/co/nstant/in/cbor/model/LanguageTaggedStringTest.java
new file mode 100644
index 0000000..a419762
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/LanguageTaggedStringTest.java
@@ -0,0 +1,17 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class LanguageTaggedStringTest {
+
+    @Test
+    public void shouldInitializeWithStrings() {
+        LanguageTaggedString lts = new LanguageTaggedString("en", "Hello");
+        assertEquals(38, lts.getTag().getValue());
+        assertEquals("en", lts.getLanguage().getString());
+        assertEquals("Hello", lts.getString().getString());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/MajorTypeTest.java b/src/test/java/co/nstant/in/cbor/model/MajorTypeTest.java
new file mode 100644
index 0000000..5ee5ff2
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/MajorTypeTest.java
@@ -0,0 +1,57 @@
+package co.nstant.in.cbor.model;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class MajorTypeTest {
+
+    @Test
+    public void shouldParseUnsignedInteger() {
+        Assert.assertEquals(MajorType.UNSIGNED_INTEGER,
+            MajorType.ofByte(0b000_00000));
+    }
+
+    @Test
+    public void shouldParseNegativeInteger() {
+        Assert.assertEquals(MajorType.NEGATIVE_INTEGER,
+            MajorType.ofByte(0b001_00000));
+    }
+
+    @Test
+    public void shouldParseByteString() {
+        Assert.assertEquals(MajorType.BYTE_STRING,
+            MajorType.ofByte(0b010_00000));
+    }
+
+    @Test
+    public void shouldParseUnicodeString() {
+        Assert.assertEquals(MajorType.UNICODE_STRING,
+            MajorType.ofByte(0b011_00000));
+    }
+
+    @Test
+    public void shouldParseArray() {
+        Assert.assertEquals(MajorType.ARRAY, MajorType.ofByte(0b100_00000));
+    }
+
+    @Test
+    public void shouldParseMap() {
+        Assert.assertEquals(MajorType.MAP, MajorType.ofByte(0b101_00000));
+    }
+
+    @Test
+    public void shouldParseTag() {
+        Assert.assertEquals(MajorType.TAG, MajorType.ofByte(0b110_00000));
+    }
+
+    @Test
+    public void shouldParseSpecial() {
+        Assert.assertEquals(MajorType.SPECIAL, MajorType.ofByte(0b111_00000));
+    }
+
+    @Test
+    public void shouldReturnInvalidOnInvalidByteValue() {
+        Assert.assertEquals(MajorType.INVALID, MajorType.ofByte(0xffffffff));
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/MapTest.java b/src/test/java/co/nstant/in/cbor/model/MapTest.java
new file mode 100644
index 0000000..a77a0a6
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/MapTest.java
@@ -0,0 +1,49 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class MapTest {
+
+    @Test
+    public void testRemove() {
+        UnicodeString key = new UnicodeString("key");
+        UnicodeString value = new UnicodeString("value");
+        Map map = new Map();
+        map.put(key, value);
+        assertEquals(1, map.getValues().size());
+        map.remove(key);
+        assertEquals(0, map.getValues().size());
+    }
+
+    @Test
+    public void testEquals() {
+        Map map1 = new Map();
+        assertEquals(map1, map1);
+        assertNotEquals(map1, new Object());
+    }
+
+    @Test
+    public void testHashcode() {
+        Map map1 = new Map();
+        Map map2 = new Map();
+        assertEquals(map1.hashCode(), map2.hashCode());
+        map1.put(new UnicodeString("key"), new UnicodeString("value"));
+        assertNotEquals(map1.hashCode(), map2.hashCode());
+    }
+
+    @Test
+    public void testToString() {
+        Map map = new Map();
+        assertEquals("{  }", map.toString());
+        map.put(new UnicodeString("key1"), new UnicodeString("value1"));
+        assertEquals("{ key1: value1 }", map.toString());
+        map.put(new UnicodeString("key2"), new UnicodeString("value2"));
+        assertEquals("{ key1: value1, key2: value2 }", map.toString());
+        map.setChunked(true);
+        assertEquals("{_ key1: value1, key2: value2 }", map.toString());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/NegativeIntegerTest.java b/src/test/java/co/nstant/in/cbor/model/NegativeIntegerTest.java
new file mode 100644
index 0000000..b99198f
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/NegativeIntegerTest.java
@@ -0,0 +1,26 @@
+package co.nstant.in.cbor.model;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborException;
+
+public class NegativeIntegerTest extends AbstractDataItemTest {
+
+    @Test
+    public void shouldEncodeAndDecode() throws CborException {
+        long maxInteger = -4_294_967_295L;
+        for (long i = -1L; i >= maxInteger; i += (maxInteger / 100_000L)) {
+            shouldEncodeAndDecode(String.valueOf(i), new NegativeInteger(i));
+        }
+        shouldEncodeAndDecode(String.valueOf(maxInteger), new NegativeInteger(maxInteger));
+
+        // Test for issue #1: Creation of 64-bit NegativeInteger >= -4294967296L fails
+        shouldEncodeAndDecode("Long.MIN_VALUE", new NegativeInteger(Long.MIN_VALUE));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldNotAcceptPositiveValues() {
+        new NegativeInteger(0);
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/RationalNumberTest.java b/src/test/java/co/nstant/in/cbor/model/RationalNumberTest.java
new file mode 100644
index 0000000..0a234f1
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/RationalNumberTest.java
@@ -0,0 +1,35 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborException;
+
+public class RationalNumberTest {
+
+    @Test(expected = CborException.class)
+    public void shouldThrowIfNumeratorIsNull() throws CborException {
+        new RationalNumber(null, new UnsignedInteger(1));
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowIfDenominatorIsNull() throws CborException {
+        new RationalNumber(new UnsignedInteger(1), null);
+    }
+
+    @Test(expected = CborException.class)
+    public void shouldThrowIfDenominatorIsZero() throws CborException {
+        new RationalNumber(new UnsignedInteger(1), new UnsignedInteger(0));
+    }
+
+    @Test
+    public void shouldSetNumeratorAndDenominator() throws CborException {
+        UnsignedInteger one = new UnsignedInteger(1);
+        UnsignedInteger two = new UnsignedInteger(2);
+        RationalNumber rationalNumber = new RationalNumber(one, two);
+        assertEquals(one, rationalNumber.getNumerator());
+        assertEquals(two, rationalNumber.getDenominator());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/SimpleValueTest.java b/src/test/java/co/nstant/in/cbor/model/SimpleValueTest.java
new file mode 100644
index 0000000..d77fc32
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/SimpleValueTest.java
@@ -0,0 +1,47 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Objects;
+
+import org.junit.Test;
+
+public class SimpleValueTest {
+
+    @Test
+    public void testHashcode() {
+        Special superClass1 = new Special(SpecialType.SIMPLE_VALUE);
+        Special superClass2 = new Special(SpecialType.SIMPLE_VALUE_NEXT_BYTE);
+        for (int i = 1; i < 256; i++) {
+            SimpleValue simpleValue = new SimpleValue(i);
+            if (i <= 23) {
+                assertEquals(simpleValue.hashCode(), superClass1.hashCode() ^ Objects.hashCode(i));
+            } else {
+                assertEquals(simpleValue.hashCode(), superClass2.hashCode() ^ Objects.hashCode(i));
+            }
+        }
+    }
+
+    @Test
+    public void testEquals1() {
+        for (int i = 0; i < 256; i++) {
+            SimpleValue simpleValue1 = new SimpleValue(i);
+            SimpleValue simpleValue2 = new SimpleValue(i);
+            assertTrue(simpleValue1.equals(simpleValue2));
+        }
+    }
+
+    @Test
+    public void testEquals2() {
+        SimpleValue simpleValue = new SimpleValue(0);
+        assertFalse(simpleValue.equals(new SimpleValue(1)));
+        assertFalse(simpleValue.equals(null));
+        assertFalse(simpleValue.equals(false));
+        assertFalse(simpleValue.equals(""));
+        assertFalse(simpleValue.equals(1));
+        assertFalse(simpleValue.equals(1.1));
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/SpecialTest.java b/src/test/java/co/nstant/in/cbor/model/SpecialTest.java
new file mode 100644
index 0000000..de945c8
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/SpecialTest.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class SpecialTest {
+
+	@Test
+	public void testToString() {
+		Special special = Special.BREAK;
+		assertEquals("BREAK", special.toString());
+	}
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/SpecialTypeTest.java b/src/test/java/co/nstant/in/cbor/model/SpecialTypeTest.java
new file mode 100644
index 0000000..1d530c7
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/SpecialTypeTest.java
@@ -0,0 +1,15 @@
+package co.nstant.in.cbor.model;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SpecialTypeTest {
+
+    @Test
+    public void shouldDetectUnallocated() {
+        Assert.assertTrue(SpecialType.ofByte(28).equals(SpecialType.UNALLOCATED));
+        Assert.assertTrue(SpecialType.ofByte(29).equals(SpecialType.UNALLOCATED));
+        Assert.assertTrue(SpecialType.ofByte(30).equals(SpecialType.UNALLOCATED));
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/TagTest.java b/src/test/java/co/nstant/in/cbor/model/TagTest.java
new file mode 100644
index 0000000..e4ad8f8
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/TagTest.java
@@ -0,0 +1,54 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Objects;
+
+import org.junit.Test;
+
+public class TagTest {
+
+    @Test
+    public void testHashcode() {
+        DataItem superClass = new DataItem(MajorType.TAG);
+        for (long i = 0; i < 256; i++) {
+            Tag tag = new Tag(i);
+            assertEquals(tag.hashCode(), superClass.hashCode() ^ Objects.hashCode(i));
+        }
+    }
+
+    @Test
+    public void testEquals1() {
+        for (int i = 0; i < 256; i++) {
+            Tag tag1 = new Tag(i);
+            Tag tag2 = new Tag(i);
+            assertTrue(tag1.equals(tag2));
+        }
+    }
+
+    @Test
+    public void testEquals2() {
+        Tag tag = new Tag(0);
+        assertTrue(tag.equals(tag));
+    }
+
+    @Test
+    public void testNotEquals() {
+        Tag tag = new Tag(0);
+        assertFalse(tag.equals(new Tag(1)));
+        assertFalse(tag.equals(null));
+        assertFalse(tag.equals(false));
+        assertFalse(tag.equals(""));
+        assertFalse(tag.equals(1));
+        assertFalse(tag.equals(1.1));
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("Tag(0)", new Tag(0).toString());
+        assertEquals("Tag(123)", new Tag(123).toString());
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/UnicodeStringTest.java b/src/test/java/co/nstant/in/cbor/model/UnicodeStringTest.java
new file mode 100644
index 0000000..27b820b
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/UnicodeStringTest.java
@@ -0,0 +1,55 @@
+package co.nstant.in.cbor.model;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UnicodeStringTest {
+
+    @Test
+    public void toStringShouldPrintNull() {
+        Assert.assertEquals("null", new UnicodeString(null).toString());
+    }
+
+    @Test
+    public void shouldEqualsSameObject() {
+        UnicodeString unicodeString = new UnicodeString("string");
+        Assert.assertTrue(unicodeString.equals(unicodeString));
+    }
+
+    @Test
+    public void shouldEqualsBothNull() {
+        UnicodeString unicodeString1 = new UnicodeString(null);
+        UnicodeString unicodeString2 = new UnicodeString(null);
+        Assert.assertTrue(unicodeString1.equals(unicodeString2));
+        Assert.assertTrue(unicodeString2.equals(unicodeString1));
+    }
+
+    @Test
+    public void shouldEqualsSameValue() {
+        UnicodeString unicodeString1 = new UnicodeString("string");
+        UnicodeString unicodeString2 = new UnicodeString("string");
+        Assert.assertTrue(unicodeString1.equals(unicodeString2));
+        Assert.assertTrue(unicodeString2.equals(unicodeString1));
+    }
+
+    @Test
+    public void shouldHashNull() {
+        Assert.assertEquals(0, new UnicodeString(null).hashCode());
+    }
+
+    @Test
+    public void shouldNotEqualOtherObjects() {
+        UnicodeString unicodeString = new UnicodeString("string");
+        Assert.assertFalse(unicodeString.equals(null));
+        Assert.assertFalse(unicodeString.equals(1));
+        Assert.assertFalse(unicodeString.equals("string"));
+    }
+
+    @Test
+    public void shouldNotEquals() {
+        UnicodeString unicodeString1 = new UnicodeString(null);
+        UnicodeString unicodeString2 = new UnicodeString("");
+        Assert.assertFalse(unicodeString1.equals(unicodeString2));
+    }
+
+}
diff --git a/src/test/java/co/nstant/in/cbor/model/UnsignedIntegerTest.java b/src/test/java/co/nstant/in/cbor/model/UnsignedIntegerTest.java
new file mode 100644
index 0000000..06b3541
--- /dev/null
+++ b/src/test/java/co/nstant/in/cbor/model/UnsignedIntegerTest.java
@@ -0,0 +1,62 @@
+package co.nstant.in.cbor.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigInteger;
+
+import org.junit.Test;
+
+import co.nstant.in.cbor.CborException;
+
+public class UnsignedIntegerTest extends AbstractDataItemTest {
+
+    @Test
+    public void shouldEncodeAndDecode() throws CborException {
+        long maxInteger = 4_294_967_295L;
+        for (long i = 0L; i < maxInteger; i += (maxInteger / 100_000L)) {
+            shouldEncodeAndDecode(String.valueOf(i), new UnsignedInteger(i));
+        }
+        shouldEncodeAndDecode(String.valueOf(maxInteger), new UnsignedInteger(maxInteger));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldNotAcceptNegativeValues() {
+        new UnsignedInteger(-1);
+    }
+
+    @Test
+    public void testToString() {
+        UnsignedInteger unsignedInteger = new UnsignedInteger(BigInteger.ZERO);
+        assertEquals("0", unsignedInteger.toString());
+    }
+
+    @Test
+    public void testEquals1() {
+        UnsignedInteger unsignedInteger = new UnsignedInteger(BigInteger.ZERO);
+        assertTrue(unsignedInteger.equals(unsignedInteger));
+    }
+
+    @Test
+    public void testEquals2() {
+        for (int i = 0; i < 256; i++) {
+            UnsignedInteger unsignedInteger1 = new UnsignedInteger(BigInteger.valueOf(i));
+            UnsignedInteger unsignedInteger2 = new UnsignedInteger(BigInteger.valueOf(i));
+            assertTrue(unsignedInteger1.equals(unsignedInteger2));
+        }
+    }
+
+    @Test
+    public void testEquals3() {
+        UnsignedInteger unsignedInteger = new UnsignedInteger(BigInteger.ZERO);
+        assertFalse(unsignedInteger.equals(new Object()));
+    }
+
+    @Test
+    public void testHashcode() {
+        UnsignedInteger unsignedInteger = new UnsignedInteger(BigInteger.ZERO);
+        assertEquals(BigInteger.ZERO.hashCode(), unsignedInteger.getValue().hashCode());
+    }
+
+}