Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
martinheidegger committed Dec 23, 2023
0 parents commit dd4afb3
Show file tree
Hide file tree
Showing 30 changed files with 2,692 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Test

on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: npm install
- run: npm run integration
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.history
coverage
.vscode
*.tgz
6 changes: 6 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.history
coverage
.vscode
.github
*.tgz
test
9 changes: 9 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The MIT License (MIT)

Copyright © 2023 Martin Heidegger

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# protobuf-varint

⚡️

Fast encoding and decoding for all variable [protobuf-style varint bytes](https://developers.google.com/protocol-buffers/docs/encoding#varints) and also decode them.

- Supports 64bit values using `longfn` or `long.js` compatible objects.
- Protocol-buffer compatible for **all** edge cases
- Codecs for all types: `int32`, `sint32`, `uint32`, `int64`, `sint64` and `uint64`
- Pure JavaScript
- No dependencies
- TypeScript declaration files 😉

## Usage

### Encoding

```js
import { uint64 } from 'protobuf-varint'
import { fromBigInt } from 'longfn'

// You can use numbers for the whole uint64 range with the longfn
// lib to be easily created from a variety of formats
const long = fromBigInt(0xffffffffffffffffn)

const fixedEncoder = uint64.fixedEncoder(long)
const buffer = new Uint8Array(
fixedEncoder.bytes, // The fixedEncoder containst the size needed to write the var-int
)
fixedEncoder.encode(long, buffer, 0) // Profit!
```

### Decoding

```js
// Use a operation object to reduce memory consumption
const target = { low: 0, high: 0, unsigned: false }
uint64.decode(buffer, 0, target)
// `target` now contains the value from the buffer!

uint64.decode.bytes // amount of bytes that were previously read
```

### Backwards Compatibility

Other encoding systems tend to use the `encode` and `encodeLength` functions
to encode content. This is discouraged because there is nearly no use-case for
`encode` on its own. `fixedEncoder` removes the need for a set of preconditions
checks that would need to be done twice, still you can use `encode` and `encodeLength`
as with others.

```js
import { encode, encodeLength } from 'protobuf-varint/sint32'

// Note this is deprecated! Use `fixedEncoder`!

const int32 = -1311313
const buffer = new Uint8Array(encodeLength(int32))
encode(int32, buffer, 0)
```

### Flexible Partial Access

Depending on your usage you can choose different variants matching your needs.

```js
import { uint64 } from 'protobuf-varint'
import * as int32 from 'protobuf-varint/int32'
import { fixedEncoder } from 'protobuf-varint/sint64'
import { decode as decodeSInt32 } from 'protobuf-varint/sint32'
import { decode as decodeSInt64 } from 'protobuf-varint/sint64'
import { codecs } from 'protobuf-varint'
codecs.int64
```

# License

[MIT](./LICENSE)
15 changes: 15 additions & 0 deletions lib/codecs.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as int32 from './int32.mjs'
import * as int64 from './int64.mjs'
import * as sint32 from './sint32.mjs'
import * as sint64 from './sint64.mjs'
import * as uint32 from './uint32.mjs'
import * as uint64 from './uint64.mjs'

export const codecs: Readonly<{
int32: typeof int32
int64: typeof int64
sint32: typeof sint32
sint64: typeof sint64
uint32: typeof uint32
uint64: typeof uint64
}>
14 changes: 14 additions & 0 deletions lib/codecs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as int32 from './int32.mjs'
import * as uint32 from './uint32.mjs'
import * as sint32 from './sint32.mjs'
import * as int64 from './int64.mjs'
import * as uint64 from './uint64.mjs'
import * as sint64 from './sint64.mjs'
export const codecs = Object.freeze({
int32,
uint32,
sint32,
int64,
uint64,
sint64,
})
9 changes: 9 additions & 0 deletions lib/errors.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const NUMBER_UNSAFE = 'Numbers are unsafe, use BigInt, Long or Bytes'
export const NUMBER_NEGATIVE = 'Numbers need to be positive (>= 0)'
export const NUMBER_FLOATING_POINT = 'Number needs to be an integer'
export const UINT32_TOO_LARGE = 'Numbers need to be smaller or equal 0xffffffff'
export const INT32_TOO_LARGE = 'Numbers need to be smaller or equal 0x7fffffff'
export const INT32_TOO_SMALL = 'Numbers need to be bigger or equal -0x80000000'
export const INVALID_LONG = 'Input not coerceable to a long number'
export const INVALID_NUMBER = 'Input not coerceable to a number'
export const NAN = 'Input is NaN'
9 changes: 9 additions & 0 deletions lib/errors.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const NUMBER_UNSAFE = 'Numbers are unsafe, use BigInt, Long or Bytes'
export const NUMBER_NEGATIVE = 'Numbers need to be positive (>= 0)'
export const NUMBER_FLOATING_POINT = 'Number needs to be an integer'
export const UINT32_TOO_LARGE = 'Numbers need to be smaller or equal 0xffffffff'
export const INT32_TOO_LARGE = 'Numbers need to be smaller or equal 0x7fffffff'
export const INT32_TOO_SMALL = 'Numbers need to be bigger or equal -0x80000000'
export const INVALID_LONG = 'Input not coerceable to a long number'
export const INVALID_NUMBER = 'Input not coerceable to a number'
export const NAN = 'Input is NaN'
9 changes: 9 additions & 0 deletions lib/index.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export * as int32 from './int32.mjs'
export * as int64 from './int64.mjs'
export * as sint32 from './sint32.mjs'
export * as sint64 from './sint64.mjs'
export * as uint32 from './uint32.mjs'
export * as uint64 from './uint64.mjs'
export * as errors from './errors.mjs'
export { codecs } from './codecs.mjs'
export * from './types.mjs'
9 changes: 9 additions & 0 deletions lib/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export * as int32 from './int32.mjs'
export * as int64 from './int64.mjs'
export * as sint32 from './sint32.mjs'
export * as sint64 from './sint64.mjs'
export * as uint32 from './uint32.mjs'
export * as uint64 from './uint64.mjs'
export * as errors from './errors.mjs'
export { codecs } from './codecs.mjs'
export * from './types.mjs'
16 changes: 16 additions & 0 deletions lib/int32.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type {
NumberDecode,
NumberEncode,
NumberEncodeLength,
NumberFixedEncode,
Validator,
} from './types.mjs'

export const decode: NumberDecode
export const encode: NumberEncode
export const encodeLength: NumberEncodeLength
export const fixedEncoder: NumberFixedEncode
export const name = 'int32'
export const validate: Validator<string>
export const MIN_VALUE = -0b10000000000000000000000000000000
export const MAX_VALUE = 0b01111111111111111111111111111111
92 changes: 92 additions & 0 deletions lib/int32.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import {
INT32_TOO_LARGE,
INT32_TOO_SMALL,
NAN,
INVALID_NUMBER,
NUMBER_FLOATING_POINT,
} from './errors.mjs'
import { fixedEncoders as fixedUint, B1, B2, B3, B4 } from './uint32.mjs'

export const name = 'int32'

export const MIN_VALUE = -0b10000000000000000000000000000000
export const MAX_VALUE = 0b01111111111111111111111111111111

const MSB = 0b10000000
const REST = 0b01111111
const POS = 0b0111
const NEG = 0b1000

export function decode(bytes, offset = 0) {
let b = bytes[offset]
if (!(b & MSB)) {
decode.bytes = 1
return b
}
let result = b & REST
b = bytes[offset + 1]
if (!(b & MSB)) {
decode.bytes = 2
return result | (b << 7)
}
result |= (b & REST) << 7
b = bytes[offset + 2]
if (!(b & MSB)) {
decode.bytes = 3
return result | (b << 14)
}
result |= (b & REST) << 14
b = bytes[offset + 3]
if (!(b & MSB)) {
decode.bytes = 4
return result | (b << 21)
}
result |= (b & REST) << 21
b = bytes[offset + 4]
decode.bytes = 5
return result + (b & POS) * 0x10000000 - (b & NEG) * 0x10000000
}
decode.bytes = 1

export function encode(int32, target, offset) {
const { bytes, encode: fixEncode } = fixedEncoder(int32)
encode.bytes = bytes
return fixEncode(int32, target, offset)
}
encode.bytes = 1

export function encodeLength(int32) {
return fixedEncoder(int32).bytes
}

const NE = 0x100000000

const [p1, p2, p3, p4, p5] = fixedUint
const p5encode = p5.encode
const neg = Object.freeze({
bytes: 5,
encode(int, out, offset) {
return p5encode(NE + int, out, offset)
},
})

export const fixedEncoder = (int) =>
int < 0
? neg
: int & B4
? p5
: int & B3
? p4
: int & B2
? p3
: int & B1
? p2
: p1

export function validate(input) {
if (typeof input !== 'number') return INVALID_NUMBER
if (isNaN(input)) return NAN
if (input < MIN_VALUE) return INT32_TOO_SMALL
if (input > MAX_VALUE) return INT32_TOO_LARGE
if (input !== (input | 0)) return NUMBER_FLOATING_POINT
}
14 changes: 14 additions & 0 deletions lib/int64.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type {
SLongDecode,
SLongEncode,
SLongEncodeLength,
SLongFixedEncode,
Validator,
} from './types.mjs'

export const decode: SLongDecode
export const encode: SLongEncode
export const encodeLength: SLongEncodeLength
export const fixedEncoder: SLongFixedEncode
export const name = 'int64'
export const validate: Validator<string>
Loading

0 comments on commit dd4afb3

Please sign in to comment.