Skip to content

Commit

Permalink
Add more complete documentation. (#7)
Browse files Browse the repository at this point in the history
* Add Binary Format diagrams and md file.
* Update header docs with examples.
* Add 'Overview' section describing usage.
* Adding tests for documentation examples.
  • Loading branch information
tonystone authored Feb 19, 2019
1 parent a30c207 commit 5112ab2
Show file tree
Hide file tree
Showing 12 changed files with 294 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .jazzy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ custom_categories:
- name: User Guide
children:
- Binary Format
- name: Classes
children:
- BinaryEncoder
- BinaryDecoder
- EncodedData
Binary file added Documentation/Binary Format Diagram.cddz
Binary file not shown.
Binary file added Documentation/Binary-Format-Container-Header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Documentation/Binary-Format-Keyed-Container.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Documentation/Binary-Format-Unkeyed-Container.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 48 additions & 1 deletion Documentation/Sections/Binary Format.md
Original file line number Diff line number Diff line change
@@ -1 +1,48 @@
### Binary Format
# Binary Format

The internal binary representation of the encoded data consists of a series of **Elements** each containing a **Header** section followed by a **Container**. The Element is essentially a wrapper around a Container defining it's type and size in bytes. The format was designed to be as compact and efficient as possible at storing the simplest to the most complex structures that can be encoded.

![Binary Format Container Header](../Binary-Format-Container-Header.png)

The Header of each Element contains a 32 bit integer representing the container type followed by a 32 integer containing the total number of bytes stored in the Container section of this Element.

## Containers
Each Container formats it's data based on the requirements of that container. There are 3 container types that can be stored corresponding to the `Encoder` and `Decoder` protocol return types for the functions `singleValueContainer()`, `unkeyedContainer()`, and `container(keyedBy:)`. An Element is created in response to each call to any one of these functions.

### Single Value Container

Each SingleValueContainer is defined by the Container structure below.
SingleValueContainers consist of the encoded type of the value it stores, the size in bytes of the value, and the value itself.

![Binary Format Single Value Container](../Binary-Format-Single-Value-Container.png)

### Unkeyed Container

An unkeyed container consists of the count of the number of elements it stores and a series of the elements
containing other containers.

![Binary Format](../Binary-Format-Unkeyed-Container.png)


### Keyed Container

A keyed container consists of the count of the number of key/Element pairs contained in the container followed by a key and Element for each.

![Binary Format](../Binary-Format-Keyed-Container.png)

Each key consists of the size in bytes of the string for the key and the value of the string itself.

> Note: Keys are not null terminated.
## Memory Alignment

Currently StickyEncoding aligns the data in the file and in memory aligned to the machine it is running on.

## Byte ordering

StickyEncoding uses the same byte order as the machine that the binary data is created on uses.





83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,89 @@

**StickyEncoding**, A high performance binary encoder for `Swift.Codable` types.

## Overview

StickyEncoding facilitates the encoding and decoding of `Codable` values into and output of a binary
format that can be stored on disk or sent over a socket.

Encoding is done using a `BinaryEncoder` instance and will encode any `Encodable` type whether you declare conformance to `Encodable` and let the compiler create the code or you manually implement the conformance yourself.

Decoding is done using a `BinaryDecoder` instance and can decode any `Decodable` type that was previously encoded using the `BinaryEncoder`. Of course you can declare `Encodable` or `Decodable` conformance by using `Codable` as well.

StickyEncoding creates a compact binary format that represents the encoded object or data type. You can read more about the format in the document [Binary Format](Documentation/Sections/Binary Format.md).

To facilitate many use cases, StickyEncoding encodes the data to an instance of `EncodedData`. EncodedData contains a binary format suitable
for writing directly to memory, disk, or into a byte array. Or in the case of decoding, the format facilitates rapid decoding to Swift instances.

### Encoding

To create an instance of a BinaryEncoder:
```Swift
let encoder = BinaryEncoder()
```

> Note: You may optionally pass your own userInfo `BinaryEncoder(userInfo:)` structure and it will be available to you during the encoding.
You can encode any top even top-level single value types including Int,
UInt, Double, Bool, and Strings. Simply pass the value to the instance
of the BinaryEncoder and call `encode`.
```Swift
let string = "You can encode single values of any type."

let encoded = try encoder.encode(string)
```
Basic structs and classes can also be encoded.
```Swift
struct Employee: Codable {
let first: String
let last: String
let employeeNumber: Int
}

let employee = Employee(first: "John", last: "Doe", employeeNumber: 2345643)

let encodedData = try encoder.encode(employee)
```
As well as Complex types with sub classes.

### Decoding

To create an instance of a BinaryDecoder:
```Swift
let decoder = BinaryDecoder()
```

> Note: You may optionally pass your own userInfo `BinaryDecoder(userInfo:)` structure and it will be available to you during the decoding.
To decode, you pass the Type of object to create, and an instance of encoded data representing that type.
```Swift
let employee = try decoder.decode(Employee.self, from: encodedData)
```

### EncodedData

An intermediate representation which represents the encoded data. This type is the direct connection between raw memory and a type that can be converted to and from a `Codable` object.

StickyEncoding uses an intermediate representation so that it can support many use cases from direct byte conversion to writing/reading directly to/from raw memory.

When encoding of an object, the intermediate representation has already been
encoded down to a form that can be rapidly written to memory.
```Swift
let encodedData = try encoder.encode(employee)

// Write the bytes directly to a file.
FileManager.default.createFile(atPath: "employee.bin", contents: Data(bytes: encodedData.bytes))
```
There are use cases that require writing to a buffer or socket in which case StickyEncoding offers a direct write method so that an intermediate structure (byte array) does not have to be created first.
```Swift
let encodedData = try encoder.encode(employee)

// Allocate byte aligned raw memory to hold the binary encoded data.
let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: encodedData.byteCount, alignment: MemoryLayout<UInt8>.alignment)

// Write the encoded data directly to the raw memory.
encodedData.write(to: buffer)
```

## Sources and Binaries

Expand Down
12 changes: 12 additions & 0 deletions Sources/StickyEncoding/BinaryDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ import Foundation
///
/// `BinaryDecoder` facilitates the decoding of binary values into semantic `Decodable` types.
///
/// To create an instance of a BinaryDecoder:
/// ```
/// let decoder = BinaryDecoder()
/// ```
///
/// > Note: You may optionally pass your own userInfo `BinaryDecoder(userInfo:)` structure and it will be available to you during the decoding.
///
/// To decode, you pass the Type of object to create, and an instance of encoded data representing that type.
/// ```
/// let employee = try decoder.decode(Employee.self, from: encodedData)
/// ```
///
open class BinaryDecoder {

/// Initializes `self`.
Expand Down
18 changes: 10 additions & 8 deletions Sources/StickyEncoding/BinaryEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,35 @@ import Foundation
///
/// ### Examples
///
/// To create an instance of an BinaryEncoder:
/// To create an instance of a BinaryEncoder:
/// ```
/// let encoder = BinaryEncoder()
/// let encoder = BinaryEncoder()
/// ```
///
/// > Note: You may optionally pass your own userInfo `BinaryEncoder(userInfo:)` structure and it will be available to you during the encoding.
///
/// You can encode any top even top-level single value types including Int,
/// UInt, Double, Bool, and Strings. Simply pass the value to the instance
/// of the BinaryEncoder and call `encode`.
/// ```
/// let string = "You can encode single values of any type."
///
/// let bytes = try encoder.encode(string).bytes
/// let encoded = try encoder.encode(string)
/// ```
/// Basic structs and classes can also be encoded.
/// ```
/// struct Employee: Codable {
/// struct Employee: Codable {
/// let first: String
/// let last: String
/// let employeeNumber: Int
/// }
/// }
///
/// let employee = Employee(first: "John", last: "Doe", employeeNumber: 2345643)
/// let employee = Employee(first: "John", last: "Doe", employeeNumber: 2345643)
///
/// let bytes = try encoder.encode(employee).bytes
/// let encodedData = try encoder.encode(employee)
/// ```
/// As well as Complex types with sub classes.
///
///
open class BinaryEncoder {

///
Expand Down
29 changes: 24 additions & 5 deletions Sources/StickyEncoding/EncodedData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,47 @@
import Swift

///
/// Intermediary type which represents the first stage of encoding. This type is the direct connection between
/// raw memory and a type that can be converted to and from an `Encodable` object.
/// An intermediate representation which represents the encoded data. This type is the direct connection between
/// raw memory and a type that can be converted to and from an `Codable` object.
///
/// StickyEncoding uses an intermediate representation so that it can support many use cases from
/// direct byte conversion to writing/reading directly to/from raw memory.
///
/// When encoding of an object, the intermediate representation has already been
/// encoded down to a form that can be rapidly written to memory.
/// ```
/// let encodedData = try encoder.encode(employee)
///
/// // Write the bytes directly to a file.
/// FileManager.default.createFile(atPath: "employee.bin", contents: Data(bytes: encodedData.bytes))
/// ```
/// There are use cases that require writing to a buffer or socket in which case StickyEncoding offers a direct write method so that an intermediate structure (byte array) does not have to be created first.
/// ```
/// let encodedData = try encoder.encode(employee)
///
/// // Allocate byte aligned raw memory to hold the binary encoded data.
/// let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: encodedData.byteCount, alignment: MemoryLayout<UInt8>.alignment)
///
/// // Write the encoded data directly to the raw memory.
/// encodedData.write(to: buffer)
/// ```
///
public class EncodedData {

// MARK: - Reading encoded data

///
/// Initializes `self` with a null value.
///
public init() {
self.storage = NullStorageContainer.null
}

///
/// Initializes `'self` using the data stored in `buffer`.
///
public init(from buffer: UnsafeRawBufferPointer) {
self.storage = StorageContainerReader.read(from: buffer)
}

///
/// Initializes `'self` using the data stored in `bytes`.
///
public init(bytes: [UInt8]) {
Expand Down
112 changes: 112 additions & 0 deletions Tests/StickyEncodingTests/DocumentationExampleTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
///
/// DocumentationExampleTests.swift
///
/// Copyright 2019 Tony Stone
///
/// 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.
///
/// Created by Tony Stone on 2/13/19.
///
import XCTest

import StickyEncoding

class DocumentationExampleTests: XCTestCase {

let encoder = BinaryEncoder()
let decoder = BinaryDecoder()

/// Documentation Examples
///
/// These test should represent the documentations examples exactly
/// with no XCTAsserts added. These tests are to make sure that the
/// examples compile and run.
///

func testBinaryEncoderExample1() throws {

let string = "You can encode single values of any type."

let encoded = try encoder.encode(string)
}

func testBinaryEncoderExample2() throws {

/// Actual code in example.
///
struct Employee: Codable {
let first: String
let last: String
let employeeNumber: Int
}

let employee = Employee(first: "John", last: "Doe", employeeNumber: 2345643)

let encodedData = try encoder.encode(employee)
}

func testBinaryDecoderExample1() throws {

struct Employee: Codable {
let first: String
let last: String
let employeeNumber: Int
}

let encodedData = try encoder.encode(Employee(first: "John", last: "Doe", employeeNumber: 2345643))

/// Actual code in example.
///
let employee = try decoder.decode(Employee.self, from: encodedData)
}

func testEncodedDataExample1() throws {

/// Code from previous example used as boiler plate for this example.
///
struct Employee: Codable {
let first: String
let last: String
let employeeNumber: Int
}

let employee = Employee(first: "John", last: "Doe", employeeNumber: 2345643)

/// Actual code in example.
///
let encodedData = try encoder.encode(employee)

FileManager.default.createFile(atPath: "employee.bin", contents: Data(bytes: encodedData.bytes))
}

func testEncodedDataExample2() throws {

/// Code from previous example used as boiler plate for this example.
///
struct Employee: Codable {
let first: String
let last: String
let employeeNumber: Int
}

let employee = Employee(first: "John", last: "Doe", employeeNumber: 2345643)

/// Actual code in example.
///
let encodedData = try encoder.encode(employee)

let buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: encodedData.byteCount, alignment: MemoryLayout<UInt8>.alignment)

encodedData.write(to: buffer)
}
}

0 comments on commit 5112ab2

Please sign in to comment.