• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokath / trice / 28134134962

24 Jun 2026 10:37PM UTC coverage: 87.212% (-3.0%) from 90.176%
28134134962

push

github

web-flow
Merge pull request #688 from rokath/wip

Wip

60 of 147 new or added lines in 6 files covered. (40.82%)

278 existing lines in 8 files now uncovered.

6104 of 6999 relevant lines covered (87.21%)

870.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.01
/internal/decoder/decoder.go
1
// SPDX-License-Identifier: MIT
2

3
// Package decoder provides several decoders for differently encoded trice streams.
4
package decoder
5

6
import (
7
        "encoding/binary"
8
        "fmt"
9
        "io"
10
        "path/filepath"
11
        "regexp"
12
        "strconv"
13
        "strings"
14
        "sync"
15
        "time"
16

17
        "github.com/rokath/trice/internal/emitter"
18
        "github.com/rokath/trice/internal/fmtspec"
19
        "github.com/rokath/trice/internal/id"
20
)
21

22
// TestTable is a struct slice generated by the trice tool -testTable option.
23
type TestTable []struct {
24
        In  []byte // byte buffer sequence
25
        Exp string // output
26
}
27

28
const (
29

30
        // LittleEndian is true for little endian trice data.
31
        LittleEndian = true
32

33
        // BigEndian is the flag value for target endianness.
34
        BigEndian = false
35

36
        // defaultSize is the beginning receive and sync buffer size.
37
        DefaultSize = 64 * 1024
38

39
        // hints is the help information in case of errors.
40
        Hints = "att:Hints:Baudrate? Encoding? Interrupt? Overflow? Parameter count? Format specifier? Password? til.json? Version?"
41

42
        UnsignedFormatSpecifier = 0 // %u -> %d
43
        SignedFormatSpecifier   = 1 // signed integer formatting
44
        FloatFormatSpecifier    = 2 // %f and relatives
45
        BooleanFormatSpecifier  = 3 // a %t (bool) found
46
        PointerFormatSpecifier  = 4 // a %p (pointer) found
47
        StringFormatSpecifier   = 5 // a %s found
48

49
        // LiFmtDefault is the default layout for location info (file and line).
50
        LiFmtDefault = "info:%21s%6d "
51
)
52

