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

rokath / trice / 23833307166

28 Mar 2026 02:16PM UTC coverage: 70.553% (+15.3%) from 55.204%
23833307166

push

github

rokath
docs: refine README presentation and links

4121 of 5841 relevant lines covered (70.55%)

907.03 hits per line

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

88.07
/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/id"
19
)
20

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

27
const (
28

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

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

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

38
        // patNextFormatSpecifier is a regex to find next format specifier in a string (exclude %%*) and NOT ignoring %s
39
        //
40
        // https://regex101.com/r/BjiD5M/1
41
        // Language C plus from language Go: %b, %F, %q
42
        // Partial implemented: %hi, %hu, %ld, %li, %lf, %Lf, %Lu, %lli, %lld
43
        // Not implemented: %s
44
        //patNextFormatSpecifier =        `%([+\-#'0-9\.0-9])*(b|c|d|e|f|g|E|F|G|h|i|l|L|n|o|O|p|q|s|t|u|U|x|X)` // assumes no `%%` inside string!
45
        patNextFormatSpecifier = `(?:^|[^%])(%[\ +\-0-9\.#]*(b|c|d|e|f|g|E|F|G|h|i|l|L|n|o|O|p|q|s|t|u|U|x|X))` // inside update.go
46

47
        // patNextFormatSSpecifier is a regex to find next format s specifier in a string
48
        // It does also match %%u positions!
49
        patNextFormatSSpecifier = `%[0-9]*s` // assumes no `%%` inside string!
50

51
        // patNextFormatUSpecifier is a regex to find next format u specifier in a string
52
        // It does also match %%u positions!
53
        patNextFormatUSpecifier = `%[0-9]*u` // assumes no `%%` inside string!
54

55
        // patNextFormatISpecifier is a regex to find next format i specifier in a string
56
        // It does also match %%i positions!
57
        patNextFormatISpecifier = `%[0-9]*i` // assumes no `%%` inside string!
58

59
        // patNextFormatXSpecifier is a regex to find next format x specifier in a string
60
        // It does also match %%x positions!
61
        patNextFormatXSpecifier = `%[0-9]*(l|o|O|x|X|b|p|t)` // assumes no `%%` inside string!
62

63
        // patNextFormatFSpecifier is a regex to find next format f specifier in a string
64
        // It does also match %%f positions!
65
        patNextFormatFSpecifier = `%[(+\-0-9\.0-9#]*(e|E|f|F|g|G)` // assumes no `%%` inside string!
66

67
        // patNextFormatBoolSpecifier is a regex to find next format f specifier in a string
68
        // It does also match %%t positions!
69
        patNextFormatBoolSpecifier = `%t` // assumes no `%%` inside string!
70

71
        // patNextFormatPointerSpecifier is a regex to find next format f specifier in a string
72
        // It does also match %%t positions!
73
        patNextFormatPointerSpecifier = `%p` // assumes no `%%` inside string!
74

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

78
        UnsignedFormatSpecifier = 0 // %u -> %d
79
        SignedFormatSpecifier   = 1 // signed integer formatting
80
        FloatFormatSpecifier    = 2 // %f and relatives
81
        BooleanFormatSpecifier  = 3 // a %t (bool) found
82
        PointerFormatSpecifier  = 4 // a %p (pointer) found
83
        StringFormatSpecifier   = 5 // a %s found
84

85
        // LiFmtDefault is the default layout for location info (file and line).
86
        LiFmtDefault = "info:%21s%6d "
87
)
88

