Skip to content

Commit bb83bb3

Browse files
authored
Merge dev into main — main should hold the latest
2 parents c665e3d + 39e15f4 commit bb83bb3

12 files changed

Lines changed: 319 additions & 26 deletions

File tree

‎encode.go‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// SPDX-License-Identifier: EUPL-1.2
22

33
// Encoding helpers for the Core framework.
4-
// Wraps encoding/hex so consumers can use core primitives for common
5-
// byte-string encodings.
4+
// Wraps encoding/base64, encoding/binary and encoding/hex so consumers can use
5+
// core primitives for common byte-string encodings — this file is their sole
6+
// owner (SPOR), so the fixed-width readers live here beside the text ones.
67
package core
78

89
import (
910
"encoding/base64"
11+
"encoding/binary"
1012
"encoding/hex"
1113
)
1214

@@ -83,3 +85,37 @@ func Base64URLDecode(s string) Result {
8385
}
8486
return Result{b, true}
8587
}
88+
89+
// BigEndianUint64 reads the first eight bytes of b as a big-endian uint64.
90+
//
91+
// n := core.BigEndianUint64(buf[:])
92+
//
93+
// A thin pass-through so callers on a hot path need not import
94+
// encoding/binary — encode.go is its sole owner (SPOR). Inlinable, so the
95+
// wrapper costs nothing over the direct call. Panics if b is shorter than
96+
// eight bytes, exactly as the stdlib does.
97+
func BigEndianUint64(b []byte) uint64 { return binary.BigEndian.Uint64(b) }
98+
99+
// LittleEndianUint64 reads the first eight bytes of b as a little-endian uint64.
100+
//
101+
// lane := core.LittleEndianUint64(data[i*8:])
102+
//
103+
// The little-endian counterpart to BigEndianUint64; see it for why this lives
104+
// here. Keccak absorbs its state in this order.
105+
func LittleEndianUint64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) }
106+
107+
// HexAppendEncode appends the hex encoding of src to dst and returns the
108+
// extended slice.
109+
//
110+
// buf = core.HexAppendEncode(buf, rnd[:])
111+
//
112+
// The append-style form matters: it lets a caller building a string in one
113+
// buffer stay at a single allocation, where HexEncode would add its own.
114+
func HexAppendEncode(dst, src []byte) []byte { return hex.AppendEncode(dst, src) }
115+
116+
// PutLittleEndianUint64 writes v into the first eight bytes of b, little-endian.
117+
//
118+
// core.PutLittleEndianUint64(sum[i*8:], state[i])
119+
//
120+
// The write-side counterpart to LittleEndianUint64, here for the same reason.
121+
func PutLittleEndianUint64(b []byte, v uint64) { binary.LittleEndian.PutUint64(b, v) }

