-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bb65acb
commit 8b9131e
Showing
10 changed files
with
875 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@siafoundation/sdk': patch | ||
--- | ||
|
||
Initial web SDK with WebTransport RHP4 protocol support. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
export type Encoder = { | ||
dataView: DataView | ||
offset: number | ||
} | ||
|
||
export function newEncoder(): Encoder { | ||
const buffer = new ArrayBuffer((1 << 22) + 200) // TODO: set based on rpc | ||
return { | ||
dataView: new DataView(buffer), | ||
offset: 0, | ||
} | ||
} | ||
|
||
export type Decoder = { | ||
dataView: DataView | ||
offset: number | ||
} | ||
|
||
export function newDecoder(buf: Uint8Array): Decoder { | ||
const dataView = new DataView(buf.buffer, buf.byteOffset, buf.byteLength) | ||
return { | ||
dataView, | ||
offset: 0, | ||
} | ||
} | ||
|
||
export function encodeBoolean(e: Encoder, b: boolean) { | ||
e.dataView.setUint8(e.offset++, b ? 1 : 0) | ||
} | ||
|
||
export function decodeBoolean(d: Decoder): boolean { | ||
return Boolean(d.dataView.getUint8(d.offset++) === 1) | ||
} | ||
|
||
export function encodeUint64(e: Encoder, n: number) { | ||
e.dataView.setBigUint64(e.offset, BigInt(n), true) | ||
e.offset += 8 | ||
} | ||
|
||
export function decodeUint64(d: Decoder): number { | ||
const n = Number(d.dataView.getBigUint64(d.offset, true)) | ||
d.offset += 8 | ||
return n | ||
} | ||
|
||
export function encodeBytes(e: Encoder, s: string) { | ||
const encoder = new TextEncoder() | ||
const arr = encoder.encode(s) | ||
|
||
for (let i = 0; i < arr.length; i++) { | ||
e.dataView.setUint8(e.offset++, arr[i]) | ||
} | ||
} | ||
|
||
export function decodeBytes(d: Decoder, length: number): string { | ||
const arr = new Uint8Array(length) | ||
|
||
for (let i = 0; i < length; i++) { | ||
arr[i] = d.dataView.getUint8(d.offset++) | ||
} | ||
|
||
const decoder = new TextDecoder('utf-8') | ||
return decoder.decode(arr) | ||
} | ||
|
||
export function encodeUint8Array(e: Encoder, a: Uint8Array) { | ||
for (let i = 0; i < a.length; i++) { | ||
e.dataView.setUint8(e.offset++, a[i]) | ||
} | ||
} | ||
|
||
export function decodeUint8Array(d: Decoder, length: number): Uint8Array { | ||
const arr = new Uint8Array(length) | ||
|
||
for (let i = 0; i < length; i++) { | ||
arr[i] = d.dataView.getUint8(d.offset++) | ||
} | ||
|
||
return arr | ||
} | ||
|
||
export function encodeString(e: Encoder, s: string) { | ||
encodeUint64(e, s.length) | ||
encodeBytes(e, s) | ||
} | ||
|
||
export function decodeString(d: Decoder): string { | ||
const length = decodeUint64(d) | ||
const s = decodeBytes(d, length) | ||
return s | ||
} | ||
|
||
export function encodeCurrency(e: Encoder, c: bigint) { | ||
// currency is 128 bits, little endian | ||
e.dataView.setBigUint64(e.offset, c & BigInt('0xFFFFFFFFFFFFFFFF'), true) | ||
e.offset += 8 | ||
e.dataView.setBigUint64(e.offset, c >> BigInt(64), true) | ||
e.offset += 8 | ||
} | ||
|
||
export function decodeCurrency(d: Decoder): bigint { | ||
const lo = d.dataView.getBigUint64(d.offset, true) | ||
d.offset += 8 | ||
const hi = d.dataView.getBigUint64(d.offset, true) | ||
d.offset += 8 | ||
return (hi << BigInt(64)) | lo | ||
} | ||
|
||
export function encodeAddress(e: Encoder, a: string) { | ||
return encodeBytes(e, a) | ||
} | ||
|
||
export function decodeAddress(d: Decoder): string { | ||
return decodeBytes(d, 32) | ||
} | ||
|
||
export function encodeSignature(e: Encoder, s: string) { | ||
return encodeBytes(e, s) | ||
} | ||
|
||
export function decodeSignature(d: Decoder): string { | ||
return decodeBytes(d, 64) | ||
} | ||
|
||
export function encodeTime(e: Encoder, t: string | Date) { | ||
const time = new Date(t) | ||
const seconds = BigInt(time.getTime()) / BigInt(1000) | ||
e.dataView.setBigUint64(e.offset, seconds, true) | ||
e.offset += 8 | ||
} | ||
|
||
export function decodeTime(d: Decoder): string { | ||
const seconds = d.dataView.getBigUint64(d.offset, true) | ||
d.offset += 8 | ||
try { | ||
const time = new Date(Number(seconds) * 1000) | ||
return time.toISOString() | ||
} catch (e) { | ||
return new Date().toISOString() | ||
} | ||
} | ||
|
||
export function encodeLengthPrefix(e: Encoder, length: number) { | ||
encodeUint64(e, length) | ||
} | ||
|
||
export function decodeLengthPrefix(d: Decoder): number { | ||
return decodeUint64(d) | ||
} | ||
|
||
export function encodeRpcId(e: Encoder, rpcName: string) { | ||
const rpcId = rpcName.padEnd(16, '\0') | ||
encodeUint8Array(e, new TextEncoder().encode(rpcId)) | ||
} | ||
|
||
export function decodeRpcId(d: Decoder): string { | ||
return decodeBytes(d, 16).replace(/\0/g, '') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { | ||
encodeHostPrices, | ||
decodeHostPrices, | ||
encodeHostSettings, | ||
decodeHostSettings, | ||
} from './encoding' | ||
import { newEncoder, newDecoder } from './encoder' | ||
import { HostPrices, HostSettings } from './types' | ||
import { TextDecoder, TextEncoder } from 'node:util' | ||
|
||
Object.defineProperties(globalThis, { | ||
TextDecoder: { value: TextDecoder }, | ||
TextEncoder: { value: TextEncoder }, | ||
}) | ||
|
||
describe('encoding', () => { | ||
it('encodeHostPrices', () => { | ||
const hostPrices: HostPrices = { | ||
contractPrice: BigInt(1000000000), | ||
collateral: BigInt(2000000000), | ||
storagePrice: BigInt(3000000000), | ||
ingressPrice: BigInt(4000000000), | ||
egressPrice: BigInt(5000000000), | ||
tipHeight: 450_000, | ||
validUntil: '2022-12-31T00:00:00.000Z', | ||
signature: | ||
'abcd567890123456789012345678901234567890123456789012345678901234', | ||
} | ||
|
||
const e = newEncoder() | ||
encodeHostPrices(e, hostPrices) | ||
const buf = new Uint8Array(e.dataView.buffer) | ||
const decoded = decodeHostPrices(newDecoder(buf)) | ||
expect(decoded).toEqual(hostPrices) | ||
}) | ||
|
||
it('encodeHostSettings', () => { | ||
const prices: HostPrices = { | ||
contractPrice: BigInt(1000000000), | ||
collateral: BigInt(2000000000), | ||
storagePrice: BigInt(3000000000), | ||
ingressPrice: BigInt(4000000000), | ||
egressPrice: BigInt(5000000000), | ||
tipHeight: 450_000, | ||
validUntil: '2022-12-31T00:00:00.000Z', | ||
signature: | ||
'abcd567890123456789012345678901234567890123456789012345678901234', | ||
} | ||
const hostSettings: HostSettings = { | ||
version: '123', | ||
netAddresses: [ | ||
{ protocol: 'protocol1', address: 'address1longer' }, | ||
{ protocol: 'protocol2longer', address: 'address2' }, | ||
], | ||
// 32 bytes | ||
walletAddress: '12345678901234567890123456789012', | ||
acceptingContracts: true, | ||
maxCollateral: BigInt(1000000000), | ||
maxDuration: 100, | ||
remainingStorage: 100, | ||
totalStorage: 100, | ||
prices, | ||
} | ||
|
||
const e = newEncoder() | ||
encodeHostSettings(e, hostSettings) | ||
const buf = new Uint8Array(e.dataView.buffer) | ||
const decoded = decodeHostSettings(newDecoder(buf)) | ||
expect(decoded).toEqual(hostSettings) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import { | ||
encodeCurrency, | ||
decodeCurrency, | ||
encodeAddress, | ||
encodeBoolean, | ||
encodeBytes, | ||
encodeString, | ||
encodeUint64, | ||
encodeLengthPrefix, | ||
decodeAddress, | ||
decodeBoolean, | ||
decodeBytes, | ||
decodeString, | ||
decodeUint64, | ||
decodeLengthPrefix, | ||
encodeSignature, | ||
decodeSignature, | ||
encodeTime, | ||
decodeTime, | ||
Encoder, | ||
Decoder, | ||
} from './encoder' | ||
import { HostPrices, HostSettings, NetAddress } from './types' | ||
|
||
export function encodeHostPrices(e: Encoder, hostPrices: HostPrices) { | ||
encodeCurrency(e, hostPrices.contractPrice) | ||
encodeCurrency(e, hostPrices.collateral) | ||
encodeCurrency(e, hostPrices.storagePrice) | ||
encodeCurrency(e, hostPrices.ingressPrice) | ||
encodeCurrency(e, hostPrices.egressPrice) | ||
encodeUint64(e, hostPrices.tipHeight) | ||
encodeTime(e, hostPrices.validUntil) | ||
encodeSignature(e, hostPrices.signature) | ||
} | ||
|
||
export function decodeHostPrices(d: Decoder): HostPrices { | ||
const contractPrice = decodeCurrency(d) | ||
const collateral = decodeCurrency(d) | ||
const storagePrice = decodeCurrency(d) | ||
const ingressPrice = decodeCurrency(d) | ||
const egressPrice = decodeCurrency(d) | ||
const tipHeight = decodeUint64(d) | ||
const validUntil = decodeTime(d) | ||
const signature = decodeSignature(d) | ||
return { | ||
contractPrice, | ||
collateral, | ||
storagePrice, | ||
ingressPrice, | ||
egressPrice, | ||
tipHeight, | ||
validUntil, | ||
signature, | ||
} | ||
} | ||
|
||
export function encodeNetAddress(e: Encoder, n: NetAddress) { | ||
encodeString(e, n.protocol) | ||
encodeString(e, n.address) | ||
} | ||
|
||
export function decodeNetAddress(d: Decoder): NetAddress { | ||
const protocol = decodeString(d) | ||
const address = decodeString(d) | ||
return { | ||
protocol, | ||
address, | ||
} | ||
} | ||
|
||
export function encodeHostSettings(e: Encoder, hostSettings: HostSettings) { | ||
encodeBytes(e, hostSettings.version) | ||
encodeLengthPrefix(e, hostSettings.netAddresses.length) | ||
for (let i = 0; i < hostSettings.netAddresses.length; i++) { | ||
encodeNetAddress(e, hostSettings.netAddresses[i]) | ||
} | ||
encodeAddress(e, hostSettings.walletAddress) | ||
encodeBoolean(e, hostSettings.acceptingContracts) | ||
encodeCurrency(e, hostSettings.maxCollateral) | ||
encodeUint64(e, hostSettings.maxDuration) | ||
encodeUint64(e, hostSettings.remainingStorage) | ||
encodeUint64(e, hostSettings.totalStorage) | ||
encodeHostPrices(e, hostSettings.prices) | ||
} | ||
|
||
export function decodeHostSettings(d: Decoder): HostSettings { | ||
const version = decodeBytes(d, 3) | ||
const netAddresses = [] | ||
const length = decodeLengthPrefix(d) | ||
for (let i = 0; i < length; i++) { | ||
netAddresses[i] = decodeNetAddress(d) | ||
} | ||
const walletAddress = decodeAddress(d) | ||
const acceptingContracts = decodeBoolean(d) | ||
const maxCollateral = decodeCurrency(d) | ||
const maxDuration = decodeUint64(d) | ||
const remainingStorage = decodeUint64(d) | ||
const totalStorage = decodeUint64(d) | ||
const prices = decodeHostPrices(d) | ||
return { | ||
version, | ||
netAddresses, | ||
walletAddress, | ||
acceptingContracts, | ||
maxCollateral, | ||
maxDuration, | ||
remainingStorage, | ||
totalStorage, | ||
prices, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { WebTransportClient } from './transport' | ||
|
||
export async function example() { | ||
const client = new WebTransportClient( | ||
'wss://your-webtransport-server.example', | ||
'cert' | ||
) | ||
await client.connect() | ||
const response = await client.sendRPCSettingsRequest() | ||
console.log('Settings:', response) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './types' | ||
export * from './transport' |
Oops, something went wrong.