Commit Graph

75 Commits

Author SHA1 Message Date
Faye Amacker
508be9165f Rename two test funcs that can panic
This commit renames two test funcs to begin with "must"
if they can panic:
- hexDecode() -> mustHexDecode()
- bigIntOrPanic() -> mustBigInt()
2025-06-29 19:51:48 -05:00
Faye Amacker
b4af226c7d Refactor tests to improve readability
This commit replaces positional field initialization in test cases
with named field initialization to improve readability.

While at it, also remove tests that were commented out.
2025-06-29 19:43:04 -05:00
Ben Luddy
d8e0aa63e6
Add optional encoding support for json.Marshaler.
If the user provides a JSON-to-CBOR transcode function, a value whose type implements json.Marshaler
and not cbor.Marshaler will be encoded by first calling its MarshalJSON method, then transcoding the
result to CBOR.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2025-06-17 10:44:47 -04:00
Ben Luddy
db9afc542a
Replace empty encode mode test case names. 2025-06-16 09:43:57 -04:00
Ben Luddy
7a9dbd0b0a
Add option to marshal encoding.TextMarshalers to CBOR text strings.
The default behavior (encoding.TextMarshaler not recognized) remains unchanged.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2025-06-16 09:43:57 -04:00
Jordan Liggitt
69da12b0b4
Adjust OmitZero tests to zero behavior
Signed-off-by: Jordan Liggitt <liggitt@google.com>
2025-03-26 09:55:55 -04:00
Jordan Liggitt
101dea75b0
Copy OmitEmpty tests to OmitZero tests
Signed-off-by: Jordan Liggitt <liggitt@google.com>
2025-03-26 09:55:54 -04:00
Faye Amacker
9fa2077793 Replace interface{} with any
This commit replaces every instance of interface{} with any,
so go1.18 or newer is required to to build.
2025-02-22 14:05:53 -06:00
Ben Luddy
ffab76a44a
Fix panic using SortFastShuffle to encode a struct with no fields.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-06-10 11:02:51 -04:00
Faye Amacker
d1b239987b Rename ByteSliceMode to ByteSliceLaterFormatMode
Also renamed related options to be consistent.
2024-06-09 21:16:42 -05:00
Faye Amacker
ed38331613 Add UserBufferEncMode interface with MarshalToBuffer()
This commit adds features related to allowing user to specify
a buffer rather than using built-in buffer pool:

- cbor.MarshalToBuffer() uses codec's default options to encode
  to user provided buffer instead of using built-in buffer pool.

- UserBufferEncMode interface extends EncMode interface with
  MarshalToBuffer() so user can provide buffer for encoding
  instead of using built-in buffer pool.

- EncOptions.UserBufferEncMode() returns UserBufferEncMode

- EncOptions.UserBufferEncModeWithTags() returns UserBufferEncMode

- EncOptions.UserBufferEncModeWithSharedTags() returns UserBufferEncMode
2024-06-09 19:27:59 -05:00
Faye Amacker
b426c3e8c6 Lint 2024-05-27 21:21:12 -05:00
Faye Amacker
fdf5bd8378 Refactor to remove more magic numbers 2024-05-27 21:14:04 -05:00
Faye Amacker
03575b4950 Refactor code and lint 2024-05-27 15:33:34 -05:00
Ben Luddy
6396be350f
Refactor sorted map encode to use fewer buffers for nested maps.
Runs a bit faster, but more importantly, only needs a single buffer to encode nested, sorted maps
instead of using multiple temporary buffers.

                                                            │ before.txt  │             after.txt              │
                                                            │   sec/op    │   sec/op     vs base               │
MarshalCanonical/Go_map[string]string_to_CBOR_map_canonical   1.464µ ± 0%   1.395µ ± 0%  -4.68% (p=0.000 n=10)
MarshalCanonical/Go_map[int]int_to_CBOR_map_canonical         192.1n ± 0%   186.2n ± 1%  -3.10% (p=0.000 n=10)
geomean                                                       530.2n        509.6n       -3.89%

                                                            │ before.txt │               after.txt               │
                                                            │    B/op    │    B/op      vs base                  │
MarshalCanonical/Go_map[string]string_to_CBOR_map_canonical   88.00 ± 0%   112.00 ± 0%  +27.27% (p=0.000 n=10)
MarshalCanonical/Go_map[int]int_to_CBOR_map_canonical         3.000 ± 0%    3.000 ± 0%        ~ (p=1.000 n=10) ¹
geomean                                                       16.25         18.33       +12.82%
¹ all samples are equal

                                                            │ before.txt │              after.txt              │
                                                            │ allocs/op  │ allocs/op   vs base                 │
