Skip to content

Commit 4846b58

Browse files
authored
jsonpb: Fix marshaling of Duration (#1221)
Negative nanosecond should not have negative sign after decimal point. Add check for max and min seconds. Fixes #1219.
1 parent 91c84e0 commit 4846b58

File tree

2 files changed

+30
-16
lines changed

2 files changed

+30
-16
lines changed

jsonpb/encode.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -166,20 +166,25 @@ func (w *jsonWriter) marshalMessage(m protoreflect.Message, indent, typeURL stri
166166
fd := fds.ByNumber(1)
167167
return w.marshalValue(fd, m.Get(fd), indent)
168168
case "Duration":
169+
const maxSecondsInDuration = 315576000000
169170
// "Generated output always contains 0, 3, 6, or 9 fractional digits,
170171
// depending on required precision."
171172
s := m.Get(fds.ByNumber(1)).Int()
172173
ns := m.Get(fds.ByNumber(2)).Int()
174+
if s < -maxSecondsInDuration || s > maxSecondsInDuration {
175+
return fmt.Errorf("seconds out of range %v", s)
176+
}
173177
if ns <= -secondInNanos || ns >= secondInNanos {
174178
return fmt.Errorf("ns out of range (%v, %v)", -secondInNanos, secondInNanos)
175179
}
176180
if (s > 0 && ns < 0) || (s < 0 && ns > 0) {
177181
return errors.New("signs of seconds and nanos do not match")
178182
}
179-
if s < 0 {
180-
ns = -ns
183+
var sign string
184+
if s < 0 || ns < 0 {
185+
sign, s, ns = "-", -1*s, -1*ns
181186
}
182-
x := fmt.Sprintf("%d.%09d", s, ns)
187+
x := fmt.Sprintf("%s%d.%09d", sign, s, ns)
183188
x = strings.TrimSuffix(x, "000")
184189
x = strings.TrimSuffix(x, "000")
185190
x = strings.TrimSuffix(x, ".000")

jsonpb/json_test.go

+22-13
Original file line numberDiff line numberDiff line change
@@ -448,10 +448,17 @@ var marshalingTests = []struct {
448448
{"Any with message and indent", marshalerAllOptions, anySimple, anySimplePrettyJSON},
449449
{"Any with WKT", marshaler, anyWellKnown, anyWellKnownJSON},
450450
{"Any with WKT and indent", marshalerAllOptions, anyWellKnown, anyWellKnownPrettyJSON},
451-
{"Duration", marshaler, &pb2.KnownTypes{Dur: &durpb.Duration{Seconds: 3}}, `{"dur":"3s"}`},
452-
{"Duration", marshaler, &pb2.KnownTypes{Dur: &durpb.Duration{Seconds: 3, Nanos: 1e6}}, `{"dur":"3.001s"}`},
453-
{"Duration beyond float64 precision", marshaler, &pb2.KnownTypes{Dur: &durpb.Duration{Seconds: 100000000, Nanos: 1}}, `{"dur":"100000000.000000001s"}`},
454-
{"negative Duration", marshaler, &pb2.KnownTypes{Dur: &durpb.Duration{Seconds: -123, Nanos: -456}}, `{"dur":"-123.000000456s"}`},
451+
{"Duration empty", marshaler, &durpb.Duration{}, `"0s"`},
452+
{"Duration with secs", marshaler, &durpb.Duration{Seconds: 3}, `"3s"`},
453+
{"Duration with -secs", marshaler, &durpb.Duration{Seconds: -3}, `"-3s"`},
454+
{"Duration with nanos", marshaler, &durpb.Duration{Nanos: 1e6}, `"0.001s"`},
455+
{"Duration with -nanos", marshaler, &durpb.Duration{Nanos: -1e6}, `"-0.001s"`},
456+
{"Duration with large secs", marshaler, &durpb.Duration{Seconds: 1e10, Nanos: 1}, `"10000000000.000000001s"`},
457+
{"Duration with 6-digit nanos", marshaler, &durpb.Duration{Nanos: 1e4}, `"0.000010s"`},
458+
{"Duration with 3-digit nanos", marshaler, &durpb.Duration{Nanos: 1e6}, `"0.001s"`},
459+
{"Duration with -secs -nanos", marshaler, &durpb.Duration{Seconds: -123, Nanos: -450}, `"-123.000000450s"`},
460+
{"Duration max value", marshaler, &durpb.Duration{Seconds: 315576000000, Nanos: 999999999}, `"315576000000.999999999s"`},
461+
{"Duration min value", marshaler, &durpb.Duration{Seconds: -315576000000, Nanos: -999999999}, `"-315576000000.999999999s"`},
455462
{"Struct", marshaler, &pb2.KnownTypes{St: &stpb.Struct{
456463
Fields: map[string]*stpb.Value{
457464
"one": {Kind: &stpb.Value_StringValue{"loneliest number"}},
@@ -524,15 +531,17 @@ func TestMarshalIllegalTime(t *testing.T) {
524531
pb proto.Message
525532
fail bool
526533
}{
527-
{&pb2.KnownTypes{Dur: &durpb.Duration{Seconds: 1, Nanos: 0}}, false},
528-
{&pb2.KnownTypes{Dur: &durpb.Duration{Seconds: -1, Nanos: 0}}, false},
529-
{&pb2.KnownTypes{Dur: &durpb.Duration{Seconds: 1, Nanos: -1}}, true},
530-
{&pb2.KnownTypes{Dur: &durpb.Duration{Seconds: -1, Nanos: 1}}, true},
531-
{&pb2.KnownTypes{Dur: &durpb.Duration{Seconds: 1, Nanos: 1000000000}}, true},
532-
{&pb2.KnownTypes{Dur: &durpb.Duration{Seconds: -1, Nanos: -1000000000}}, true},
533-
{&pb2.KnownTypes{Ts: &tspb.Timestamp{Seconds: 1, Nanos: 1}}, false},
534-
{&pb2.KnownTypes{Ts: &tspb.Timestamp{Seconds: 1, Nanos: -1}}, true},
535-
{&pb2.KnownTypes{Ts: &tspb.Timestamp{Seconds: 1, Nanos: 1000000000}}, true},
534+
{&durpb.Duration{Seconds: 1, Nanos: 0}, false},
535+
{&durpb.Duration{Seconds: -1, Nanos: 0}, false},
536+
{&durpb.Duration{Seconds: 1, Nanos: -1}, true},
537+
{&durpb.Duration{Seconds: -1, Nanos: 1}, true},
538+
{&durpb.Duration{Seconds: 315576000001}, true},
539+
{&durpb.Duration{Seconds: -315576000001}, true},
540+
{&durpb.Duration{Seconds: 1, Nanos: 1000000000}, true},
541+
{&durpb.Duration{Seconds: -1, Nanos: -1000000000}, true},
542+
{&tspb.Timestamp{Seconds: 1, Nanos: 1}, false},
543+
{&tspb.Timestamp{Seconds: 1, Nanos: -1}, true},
544+
{&tspb.Timestamp{Seconds: 1, Nanos: 1000000000}, true},
536545
}
537546
for _, tt := range tests {
538547
_, err := marshaler.MarshalToString(tt.pb)

0 commit comments

Comments
 (0)