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

rokath / trice / 20314806283

17 Dec 2025 07:12PM UTC coverage: 46.561% (-9.6%) from 56.117%
20314806283

push

github

rokath
minor corrections

2532 of 5438 relevant lines covered (46.56%)

868.71 hits per line

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

33.12
/internal/decoder/decoder.go
1
// Copyright 2020 Thomas.Hoehenleitner [at] seerose.net
2
// Use of this source code is governed by a license that can be found in the LICENSE file.
3

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

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

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

22
// TestTable ist 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
        // patNextFormatSpecifier is a regex to find next format specifier in a string (exclude %%*) and NOT ignoring %s
40
        //
41
        // https://regex101.com/r/BjiD5M/1
42
        // Language C plus from language Go: %b, %F, %q
43
        // Partial implemented: %hi, %hu, %ld, %li, %lf, %Lf, %Lu, %lli, %lld
44
        // Not implemented: %s
45
        //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!
46
        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
47

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

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

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

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

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

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

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

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

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

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 is a helper for the cycle counter automatic.
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
        TargetTimeStampUnitPassed       bool           // TargetTimeStampUnitPassed is true when flag was TargetTimeStampUnit passed.
125
        ShowTargetStamp32Passed         bool           // ShowTargetStamp32Passed is true when flag was TargetTimeStamp32 passed.
126
        ShowTargetStamp16Passed         bool           // ShowTargetStamp16Passed is true when flag was TargetTimeStamp16 passed.
127
        ShowTargetStamp0Passed          bool           // ShowTargetStamp0Passed is true when flag was TargetTimeStamp0 passed.
128
        LocationInformationFormatString = LiFmtDefault // LocationInformationFormatString is the format string for target location: line number and file name.
129
        LiFmtDefaultLen                 int            // Compute as var from LocationInformationFormatString.
130
        TargetLocationExists            bool           // TargetLocationExists is set in dependence of p.COBSModeDescriptor. (obsolete)
131
        PackageFraming                  string         // Framing is used for packing. Valid values COBS, TCOBS, TCOBSv1 (same as TCOBS)
132
        IDBits                          = 14           // IDBits holds count of bits used for ID (used at least in trexDecoder)
133
        NewlineIndent                   = -1           // Used for trice messages containing several newlines in format string for formatting.
134
        TriceStatistics                 bool           // Keep the occured count for each Trice log when Trice is closed.
135
        IDStat                          map[id.TriceID]int
136
)
137

138
func init() {
1✔
139
        IDStat = make(map[id.TriceID]int)
1✔
140
        LiFmtDefaultLen = locationInformationWidth()
1✔
141
}
1✔
142

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

146
// Decoder is providing a byte reader returning decoded trice's.
147
// SetInput allows switching the input stream to a different source.
148
type Decoder interface {
149
        io.Reader
150
        SetInput(io.Reader)
151
}
152

153
// DecoderData is the common data struct for all decoders.
154
type DecoderData struct {
155
        W           io.Writer          // io.Stdout or the like
156
        In          io.Reader          // in is the inner reader, which is used to get raw bytes
157
        InnerBuffer []byte             // avoid repeated allocation (trex)
158
        IBuf        []byte             // iBuf holds unprocessed (raw) bytes for interpretation.
159
        B           []byte             // read buffer holds a single decoded TCOBS package, which can contain several trices.
160
        B0          []byte             // initial value for B
161
        Endian      bool               // endian is true for LittleEndian and false for BigEndian
162
        TriceSize   int                // trice head and payload size as number of bytes
163
        ParamSpace  int                // trice payload size after head
164
        SLen        int                // string length for TRICE_S
165
        Lut         id.TriceIDLookUp   // id look-up map for translation
166
        LutMutex    *sync.RWMutex      // to avoid concurrent map read and map write during map refresh triggered by filewatcher
167
        Li          id.TriceIDLookUpLI // location information map
168
        Trice       id.TriceFmt        // id.TriceFmt // received trice
169
}
170

171
// SetInput allows switching the input stream to a different source.
172
//
173
// This function is for easier testing with cycle counters.
174
func (p *DecoderData) SetInput(r io.Reader) {
×
175
        p.In = r
×
176
}
×
177

