goscript 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/cmd/goscript-wasm/main.go +38 -6
  2. package/compiler/diagnostic.go +104 -12
  3. package/compiler/diagnostic_test.go +106 -0
  4. package/compiler/gotest/runner.go +99 -17
  5. package/compiler/gotest/runner_test.go +65 -0
  6. package/compiler/index.test.ts +23 -0
  7. package/compiler/lowered-program.go +9 -7
  8. package/compiler/lowering.go +361 -72
  9. package/compiler/lowering_bench_test.go +1 -0
  10. package/compiler/lowering_internal_test.go +18 -0
  11. package/compiler/protobuf-ts-binding.go +65 -12
  12. package/compiler/protobuf-ts-binding_test.go +339 -0
  13. package/compiler/runtime-contract.go +4 -0
  14. package/compiler/runtime-contract_test.go +2 -0
  15. package/compiler/service.go +1 -0
  16. package/compiler/skeleton_test.go +60 -3
  17. package/compiler/wasm/compile_test.go +37 -4
  18. package/compiler/wasm-api.go +57 -7
  19. package/dist/gs/builtin/hostio.js +6 -1
  20. package/dist/gs/builtin/hostio.js.map +1 -1
  21. package/dist/gs/builtin/slice.d.ts +11 -1
  22. package/dist/gs/builtin/slice.js +158 -2
  23. package/dist/gs/builtin/slice.js.map +1 -1
  24. package/dist/gs/crypto/aes/index.d.ts +15 -0
  25. package/dist/gs/crypto/aes/index.js +57 -0
  26. package/dist/gs/crypto/aes/index.js.map +1 -0
  27. package/dist/gs/crypto/cipher/index.d.ts +41 -0
  28. package/dist/gs/crypto/cipher/index.js +255 -0
  29. package/dist/gs/crypto/cipher/index.js.map +1 -0
  30. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
  31. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +30 -5
  32. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  33. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.d.ts +1 -0
  34. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js +17 -11
  35. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js.map +1 -1
  36. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.d.ts +31 -0
  37. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js +117 -0
  38. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js.map +1 -0
  39. package/dist/gs/internal/byteorder/index.js +2 -2
  40. package/dist/gs/internal/byteorder/index.js.map +1 -1
  41. package/dist/gs/io/io.js +18 -2
  42. package/dist/gs/io/io.js.map +1 -1
  43. package/dist/gs/reflect/type.js +57 -0
  44. package/dist/gs/reflect/type.js.map +1 -1
  45. package/dist/gs/runtime/debug/index.js +2 -1
  46. package/dist/gs/runtime/debug/index.js.map +1 -1
  47. package/dist/gs/sync/atomic/doc_64.gs.js +7 -6
  48. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  49. package/go.mod +2 -2
  50. package/go.sum +2 -0
  51. package/gs/builtin/hostio.test.ts +22 -1
  52. package/gs/builtin/hostio.ts +6 -1
  53. package/gs/builtin/runtime-contract.test.ts +28 -0
  54. package/gs/builtin/slice.ts +225 -20
  55. package/gs/crypto/aes/index.test.ts +120 -0
  56. package/gs/crypto/aes/index.ts +76 -0
  57. package/gs/crypto/cipher/index.ts +345 -0
  58. package/gs/crypto/cipher/meta.json +6 -0
  59. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +162 -0
  60. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +41 -5
  61. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.test.ts +18 -0
  62. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.ts +17 -11
  63. package/gs/golang.org/x/crypto/chacha20poly1305/index.test.ts +91 -0
  64. package/gs/golang.org/x/crypto/chacha20poly1305/index.ts +245 -0
  65. package/gs/internal/byteorder/index.test.ts +2 -2
  66. package/gs/internal/byteorder/index.ts +2 -2
  67. package/gs/io/io.test.ts +56 -1
  68. package/gs/io/io.ts +19 -2
  69. package/gs/reflect/type.ts +64 -0
  70. package/gs/reflect/typefor.test.ts +21 -1
  71. package/gs/runtime/debug/index.test.ts +32 -4
  72. package/gs/runtime/debug/index.ts +5 -2
  73. package/gs/sync/atomic/doc_64.gs.ts +6 -7
  74. package/gs/sync/atomic/doc_64.test.ts +43 -0
  75. package/package.json +10 -3