‎encode_test.go‎

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,86 @@ func TestEncode_Base64URLDecode_Ugly(t *T) {
132132
_, ok := r.Value.(error)
133133
AssertTrue(t, ok)
134134
}
135+
136+
// --- BigEndianUint64 ---
137+
138+
func TestEncode_BigEndianUint64_Good(t *T) {
139+
AssertEqual(t, uint64(1), BigEndianUint64([]byte{0, 0, 0, 0, 0, 0, 0, 1}))
140+
}
141+
142+
// Bad: byte order is the whole point — the same bytes read big-endian and
143+
// little-endian must not agree, or a caller has picked the wrong one.
144+
func TestEncode_BigEndianUint64_Bad(t *T) {
145+
b := []byte{1, 0, 0, 0, 0, 0, 0, 0}
146+
AssertEqual(t, uint64(1)<<56, BigEndianUint64(b))
147+
AssertFalse(t, BigEndianUint64(b) == LittleEndianUint64(b))
148+
}
149+
150+
// Ugly: only the first eight bytes are read, so a longer slice is not an error
151+
// and the tail is ignored.
152+
func TestEncode_BigEndianUint64_Ugly(t *T) {
153+
AssertEqual(t, ^uint64(0), BigEndianUint64([]byte{255, 255, 255, 255, 255, 255, 255, 255, 9, 9}))
154+
}
155+
156+
// --- LittleEndianUint64 ---
157+
158+
func TestEncode_LittleEndianUint64_Good(t *T) {
159+
AssertEqual(t, uint64(1), LittleEndianUint64([]byte{1, 0, 0, 0, 0, 0, 0, 0}))
160+
}
161+
162+
func TestEncode_LittleEndianUint64_Bad(t *T) {
163+
AssertEqual(t, uint64(1)<<56, LittleEndianUint64([]byte{0, 0, 0, 0, 0, 0, 0, 1}))
164+
}
165+
166+
// Ugly: round-trips with the write side, which is how Keccak absorbs and
167+
// squeezes its state.
168+
func TestEncode_LittleEndianUint64_Ugly(t *T) {
169+
buf := make([]byte, 8)
170+
PutLittleEndianUint64(buf, 0xDEADBEEFCAFEBABE)
171+
AssertEqual(t, uint64(0xDEADBEEFCAFEBABE), LittleEndianUint64(buf))
172+
}
173+
174+
// --- PutLittleEndianUint64 ---
175+
176+
func TestEncode_PutLittleEndianUint64_Good(t *T) {
177+
buf := make([]byte, 8)
178+
PutLittleEndianUint64(buf, 1)
179+
AssertEqual(t, byte(1), buf[0])
180+
AssertEqual(t, byte(0), buf[7])
181+
}
182+
183+
// Bad: writes exactly eight bytes and nothing beyond them, so a caller can
184+
// write into the middle of a larger buffer without clobbering its neighbours.
185+
func TestEncode_PutLittleEndianUint64_Bad(t *T) {
186+
buf := make([]byte, 10)
187+
buf[8], buf[9] = 7, 7
188+
PutLittleEndianUint64(buf[:8], ^uint64(0))
189+
AssertEqual(t, byte(7), buf[8])
190+
AssertEqual(t, byte(7), buf[9])
191+
}
192+
193+
func TestEncode_PutLittleEndianUint64_Ugly(t *T) {
194+
buf := make([]byte, 16)
195+
PutLittleEndianUint64(buf[8:], 0x0102030405060708)
196+
AssertEqual(t, uint64(0), LittleEndianUint64(buf[:8]))
197+
AssertEqual(t, uint64(0x0102030405060708), LittleEndianUint64(buf[8:]))
198+
}
199+
200+
// --- HexAppendEncode ---
201+
202+
func TestEncode_HexAppendEncode_Good(t *T) {
203+
AssertEqual(t, "0aff", AsString(HexAppendEncode(nil, []byte{0x0a, 0xff})))
204+
}
205+
206+
// Bad: an empty src appends nothing at all — dst comes back untouched rather
207+
// than gaining a separator or a padding byte.
208+
func TestEncode_HexAppendEncode_Bad(t *T) {
209+
AssertEqual(t, "id-", AsString(HexAppendEncode([]byte("id-"), nil)))
210+
}
211+
212+
// Ugly: the reason this exists over HexEncode — it extends dst in place, so a
213+
// caller building an identifier stays at one allocation.
214+
func TestEncode_HexAppendEncode_Ugly(t *T) {
215+
AssertEqual(t, "id-42-0aff", AsString(HexAppendEncode([]byte("id-42-"), []byte{0x0a, 0xff})))
216+
AssertEqual(t, HexEncode([]byte{0x0a, 0xff}), AsString(HexAppendEncode(nil, []byte{0x0a, 0xff})))
217+
}

‎info.go‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,16 @@ func EnvKeys() []string {
175175
}
176176
return keys
177177
}
178+
179+
// Pinner pins Go memory in place for the lifetime of a cgo call, so a pointer
180+
// handed to C stays valid across a garbage collection.
181+
//
182+
// var p core.Pinner
183+
// p.Pin(&buf[0])
184+
// defer p.Unpin()
185+
//
186+
// Aliased here rather than imported at the use site because info.go is
187+
// runtime's sole owner (SPOR). The consumer is PinnedView in unsafe.go, which
188+
// documents the safety contract — a slice holding Go pointers makes Pin panic
189+
// under the race detector.
190+
type Pinner = runtime.Pinner

‎info_test.go‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,52 @@ func TestInfo_StackBuf_Ugly(t *T) {
274274
AssertContains(t, first, "goroutine")
275275
AssertContains(t, string(StackBuf()), "TestInfo_StackBuf_Ugly")
276276
}
277+
278+
// --- Pinner ---
279+
280+
// Pinner keeps a Go allocation at a fixed address across a garbage collection,
281+
// so a pointer handed to C stays valid. Its observable contract is that the
282+
// address does not move while pinned and that the value survives — which is
283+
// what these check, since the pin itself is invisible from Go.
284+
285+
func TestInfo_Pinner_Good(t *T) {
286+
buf := make([]byte, 8)
287+
buf[0] = 42
288+
289+
var p Pinner
290+
p.Pin(&buf[0])
291+
defer p.Unpin()
292+
293+
AssertEqual(t, byte(42), buf[0])
294+
}
295+
296+
// Bad: Unpin is what releases the pin, and pinning again after it must work —
297+
// a Pinner that could only be used once would leak for every reused buffer.
298+
func TestInfo_Pinner_Bad(t *T) {
299+
buf := make([]byte, 4)
300+
301+
var p Pinner
302+
p.Pin(&buf[0])
303+
p.Unpin()
304+
305+
p.Pin(&buf[0])
306+
p.Unpin()
307+
308+
AssertEqual(t, 4, len(buf))
309+
}
310+
311+
// Ugly: several allocations pinned through one Pinner are all released by a
312+
// single Unpin — the shape PinnedView relies on.
313+
func TestInfo_Pinner_Ugly(t *T) {
314+
a := make([]byte, 2)
315+
b := make([]int32, 2)
316+
a[1], b[1] = 9, 9
317+
318+
var p Pinner
319+
p.Pin(&a[0])
320+
p.Pin(&b[0])
321+
p.Unpin()
322+
323+
AssertEqual(t, byte(9), a[1])
324+
AssertEqual(t, int32(9), b[1])
325+
}