MarshalCanonical/Go_map[string]string_to_CBOR_map_canonical   2.000 ± 0%   2.000 ± 0%       ~ (p=1.000 n=10) ¹
MarshalCanonical/Go_map[int]int_to_CBOR_map_canonical         1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                                       1.414        1.414       +0.00%
¹ all samples are equal

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-19 19:56:11 -04:00
Faye Amacker
710fe00ffa Lint 2024-05-12 18:22:04 -05:00
Faye Amacker
8964976b42
Merge branch 'master' into bignum-options 2024-05-12 17:25:19 -05:00
Ben Luddy
fcbe98d114
Add options to disable BinaryMarshaler/BinaryUnmarshaler support.
By default, values whose type implements BinaryMarshaler encode to a byte string whose contents are
the result of calling MarshalBinary, and decoding a byte string into a BinaryUnmarshaler calls
UnmarshalBinary on the contents of the byte string. These options make it possible to disable both
behaviors.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-10 15:26:53 -04:00
Ben Luddy
7f27a44147
Add option to reject decoding bignum tags and encoding big.Int.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-10 14:22:46 -04:00
Faye Amacker
ca791949b1
Merge pull request #476 from benluddy/stdlib-json-byteslice-compatibility
Support automatic conversion between text and binary string representations
2024-05-05 17:29:15 -05:00
Ben Luddy
83e9c2bff8
Support auto conversion of byte strings to and from text encodings.
These options improve interoperability with programs that use JSON to encode and decode objects to
and from both struct types and empty interface values.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-29 12:53:24 -04:00
Ben Luddy
d981dece35
Encode structs directly to output buffer.
For variable-length structs (structs with omitempty fields), encoding to the unused capacity at the
end of the output buffer while counting nonempty items is cheaper than using a separate temporary
buffer (no pool interactions and better spatial locality). Copying the items can be avoided entirely
by reserving space in the output buffer for the head if the encoded length of the head can be
predicted before checking optional fields.

                                                                     │ before.txt  │              after.txt              │
                                                                     │   sec/op    │   sec/op     vs base                │
Marshal/Go_struct_to_CBOR_map                                          1.404µ ± 0%   1.408µ ± 1%        ~ (p=0.170 n=10)
Marshal/Go_struct_many_fields_all_omitempty_all_empty_to_CBOR_map      443.8n ± 0%   430.6n ± 0%   -2.99% (p=0.000 n=10)
Marshal/Go_struct_some_fields_all_omitempty_all_empty_to_CBOR_map      181.7n ± 0%   163.5n ± 0%  -10.04% (p=0.000 n=10)
Marshal/Go_struct_many_fields_all_omitempty_all_nonempty_to_CBOR_map   813.5n ± 0%   784.8n ± 0%   -3.53% (p=0.000 n=10)
Marshal/Go_struct_some_fields_all_omitempty_all_nonempty_to_CBOR_map   300.8n ± 0%   275.4n ± 0%   -8.43% (p=0.000 n=10)
Marshal/Go_struct_many_fields_one_omitempty_to_CBOR_map                763.8n ± 0%   727.7n ± 0%   -4.73% (p=0.000 n=10)
Marshal/Go_struct_some_fields_one_omitempty_to_CBOR_map                284.2n ± 0%   257.6n ± 0%   -9.36% (p=0.000 n=10)
Marshal/Go_struct_keyasint_to_CBOR_map                                 1.422µ ± 0%   1.414µ ± 1%   -0.56% (p=0.029 n=10)
Marshal/Go_struct_toarray_to_CBOR_array                                1.341µ ± 1%   1.338µ ± 1%        ~ (p=0.340 n=10)
MarshalCanonical/Go_struct_to_CBOR_map                                 386.4n ± 0%   392.4n ± 0%   +1.57% (p=0.000 n=10)
MarshalCanonical/Go_struct_to_CBOR_map_canonical                       386.9n ± 0%   384.8n ± 0%   -0.52% (p=0.001 n=10)
geomean                                                                560.5n        540.4n        -3.59%

                                                                     │ before.txt │              after.txt              │
                                                                     │    B/op    │    B/op     vs base                 │