89
var (
90
        // Verbose gives more information on output if set. The value is injected from main packages.
91
        Verbose bool
92

93
        // ShowID is used as format string for displaying the first trice ID at the start of each line if not "".
94
        ShowID string
95

96
        // decoder.LastTriceID is last decoded ID. It is used for switch -showID.
97
        LastTriceID id.TriceID
98

99
        // TestTableMode is a special option for easy decoder test table generation.
100
        TestTableMode bool
101

102
        // Unsigned if true, forces hex and in values printed as unsigned values.
103
        Unsigned bool
104

105
        matchNextFormatSpecifier        = regexp.MustCompile(patNextFormatSpecifier)
106
        matchNextFormatSSpecifier       = regexp.MustCompile(patNextFormatSSpecifier)
107
        matchNextFormatUSpecifier       = regexp.MustCompile(patNextFormatUSpecifier)
108
        matchNextFormatISpecifier       = regexp.MustCompile(patNextFormatISpecifier)
109
        matchNextFormatXSpecifier       = regexp.MustCompile(patNextFormatXSpecifier)
110
        matchNextFormatFSpecifier       = regexp.MustCompile(patNextFormatFSpecifier)
111
        matchNextFormatBoolSpecifier    = regexp.MustCompile(patNextFormatBoolSpecifier)
112
        matchNextFormatPointerSpecifier = regexp.MustCompile(patNextFormatPointerSpecifier)
113

114
        DebugOut                        = false        // DebugOut enables debug information.
115
        DumpLineByteCount               int            // DumpLineByteCount is the bytes per line for the dumpDec decoder.
116
        InitialCycle                    = true         // InitialCycle helps with automatic cycle counter checks.
117
        TargetTimestamp                 uint64         // targetTimestamp contains target specific timestamp value.
118
        TargetTimestampSize             int            // TargetTimestampSize is set in dependence of trice type.
119
        TargetLocation                  uint32         // targetLocation contains 16 bit file id in high and 16 bit line number in low part.
120
        TargetStamp                     string         // TargetTimeStampUnit is the target timestamps time base for default formatting.
121
        TargetStamp32                   string         // ShowTargetStamp32 is the format string for target timestamps.
122
        TargetStamp16                   string         // ShowTargetStamp16 is the format string for target timestamps.
123
        TargetStamp0                    string         // ShowTargetStamp0 is the format string for target timestamps.
124
        TargetStamp32Delta              string         // TargetStamp32Delta is the format string for 32-bit target timestamp deltas.
125
        TargetStamp16Delta              string         // TargetStamp16Delta is the format string for 16-bit target timestamp deltas.
126
        TargetStamp0Delta               string         // TargetStamp0Delta is the format string for delta column alignment without target timestamps.
127
        TargetTimeStampUnitPassed       bool           // TargetTimeStampUnitPassed is true when flag was TargetTimeStampUnit passed.
128
        ShowTargetStamp32Passed         bool           // ShowTargetStamp32Passed is true when flag was TargetTimeStamp32 passed.
129
        ShowTargetStamp16Passed         bool           // ShowTargetStamp16Passed is true when flag was TargetTimeStamp16 passed.
130
        ShowTargetStamp0Passed          bool           // ShowTargetStamp0Passed is true when flag was TargetTimeStamp0 passed.
131
        ShowTargetStamp32DeltaPassed    bool           // ShowTargetStamp32DeltaPassed is true when flag TargetStamp32Delta was passed.
132
        ShowTargetStamp16DeltaPassed    bool           // ShowTargetStamp16DeltaPassed is true when flag TargetStamp16Delta was passed.
133
        ShowTargetStamp0DeltaPassed     bool           // ShowTargetStamp0DeltaPassed is true when flag TargetStamp0Delta was passed.
134
        LocationInformationFormatString = LiFmtDefault // LocationInformationFormatString is the format string for target location: line number and file name.
135
        LiFmtDefaultLen                 int            // Compute as var from LocationInformationFormatString.
136
        TargetLocationExists            bool           // TargetLocationExists is set in dependence of p.COBSModeDescriptor. (obsolete)
137
        PackageFraming                  string         // Framing is used for packing. Valid values COBS, TCOBS, TCOBSv1 (same as TCOBS)
138
        IDBits                          = 14           // IDBits holds count of bits used for ID (used at least in trexDecoder)
139
        NewlineIndent                   = -1           // Used for trice messages containing several newlines in format string for formatting.
140
        TriceStatistics                 bool           // Keep the occurred count for each Trice log when Trice is closed.
141
        IDStat                          map[id.TriceID]int
142
)
143

144
func init() {
9✔
145
        IDStat = make(map[id.TriceID]int)
9✔
146
        LiFmtDefaultLen = locationInformationWidth()
9✔
147
}
9✔
148

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

152
// Config contains common constructor parameters for all decoders.
153
type Config struct {
154
        Out         io.Writer
155
        LUT         id.TriceIDLookUp
156
        LUTMutex    *sync.RWMutex
157
        LI          id.TriceIDLookUpLI
158
        In          io.Reader
159
        Endian      bool
160
        NeedBuffers bool // NeedBuffers allocates B, B0 and InnerBuffer for framed decoders.
161
}
162