53
var (
54
        // Verbose gives more information on output if set. The value is injected from main packages.
55
        Verbose bool
56

57
        // ShowID is used as format string for displaying the first trice ID at the start of each line if not "".
58
        ShowID string
59

60
        // TypeX0 controls how selector-0 records are interpreted by decoders that support typeX0.
61
        TypeX0 = "error"
62

63
        // BlankMetadata requests metadata columns without their value content for the current decoded output.
64
        BlankMetadata bool
65

66
        // decoder.LastTriceID is last decoded ID. It is used for switch -showID.
67
        LastTriceID id.TriceID
68

69
        // TestTableMode is a special option for easy decoder test table generation.
70
        TestTableMode bool
71

72
        // Unsigned if true, forces hex and in values printed as unsigned values.
73
        Unsigned bool
74

75
        DebugOut                        = false        // DebugOut enables debug information.
76
        DumpLineByteCount               int            // DumpLineByteCount is the bytes per line for the dumpDec decoder.
77
        InitialCycle                    = true         // InitialCycle helps with automatic cycle counter checks.
78
        TargetTimestamp                 uint64         // targetTimestamp contains target specific timestamp value.
79
        TargetTimestampSize             int            // TargetTimestampSize is set in dependence of trice type.
80
        TargetLocation                  uint32         // targetLocation contains 16 bit file id in high and 16 bit line number in low part.
81
        TargetStamp                     string         // TargetTimeStampUnit is the target timestamps time base for default formatting.
82
        TargetStamp32                   string         // ShowTargetStamp32 is the format string for target timestamps.
83
        TargetStamp16                   string         // ShowTargetStamp16 is the format string for target timestamps.
84
        TargetStamp0                    string         // ShowTargetStamp0 is the format string for target timestamps.
85
        TargetStamp32Delta              string         // TargetStamp32Delta is the format string for 32-bit target timestamp deltas.
86
        TargetStamp16Delta              string         // TargetStamp16Delta is the format string for 16-bit target timestamp deltas.
87
        TargetStamp0Delta               string         // TargetStamp0Delta is the format string for delta column alignment without target timestamps.
88
        TargetTimeStampUnitPassed       bool           // TargetTimeStampUnitPassed is true when flag was TargetTimeStampUnit passed.
89
        ShowTargetStamp32Passed         bool           // ShowTargetStamp32Passed is true when flag was TargetTimeStamp32 passed.
90
        ShowTargetStamp16Passed         bool           // ShowTargetStamp16Passed is true when flag was TargetTimeStamp16 passed.
91
        ShowTargetStamp0Passed          bool           // ShowTargetStamp0Passed is true when flag was TargetTimeStamp0 passed.
92
        ShowTargetStamp32DeltaPassed    bool           // ShowTargetStamp32DeltaPassed is true when flag TargetStamp32Delta was passed.
93
        ShowTargetStamp16DeltaPassed    bool           // ShowTargetStamp16DeltaPassed is true when flag TargetStamp16Delta was passed.
94
        ShowTargetStamp0DeltaPassed     bool           // ShowTargetStamp0DeltaPassed is true when flag TargetStamp0Delta was passed.
95
        LocationInformationFormatString = LiFmtDefault // LocationInformationFormatString is the format string for target location: line number and file name.
96
        LiFmtDefaultLen                 int            // Compute as var from LocationInformationFormatString.
97
        TargetLocationExists            bool           // TargetLocationExists is set in dependence of p.COBSModeDescriptor. (obsolete)
98
        PackageFraming                  string         // Framing is used for packing. Valid values COBS, TCOBS, TCOBSv1 (same as TCOBS)
99
        IDBits                          = 14           // IDBits holds count of bits used for ID (used at least in trexDecoder)
100
        NewlineIndent                   = -1           // Used for trice messages containing several newlines in format string for formatting.
101
        TriceStatistics                 bool           // Keep the occurred count for each Trice log when Trice is closed.
102
        IDStat                          map[id.TriceID]int
103
)
104

105
func init() {
16✔
106
        IDStat = make(map[id.TriceID]int)
16✔
107
        LiFmtDefaultLen = locationInformationWidth()
16✔
108
}
16✔
109

110
// New abstracts the function type for a new decoder.
111
type New func(out io.Writer, lut id.TriceIDLookUp, m *sync.RWMutex, li id.TriceIDLookUpLI, in io.Reader, endian bool) Decoder
112

113
// Config contains common constructor parameters for all decoders.
114
type Config struct {
115
        Out         io.Writer
116
        LUT         id.TriceIDLookUp
117
        LUTMutex    *sync.RWMutex
118
        LI          id.TriceIDLookUpLI
119
        In          io.Reader
120
        Endian      bool
121
        NeedBuffers bool // NeedBuffers allocates B, B0 and InnerBuffer for framed decoders.
122
}
123

124
// Decoder is providing a byte reader returning decoded trice's.
125
// SetInput allows switching the input stream to a different source.
126
type Decoder interface {
127
        io.Reader
128
        SetInput(io.Reader)
129
}
130

131
// DecoderData is the common data struct for all decoders.
132
type DecoderData struct {
133
        W           io.Writer          // io.Stdout or the like
134
        In          io.Reader          // in is the inner reader, which is used to get raw bytes
135
        InnerBuffer []byte             // avoid repeated allocation (trex)
136
        IBuf        []byte             // iBuf holds unprocessed (raw) bytes for interpretation.
137
        B           []byte             // read buffer holds a single decoded TCOBS package, which can contain several trices.
138
        B0          []byte             // initial value for B
139
        Endian      bool               // endian is true for LittleEndian and false for BigEndian
140
        TriceSize   int                // trice head and payload size as number of bytes
141
        ParamSpace  int                // trice payload size after head
142
        SLen        int                // string length for TRICE_S
143
        Lut         id.TriceIDLookUp   // id look-up map for translation
144
        LutMutex    *sync.RWMutex      // to avoid concurrent map read and map write during map refresh triggered by filewatcher
145
        Li          id.TriceIDLookUpLI // location information map
146
        Trice       id.TriceFmt        // id.TriceFmt // received trice
147
}
148

