Skip to content

Commit 01ed4a6

Browse files
mcyfowles
authored andcommitted
Implement schema decoding for packed encodings
1 parent b812e8e commit 01ed4a6

File tree

7 files changed

+236
-41
lines changed

7 files changed

+236
-41
lines changed

internal/print/print.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,52 @@ func (p *Printer) EndBlock() *Line {
307307
p.lines = p.lines[:bi.start+1]
308308
return start
309309
}
310+
311+
// Folds the last count lines into lines with `cols` columns each.
312+
func (p *Printer) FoldIntoColumns(cols, count int) {
313+
toFold := p.lines.PopN(count)
314+
widths := make([]int, cols)
315+
316+
for len(toFold) > 0 {
317+
for i := range widths {
318+
widths[i] = 0
319+
}
320+
321+
end := len(toFold)
322+
for i, line := range toFold {
323+
if len(line.remarks) != 0 {
324+
end = i
325+
break
326+
}
327+
328+
len := utf8.RuneCount(line.Bytes())
329+
w := &widths[i%cols]
330+
if len > *w {
331+
*w = len
332+
}
333+
}
334+
if end == 0 {
335+
end = 1
336+
}
337+
338+
for i, line := range toFold[:end] {
339+
if i%cols == 0 {
340+
p.NewLine()
341+
} else {
342+
p.Write(" ")
343+
}
344+
345+
needed := widths[i%cols] - utf8.RuneCount(line.Bytes())
346+
for i := 0; i < needed; i++ {
347+
p.Write(" ")
348+
}
349+
p.Current().Write(line.Bytes())
350+
if len(line.remarks) != 0 {
351+
// This will execute at most once per loop.
352+
p.Current().remarks = line.remarks
353+
}
354+
}
355+
356+
toFold = toFold[end:]
357+
}
358+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
go test fuzz v1
2+
[]byte("00\xf9\x00")

testdata/message-fields.pb.golden

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
10: 110i64 # optional_sfixed64
1212
11: 111.0i32 # optional_float, 0x42de0000i32
1313
12: 112.0 # optional_double, 0x405c000000000000i64
14-
13: 1 # optional_bool
14+
13: true # optional_bool
1515
14: {"115"} # optional_string
1616
15: {"116"} # optional_bytes
1717
16: !{ # optionalgroup
@@ -58,8 +58,8 @@
5858
41: 311.0i32 # repeated_float, 0x439b8000i32
5959
42: 212.0 # repeated_double, 0x406a800000000000i64
6060
42: 312.0 # repeated_double, 0x4073800000000000i64
61-
43: 1 # repeated_bool
62-
43: 0 # repeated_bool
61+
43: true # repeated_bool
62+
43: false # repeated_bool
6363
44: {"215"} # repeated_string
6464
44: {"315"} # repeated_string
6565
45: {"216"} # repeated_bytes
@@ -112,7 +112,7 @@
112112
70: 410i64 # default_sfixed64
113113
71: 411.0i32 # default_float, 0x43cd8000i32
114114
72: 412.0 # default_double, 0x4079c00000000000i64
115-
73: 0 # default_bool
115+
73: false # default_bool
116116
74: {"415"} # default_string
117117
75: {"416"} # default_bytes
118118
81: 1 # default_nested_enum

testdata/packed-big.pb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
�2**************************************************�� %)+/5;=CGIOSYaegkmq���������������������������������������������������������������������������������������������������������������������������������������������������������������� � � � � � � � � � �����Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@���Q�3@

testdata/packed-big.pb.golden

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# packed-big.pb Schema=unittest.TestPackedTypes
2+
90: {
3+
42 42 42 42 42 42 42 42
4+
42 42 42 42 42 42 42 42
5+
42 42 42 42 42 42 42 42
6+
42 42 42 42 42 42 42 42
7+
42 42 42 42 42 42 42 42
8+
42 42 42 42 42 42 42 42
9+
42 42
10+
}
11+
90: {
12+
2 3 5 7 11 13 17 19
13+
23 29 31 37 41 43 47 53
14+
59 61 67 71 73 79 83 89
15+
97 101 103 107 109 113 127 131
16+
137 139 149 151 157 163 167 173
17+
179 181 191 193 197 199 211 223
18+
227 229 233 239 241 251 257 263
19+
269 271 277 281 283 293 307 311
20+
313 317 331 337 347 349 353 359
21+
367 373 379 383 389 397 401 409
22+
419 421 431 433 439 443 449 457
23+
461 463 467 479 487 491 499 503
24+
509 521 523 541 547 557 563 569
25+
571 577 587 593 599 601 607 613
26+
617 619 631 641 643 647 653 659
27+
661 673 677 683 691 701 709 719
28+
727 733 739 743 751 757 761 769
29+
773 787 797 809 811 821 823 827
30+
829 839 853 857 859 863 877 881
31+
883 887 907 911 919 929 937 941
32+
947 953 967 971 977 983 991 997
33+
1009 1013 1019 1021 1031 1033 1039 1049
34+
1051 1061 1063 1069 1087 1091 1093 1097
35+
1103 1109 1117 1123 1129 1151 1153 1163
36+
1171 1181 1187 1193 1201 1213 1217 1223
37+
}
38+
101: {
39+
19.97 # 0x4033f851eb851eb8i64
40+
19.97 # 0x4033f851eb851eb8i64
41+
19.97 # 0x4033f851eb851eb8i64
42+
19.97 # 0x4033f851eb851eb8i64
43+
19.97 # 0x4033f851eb851eb8i64
44+
19.97 # 0x4033f851eb851eb8i64
45+
19.97 # 0x4033f851eb851eb8i64
46+
19.97 # 0x4033f851eb851eb8i64
47+
19.97 # 0x4033f851eb851eb8i64
48+
19.97 # 0x4033f851eb851eb8i64
49+
19.97 # 0x4033f851eb851eb8i64
50+
19.97 # 0x4033f851eb851eb8i64
51+
19.97 # 0x4033f851eb851eb8i64
52+
19.97 # 0x4033f851eb851eb8i64
53+
19.97 # 0x4033f851eb851eb8i64
54+
19.97 # 0x4033f851eb851eb8i64
55+
19.97 # 0x4033f851eb851eb8i64
56+
19.97 # 0x4033f851eb851eb8i64
57+
19.97 # 0x4033f851eb851eb8i64
58+
19.97 # 0x4033f851eb851eb8i64
59+
19.97 # 0x4033f851eb851eb8i64
60+
19.97 # 0x4033f851eb851eb8i64
61+
19.97 # 0x4033f851eb851eb8i64
62+
19.97 # 0x4033f851eb851eb8i64
63+
19.97 # 0x4033f851eb851eb8i64
64+
19.97 # 0x4033f851eb851eb8i64
65+
19.97 # 0x4033f851eb851eb8i64
66+
}

testdata/packed-schema.pb.golden

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# packed.pb Schema=unittest.TestPackedTypes
2+
90: {601 701}
3+
91: {602 702}
4+
92: {603 703}
5+
93: {604 704}
6+
94: {605z 705z}
7+
95: {606z 706z}
8+
96: {607i32 707i32}
9+
97: {608i64 708i64}
10+
98: {609i32 709i32}
11+
99: {610i64 710i64}
12+
100: {
13+
611.0i32 # 0x4418c000i32
14+
711.0i32 # 0x4431c000i32
15+
}
16+
101: {
17+
612.0 # 0x4083200000000000i64
18+
712.0 # 0x4086400000000000i64
19+
}
20+
102: {true false}
21+
103: {5 6}

writer.go

Lines changed: 93 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func (w *writer) decodeVarint(src []byte, fd protoreflect.FieldDescriptor) ([]by
141141
src = rest
142142

143143
if extra > 0 {
144-
w.Writef("long-form:%d", extra)
144+
w.Writef("long-form:%d ", extra)
145145
}
146146

147147
ftype := protoreflect.Int64Kind
@@ -153,9 +153,19 @@ func (w *writer) decodeVarint(src []byte, fd protoreflect.FieldDescriptor) ([]by
153153
// make sense (like a double), we fall back on int64. We ignore 32-bit-ness:
154154
// everything is 64 bit here.
155155
switch ftype {
156+
case protoreflect.BoolKind:
157+
switch value {
158+
case 0:
159+
w.Write("false")
160+
return src, true
161+
case 1:
162+
w.Write("true")
163+
return src, true
164+
}
165+
fallthrough
156166
case protoreflect.Uint32Kind, protoreflect.Uint64Kind,
157167
protoreflect.Fixed32Kind, protoreflect.Fixed64Kind:
158-
w.Writef("%d", value)
168+
w.Write(value)
159169
case protoreflect.Sint32Kind, protoreflect.Sint64Kind:
160170
// Undo ZigZag encoding, then print as signed.
161171
value = (value >> 1) ^ -(value & 1)
@@ -170,7 +180,7 @@ func (w *writer) decodeVarint(src []byte, fd protoreflect.FieldDescriptor) ([]by
170180
}
171181
fallthrough
172182
default:
173-
w.Writef("%d", int64(value))
183+
w.Write(int64(value))
174184
}
175185

176186
return src, true
@@ -405,46 +415,92 @@ func (w *writer) decodeField(src []byte) ([]byte, bool) {
405415
})
406416
}
407417

408-
// First, assume this is a message.
409-
startLine := w.Mark()
410-
src2 := delimited
411-
outerGroups := w.groups
412-
w.groups = nil
418+
ftype := protoreflect.MessageKind
413419
if fd != nil {
414-
w.descs.Push(fd.Message())
420+
ftype = fd.Kind()
415421
}
416-
for len(src2) > 0 {
417-
w.NewLine()
418-
s, ok := w.decodeField(src2)
419-
if !ok {
420-
// Clip off an incompletely printed line.
421-
w.DiscardLine()
422-
break
422+
423+
decodePacked := func(decode func([]byte, protoreflect.FieldDescriptor) ([]byte, bool)) {
424+
count := 0
425+
for ; ; count++ {
426+
w.NewLine()
427+
s, ok := decode(delimited, fd)
428+
if !ok {
429+
w.DiscardLine()
430+
break
431+
}
432+
delimited = s
423433
}
424-
src2 = s
425-
}
426-
if fd != nil {
427-
w.descs.Pop()
434+
435+
w.FoldIntoColumns(8, count)
428436
}
429437

430-
// Order does not matter for fixing up unclosed groups
431-
for _ = range w.groups {
432-
w.resetGroup()
438+
switch ftype {
439+
case protoreflect.BoolKind, protoreflect.EnumKind,
440+
protoreflect.Int32Kind, protoreflect.Int64Kind,
441+
protoreflect.Uint32Kind, protoreflect.Uint64Kind,
442+
protoreflect.Sint32Kind, protoreflect.Sint64Kind:
443+
decodePacked(w.decodeVarint)
444+
goto decodeBytes
445+
446+
case protoreflect.Fixed32Kind, protoreflect.Sfixed32Kind,
447+
protoreflect.FloatKind:
448+
decodePacked(w.decodeI32)
449+
goto decodeBytes
450+
451+
case protoreflect.Fixed64Kind, protoreflect.Sfixed64Kind,
452+
protoreflect.DoubleKind:
453+
decodePacked(w.decodeI64)
454+
goto decodeBytes
455+
456+
case protoreflect.StringKind, protoreflect.BytesKind:
457+
goto decodeUtf8
433458
}
434-
w.groups = outerGroups
435-
436-
// If we consumed all the bytes, we're done and can wrap up. However, if we
437-
// consumed *some* bytes, and the user requested unconditional message
438-
// parsing, we'll continue regardless. We don't bother in the case where we
439-
// failed at the start because the `...` case below will do a cleaner job.
440-
if len(src2) == 0 || (w.AllFieldsAreMessages && len(src2) < len(delimited)) {
441-
delimited = src2
442-
goto justBytes
443-
} else {
444-
w.Reset(startLine)
459+
460+
// This is in a block so that the gotos can jump over the declarations
461+
// safely.
462+
{
463+
startLine := w.Mark()
464+
src2 := delimited
465+
outerGroups := w.groups
466+
w.groups = nil
467+
if fd != nil {
468+
w.descs.Push(fd.Message())
469+
}
470+
for len(src2) > 0 {
471+
w.NewLine()
472+
s, ok := w.decodeField(src2)
473+
if !ok {
474+
// Clip off an incompletely printed line.
475+
w.DiscardLine()
476+
break
477+
}
478+
src2 = s
479+
}
480+
if fd != nil {
481+
w.descs.Pop()
482+
}
483+
484+
// Order does not matter for fixing up unclosed groups
485+
for range w.groups {
486+
w.resetGroup()
487+
}
488+
w.groups = outerGroups
489+
490+
// If we consumed all the bytes, we're done and can wrap up. However, if we
491+
// consumed *some* bytes, and the user requested unconditional message
492+
// parsing, we'll continue regardless. We don't bother in the case where we
493+
// failed at the start because the `...` case below will do a cleaner job.
494+
if len(src2) == 0 || (w.AllFieldsAreMessages && len(src2) < len(delimited)) {
495+
delimited = src2
496+
goto decodeBytes
497+
} else {
498+
w.Reset(startLine)
499+
}
445500
}
446501

447502
// Otherwise, maybe it's a UTF-8 string.
503+
decodeUtf8:
448504
if !w.NoQuotedStrings && utf8.Valid(delimited) {
449505
runes := utf8.RuneCount(delimited)
450506

@@ -456,7 +512,7 @@ func (w *writer) decodeField(src []byte) ([]byte, bool) {
456512
}
457513
}
458514
if float64(unprintable)/float64(runes) > 0.3 {
459-
goto justBytes
515+
goto decodeBytes
460516
}
461517

462518
w.NewLine()
@@ -489,11 +545,11 @@ func (w *writer) decodeField(src []byte) ([]byte, bool) {
489545
}
490546
w.Write("\"")
491547
delimited = nil
492-
goto justBytes
548+
goto decodeBytes
493549
}
494550

495551
// Who knows what it is? Bytes or something.
496-
justBytes:
552+
decodeBytes:
497553
w.dumpHexString(delimited)
498554
if !w.ExplicitLengthPrefixes {
499555
w.NewLine()

0 commit comments

Comments
 (0)