Marshal/Go_struct_to_CBOR_map                                          208.0 ± 0%   208.0 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_all_omitempty_all_empty_to_CBOR_map      1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_all_omitempty_all_empty_to_CBOR_map      1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_all_omitempty_all_nonempty_to_CBOR_map   176.0 ± 0%   176.0 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_all_omitempty_all_nonempty_to_CBOR_map   48.00 ± 0%   48.00 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_one_omitempty_to_CBOR_map                160.0 ± 0%   160.0 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_one_omitempty_to_CBOR_map                48.00 ± 0%   48.00 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_keyasint_to_CBOR_map                                 192.0 ± 0%   192.0 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_toarray_to_CBOR_array                                192.0 ± 0%   192.0 ± 0%       ~ (p=1.000 n=10) ¹
MarshalCanonical/Go_struct_to_CBOR_map                                 64.00 ± 0%   64.00 ± 0%       ~ (p=1.000 n=10) ¹
MarshalCanonical/Go_struct_to_CBOR_map_canonical                       64.00 ± 0%   64.00 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                                                46.18        46.18       +0.00%
¹ all samples are equal

                                                                     │ before.txt │              after.txt              │
                                                                     │ allocs/op  │ allocs/op   vs base                 │
Marshal/Go_struct_to_CBOR_map                                          1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_all_omitempty_all_empty_to_CBOR_map      1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_all_omitempty_all_empty_to_CBOR_map      1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_all_omitempty_all_nonempty_to_CBOR_map   1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_all_omitempty_all_nonempty_to_CBOR_map   1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_one_omitempty_to_CBOR_map                1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_one_omitempty_to_CBOR_map                1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_keyasint_to_CBOR_map                                 1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_toarray_to_CBOR_array                                1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
MarshalCanonical/Go_struct_to_CBOR_map                                 1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
MarshalCanonical/Go_struct_to_CBOR_map_canonical                       1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                                                1.000        1.000       +0.00%
¹ all samples are equal

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-29 11:49:33 -04:00
Faye Amacker
8ef865cdae
Merge pull request #515 from benluddy/sortmode-determinism
Add SortMode to encode struct fields in a less predictable order.
2024-04-20 18:38:00 -05:00
Ben Luddy
cdc2c42a47
Allow rejection of NaN and Inf float values on encode and decode.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-14 21:45:02 -04:00
Ben Luddy
4bbff65cd6
Add shuffled SortMode for encoding struct fields less predictably.
The motivation is to prevent programs that consume the output from assuming (incorrectly) that it
was encoded deterministically, even when implementation details make the output apparently stable.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-12 17:04:44 -04:00
Faye Amacker
a3a1d711f1 Use custom DecModes to check Marshaler data
This commit checks Marshaler data for well-formedness
(and very limited partial validation) by reusing one
of four DecModes created during startup.  These modes
are safe for parallel use by different Marshalers.
2024-02-19 18:21:28 -06:00
Faye Amacker
accf57c32e Check well-formedness of data from MarshalCBOR
MarshalerError is returned if CBOR data item returned from
MarshalCBOR() fails either:
- well-formedness check, or
- tag validation for builtin tags 0-3
2024-02-04 20:46:52 -06:00
Ben Luddy
7c1f8f3e33
Fix EncOption/DecOption unset fields on mode regurgitation.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-01-29 23:33:39 -05:00
Ben Luddy
a29413c8fe
Add option to control the output CBOR type of struct field names.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-01-22 11:06:33 -05:00
Ben Luddy
30aefa6c0b
Add option to encode Go strings to CBOR byte strings.
The StringType encode option supports use cases that must not produce invalid CBOR (even if
well-formed) and must be able to encode Go strings that do not contain valid UTF-8 sequences,
without the overhead of sanitizing all input Go values. The default value of the option is
TextStringType and encodes Go strings to CBOR major type 3, which is identical to the preexisting
behavior.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-01-08 16:23:59 -05:00
Faye Amacker
c5c410b12c Add another test and fix lint errors 2024-01-07 13:31:52 -06:00
Faye Amacker
0cf56c3dab
Merge pull request #453 from dinhxuanvu/json-omitempty
Add encoding option to specify how omitempty fields are encoded
2024-01-07 13:09:56 -06:00
Vu Dinh
f95914be2b Address feedbacks
Signed-off-by: Vu Dinh <vudinh@outlook.com>
2024-01-04 12:46:44 -05:00
Faye Amacker
dee1f16979 Refactor tests for readability 2023-12-28 21:00:16 -06:00
Faye Amacker
4206c1d5f0 Refactor tests for readability 2023-12-28 20:34:09 -06:00
Vu Dinh
0d142f9697 Add encoding option to specify how omitempty fields are encoded
The current behavior in the library is to omits if field value
would encode as empty JSON value. This behavior is different from
the bahavior in golang encoding/json which omits if field value
is an empty Go value (defined as false, 0, a nil pointer, a nil
interface value, and any empty array, slice, map, or string).
The OmitEmptyMode = 1 is added to encoding option so that cbor
would decode omitempty fields similarly to encoding/json in go.