163
// Decoder is providing a byte reader returning decoded trice's.
164
// SetInput allows switching the input stream to a different source.
165
type Decoder interface {
166
        io.Reader
167
        SetInput(io.Reader)
168
}
169

170
// DecoderData is the common data struct for all decoders.
171
type DecoderData struct {
172
        W           io.Writer          // io.Stdout or the like
173
        In          io.Reader          // in is the inner reader, which is used to get raw bytes
174
        InnerBuffer []byte             // avoid repeated allocation (trex)
175
        IBuf        []byte             // iBuf holds unprocessed (raw) bytes for interpretation.
176
        B           []byte             // read buffer holds a single decoded TCOBS package, which can contain several trices.
177
        B0          []byte             // initial value for B
178
        Endian      bool               // endian is true for LittleEndian and false for BigEndian
179
        TriceSize   int                // trice head and payload size as number of bytes
180
        ParamSpace  int                // trice payload size after head
181
        SLen        int                // string length for TRICE_S
182
        Lut         id.TriceIDLookUp   // id look-up map for translation
183
        LutMutex    *sync.RWMutex      // to avoid concurrent map read and map write during map refresh triggered by filewatcher
184
        Li          id.TriceIDLookUpLI // location information map
185
        Trice       id.TriceFmt        // id.TriceFmt // received trice
186
}
187

188
// NewDecoderData initializes the common base fields for a decoder.
189
//
190
// Callers can request pre-allocated internal working buffers with NeedBuffers.
191
func NewDecoderData(c Config) DecoderData {
57✔
192
        if c.Out == nil {
80✔
193
                c.Out = io.Discard
23✔
194
        }
23✔
195
        if c.LUTMutex == nil {
98✔
196
                c.LUTMutex = new(sync.RWMutex)
41✔
197
        }
41✔
198
        d := DecoderData{
57✔
199
                W:        c.Out,
57✔
200
                In:       c.In,
57✔
201
                IBuf:     make([]byte, 0, DefaultSize),
57✔
202
                Endian:   c.Endian,
57✔
203
                Lut:      c.LUT,
57✔
204
                LutMutex: c.LUTMutex,
57✔
205
                Li:       c.LI,
57✔
206
        }
57✔
207
        if c.NeedBuffers {
87✔
208
                d.B = make([]byte, 0, DefaultSize)
30✔
209
                d.B0 = make([]byte, DefaultSize)
30✔
210
                d.InnerBuffer = make([]byte, DefaultSize)
30✔
211
        }
30✔
212
        return d
57✔
213
}
214

215
// SetInput allows switching the input stream to a different source.
216
//
217
// This function is for easier testing with cycle counters.
218
func (p *DecoderData) SetInput(r io.Reader) {
7✔
219
        p.In = r
7✔
220
}
7✔
221

222
// ReadU16 returns the 2 b bytes as uint16 according the specified endianness
223
func (p *DecoderData) ReadU16(b []byte) uint16 {
40✔
224
        if p.Endian {
79✔
225
                return binary.LittleEndian.Uint16(b)
39✔
226
        }
39✔
227
        return binary.BigEndian.Uint16(b)
1✔
228
}
229

230
// ReadU32 returns the 4 b bytes as uint32 according the specified endianness
231
func (p *DecoderData) ReadU32(b []byte) uint32 {
15✔
232
        if p.Endian {
29✔
233
                return binary.LittleEndian.Uint32(b)
14✔
234
        }
14✔
235
        return binary.BigEndian.Uint32(b)
1✔
236
}
237

238
// ReadU64 returns the 8 b bytes as uint64 according the specified endianness
239
func (p *DecoderData) ReadU64(b []byte) uint64 {
4✔
240
        if p.Endian {
7✔
241
                return binary.LittleEndian.Uint64(b)
3✔
242
        }
3✔
243
        return binary.BigEndian.Uint64(b)
1✔
244
}
245