‎int.go‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,37 @@ func ParseInt(s string, base int, bitSize int) Result {
5050
}
5151
return Result{i, true}
5252
}
53+
54+
// The Append* family formats into a caller-supplied buffer and returns the
55+
// extended slice, so a caller assembling a line stays at one allocation where
56+
// the Format*/Itoa forms would each add their own. int.go is strconv's sole
57+
// owner (SPOR), so they live here rather than at the call sites that need them
58+
// — the structured logger's field encoder and ID(). All are inlinable
59+
// pass-throughs, so routing through core costs nothing over the direct call.
60+
61+
// AppendInt appends the base-`base` text of i to dst.
62+
//
63+
// buf = core.AppendInt(buf[:0], -42, 10)
64+
func AppendInt(dst []byte, i int64, base int) []byte { return strconv.AppendInt(dst, i, base) }
65+
66+
// AppendUint appends the base-`base` text of i to dst.
67+
//
68+
// buf = core.AppendUint(buf, counter.Add(1), 10)
69+
func AppendUint(dst []byte, i uint64, base int) []byte { return strconv.AppendUint(dst, i, base) }
70+
71+
// AppendBool appends "true" or "false" to dst.
72+
//
73+
// buf = core.AppendBool(buf[:0], ok)
74+
func AppendBool(dst []byte, b bool) []byte { return strconv.AppendBool(dst, b) }
75+
76+
// AppendFloat appends the text of f to dst, formatted as by FormatFloat.
77+
//
78+
// buf = core.AppendFloat(buf[:0], f, 'g', -1, 64)
79+
func AppendFloat(dst []byte, f float64, fmt byte, prec, bitSize int) []byte {
80+
return strconv.AppendFloat(dst, f, fmt, prec, bitSize)
81+
}
82+
83+
// AppendQuote appends the double-quoted Go string literal of s to dst.
84+
//
85+
// buf = core.AppendQuote(buf[:0], v)
86+
func AppendQuote(dst []byte, s string) []byte { return strconv.AppendQuote(dst, s) }