@@ -345,6 +345,7 @@ export function EqualVTMapImplicit<K, V extends EqualVT<V>>(
345
345
  }
346
346
 
347
347
  type BoundMessageType = {
348
+ typeName?: string
348
349
  fields?: { list(): readonly BoundFieldInfo[] }
349
350
  clone?: (value: any) => any
350
351
  equals?: (a: any, b: any) => boolean
@@ -371,6 +372,8 @@ type BoundMessageCtor<T = any> = {
371
372
  __protobufTypeScriptMessage?: BoundMessageType
372
373
  }
373
374
 
375
+ const protobufScalarTypeBytes = 12
376
+
374
377
  function boundMessageType(ctor: BoundMessageCtor): BoundMessageType {
375
378
  const messageType = ctor.__protobufTypeScriptMessage
376
379
  if (messageType == null) {
@@ -401,8 +404,16 @@ function boundFieldValue(source: any, field: BoundFieldInfo): any {
401
404
  return source[boundFieldGoName(field)]
402
405
  }
403
406
 
404
- function toTypeScriptScalarValue(value: any): any {
407
+ function toTypeScriptScalarValue(value: any, field?: BoundFieldInfo): any {
405
408
  const unwrapped = $.pointerValueOrNil(value)
409
+ if (
410
+ field?.kind === 'scalar' &&
411
+ field.T === protobufScalarTypeBytes &&
412
+ unwrapped != null &&
413
+ !(unwrapped instanceof Uint8Array)
414
+ ) {
415
+ return Uint8Array.from($.asArray(unwrapped))
416
+ }
406
417
  if (unwrapped instanceof Uint8Array) {
407
418
  return unwrapped
408
419
  }
@@ -440,7 +451,7 @@ function toTypeScriptFieldValue(
440
451
  const messageType = resolveBoundMessageType(field.T)
441
452
  return toTypeScriptMessage(value, messageType, ctor)
442
453
  }
443
- return toTypeScriptScalarValue(value)
454
+ return toTypeScriptScalarValue(value, field)
444
455
  }
445
456
 
446
457
  function toTypeScriptMessage(
@@ -520,6 +531,23 @@ function fromTypeScriptFieldValue(
520
531
  return fromTypeScriptScalarValue(value)
521
532
  }
522
533
 
534
+ function fromTypeScriptWellKnownMessage(
535
+ value: any,
536
+ ctor: BoundMessageCtor,
537
+ messageType?: BoundMessageType,
538
+ ): any {
539
+ if (
540
+ messageType?.typeName === 'google.protobuf.Timestamp' &&
541
+ value instanceof Date
542
+ ) {
543
+ const ms = value.getTime()
544
+ const seconds = Math.floor(ms / 1000)
545
+ const nanos = Math.trunc((ms - seconds * 1000) * 1000000)
546
+ return new ctor({ Seconds: seconds, Nanos: nanos })
547
+ }
548
+ return undefined
549
+ }
550
+
523
551
  function fromTypeScriptMessage(
524
552
  value: any,
525
553
  ctor: BoundMessageCtor,
@@ -528,6 +556,10 @@ function fromTypeScriptMessage(
528
556
  if (value == null) {
529
557
  return null
530
558
  }
559
+ const wellKnown = fromTypeScriptWellKnownMessage(value, ctor, messageType)
560
+ if (wellKnown !== undefined) {
561
+ return wellKnown
562
+ }
531
563
  const out = new ctor()
532
564
  const fields = (messageType ?? boundMessageType(ctor)).fields?.list()
533
565
  if (fields == null) {
@@ -579,14 +611,17 @@ export function MarshalBoundMessageVT<T>(
579
611
  ctor: BoundMessageCtor<T>,
580
612
  value: T | $.VarRef<T> | null,
581
613
  ): [$.Slice<number>, $.GoError] {
614
+ let typeName = ctor.name
582
615
  try {
583
616
  const messageType = boundMessageType(ctor)
617
+ typeName = messageType.typeName ?? typeName
584
618
  return [
585
619
  messageType.toBinary(toTypeScriptMessage(value, messageType, ctor)),
586
620
  null,
587
621
  ]
588
622
  } catch (err) {
589
- return [null, $.toGoError(err as Error)]
623
+ const msg = err instanceof Error ? err.message : String(err)
624
+ return [null, $.toGoError(new Error(`marshal ${typeName}: ${msg}`))]
590
625
  }
591
626
  }
592
627
 
@@ -599,12 +634,13 @@ export function MarshalBoundMessageToSizedBufferVT<T>(
599
634
  if (err != null) {
600
635
  return [0, err]
601
636
  }
602
- const offset = $.len(dAtA) - $.len(bytes)
637
+ const byteCount = $.len(bytes)
638
+ const offset = $.len(dAtA) - byteCount
603
639
  const src = $.asArray(bytes)
604
640
  for (let i = 0; i < src.length; i++) {
605
641
  ;(dAtA as any)[offset + i] = src[i]
606
642
  }
607
- return [offset, null]
643
+ return [byteCount, null]
608
644
  }
609
645
 
610
646
  export function SizeBoundMessageVT<T>(
@@ -113,6 +113,24 @@ describe('protobuf-go-lite/json override', () => {
113
113
  ])
114
114
  })
115
115
 
116
+ it('matches jsoniter value types while reading object fields', () => {
117
+ const state = NewUnmarshalState(
118
+ jsonBytes('{"id":"step","config":{"kind":"inline"}}'),
119
+ DefaultUnmarshalerConfig,
120
+ )
121
+
122
+ expect(state?.ReadObjectField()).toBe('id')
123
+ expect(state?.WhatIsNext()).toBe(1)
124
+ expect(state?.ReadString()).toBe('step')
125
+ expect(state?.ReadObjectField()).toBe('config')
126
+ expect(state?.WhatIsNext()).toBe(6)
127
+ expect(
128
+ new TextDecoder().decode(new Uint8Array(state?.SkipAndReturnBytes())),
129
+ ).toBe('{"kind":"inline"}')
130
+ expect(state?.ReadObjectField()).toBe('')
131
+ expect(state?.Err()).toBeNull()
132
+ })
133
+
116
134
  it('tracks unmarshaled field masks through nested fields', () => {
117
135
  const state = NewUnmarshalState(jsonBytes('{}'), DefaultUnmarshalerConfig)
118
136
  state?.AddField('top')
@@ -423,6 +423,7 @@ export class UnmarshalState {
423
423
  private path: string[]
424
424
  private objectEntries: Array<[string, unknown]> | null = null
425
425
  private objectIndex = 0
426
+ private objectValue: unknown = undefined
426
427
 
427
428
  constructor(
428
429
  init?: Partial<{
@@ -612,16 +613,21 @@ export class UnmarshalState {
612
613
  }
613
614
 
614
615
  public ReadObjectField(): string {
615
- const record = recordValue(this.value)
616
- if (record == null) {
617
- this.SetErrorf('expected JSON object')
618
- return ''
619
- }
620
616
  if (this.objectEntries == null) {
617
+ const record = recordValue(this.value)
618
+ if (record == null) {
619
+ this.SetErrorf('expected JSON object')
620
+ return ''
621
+ }
621
622
  this.objectEntries = Object.entries(record)
622
623
  this.objectIndex = 0
624
+ this.objectValue = this.value
623
625
  }
624
626
  if (this.objectIndex >= this.objectEntries.length) {
627
+ this.value = this.objectValue
628
+ this.objectEntries = null
629
+ this.objectIndex = 0
630
+ this.objectValue = undefined
625
631
  return ''
626
632
  }
627
633
  const [key, value] = this.objectEntries[this.objectIndex]
@@ -747,22 +753,22 @@ export class UnmarshalState {
747
753
 
748
754
  public WhatIsNext(): number {
749
755
  if (this.value === null) {
750
- return 0
756
+ return 3
751
757
  }
752
758
  if (Array.isArray(this.value)) {
753
- return 1
759
+ return 5
754
760
  }
755
761
  if (typeof this.value === 'object') {
756
- return 2
762
+ return 6
757
763
  }
758
764
  if (typeof this.value === 'string') {
759
- return 3
765
+ return 1
760
766
  }
761
767
  if (typeof this.value === 'number') {
762
- return 4
768
+ return 2
763
769
  }
764
770
  if (typeof this.value === 'boolean') {
765
- return 5
771
+ return 4
766
772
  }
767
773
  return 0
768
774
  }
@@ -0,0 +1,91 @@
1
+ import { describe, expect, test } from 'vitest'
2
+
3
+ import { New, NewX, NonceSize, NonceSizeX, Overhead } from './index.js'
4
+
5
+ describe('chacha20poly1305 override', () => {
6
+ test('matches the RFC 8439 ChaCha20-Poly1305 AEAD vector', () => {
7
+ const [aead, err] = New(
8
+ hex('808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f'),
9
+ )
10
+ expect(err).toBeNull()
11
+ expect(aead?.NonceSize()).toBe(NonceSize)
12
+ expect(aead?.Overhead()).toBe(Overhead)
13
+
14
+ const nonce = hex('070000004041424344454647')
15
+ const aad = hex('50515253c0c1c2c3c4c5c6c7')
16
+ const plaintext = hex(
17
+ '4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e',
18
+ )
19
+ const sealed = aead!.Seal(null, nonce, plaintext, aad)
20
+ expect(toHex(sealed)).toBe(
21
+ 'd31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd0600691',
22
+ )
23
+
24
+ const [opened, openErr] = aead!.Open(null, nonce, sealed, aad)
25
+ expect(openErr).toBeNull()
26
+ expect(toHex(opened)).toBe(toHex(plaintext))
27
+ })
28
+
29
+ test('rejects tampered ciphertexts', () => {
30
+ const [aead, err] = New(
31
+ hex('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'),
32
+ )
33
+ expect(err).toBeNull()
34
+ const sealed = aead!.Seal(
35
+ null,
36
+ hex('000000000000000000000002'),
37
+ hex('68656c6c6f'),
38
+ null,
39
+ )
40
+ sealed![0] ^= 1
41
+
42
+ const [opened, openErr] = aead!.Open(
43
+ null,
44
+ hex('000000000000000000000002'),
45
+ sealed,
46
+ null,
47
+ )
48
+ expect(opened).toBeNull()
49
+ expect(openErr?.Error()).toBe(
50
+ 'chacha20poly1305: message authentication failed',
51
+ )
52
+ })
53
+
54
+ test('round trips XChaCha20-Poly1305', () => {
55
+ const [aead, err] = NewX(
56
+ hex('1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0'),
57
+ )
58
+ expect(err).toBeNull()
59
+ expect(aead?.NonceSize()).toBe(NonceSizeX)
60
+ expect(aead?.Overhead()).toBe(Overhead)
61
+
62
+ const nonce = hex('000000000102030405060708090a0b0c0d0e0f1011121314')
63
+ const aad = hex('f33388860000000000004e91')
64
+ const plaintext = hex('496e7465726f70657261626c6520584368614368613230')
65
+ const sealed = aead!.Seal(null, nonce, plaintext, aad)
66
+
67
+ const [opened, openErr] = aead!.Open(null, nonce, sealed, aad)
68
+ expect(openErr).toBeNull()
69
+ expect(toHex(opened)).toBe(toHex(plaintext))
70
+ })
71
+
72
+ test('rejects invalid key length', () => {
73
+ const [aead, err] = New(new Uint8Array(31))
74
+ expect(aead).toBeNull()
75
+ expect(err?.Error()).toBe('chacha20poly1305: bad key length')
76
+ })
77
+ })
78
+
79
+ function hex(input: string): Uint8Array {
80
+ const out = new Uint8Array(input.length / 2)
81
+ for (let idx = 0; idx < out.length; idx++) {
82
+ out[idx] = Number.parseInt(input.slice(idx * 2, idx * 2 + 2), 16)
83
+ }
84
+ return out
85
+ }
86
+
87
+ function toHex(input: Uint8Array | number[] | null): string {
88
+ return Array.from(input ?? [])
89
+ .map((value) => value.toString(16).padStart(2, '0'))
90
+ .join('')
91
+ }
@@ -0,0 +1,245 @@
1
+ import * as $ from '@goscript/builtin/index.js'
2
+ import * as errors from '@goscript/errors/index.js'
3
+ import {
4
+ chacha20poly1305 as nobleChaCha20Poly1305,
5
+ xchacha20poly1305 as nobleXChaCha20Poly1305,
6
+ } from '@noble/ciphers/chacha.js'
7
+
8
+ export const KeySize = 32
9
+ export const NonceSize = 12
10
+ export const NonceSizeX = 24
11
+ export const Overhead = 16
12
+
13
+ const maxIETFPlaintext = 2 ** 38 - 64
14
+ const maxIETFCiphertext = 2 ** 38 - 48
15
+
16
+ type NobleAEAD = typeof nobleChaCha20Poly1305 | typeof nobleXChaCha20Poly1305
17
+
18
+ export let errOpen: $.GoError = errors.New(
19
+ 'chacha20poly1305: message authentication failed',
20
+ )
21
+
22
+ export function __goscript_set_errOpen(value: $.GoError): void {
23
+ errOpen = value
24
+ }
25
+
26
+ export interface AEAD {
27
+ NonceSize(): number
28
+ Overhead(): number
29
+ Seal(
30
+ dst: $.Slice<number>,
31
+ nonce: $.Slice<number>,
32
+ plaintext: $.Slice<number>,
33
+ additionalData: $.Slice<number>,
34
+ ): $.Slice<number>
35
+ Open(
36
+ dst: $.Slice<number>,
37
+ nonce: $.Slice<number>,
38
+ ciphertext: $.Slice<number>,
39
+ additionalData: $.Slice<number>,
40
+ ): [$.Slice<number>, $.GoError]
41
+ }
42
+
43
+ export class chacha20poly1305 implements AEAD {
44
+ readonly key: Uint8Array
45
+
46
+ constructor(key: Uint8Array) {
47
+ this.key = key.slice()
48
+ }
49
+
50
+ NonceSize(): number {
51
+ return NonceSize
52
+ }
53
+
54
+ Overhead(): number {
55
+ return Overhead
56
+ }
57
+
58
+ Seal(
59
+ dst: $.Slice<number>,
60
+ nonce: $.Slice<number>,
61
+ plaintext: $.Slice<number>,
62
+ additionalData: $.Slice<number>,
63
+ ): $.Slice<number> {
64
+ return seal(
65
+ dst,
66
+ nobleChaCha20Poly1305,
67
+ this.key,
68
+ nonce,
69
+ NonceSize,
70
+ plaintext,
71
+ additionalData,
72
+ )
73
+ }
74
+
75
+ Open(
76
+ dst: $.Slice<number>,
77
+ nonce: $.Slice<number>,
78
+ ciphertext: $.Slice<number>,
79
+ additionalData: $.Slice<number>,
80
+ ): [$.Slice<number>, $.GoError] {
81
+ return open(
82
+ dst,
83
+ nobleChaCha20Poly1305,
84
+ this.key,
85
+ nonce,
86
+ NonceSize,
87
+ ciphertext,
88
+ additionalData,
89
+ )
90
+ }
91
+ }
92
+
93
+ export class xchacha20poly1305 implements AEAD {
94
+ readonly key: Uint8Array
95
+
96
+ constructor(key: Uint8Array) {
97
+ this.key = key.slice()
98
+ }
99
+
100
+ NonceSize(): number {
101
+ return NonceSizeX
102
+ }
103
+
104
+ Overhead(): number {
105
+ return Overhead
106
+ }
107
+
108
+ Seal(
109
+ dst: $.Slice<number>,
110
+ nonce: $.Slice<number>,
111
+ plaintext: $.Slice<number>,
112
+ additionalData: $.Slice<number>,
113
+ ): $.Slice<number> {
114
+ return seal(
115
+ dst,
116
+ nobleXChaCha20Poly1305,
117
+ this.key,
118
+ nonce,
119
+ NonceSizeX,
120
+ plaintext,
121
+ additionalData,
122
+ )
123
+ }
124
+
125
+ Open(
126
+ dst: $.Slice<number>,
127
+ nonce: $.Slice<number>,
128
+ ciphertext: $.Slice<number>,
129
+ additionalData: $.Slice<number>,
130
+ ): [$.Slice<number>, $.GoError] {
131
+ return open(
132
+ dst,
133
+ nobleXChaCha20Poly1305,
134
+ this.key,
135
+ nonce,
136
+ NonceSizeX,
137
+ ciphertext,
138
+ additionalData,
139
+ )
140
+ }
141
+ }
142
+
143
+ export function New(key: $.Slice<number>): [AEAD | null, $.GoError] {
144
+ const keyBytes = bytes(key)
145
+ if (keyBytes.length !== KeySize) {
146
+ return [null, errors.New('chacha20poly1305: bad key length')]
147
+ }
148
+ return [
149
+ $.interfaceValue<AEAD | null>(
150
+ new chacha20poly1305(keyBytes),
151
+ '*chacha20poly1305.chacha20poly1305',
152
+ ),
153
+ null,
154
+ ]
155
+ }
156
+
157
+ export function NewX(key: $.Slice<number>): [AEAD | null, $.GoError] {
158
+ const keyBytes = bytes(key)
159
+ if (keyBytes.length !== KeySize) {
160
+ return [null, errors.New('chacha20poly1305: bad key length')]
161
+ }
162
+ return [
163
+ $.interfaceValue<AEAD | null>(
164
+ new xchacha20poly1305(keyBytes),
165
+ '*chacha20poly1305.xchacha20poly1305',
166
+ ),
167
+ null,
168
+ ]
169
+ }
170
+
171
+ function seal(
172
+ dst: $.Slice<number>,
173
+ cipher: NobleAEAD,
174
+ key: Uint8Array,
175
+ nonce: $.Slice<number>,
176
+ expectedNonceSize: number,
177
+ plaintext: $.Slice<number>,
178
+ additionalData: $.Slice<number>,
179
+ ): $.Slice<number> {
180
+ const nonceBytes = bytes(nonce)
181
+ if (nonceBytes.length !== expectedNonceSize) {
182
+ $.panic('chacha20poly1305: bad nonce length passed to Seal')
183
+ }
184
+ const plaintextBytes = bytes(plaintext)
185
+ if (plaintextBytes.length > maxIETFPlaintext) {
186
+ $.panic('chacha20poly1305: plaintext too large')
187
+ }
188
+
189
+ const sealed = cipher(key, nonceBytes, bytes(additionalData)).encrypt(
190
+ plaintextBytes,
191
+ )
192
+ return appendBytes(dst, sealed)
193
+ }
194
+
195
+ function open(
196
+ dst: $.Slice<number>,
197
+ cipher: NobleAEAD,
198
+ key: Uint8Array,
199
+ nonce: $.Slice<number>,
200
+ expectedNonceSize: number,
201
+ ciphertext: $.Slice<number>,
202
+ additionalData: $.Slice<number>,
203
+ ): [$.Slice<number>, $.GoError] {
204
+ const nonceBytes = bytes(nonce)
205
+ if (nonceBytes.length !== expectedNonceSize) {
206
+ $.panic('chacha20poly1305: bad nonce length passed to Open')
207
+ }
208
+ const ciphertextBytes = bytes(ciphertext)
209
+ if (ciphertextBytes.length < Overhead) {
210
+ return [null, errOpen]
211
+ }
212
+ if (ciphertextBytes.length > maxIETFCiphertext) {
213
+ $.panic('chacha20poly1305: ciphertext too large')
214
+ }
215
+
216
+ try {
217
+ return [
218
+ appendBytes(
219
+ dst,
220
+ cipher(key, nonceBytes, bytes(additionalData)).decrypt(ciphertextBytes),
221
+ ),
222
+ null,
223
+ ]
224
+ } catch {
225
+ return [null, errOpen]
226
+ }
227
+ }
228
+
229
+ function bytes(value: $.Slice<number>): Uint8Array {
230
+ return $.bytesToUint8Array(value).slice()
231
+ }
232
+
233
+ function appendBytes(
234
+ dst: $.Slice<number>,
235
+ suffix: Uint8Array,
236
+ ): $.Slice<number> {
237
+ const prefix = $.bytesToUint8Array(dst)
238
+ if (prefix.length === 0) {
239
+ return suffix.slice()
240
+ }
241
+ const out = new Uint8Array(prefix.length + suffix.length)
242
+ out.set(prefix)
243
+ out.set(suffix, prefix.length)
244
+ return out
245
+ }
@@ -16,7 +16,7 @@ describe('internal/byteorder uint64', () => {
16
16
  BEPutUint64(bytes, 0x0102030405060708n)
17
17
 
18
18
  expect(Array.from(bytes)).toEqual([1, 2, 3, 4, 5, 6, 7, 8])
19
- expect(BEUint64(bytes)).toBe(Number(0x0102030405060708n))
19
+ expect(BEUint64(bytes)).toBe(0x0102030405060708n)
20
20
  })
21
21
 
22
22
  test('reads and writes little-endian bigint values', () => {
@@ -25,7 +25,7 @@ describe('internal/byteorder uint64', () => {
25
25
  LEPutUint64(bytes, 0x0102030405060708n)
26
26
 
27
27
  expect(Array.from(bytes)).toEqual([8, 7, 6, 5, 4, 3, 2, 1])
28
- expect(LEUint64(bytes)).toBe(Number(0x0102030405060708n))
28
+ expect(LEUint64(bytes)).toBe(0x0102030405060708n)
29
29
  })
30
30
 
31
31
  test('appends uint64 values', () => {
@@ -13,7 +13,7 @@ export function BEUint32(b: $.Bytes): number {
13
13
  }
14
14
 
15
15
  export function BEUint64(b: $.Bytes): number {
16
- return Number(
16
+ return $.uint(
17
17
  (BigInt(b![0]) << 56n) |
18
18
  (BigInt(b![1]) << 48n) |
19
19
  (BigInt(b![2]) << 40n) |
@@ -34,7 +34,7 @@ export function LEUint32(b: $.Bytes): number {
34
34
  }
35
35
 
36
36
  export function LEUint64(b: $.Bytes): number {
37
- return Number(
37
+ return $.uint(
38
38
  BigInt(b![0]) |
39
39
  (BigInt(b![1]) << 8n) |
40
40
  (BigInt(b![2]) << 16n) |
package/gs/io/io.test.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import * as $ from '@goscript/builtin/index.js'
2
- import { LimitedReader, MultiWriter, NopCloser, Pipe, TeeReader } from './index.js'
2
+ import {
3
+ LimitedReader,
4
+ MultiWriter,
5
+ NewSectionReader,
6
+ NopCloser,
7
+ Pipe,
8
+ TeeReader,
9
+ } from './index.js'
3
10
  import { describe, expect, test } from 'vitest'
4
11
 
5
12
  class sliceReader {
@@ -22,6 +29,15 @@ class captureWriter {
22
29
  }
23
30
  }
24
31
 
32
+ class syncReaderAt {
33
+ constructor(private data: Uint8Array) {}
34
+
35
+ ReadAt(p: $.Bytes, off: number): [number, $.GoError] {
36
+ const n = $.copy(p, this.data.subarray(off))
37
+ return [n, n < $.len(p) ? (new Error('EOF') as $.GoError) : null]
38
+ }
39
+ }
40
+
25
41
  describe('io override', () => {
26
42
  test('LimitedReader accepts generated struct-literal construction', async () => {
27
43
  const reader = new LimitedReader({
@@ -118,6 +134,45 @@ describe('io override', () => {
118
134
  expect(Buffer.from(chunks).toString('utf8')).toBe('abc')
119
135
  })
120
136
 
137
+ test('SectionReader preserves sync reads for sync ReaderAt', () => {
138
+ const reader = NewSectionReader(
139
+ new syncReaderAt($.stringToBytes('abcdef')),
140
+ 1,
141
+ 3,
142
+ )
143
+ const buf = new Uint8Array(4)
144
+
145
+ const result = reader.Read(buf)
146
+ expect(result).not.toBeInstanceOf(Promise)
147
+ const [n, err] = result
148
+
149
+ expect(err).toBeNull()
150
+ expect(n).toBe(3)
151
+ expect(Buffer.from(buf.subarray(0, n)).toString('utf8')).toBe('bcd')
152
+ })
153
+
154
+ test('SectionReader awaits async ReaderAt', async () => {
155
+ const reader = NewSectionReader(
156
+ {
157
+ async ReadAt(p: $.Bytes, off: number): Promise<[number, $.GoError]> {
158
+ await Promise.resolve()
159
+ const data = $.stringToBytes('abcdef')
160
+ const n = $.copy(p, data.subarray(off))
161
+ return [n, n < $.len(p) ? (new Error('EOF') as $.GoError) : null]
162
+ },
163
+ } as any,
164
+ 1,
165
+ 3,
166
+ )
167
+ const buf = new Uint8Array(4)
168
+
169
+ const [n, err] = await reader.Read(buf)
170
+
171
+ expect(err).toBeNull()
172
+ expect(n).toBe(3)
173
+ expect(Buffer.from(buf.subarray(0, n)).toString('utf8')).toBe('bcd')
174
+ })
175
+
121
176
  test('PipeReader waits for a later write', async () => {
122
177
  const [reader, writer] = Pipe()
123
178
  const buf = new Uint8Array(5)
package/gs/io/io.ts CHANGED
@@ -363,7 +363,15 @@ export class SectionReader implements Reader, Seeker, ReaderAt {
363
363
  p = $.goSlice(p, 0, max)
364
364
  }
365
365
 
366
- const [n, err] = this.r.ReadAt(p, this.off)
366
+ const res = this.r.ReadAt(p, this.off) as any
367
+ if (res instanceof Promise) {
368
+ return res.then(([n, err]: [number, $.GoError]) => {
369
+ this.off += n
370
+ return [n, err]
371
+ }) as any
372
+ }
373
+
374
+ const [n, err] = res
367
375
  this.off += n
368
376
  return [n, err]
369
377
  }
@@ -400,7 +408,16 @@ export class SectionReader implements Reader, Seeker, ReaderAt {
400
408
  off += this.base
401
409
  if (off + $.len(p) > this.limit) {
402
410
  p = $.goSlice(p, 0, this.limit - off)
403
- const [n, err] = this.r.ReadAt(p, off)
411
+ const res = this.r.ReadAt(p, off) as any
412
+ if (res instanceof Promise) {
413
+ return res.then(([n, err]: [number, $.GoError]) => {
414
+ if (err === null) {
415
+ return [n, EOF]
416
+ }
417
+ return [n, err]
418
+ }) as any
419
+ }
420
+ const [n, err] = res
404
421
  if (err === null) {
405
422
  return [n, EOF]
406
423
  }