246
// UReplaceN checks all format specifier in i and replaces %nu with %nd and returns that result as o.
247
//
248
// If a replacement took place on position k u[k] is 0. Afterwards len(u) is amount of found format specifiers.
249
// Additional, if UnsignedHex is true, for FormatX specifiers u[k] is also 1.
250
// If a float format specifier was found at position k, u[k] is 2,
251
// http://www.cplusplus.com/reference/cstdio/printf/
252
// https://www.codingunit.com/printf-format-specifiers-format-conversions-and-formatted-output
253
func UReplaceN(i string) (o string, u []int) {
22✔
254
        o = i
22✔
255
        i = strings.ReplaceAll(i, "%%", "__") // this makes regex easier and faster
22✔
256
        var offset int
22✔
257
        for {
66✔
258
                s := i[offset:] // remove processed part
44✔
259
                loc := matchNextFormatSpecifier.FindStringIndex(s)
44✔
260
                if nil == loc { // no (more) fm found
66✔
261
                        return
22✔
262
                }
22✔
263
                offset += loc[1] // track position
22✔
264
                fm := s[loc[0]:loc[1]]
22✔
265
                locPointer := matchNextFormatPointerSpecifier.FindStringIndex(fm)
22✔
266
                if nil != locPointer { // a %p found
22✔
267
                        // This would require `unsafe.Pointer(uintptr(n))` inside unSignedOrSignedOut.
×
268
                        // There are false positive windows vet warnings:
×
269
                        // https://stackoverflow.com/questions/43767898/casting-a-int-to-a-pointer
×
270
                        // https://github.com/golang/go/issues/41205
×
271
                        // As workaround replace %p with %x in the format strings.
×
272
                        // Then trice64( "%p", -1 ) could be a problem when using `trice log -unsigned false`
×
273
                        // But that we simply ignore right now.
×
274
                        o = o[:offset-1] + "x" + o[offset:]   // replace %np -> %nx
×
275
                        u = append(u, PointerFormatSpecifier) // pointer value
×
276
                        continue
×
277
                }
278
                locBool := matchNextFormatBoolSpecifier.FindStringIndex(fm)
22✔
279
                if nil != locBool { // a %t found
22✔
280
                        u = append(u, BooleanFormatSpecifier) // bool value
×
281
                        continue
×
282
                }
283
                locS := matchNextFormatSSpecifier.FindStringIndex(fm)
22✔
284
                if nil != locS { // a %ns found
24✔
285
                        u = append(u, StringFormatSpecifier) // float value
2✔
286
                        continue
2✔
287
                }
288
                locF := matchNextFormatFSpecifier.FindStringIndex(fm)
20✔
289
                if nil != locF { // a %nf found
21✔
290
                        u = append(u, FloatFormatSpecifier) // float value
1✔
291
                        continue
1✔
292
                }
293
                locU := matchNextFormatUSpecifier.FindStringIndex(fm)
19✔
294
                if nil != locU { // a %nu found
21✔
295
                        o = o[:offset-1] + "d" + o[offset:]    // replace %nu -> %nd
2✔
296
                        u = append(u, UnsignedFormatSpecifier) // no negative values
2✔
297
                        continue
2✔
298
                }
299
                locI := matchNextFormatISpecifier.FindStringIndex(fm)
17✔
300
                if nil != locI { // a %ni found
17✔
301
                        o = o[:offset-1] + "d" + o[offset:]  // replace %ni -> %nd
×
302
                        u = append(u, SignedFormatSpecifier) // also negative values
×
303
                        continue
×
304
                }
305
                locX := matchNextFormatXSpecifier.FindStringIndex(fm)
17✔
306
                if nil != locX { // a %nx, %nX or, %no, %nO or %nb found
18✔
307
                        if Unsigned {
1✔
308
                                u = append(u, 0) // no negative values
×
309
                        } else {
1✔
310
                                u = append(u, 1) // also negative values
1✔
311
                        }
1✔
312
                        continue
1✔
313
                }
314
                u = append(u, 1) // keep sign in all other cases(also negative values)
16✔
315
        }
316
}
317

318
// Dump prints the byte slice as hex in one line
319
func Dump(w io.Writer, b []byte) {
6✔
320
        for _, x := range b {
13✔
321
                fmt.Fprintf(w, "%02x ", x)
7✔
322
        }
7✔
323
        fmt.Fprintln(w, "")
6✔
324
}
325