‎int_test.go‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,89 @@ func TestInt_ParseInt_Ugly(t *T) {
9191
_, ok := r.Value.(error)
9292
AssertTrue(t, ok)
9393
}
94+
95+
// --- AppendInt ---
96+
97+
func TestInt_AppendInt_Good(t *T) {
98+
AssertEqual(t, "42", AsString(AppendInt(nil, 42, 10)))
99+
}
100+
101+
func TestInt_AppendInt_Bad(t *T) {
102+
AssertEqual(t, "-ff", AsString(AppendInt(nil, -255, 16)))
103+
}
104+
105+
// Ugly: the point of the Append form is that it EXTENDS dst rather than
106+
// replacing it — a caller assembling a line depends on the prefix surviving.
107+
func TestInt_AppendInt_Ugly(t *T) {
108+
AssertEqual(t, "n=-7", AsString(AppendInt([]byte("n="), -7, 10)))
109+
AssertEqual(t, "0", AsString(AppendInt(nil, 0, 2)))
110+
}
111+
112+
// --- AppendUint ---
113+
114+
func TestInt_AppendUint_Good(t *T) {
115+
AssertEqual(t, "255", AsString(AppendUint(nil, 255, 10)))
116+
}
117+
118+
func TestInt_AppendUint_Bad(t *T) {
119+
AssertEqual(t, "ff", AsString(AppendUint(nil, 255, 16)))
120+
}
121+
122+
// Ugly: appends to a non-empty dst, and the unsigned maximum — the value an
123+
// int64-shaped formatter would have rendered negative.
124+
func TestInt_AppendUint_Ugly(t *T) {
125+
AssertEqual(t, "id-1", AsString(AppendUint([]byte("id-"), 1, 10)))
126+
AssertEqual(t, "18446744073709551615", AsString(AppendUint(nil, ^uint64(0), 10)))
127+
}
128+
129+
// --- AppendBool ---
130+
131+
func TestInt_AppendBool_Good(t *T) {
132+
AssertEqual(t, "true", AsString(AppendBool(nil, true)))
133+
}
134+
135+
func TestInt_AppendBool_Bad(t *T) {
136+
AssertEqual(t, "false", AsString(AppendBool(nil, false)))
137+
}
138+
139+
func TestInt_AppendBool_Ugly(t *T) {
140+
AssertEqual(t, "ok=true", AsString(AppendBool([]byte("ok="), true)))
141+
}
142+
143+
// --- AppendFloat ---
144+
145+
func TestInt_AppendFloat_Good(t *T) {
146+
AssertEqual(t, "1.5", AsString(AppendFloat(nil, 1.5, 'g', -1, 64)))
147+
}
148+
149+
// Bad: the two values a float field can hold that are not numbers. Built by
150+
// division rather than a math helper because a test here may only import
151+
// dappco.re/go, and core exposes no NaN/Inf constructor.
152+
func TestInt_AppendFloat_Bad(t *T) {
153+
zero := 0.0
154+
AssertEqual(t, "NaN", AsString(AppendFloat(nil, zero/zero, 'g', -1, 64)))
155+
AssertEqual(t, "+Inf", AsString(AppendFloat(nil, 1.0/zero, 'g', -1, 64)))
156+
}
157+
158+
// Ugly: 'g' drops the exponent for small magnitudes but not large ones, and a
159+
// 32-bit bitSize rounds to the nearest float32 — both surprising but correct.
160+
func TestInt_AppendFloat_Ugly(t *T) {
161+
AssertEqual(t, "1e+21", AsString(AppendFloat(nil, 1e21, 'g', -1, 64)))
162+
AssertEqual(t, "0.1", AsString(AppendFloat(nil, float64(float32(0.1)), 'g', -1, 32)))
163+
}
164+
165+
// --- AppendQuote ---
166+
167+
func TestInt_AppendQuote_Good(t *T) {
168+
AssertEqual(t, `"hello"`, AsString(AppendQuote(nil, "hello")))
169+
}
170+
171+
func TestInt_AppendQuote_Bad(t *T) {
172+
AssertEqual(t, `""`, AsString(AppendQuote(nil, "")))
173+
}
174+
175+
// Ugly: the reason the logger quotes at all — an embedded quote, backslash or
176+
// newline has to come back escaped or the line it is written into is corrupt.
177+
func TestInt_AppendQuote_Ugly(t *T) {
178+
AssertEqual(t, `"a\"b\\c\n"`, AsString(AppendQuote(nil, "a\"b\\c\n")))
179+
}

‎log.go‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package core
77

88
import (
99
goio "io"
10-
"strconv"
1110
)
1211

1312
// Level defines logging verbosity.
@@ -278,19 +277,19 @@ func (l *Log) log(level Level, prefix, msg string, keyvals ...any) {
278277
// previous behaviour for less common values.
279278
switch v := val.(type) {
280279
case string:
281-
line.Write(strconv.AppendQuote(scratch[:0], v))
280+
line.Write(AppendQuote(scratch[:0], v))
282281
case int:
283-
line.Write(strconv.AppendInt(scratch[:0], int64(v), 10))
282+
line.Write(AppendInt(scratch[:0], int64(v), 10))
284283
case int64:
285-
line.Write(strconv.AppendInt(scratch[:0], v, 10))
284+
line.Write(AppendInt(scratch[:0], v, 10))
286285
case uint:
287-
line.Write(strconv.AppendUint(scratch[:0], uint64(v), 10))
286+
line.Write(AppendUint(scratch[:0], uint64(v), 10))
288287
case uint64:
289-
line.Write(strconv.AppendUint(scratch[:0], v, 10))
288+
line.Write(AppendUint(scratch[:0], v, 10))
290289
case bool:
291-
line.Write(strconv.AppendBool(scratch[:0], v))
290+
line.Write(AppendBool(scratch[:0], v))
292291
case float64:
293-
line.Write(strconv.AppendFloat(scratch[:0], v, 'g', -1, 64))
292+
line.Write(AppendFloat(scratch[:0], v, 'g', -1, 64))
294293
default:
295294
line.WriteString(Sprintf("%v", val))
296295
}

‎pool.go‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
package core
88

9-
import "sync"
10-
119
// Pool is a concurrency-safe LIFO free-list of reusable T values — the zero-alloc
1210
// recycling primitive behind hot-path scratch buffers and device handles. Get
1311
// pops a recycled value (or the zero T when empty, so the caller builds a fresh
@@ -26,7 +24,7 @@ import "sync"
2624
// }
2725
// defer pool.Put(s)
2826
type Pool[T any] struct {
29-
mu sync.Mutex
27+
mu Mutex
3028
items []T
3129
}
3230

0 commit comments

Comments
 (0)