Signed-off-by: Vu Dinh <vudinh@outlook.com>
2023-12-19 10:21:41 -05:00
Faye Amacker
ad9dc1c161 Make valid() reject negative values for modes
For modes of integer type, make valid() reject negative values
since 0 is default and non-negative values are used.

Also reformat comments with gofmt.
2023-11-04 19:44:51 -05:00
Faye Amacker
d8c73c8619 Refactor NilContainersMode option
- Moved NilContainersMode from decode.go to encode.go
  because it is only an encoding option.
- Renamed NullForNil to NilContainerAsNull.
- Renamed EmptyForNil to NilContainerAsEmpty.
- Updated and added some comments.
2022-12-29 18:35:28 -06:00
Faye Amacker
75d03846c5
Merge pull request #352 from dedefer/add_nil_containers_encode_options
Add option to make nil containers encode as empty containers.

The newly added encoding option is likely to be renamed before next release.
2022-12-29 18:13:03 -06:00
Faye Amacker
7704fa5efa
Add support for unassigned/reserved CBOR simple values (#370)
Add a SimpleValue type which is distinct from Go's numeric types.

Add support for properly encoding and decoding all simple values,
including 252 unassigned/reserved simple values.

Improve support for simple values as map keys by making them
distinct from uint64 values 0-255.

CBOR simple values are a subset of major type 7
that is not floating-point.

Only 4 simple values were previously supported by this codec:
- false
- true
- null
- undefined

The other 252 simple values are unassigned or reserved by IANA.
2022-11-13 19:27:24 -06:00
Danila Fomin
64ed7b735b add option to enforce nil container marshaling as empty containers 2022-06-06 17:05:08 +03:00
Faye Amacker
b5ec710759 Fix revive lint errors 2021-12-05 13:42:27 -06:00
Faye Amacker
58b1cf4afc
Support omitempty for struct & BinaryMarshaler types (#243)
Struct field with "omitempty" option is omitted during encoding if:
- field is false
- field is 0
- field is empty string, array, slice, or map
- field is null pointer or interface
- field is nil or empty slice for cbor.RawMessage
- (new) field is nil or empty slice for types implementing cbor.BinaryMarshaler
- (new) field is empty struct

Currently "omitempty" option is a no-op for the following types:
- time.Time
- big.Int
- cbor.Tag
- cbor.RawTag
- types implementing cbor.Marshaler

Removed Go's copyright notice in encode.go.

Updated README and LICENSE to remove Go's copyright and license.

Closes #232
2020-08-18 13:19:54 -05:00
Faye Amacker
58b82b5bfc
Add missing attribution (#239)
Added missing attribution in encode_test.go.

Refactored and renamed variables, functions, and types.

Modified documentation.

Closes #233
Closes #237
2020-05-25 22:19:12 -05:00
Faye Amacker
bffaf0cda7
Add support for CBOR tags 2 and 3 <-> big.Int (#231)
By default, encode big.Int value to CBOR integer (if value fits),
otherwise encode to CBOR bignum (CBOR tag 2 or 3).

BigIntConvert is the CBOR encoding option for big.Int:

- BigIntConvertShortest: (default) encode big.Int to CBOR integer
  if value fits, otherwise encode to CBOR bignum.

- BigIntConvertNone: encode big.Int to CBOR bignum without
  converting it to another CBOR type.

Decoder can decode the following to big.Int:

- CBOR bignum (tags 2 and 3)

- CBOR integers (major type 0 and 1)

When decoding CBOR bignum to empty interface, decode to big.Int.

When decoding CBOR negative integer (major type 1) to empty interface:

- if value fits int64, decode to int64.

- if value doesn't fit int64, decode to big.Int.

Closes #209
2020-05-20 18:49:40 -05:00
Faye Amacker
cb23445252
Add CBOR encoding and decoding options, update CTAP2
Add these 2 options to both EncOptions and DecOptions:

* IndefLength: IndefLengthAllowed, IndefLengthForbidden
* TagsMd: TagsAllowed, TagsForbidden

Add 3 more options to DecOptions:

* MaxMapArrayElements (default = 128x1024)
* MaxMapPairs (default = 128x1024)
* MaxNestingLevels (default = 32)

Update func CTAP2EncOptions() to return 2 additional presets:

* TagsMd = TagsForbidden
* IndefLength = IndefLengthForbidden

Closes: #155
Closes: #167
2020-02-23 00:45:36 -06:00
Faye Amacker
e882cb5e44
Add CBOR duplicate map key options (#152)
This CBOR library provides 2 options for duplicate map keys:

- DupMapKeyQuiet: Turn off detection of duplicate map keys. It tries
to use a "keep fastest" method by choosing either "keep first" or
"keep last" depending on the Go data type.

- DupMapKeyEnforcedAPF: Turn on detection and rejection of duplidate
map keys. Decoding stops immediately and returns DupMapKeyError when the
first duplicate key is detected. The error includes the duplicate map key
and the index number.

APF suffix means "Allow Partial Fill" so the destination map or struct
can contain some decoded values at the time of error. It is the caller's
responsibility to respond to the DuplicateMapKeyErr by discarding the
partially filled result if that's required by their protocol.

Detection of duplicate map keys relies on whether the CBOR map key would
be a duplicate "key" when decoded and applied to the user-provided Go map
or struct.
2020-02-16 21:49:31 -06:00
Faye Amacker
6de2d8add3
v2.1 CBOR tags, faster CBOR decoding & less memory (#149)
CBOR tag support includes:
- API to use TagSet to register user-defined Go type with CBOR tag number.
- API to encode Go value with CBOR tag number without registering.
- API to extract CBOR tag number with Tag or RawTag structs.
- API to use immutable TagSet or mutable shared TagSet (shared tag registry).
- EncOptions/DecOptions settings to tag or not tag time.Time values.

Improved decoding speed and memory for "keyasint" struct (COSE, CWT, etc.):
- speed is faster by 24% - 28%.
- mem allocs is reduced by 44% - 61%.
- mem bytes is reduced by 13% - 26%.

Encoding is a bit slower around 2% - 4% because of added features.  Optimization
of encoding can be tackled in future releases because it is still very fast.

TagSet is a Go interface to register tag support for user-defined Go types.

EncMode and DecMode are reusable interfaces that are safe for concurrency.
They export codec functions with identical signatures to encoding/json.

EncMode can be created by these functions:
	EncOptions{...}.EncMode()
	EncOptions{...}.EncModeWithTags(TagSet)
	EncOptions{...}.EncModeWithSharedTags(TagSet)

DecMode can be created by these functions:
	DecOptions{...}.DecMode()
	DecOptions{...}.DecModeWithTags(TagSet)
	DecOptions{...}.DecModeWithSharedTags(TagSet)

Tag and RawTag are structs used to encode/decode CBOR tag number with tag content.

See docs and closed issues for more details.

Closed: #44
Closed: #47
Closed: #125
Closed: #147
Closed: #151
2020-02-14 18:43:04 -06:00
Faye Amacker
158e5313bc
Add v2.0 API, faster CBOR, fewer allocs
Some checks failed
ci / Test on ${{matrix.os}} (ubuntu-latest) (push) Has been cancelled
cover ≥97% / Coverage (push) Has been cancelled
ci / Test on ${{matrix.os}} (macos-latest) (push) Has been cancelled
linters / Lint (push) Has been cancelled
Implement v2.0 API for easier CBOR encoding/decoding.  See issue #117 for summary.

Replaced EncOptions.TimeRFC3339 bool with TimeMode values:
* TimeUnix                   // secs, converted to smallest CBOR integer that fits
* TimeUnixMicro           // μs, CBOR float with subsecs in fractional part
* TimeUnixDynamic      // secs or μs, either int or float depending on empty subsecs
* TimeRFC3339            // secs, string
* TimeRFC3339Nano    // ns, string with trailing zeros removed

Decode CBOR time NaN and Infinity values to Go's "zero time" value.  
This is how CBOR Null values are decoded to time.Time.

Removed deprecated options (Canonical & CTAP2Canoical) in EncOptions.

Removed Valid() function.

Improved performance and memory alloc by 
* using pointer receivers for types implementing sort.Interface and others.
* storing pointer in sync.Pool.
* storing pointer as map value in sync.Map.

Used roughly 20 linters and resolved many reported issues.

Switched to GitHub Actions for CI to lint, test, and check code coverage.

Extensive improvements to README.md.  

Thanks @x448 for API design ideas, travis & github ci configs, filing issues, 
and improving README.md.

Closes: #107
Closes: #119
Closes: #120
Closes: #123 
Closes: #124 
Closes: #127
2020-02-02 23:24:18 -06:00
Faye Amacker
0bed4727aa Update copyright to only have year in LICENSE file
Each source file doesn't mention year and LICENSE file has "2019 - present".
2020-01-18 16:45:39 -06:00