326
func RecordForStatistics(tid id.TriceID) {
21✔
327
        if !TriceStatistics && !emitter.AllStatistics {
40✔
328
                return
19✔
329
        }
19✔
330
        count := IDStat[tid]
2✔
331
        count++
2✔
332
        IDStat[tid] = count
2✔
333
}
334

335
var (
336
        IDLUT id.TriceIDLookUp
337
        LILUT id.TriceIDLookUpLI
338
)
339

340
func PrintTriceStatistics(w io.Writer) {
1✔
341
        if !TriceStatistics && !emitter.AllStatistics {
1✔
342
                return
×
343
        }
×
344
        var sum int
1✔
345
        fmt.Fprintf(w, "\nTrice Statistics:                  (n: Trice ends with no newline, if 0 ↴)\n\n")
1✔
346
        fmt.Fprintln(w, `   Count |       Location Information       | Line |  ID   |    Type    |n| Format String`)
1✔
347
        fmt.Fprintln(w, " ------- | -------------------------------- | ---- | ----- | ---------- |-|----------------------------------------")
1✔
348
        for tid, count := range IDStat {
2✔
349
                sum += count
1✔
350
                trice := IDLUT[tid]
1✔
351
                li := LILUT[tid]
1✔
352
                var found bool
1✔
353
                trice.Strg, found = strings.CutSuffix(trice.Strg, `\n`)
1✔
354
                newline := ` `
1✔
355
                if !found {
1✔
356
                        newline = `0`
×
357
                }
×
358
                file := id.ToLIPath(li.File)
1✔
359
                fmt.Fprintf(w, "%8d | %32s |%5d | %5d | %10s |%s| %s\n", count, file, li.Line, tid, trice.Type, newline, emitter.Colorize(trice.Strg))
1✔
360
        }
361
        fmt.Fprintln(w, " ------------------------------------------------------------------------------------------------------------------")
1✔
362
        fmt.Fprintf(w, "%8d Trice messsges\n", sum)
1✔
363
}
364

365
// LocationInformation returns optional location information for id.
366
func LocationInformation(tid id.TriceID, li id.TriceIDLookUpLI) string {
4✔
367
        if li != nil && LocationInformationFormatString != "off" && LocationInformationFormatString != "none" {
6✔
368
                if li, ok := li[tid]; ok {
3✔
369
                        return fmt.Sprintf(LocationInformationFormatString, filepath.Base(li.File), li.Line)
1✔
370
                } else {
2✔
371
                        return fmt.Sprintf(LocationInformationFormatString, "", 0)
1✔
372
                }
1✔
373
        } else {
2✔
374
                if Verbose {
3✔
375
                        return "no li"
1✔
376
                }
1✔
377
        }
378
        return ""
1✔
379
}
380

381
// locationInformationWidth computes byte count for location information.
382
func locationInformationWidth() (sum int) {
10✔
383
        re := regexp.MustCompile("[0-9]+")
10✔
384
        s := re.FindAllString(LocationInformationFormatString, -1)
10✔
385
        for _, n := range s {
30✔
386
                v, err := strconv.Atoi(n)
20✔
387
                if err != nil {
20✔
388
                        panic(err)
×
389
                }
390
                sum += v
20✔
391
        }
392
        return sum + 1 // plus space after line number
10✔
393
}
394

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

3✔
399
        const (
3✔
400
                minValidYear = 2000
3✔
401
                maxValidYear = 2038
3✔
402
                wrapOffset   = 1 << 32 // 2^32 seconds
3✔
403
        )
3✔
404

3✔
405
        // Interpret the timestamp as time.Time
3✔
406
        t := time.Unix(int64(ts32), 0).UTC()
3✔
407

3✔
408
        if t.Year() >= minValidYear && t.Year() <= maxValidYear {
4✔
409
                return t
1✔
410
        }
1✔
411

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

2✔
415
        // If the corrected timestamp is plausible, return it
2✔
416
        if tWrapped.Year() > maxValidYear && tWrapped.Year() <= maxValidYear+100 {
3✔
417
                return tWrapped
1✔
418
        }
1✔
419

420
        // Fallback: return the original timestamp and print a warning
421
        fmt.Printf("WARNING: Timestamp %v (%d) is outside the expected year range\n", t, ts32)
1✔
422
        return t
1✔
423
}
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