149
// NewDecoderData initializes the common base fields for a decoder.
150
//
151
// Callers can request pre-allocated internal working buffers with NeedBuffers.
152
func NewDecoderData(c Config) DecoderData {
82✔
153
        if c.Out == nil {
127✔
154
                c.Out = io.Discard
45✔
155
        }
45✔
156
        if c.LUTMutex == nil {
145✔
157
                c.LUTMutex = new(sync.RWMutex)
63✔
158
        }
63✔
159
        d := DecoderData{
82✔
160
                W:        c.Out,
82✔
161
                In:       c.In,
82✔
162
                IBuf:     make([]byte, 0, DefaultSize),
82✔
163
                Endian:   c.Endian,
82✔
164
                Lut:      c.LUT,
82✔
165
                LutMutex: c.LUTMutex,
82✔
166
                Li:       c.LI,
82✔
167
        }
82✔
168
        if c.NeedBuffers {
116✔
169
                d.B = make([]byte, 0, DefaultSize)
34✔
170
                d.B0 = make([]byte, DefaultSize)
34✔
171
                d.InnerBuffer = make([]byte, DefaultSize)
34✔
172
        }
34✔
173
        return d
82✔
174
}
175

176
// SetInput allows switching the input stream to a different source.
177
//
178
// This function is for easier testing with cycle counters.
179
func (p *DecoderData) SetInput(r io.Reader) {
7✔
180
        p.In = r
7✔
181
}
7✔
182

183
// ReadU16 returns the 2 b bytes as uint16 according the specified endianness
184
func (p *DecoderData) ReadU16(b []byte) uint16 {
62✔
185
        if p.Endian {
123✔
186
                return binary.LittleEndian.Uint16(b)
61✔
187
        }
61✔
188
        return binary.BigEndian.Uint16(b)
1✔
189
}
190

191
// ReadU32 returns the 4 b bytes as uint32 according the specified endianness
192
func (p *DecoderData) ReadU32(b []byte) uint32 {
22✔
193
        if p.Endian {
43✔
194
                return binary.LittleEndian.Uint32(b)
21✔
195
        }
21✔
196
        return binary.BigEndian.Uint32(b)
1✔
197
}
198

199
// ReadU64 returns the 8 b bytes as uint64 according the specified endianness
200
func (p *DecoderData) ReadU64(b []byte) uint64 {
8✔
201
        if p.Endian {
15✔
202
                return binary.LittleEndian.Uint64(b)
7✔
203
        }
7✔
204
        return binary.BigEndian.Uint64(b)
1✔
205
}
206