178
// ReadU16 returns the 2 b bytes as uint16 according the specified endianness
179
func (p *DecoderData) ReadU16(b []byte) uint16 {
×
180
        if p.Endian {
×
181
                return binary.LittleEndian.Uint16(b)
×
182
        }
×
183
        return binary.BigEndian.Uint16(b)
×
184
}
185

186
// ReadU32 returns the 4 b bytes as uint32 according the specified endianness
187
func (p *DecoderData) ReadU32(b []byte) uint32 {
×
188
        if p.Endian {
×
189
                return binary.LittleEndian.Uint32(b)
×
190
        }
×
191
        return binary.BigEndian.Uint32(b)
×
192
}
193

194
// ReadU64 returns the 8 b bytes as uint64 according the specified endianness
195
func (p *DecoderData) ReadU64(b []byte) uint64 {
×
196
        if p.Endian {
×
197
                return binary.LittleEndian.Uint64(b)
×
198
        }
×
199
        return binary.BigEndian.Uint64(b)
×
200
}
201

202
// UReplaceN checks all format specifier in i and replaces %nu with %nd and returns that result as o.
203
//
204
// If a replacement took place on position k u[k] is 0. Afterwards len(u) is amount of found format specifiers.
205
// Additional, if UnsignedHex is true, for FormatX specifiers u[k] is also 1.
206
// If a float format specifier was found at position k, u[k] is 2,
207
// http://www.cplusplus.com/reference/cstdio/printf/
208
// https://www.codingunit.com/printf-format-specifiers-format-conversions-and-formatted-output
209
func UReplaceN(i string) (o string, u []int) {
6✔
210
        o = i
6✔
211
        i = strings.ReplaceAll(i, "%%", "__") // this makes regex easier and faster
6✔
212
        var offset int
6✔
213
        for {
20✔
214
                s := i[offset:] // remove processed part
14✔
215
                loc := matchNextFormatSpecifier.FindStringIndex(s)
14✔
216
                if nil == loc { // no (more) fm found
20✔
217
                        return
6✔
218
                }
6✔
219
                offset += loc[1] // track position
8✔
220
                fm := s[loc[0]:loc[1]]
8✔
221
                locPointer := matchNextFormatPointerSpecifier.FindStringIndex(fm)
8✔
222
                if nil != locPointer { // a %p found
8✔
223
                        // This would require `unsafe.Pointer(uintptr(n))` inside unSignedOrSignedOut.
×
224
                        // There are false positive windows vet warnings:
×
225
                        // https://stackoverflow.com/questions/43767898/casting-a-int-to-a-pointer
×
226
                        // https://github.com/golang/go/issues/41205
×
227
                        // As workaround replace %p with %x in the format strings.
×
228
                        // Then trice64( "%p", -1 ) could be a problem when using `trice log -unsigned false`
×
229
                        // But that we simply ignore right now.
×
230
                        o = o[:offset-1] + "x" + o[offset:]   // replace %np -> %nx
×
231
                        u = append(u, PointerFormatSpecifier) // pointer value
×
232
                        continue
×
233
                }
234
                locBool := matchNextFormatBoolSpecifier.FindStringIndex(fm)
8✔
235
                if nil != locBool { // a %t found
8✔
236
                        u = append(u, BooleanFormatSpecifier) // bool value
×
237
                        continue
×
238
                }
239
                locS := matchNextFormatSSpecifier.FindStringIndex(fm)
8✔
240
                if nil != locS { // a %ns found
9✔
241
                        u = append(u, StringFormatSpecifier) // float value
1✔
242
                        continue
1✔
243
                }
244
                locF := matchNextFormatFSpecifier.FindStringIndex(fm)
7✔
245
                if nil != locF { // a %nf found
8✔
246
                        u = append(u, FloatFormatSpecifier) // float value
1✔
247
                        continue
1✔
248
                }
249
                locU := matchNextFormatUSpecifier.FindStringIndex(fm)
6✔
250
                if nil != locU { // a %nu found
8✔
251
                        o = o[:offset-1] + "d" + o[offset:]    // replace %nu -> %nd
2✔
252
                        u = append(u, UnsignedFormatSpecifier) // no negative values
2✔
253
                        continue
2✔
254
                }
255
                locI := matchNextFormatISpecifier.FindStringIndex(fm)
4✔
256
                if nil != locI { // a %ni found
4✔
257
                        o = o[:offset-1] + "d" + o[offset:]  // replace %ni -> %nd
×
258
                        u = append(u, SignedFormatSpecifier) // also negative values
×
259
                        continue
×
260
                }
261
                locX := matchNextFormatXSpecifier.FindStringIndex(fm)
4✔
262
                if nil != locX { // a %nx, %nX or, %no, %nO or %nb found
5✔
263
                        if Unsigned {
1✔
264
                                u = append(u, 0) // no negative values
×
265
                        } else {
1✔
266
                                u = append(u, 1) // also negative values
1✔
267
                        }
1✔
268
                        continue
1✔
269
                }
270
                u = append(u, 1) // keep sign in all other cases(also negative values)
3✔
271
        }
272
}
273

274
// Dump prints the byte slice as hex in one line
275
func Dump(w io.Writer, b []byte) {
×
276
        for _, x := range b {
×
277
                fmt.Fprintf(w, "%02x ", x)
×
278
        }
×
279
        fmt.Fprintln(w, "")
×
280
}
281

282
func RecordForStatistics(tid id.TriceID) {
×
283
        if !TriceStatistics && !emitter.AllStatistics {
×
284
                return
×
285
        }
×
286
        count := IDStat[tid]
×
287
        count++
×
288
        IDStat[tid] = count
×
289
}
290

291
var (
292
        IDLUT id.TriceIDLookUp
293
        LILUT id.TriceIDLookUpLI
294
)
295

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

321
// LocationInformation returns optional location information for id.
322
func LocationInformation(tid id.TriceID, li id.TriceIDLookUpLI) string {
×
323
        if li != nil && LocationInformationFormatString != "off" && LocationInformationFormatString != "none" {
×
324
                if li, ok := li[tid]; ok {
×
325
                        return fmt.Sprintf(LocationInformationFormatString, filepath.Base(li.File), li.Line)
×
326
                } else {
×
327
                        return fmt.Sprintf(LocationInformationFormatString, "", 0)
×
328
                }
×
329
        } else {
×
330
                if Verbose {
×
331
                        return "no li"
×
332
                }
×
333
        }
334
        return ""
×
335
}
336

337
// locationInformationWidth computes byte count for location information.
338
func locationInformationWidth() (sum int) {
1✔
339
        re := regexp.MustCompile("[0-9]+")
1✔
340
        s := re.FindAllString(LocationInformationFormatString, -1)
1✔
341
        for _, n := range s {
3✔
342
                v, err := strconv.Atoi(n)
2✔
343
                if err != nil {
2✔
344
                        panic(err)
×
345
                }
346
                sum += v
2✔
347
        }
348
        return sum + 1 // plus space after line number
1✔
349
}
350

351
// CorrectWrappedTimestamp checks whether a 32-bit timestamp falls outside the valid range
352
// and virtually sets a 33rd bit by adding 2^32 seconds to it
353
func CorrectWrappedTimestamp(ts32 uint32) time.Time {
×
354

×
355
        const (
×
356
                minValidYear = 2000
×
357
                maxValidYear = 2038
×
358
                wrapOffset   = 1 << 32 // 2^32 seconds
×
359
        )
×
360

×
361
        // Interpret the timestamp as time.Time
×
362
        t := time.Unix(int64(ts32), 0).UTC()
×
363

×
364
        if t.Year() >= minValidYear && t.Year() <= maxValidYear {
×
365
                return t
×
366
        }
×
367

368
        // Apply wraparound correction by adding 2^32 seconds
369
        tWrapped := time.Unix(int64(ts32)+wrapOffset, 0).UTC()
×
370

×
371
        // If the corrected timestamp is plausible, return it
×
372
        if tWrapped.Year() > maxValidYear && tWrapped.Year() <= maxValidYear+100 {
×
373
                return tWrapped
×
374
        }
×
375

376
        // Fallback: return the original timestamp and print a warning
377
        fmt.Printf("WARNING: Timestamp %v (%d) is outside the expected year range\n", t, ts32)
×
378
        return t
×
379
}
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