207
// UReplaceN checks all format specifier in i and replaces %nu with %nd and returns that result as o.
208
//
209
// If a replacement took place on position k u[k] is 0. Afterwards len(u) is amount of found format specifiers.
210
// Additional, if UnsignedHex is true, for FormatX specifiers u[k] is also 1.
211
// If a float format specifier was found at position k, u[k] is 2,
212
// http://www.cplusplus.com/reference/cstdio/printf/
213
// https://www.codingunit.com/printf-format-specifiers-format-conversions-and-formatted-output
214
func UReplaceN(i string) (o string, u []int) {
43✔
215
        // Normalize valid C length modifiers first so that both the source scanner and
43✔
216
        // the runtime decoder share the same understanding. This fixes Issue #649 and
43✔
217
        // related %l... cases without changing the original format string stored in til.json.
43✔
218
        o, specs := fmtspec.Normalize(i)
43✔
219
        for _, spec := range specs {
88✔
220
                switch spec.Kind {
45✔
221
                case fmtspec.KindPointer:
×
222
                        // This would require `unsafe.Pointer(uintptr(n))` inside unSignedOrSignedOut.
×
223
                        // There are false positive windows vet warnings:
×
224
                        // https://stackoverflow.com/questions/43767898/casting-a-int-to-a-pointer
×
UNCOV
225
                        // https://github.com/golang/go/issues/41205
×
UNCOV
226
                        // As workaround replace %p with %x in the format strings.
×
UNCOV
227
                        o = replaceNextConversion(o, 'p', 'x')
×
UNCOV
228
                        u = append(u, PointerFormatSpecifier)
×
UNCOV
229
                case fmtspec.KindBool:
×
UNCOV
230
                        u = append(u, BooleanFormatSpecifier)
×
231
                case fmtspec.KindString:
3✔
232
                        u = append(u, StringFormatSpecifier)
3✔
233
                case fmtspec.KindFloat:
2✔
234
                        u = append(u, FloatFormatSpecifier)
2✔
235
                case fmtspec.KindUnsigned:
4✔
236
                        // Keep the longstanding Trice behavior: Go fmt prints unsigned payloads
4✔
237
                        // via %d while the decoder chooses an unsigned Go integer type below.
4✔
238
                        o = replaceNextConversion(o, 'u', 'd')
4✔
239
                        u = append(u, UnsignedFormatSpecifier)
4✔
240
                case fmtspec.KindBasedInteger:
7✔
241
                        // Keep `%d`/`%i` signed even when -unsigned is active. The historical
7✔
242
                        // `Unsigned` switch only affected the base-changing integer verbs handled
7✔
243
                        // by the old locX branch (%x/%X/%o/%O/%b/...). Issue #649 added the shared
7✔
244
                        // parser, but this behavior must stay intact to avoid changing `%d` into
7✔
245
                        // an unsigned decimal print, as seen in the intensive PC target tests.
7✔
246
                        if Unsigned {
10✔
247
                                u = append(u, UnsignedFormatSpecifier)
3✔
248
                        } else {
7✔
249
                                u = append(u, SignedFormatSpecifier)
4✔
250
                        }
4✔
251
                case fmtspec.KindSigned:
29✔
252
                        u = append(u, SignedFormatSpecifier)
29✔
253
                }
254
        }
255
        return
43✔
256
}
257

258
func replaceNextConversion(format string, from, to byte) string {
4✔
259
        for i := 0; i < len(format); i++ {
23✔
260
                if format[i] != '%' {
33✔
261
                        continue
14✔
262
                }
263
                if i+1 < len(format) && format[i+1] == '%' {
5✔
UNCOV
264
                        i++
×
UNCOV
265
                        continue
×
266
                }
267
                for j := i + 1; j < len(format); j++ {
11✔
268
                        if format[j] == from {
10✔
269
                                return format[:j] + string(to) + format[j+1:]
4✔
270
                        }
4✔
271
                        if isASCIIAlpha(format[j]) {
3✔
272
                                break
1✔
273
                        }
274
                }
275
        }
UNCOV
276
        return format
×
277
}
278

279
func isASCIIAlpha(b byte) bool {
2✔
280
        return ('a' <= b && b <= 'z') || ('A' <= b && b <= 'Z')
2✔
281
}
2✔
282

283
// Dump prints the byte slice as hex in one line
284
func Dump(w io.Writer, b []byte) {
6✔
285
        for _, x := range b {
13✔
286
                fmt.Fprintf(w, "%02x ", x)
7✔
287
        }
7✔
288
        fmt.Fprintln(w, "")
6✔
289
}
290

291
func RecordForStatistics(tid id.TriceID) {
28✔
292
        if !TriceStatistics && !emitter.AllStatistics {
54✔
293
                return
26✔
294
        }
26✔
295
        count := IDStat[tid]
2✔
296
        count++
2✔
297
        IDStat[tid] = count
2✔
298
}
299

300
var (
301
        IDLUT id.TriceIDLookUp
302
        LILUT id.TriceIDLookUpLI
303
)
304

305
func PrintTriceStatistics(w io.Writer) {
2✔
306
        if !TriceStatistics && !emitter.AllStatistics {
3✔
307
                return
1✔
308
        }
1✔
309
        var sum int
1✔
310
        fmt.Fprintf(w, "\nTrice Statistics:                  (n: Trice ends with no newline, if 0 ↴)\n\n")
1✔
311
        fmt.Fprintln(w, `   Count |       Location Information       | Line |  ID   |    Type    |n| Format String`)
1✔
312
        fmt.Fprintln(w, " ------- | -------------------------------- | ---- | ----- | ---------- |-|----------------------------------------")
1✔
313
        for tid, count := range IDStat {
2✔
314
                sum += count
1✔
315
                trice := IDLUT[tid]
1✔
316
                li := LILUT[tid]
1✔
317
                var found bool
1✔
318
                trice.Strg, found = strings.CutSuffix(trice.Strg, `\n`)
1✔
319
                newline := ` `
1✔
320
                if !found {
1✔
UNCOV
321
                        newline = `0`
×
UNCOV
322
                }
×
323
                file := id.ToLIPath(li.File)
1✔
324
                fmt.Fprintf(w, "%8d | %32s |%5d | %5d | %10s |%s| %s\n", count, file, li.Line, tid, trice.Type, newline, emitter.Colorize(trice.Strg))
1✔
325
        }
326
        fmt.Fprintln(w, " ------------------------------------------------------------------------------------------------------------------")
1✔
327
        fmt.Fprintf(w, "%8d Trice messsges\n", sum)
1✔
328
}
329

330
// LocationInformation returns optional location information for id.
331
func LocationInformation(tid id.TriceID, li id.TriceIDLookUpLI) string {
4✔
332
        if li != nil && LocationInformationFormatString != "off" && LocationInformationFormatString != "none" {
6✔
333
                if li, ok := li[tid]; ok {
3✔
334
                        return fmt.Sprintf(LocationInformationFormatString, filepath.Base(li.File), li.Line)
1✔
335
                } else {
2✔
336
                        return fmt.Sprintf(LocationInformationFormatString, "", 0)
1✔
337
                }
1✔
338
        } else {
2✔
339
                if Verbose {
3✔
340
                        return "no li"
1✔
341
                }
1✔
342
        }
343
        return ""
1✔
344
}
345

346
// locationInformationWidth computes byte count for location information.
347
func locationInformationWidth() (sum int) {
17✔
348
        re := regexp.MustCompile("[0-9]+")
17✔
349
        s := re.FindAllString(LocationInformationFormatString, -1)
17✔
350
        for _, n := range s {
51✔
351
                v, err := strconv.Atoi(n)
34✔
352
                if err != nil {
34✔
UNCOV
353
                        panic(err)
×
354
                }
355
                sum += v
34✔
356
        }
357
        return sum + 1 // plus space after line number
17✔
358
}
359

360
// CorrectWrappedTimestamp checks whether a 32-bit timestamp falls outside the valid range
361
// and virtually sets a 33rd bit by adding 2^32 seconds to it
362
func CorrectWrappedTimestamp(ts32 uint32) time.Time {
3✔
363

3✔
364
        const (
3✔
365
                minValidYear = 2000
3✔
366
                maxValidYear = 2038
3✔
367
                wrapOffset   = 1 << 32 // 2^32 seconds
3✔
368
        )
3✔
369

3✔
370
        // Interpret the timestamp as time.Time
3✔
371
        t := time.Unix(int64(ts32), 0).UTC()
3✔
372

3✔
373
        if t.Year() >= minValidYear && t.Year() <= maxValidYear {
4✔
374
                return t
1✔
375
        }
1✔
376

377
        // Apply wraparound correction by adding 2^32 seconds
378
        tWrapped := time.Unix(int64(ts32)+wrapOffset, 0).UTC()
2✔
379

2✔
380
        // If the corrected timestamp is plausible, return it
2✔
381
        if tWrapped.Year() > maxValidYear && tWrapped.Year() <= maxValidYear+100 {
3✔
382
                return tWrapped
1✔
383
        }
1✔
384

385
        // Fallback: return the original timestamp and print a warning
386
        fmt.Printf("WARNING: Timestamp %v (%d) is outside the expected year range\n", t, ts32)
1✔
387
        return t
1✔
388
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc