mirror of
https://codeberg.org/superseriousbusiness/gotosocial.git
synced 2024-12-27 03:18:16 +03:00
acc333c40b
When GTS is running in a container runtime which has configured CPU or memory limits or under an init system that uses cgroups to impose CPU and memory limits the values the Go runtime sees for GOMAXPROCS and GOMEMLIMIT are still based on the host resources, not the cgroup. At least for the throttling middlewares which use GOMAXPROCS to configure their queue size, this can result in GTS running with values too big compared to the resources that will actuall be available to it. This introduces 2 dependencies which can pick up resource contraints from the current cgroup and tune the Go runtime accordingly. This should result in the different queues being appropriately sized and in general more predictable performance. These dependencies are a no-op on non-Linux systems or if running in a cgroup that doesn't set a limit on CPU or memory. The automatic tuning of GOMEMLIMIT can be disabled by either explicitly setting GOMEMLIMIT yourself or by setting AUTOMEMLIMIT=off. The automatic tuning of GOMAXPROCS can similarly be counteracted by setting GOMAXPROCS yourself.
930 lines
23 KiB
Go
930 lines
23 KiB
Go
// Protocol Buffers for Go with Gadgets
|
|
//
|
|
// Copyright (c) 2013, The GoGo Authors. All rights reserved.
|
|
// http://github.com/gogo/protobuf
|
|
//
|
|
// Go support for Protocol Buffers - Google's data interchange format
|
|
//
|
|
// Copyright 2010 The Go Authors. All rights reserved.
|
|
// https://github.com/golang/protobuf
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
// in the documentation and/or other materials provided with the
|
|
// distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
package proto
|
|
|
|
// Functions for writing the text protocol buffer format.
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
newline = []byte("\n")
|
|
spaces = []byte(" ")
|
|
endBraceNewline = []byte("}\n")
|
|
backslashN = []byte{'\\', 'n'}
|
|
backslashR = []byte{'\\', 'r'}
|
|
backslashT = []byte{'\\', 't'}
|
|
backslashDQ = []byte{'\\', '"'}
|
|
backslashBS = []byte{'\\', '\\'}
|
|
posInf = []byte("inf")
|
|
negInf = []byte("-inf")
|
|
nan = []byte("nan")
|
|
)
|
|
|
|
type writer interface {
|
|
io.Writer
|
|
WriteByte(byte) error
|
|
}
|
|
|
|
// textWriter is an io.Writer that tracks its indentation level.
|
|
type textWriter struct {
|
|
ind int
|
|
complete bool // if the current position is a complete line
|
|
compact bool // whether to write out as a one-liner
|
|
w writer
|
|
}
|
|
|
|
func (w *textWriter) WriteString(s string) (n int, err error) {
|
|
if !strings.Contains(s, "\n") {
|
|
if !w.compact && w.complete {
|
|
w.writeIndent()
|
|
}
|
|
w.complete = false
|
|
return io.WriteString(w.w, s)
|
|
}
|
|
// WriteString is typically called without newlines, so this
|
|
// codepath and its copy are rare. We copy to avoid
|
|
// duplicating all of Write's logic here.
|
|
return w.Write([]byte(s))
|
|
}
|
|
|
|
func (w *textWriter) Write(p []byte) (n int, err error) {
|
|
newlines := bytes.Count(p, newline)
|
|
if newlines == 0 {
|
|
if !w.compact && w.complete {
|
|
w.writeIndent()
|
|
}
|
|
n, err = w.w.Write(p)
|
|
w.complete = false
|
|
return n, err
|
|
}
|
|
|
|
frags := bytes.SplitN(p, newline, newlines+1)
|
|
if w.compact {
|
|
for i, frag := range frags {
|
|
if i > 0 {
|
|
if err := w.w.WriteByte(' '); err != nil {
|
|
return n, err
|
|
}
|
|
n++
|
|
}
|
|
nn, err := w.w.Write(frag)
|
|
n += nn
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
for i, frag := range frags {
|
|
if w.complete {
|
|
w.writeIndent()
|
|
}
|
|
nn, err := w.w.Write(frag)
|
|
n += nn
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
if i+1 < len(frags) {
|
|
if err := w.w.WriteByte('\n'); err != nil {
|
|
return n, err
|
|
}
|
|
n++
|
|
}
|
|
}
|
|
w.complete = len(frags[len(frags)-1]) == 0
|
|
return n, nil
|
|
}
|
|
|
|
func (w *textWriter) WriteByte(c byte) error {
|
|
if w.compact && c == '\n' {
|
|
c = ' '
|
|
}
|
|
if !w.compact && w.complete {
|
|
w.writeIndent()
|
|
}
|
|
err := w.w.WriteByte(c)
|
|
w.complete = c == '\n'
|
|
return err
|
|
}
|
|
|
|
func (w *textWriter) indent() { w.ind++ }
|
|
|
|
func (w *textWriter) unindent() {
|
|
if w.ind == 0 {
|
|
log.Print("proto: textWriter unindented too far")
|
|
return
|
|
}
|
|
w.ind--
|
|
}
|
|
|
|
func writeName(w *textWriter, props *Properties) error {
|
|
if _, err := w.WriteString(props.OrigName); err != nil {
|
|
return err
|
|
}
|
|
if props.Wire != "group" {
|
|
return w.WriteByte(':')
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func requiresQuotes(u string) bool {
|
|
// When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted.
|
|
for _, ch := range u {
|
|
switch {
|
|
case ch == '.' || ch == '/' || ch == '_':
|
|
continue
|
|
case '0' <= ch && ch <= '9':
|
|
continue
|
|
case 'A' <= ch && ch <= 'Z':
|
|
continue
|
|
case 'a' <= ch && ch <= 'z':
|
|
continue
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isAny reports whether sv is a google.protobuf.Any message
|
|
func isAny(sv reflect.Value) bool {
|
|
type wkt interface {
|
|
XXX_WellKnownType() string
|
|
}
|
|
t, ok := sv.Addr().Interface().(wkt)
|
|
return ok && t.XXX_WellKnownType() == "Any"
|
|
}
|
|
|
|
// writeProto3Any writes an expanded google.protobuf.Any message.
|
|
//
|
|
// It returns (false, nil) if sv value can't be unmarshaled (e.g. because
|
|
// required messages are not linked in).
|
|
//
|
|
// It returns (true, error) when sv was written in expanded format or an error
|
|
// was encountered.
|
|
func (tm *TextMarshaler) writeProto3Any(w *textWriter, sv reflect.Value) (bool, error) {
|
|
turl := sv.FieldByName("TypeUrl")
|
|
val := sv.FieldByName("Value")
|
|
if !turl.IsValid() || !val.IsValid() {
|
|
return true, errors.New("proto: invalid google.protobuf.Any message")
|
|
}
|
|
|
|
b, ok := val.Interface().([]byte)
|
|
if !ok {
|
|
return true, errors.New("proto: invalid google.protobuf.Any message")
|
|
}
|
|
|
|
parts := strings.Split(turl.String(), "/")
|
|
mt := MessageType(parts[len(parts)-1])
|
|
if mt == nil {
|
|
return false, nil
|
|
}
|
|
m := reflect.New(mt.Elem())
|
|
if err := Unmarshal(b, m.Interface().(Message)); err != nil {
|
|
return false, nil
|
|
}
|
|
w.Write([]byte("["))
|
|
u := turl.String()
|
|
if requiresQuotes(u) {
|
|
writeString(w, u)
|
|
} else {
|
|
w.Write([]byte(u))
|
|
}
|
|
if w.compact {
|
|
w.Write([]byte("]:<"))
|
|
} else {
|
|
w.Write([]byte("]: <\n"))
|
|
w.ind++
|
|
}
|
|
if err := tm.writeStruct(w, m.Elem()); err != nil {
|
|
return true, err
|
|
}
|
|
if w.compact {
|
|
w.Write([]byte("> "))
|
|
} else {
|
|
w.ind--
|
|
w.Write([]byte(">\n"))
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
|
if tm.ExpandAny && isAny(sv) {
|
|
if canExpand, err := tm.writeProto3Any(w, sv); canExpand {
|
|
return err
|
|
}
|
|
}
|
|
st := sv.Type()
|
|
sprops := GetProperties(st)
|
|
for i := 0; i < sv.NumField(); i++ {
|
|
fv := sv.Field(i)
|
|
props := sprops.Prop[i]
|
|
name := st.Field(i).Name
|
|
|
|
if name == "XXX_NoUnkeyedLiteral" {
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(name, "XXX_") {
|
|
// There are two XXX_ fields:
|
|
// XXX_unrecognized []byte
|
|
// XXX_extensions map[int32]proto.Extension
|
|
// The first is handled here;
|
|
// the second is handled at the bottom of this function.
|
|
if name == "XXX_unrecognized" && !fv.IsNil() {
|
|
if err := writeUnknownStruct(w, fv.Interface().([]byte)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
|
// Field not filled in. This could be an optional field or
|
|
// a required field that wasn't filled in. Either way, there
|
|
// isn't anything we can show for it.
|
|
continue
|
|
}
|
|
if fv.Kind() == reflect.Slice && fv.IsNil() {
|
|
// Repeated field that is empty, or a bytes field that is unused.
|
|
continue
|
|
}
|
|
|
|
if props.Repeated && fv.Kind() == reflect.Slice {
|
|
// Repeated field.
|
|
for j := 0; j < fv.Len(); j++ {
|
|
if err := writeName(w, props); err != nil {
|
|
return err
|
|
}
|
|
if !w.compact {
|
|
if err := w.WriteByte(' '); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
v := fv.Index(j)
|
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
|
// A nil message in a repeated field is not valid,
|
|
// but we can handle that more gracefully than panicking.
|
|
if _, err := w.Write([]byte("<nil>\n")); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
if len(props.Enum) > 0 {
|
|
if err := tm.writeEnum(w, v, props); err != nil {
|
|
return err
|
|
}
|
|
} else if err := tm.writeAny(w, v, props); err != nil {
|
|
return err
|
|
}
|
|
if err := w.WriteByte('\n'); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if fv.Kind() == reflect.Map {
|
|
// Map fields are rendered as a repeated struct with key/value fields.
|
|
keys := fv.MapKeys()
|
|
sort.Sort(mapKeys(keys))
|
|
for _, key := range keys {
|
|
val := fv.MapIndex(key)
|
|
if err := writeName(w, props); err != nil {
|
|
return err
|
|
}
|
|
if !w.compact {
|
|
if err := w.WriteByte(' '); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// open struct
|
|
if err := w.WriteByte('<'); err != nil {
|
|
return err
|
|
}
|
|
if !w.compact {
|
|
if err := w.WriteByte('\n'); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
w.indent()
|
|
// key
|
|
if _, err := w.WriteString("key:"); err != nil {
|
|
return err
|
|
}
|
|
if !w.compact {
|
|
if err := w.WriteByte(' '); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := tm.writeAny(w, key, props.MapKeyProp); err != nil {
|
|
return err
|
|
}
|
|
if err := w.WriteByte('\n'); err != nil {
|
|
return err
|
|
}
|
|
// nil values aren't legal, but we can avoid panicking because of them.
|
|
if val.Kind() != reflect.Ptr || !val.IsNil() {
|
|
// value
|
|
if _, err := w.WriteString("value:"); err != nil {
|
|
return err
|
|
}
|
|
if !w.compact {
|
|
if err := w.WriteByte(' '); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := tm.writeAny(w, val, props.MapValProp); err != nil {
|
|
return err
|
|
}
|
|
if err := w.WriteByte('\n'); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// close struct
|
|
w.unindent()
|
|
if err := w.WriteByte('>'); err != nil {
|
|
return err
|
|
}
|
|
if err := w.WriteByte('\n'); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if props.proto3 && fv.Kind() == reflect.Slice && fv.Len() == 0 {
|
|
// empty bytes field
|
|
continue
|
|
}
|
|
if props.proto3 && fv.Kind() != reflect.Ptr && fv.Kind() != reflect.Slice {
|
|
// proto3 non-repeated scalar field; skip if zero value
|
|
if isProto3Zero(fv) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
if fv.Kind() == reflect.Interface {
|
|
// Check if it is a oneof.
|
|
if st.Field(i).Tag.Get("protobuf_oneof") != "" {
|
|
// fv is nil, or holds a pointer to generated struct.
|
|
// That generated struct has exactly one field,
|
|
// which has a protobuf struct tag.
|
|
if fv.IsNil() {
|
|
continue
|
|
}
|
|
inner := fv.Elem().Elem() // interface -> *T -> T
|
|
tag := inner.Type().Field(0).Tag.Get("protobuf")
|
|
props = new(Properties) // Overwrite the outer props var, but not its pointee.
|
|
props.Parse(tag)
|
|
// Write the value in the oneof, not the oneof itself.
|
|
fv = inner.Field(0)
|
|
|
|
// Special case to cope with malformed messages gracefully:
|
|
// If the value in the oneof is a nil pointer, don't panic
|
|
// in writeAny.
|
|
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
|
// Use errors.New so writeAny won't render quotes.
|
|
msg := errors.New("/* nil */")
|
|
fv = reflect.ValueOf(&msg).Elem()
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := writeName(w, props); err != nil {
|
|
return err
|
|
}
|
|
if !w.compact {
|
|
if err := w.WriteByte(' '); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(props.Enum) > 0 {
|
|
if err := tm.writeEnum(w, fv, props); err != nil {
|
|
return err
|
|
}
|
|
} else if err := tm.writeAny(w, fv, props); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := w.WriteByte('\n'); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Extensions (the XXX_extensions field).
|
|
pv := sv
|
|
if pv.CanAddr() {
|
|
pv = sv.Addr()
|
|
} else {
|
|
pv = reflect.New(sv.Type())
|
|
pv.Elem().Set(sv)
|
|
}
|
|
if _, err := extendable(pv.Interface()); err == nil {
|
|
if err := tm.writeExtensions(w, pv); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
|
|
|
// writeAny writes an arbitrary field.
|
|
func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Properties) error {
|
|
v = reflect.Indirect(v)
|
|
|
|
if props != nil {
|
|
if len(props.CustomType) > 0 {
|
|
custom, ok := v.Interface().(Marshaler)
|
|
if ok {
|
|
data, err := custom.Marshal()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := writeString(w, string(data)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
} else if len(props.CastType) > 0 {
|
|
if _, ok := v.Interface().(interface {
|
|
String() string
|
|
}); ok {
|
|
switch v.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
_, err := fmt.Fprintf(w, "%d", v.Interface())
|
|
return err
|
|
}
|
|
}
|
|
} else if props.StdTime {
|
|
t, ok := v.Interface().(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("stdtime is not time.Time, but %T", v.Interface())
|
|
}
|
|
tproto, err := timestampProto(t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
propsCopy := *props // Make a copy so that this is goroutine-safe
|
|
propsCopy.StdTime = false
|
|
err = tm.writeAny(w, reflect.ValueOf(tproto), &propsCopy)
|
|
return err
|
|
} else if props.StdDuration {
|
|
d, ok := v.Interface().(time.Duration)
|
|
if !ok {
|
|
return fmt.Errorf("stdtime is not time.Duration, but %T", v.Interface())
|
|
}
|
|
dproto := durationProto(d)
|
|
propsCopy := *props // Make a copy so that this is goroutine-safe
|
|
propsCopy.StdDuration = false
|
|
err := tm.writeAny(w, reflect.ValueOf(dproto), &propsCopy)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Floats have special cases.
|
|
if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 {
|
|
x := v.Float()
|
|
var b []byte
|
|
switch {
|
|
case math.IsInf(x, 1):
|
|
b = posInf
|
|
case math.IsInf(x, -1):
|
|
b = negInf
|
|
case math.IsNaN(x):
|
|
b = nan
|
|
}
|
|
if b != nil {
|
|
_, err := w.Write(b)
|
|
return err
|
|
}
|
|
// Other values are handled below.
|
|
}
|
|
|
|
// We don't attempt to serialise every possible value type; only those
|
|
// that can occur in protocol buffers.
|
|
switch v.Kind() {
|
|
case reflect.Slice:
|
|
// Should only be a []byte; repeated fields are handled in writeStruct.
|
|
if err := writeString(w, string(v.Bytes())); err != nil {
|
|
return err
|
|
}
|
|
case reflect.String:
|
|
if err := writeString(w, v.String()); err != nil {
|
|
return err
|
|
}
|
|
case reflect.Struct:
|
|
// Required/optional group/message.
|
|
var bra, ket byte = '<', '>'
|
|
if props != nil && props.Wire == "group" {
|
|
bra, ket = '{', '}'
|
|
}
|
|
if err := w.WriteByte(bra); err != nil {
|
|
return err
|
|
}
|
|
if !w.compact {
|
|
if err := w.WriteByte('\n'); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
w.indent()
|
|
if v.CanAddr() {
|
|
// Calling v.Interface on a struct causes the reflect package to
|
|
// copy the entire struct. This is racy with the new Marshaler
|
|
// since we atomically update the XXX_sizecache.
|
|
//
|
|
// Thus, we retrieve a pointer to the struct if possible to avoid
|
|
// a race since v.Interface on the pointer doesn't copy the struct.
|
|
//
|
|
// If v is not addressable, then we are not worried about a race
|
|
// since it implies that the binary Marshaler cannot possibly be
|
|
// mutating this value.
|
|
v = v.Addr()
|
|
}
|
|
if v.Type().Implements(textMarshalerType) {
|
|
text, err := v.Interface().(encoding.TextMarshaler).MarshalText()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err = w.Write(text); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if v.Kind() == reflect.Ptr {
|
|
v = v.Elem()
|
|
}
|
|
if err := tm.writeStruct(w, v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
w.unindent()
|
|
if err := w.WriteByte(ket); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
_, err := fmt.Fprint(w, v.Interface())
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// equivalent to C's isprint.
|
|
func isprint(c byte) bool {
|
|
return c >= 0x20 && c < 0x7f
|
|
}
|
|
|
|
// writeString writes a string in the protocol buffer text format.
|
|
// It is similar to strconv.Quote except we don't use Go escape sequences,
|
|
// we treat the string as a byte sequence, and we use octal escapes.
|
|
// These differences are to maintain interoperability with the other
|
|
// languages' implementations of the text format.
|
|
func writeString(w *textWriter, s string) error {
|
|
// use WriteByte here to get any needed indent
|
|
if err := w.WriteByte('"'); err != nil {
|
|
return err
|
|
}
|
|
// Loop over the bytes, not the runes.
|
|
for i := 0; i < len(s); i++ {
|
|
var err error
|
|
// Divergence from C++: we don't escape apostrophes.
|
|
// There's no need to escape them, and the C++ parser
|
|
// copes with a naked apostrophe.
|
|
switch c := s[i]; c {
|
|
case '\n':
|
|
_, err = w.w.Write(backslashN)
|
|
case '\r':
|
|
_, err = w.w.Write(backslashR)
|
|
case '\t':
|
|
_, err = w.w.Write(backslashT)
|
|
case '"':
|
|
_, err = w.w.Write(backslashDQ)
|
|
case '\\':
|
|
_, err = w.w.Write(backslashBS)
|
|
default:
|
|
if isprint(c) {
|
|
err = w.w.WriteByte(c)
|
|
} else {
|
|
_, err = fmt.Fprintf(w.w, "\\%03o", c)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return w.WriteByte('"')
|
|
}
|
|
|
|
func writeUnknownStruct(w *textWriter, data []byte) (err error) {
|
|
if !w.compact {
|
|
if _, err := fmt.Fprintf(w, "/* %d unknown bytes */\n", len(data)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
b := NewBuffer(data)
|
|
for b.index < len(b.buf) {
|
|
x, err := b.DecodeVarint()
|
|
if err != nil {
|
|
_, ferr := fmt.Fprintf(w, "/* %v */\n", err)
|
|
return ferr
|
|
}
|
|
wire, tag := x&7, x>>3
|
|
if wire == WireEndGroup {
|
|
w.unindent()
|
|
if _, werr := w.Write(endBraceNewline); werr != nil {
|
|
return werr
|
|
}
|
|
continue
|
|
}
|
|
if _, ferr := fmt.Fprint(w, tag); ferr != nil {
|
|
return ferr
|
|
}
|
|
if wire != WireStartGroup {
|
|
if err = w.WriteByte(':'); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if !w.compact || wire == WireStartGroup {
|
|
if err = w.WriteByte(' '); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
switch wire {
|
|
case WireBytes:
|
|
buf, e := b.DecodeRawBytes(false)
|
|
if e == nil {
|
|
_, err = fmt.Fprintf(w, "%q", buf)
|
|
} else {
|
|
_, err = fmt.Fprintf(w, "/* %v */", e)
|
|
}
|
|
case WireFixed32:
|
|
x, err = b.DecodeFixed32()
|
|
err = writeUnknownInt(w, x, err)
|
|
case WireFixed64:
|
|
x, err = b.DecodeFixed64()
|
|
err = writeUnknownInt(w, x, err)
|
|
case WireStartGroup:
|
|
err = w.WriteByte('{')
|
|
w.indent()
|
|
case WireVarint:
|
|
x, err = b.DecodeVarint()
|
|
err = writeUnknownInt(w, x, err)
|
|
default:
|
|
_, err = fmt.Fprintf(w, "/* unknown wire type %d */", wire)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := w.WriteByte('\n'); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeUnknownInt(w *textWriter, x uint64, err error) error {
|
|
if err == nil {
|
|
_, err = fmt.Fprint(w, x)
|
|
} else {
|
|
_, err = fmt.Fprintf(w, "/* %v */", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
type int32Slice []int32
|
|
|
|
func (s int32Slice) Len() int { return len(s) }
|
|
func (s int32Slice) Less(i, j int) bool { return s[i] < s[j] }
|
|
func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
|
|
// writeExtensions writes all the extensions in pv.
|
|
// pv is assumed to be a pointer to a protocol message struct that is extendable.
|
|
func (tm *TextMarshaler) writeExtensions(w *textWriter, pv reflect.Value) error {
|
|
emap := extensionMaps[pv.Type().Elem()]
|
|
e := pv.Interface().(Message)
|
|
|
|
var m map[int32]Extension
|
|
var mu sync.Locker
|
|
if em, ok := e.(extensionsBytes); ok {
|
|
eb := em.GetExtensions()
|
|
var err error
|
|
m, err = BytesToExtensionsMap(*eb)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mu = notLocker{}
|
|
} else if _, ok := e.(extendableProto); ok {
|
|
ep, _ := extendable(e)
|
|
m, mu = ep.extensionsRead()
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Order the extensions by ID.
|
|
// This isn't strictly necessary, but it will give us
|
|
// canonical output, which will also make testing easier.
|
|
|
|
mu.Lock()
|
|
ids := make([]int32, 0, len(m))
|
|
for id := range m {
|
|
ids = append(ids, id)
|
|
}
|
|
sort.Sort(int32Slice(ids))
|
|
mu.Unlock()
|
|
|
|
for _, extNum := range ids {
|
|
ext := m[extNum]
|
|
var desc *ExtensionDesc
|
|
if emap != nil {
|
|
desc = emap[extNum]
|
|
}
|
|
if desc == nil {
|
|
// Unknown extension.
|
|
if err := writeUnknownStruct(w, ext.enc); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
}
|
|
|
|
pb, err := GetExtension(e, desc)
|
|
if err != nil {
|
|
return fmt.Errorf("failed getting extension: %v", err)
|
|
}
|
|
|
|
// Repeated extensions will appear as a slice.
|
|
if !desc.repeated() {
|
|
if err := tm.writeExtension(w, desc.Name, pb); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
v := reflect.ValueOf(pb)
|
|
for i := 0; i < v.Len(); i++ {
|
|
if err := tm.writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (tm *TextMarshaler) writeExtension(w *textWriter, name string, pb interface{}) error {
|
|
if _, err := fmt.Fprintf(w, "[%s]:", name); err != nil {
|
|
return err
|
|
}
|
|
if !w.compact {
|
|
if err := w.WriteByte(' '); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := tm.writeAny(w, reflect.ValueOf(pb), nil); err != nil {
|
|
return err
|
|
}
|
|
if err := w.WriteByte('\n'); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *textWriter) writeIndent() {
|
|
if !w.complete {
|
|
return
|
|
}
|
|
remain := w.ind * 2
|
|
for remain > 0 {
|
|
n := remain
|
|
if n > len(spaces) {
|
|
n = len(spaces)
|
|
}
|
|
w.w.Write(spaces[:n])
|
|
remain -= n
|
|
}
|
|
w.complete = false
|
|
}
|
|
|
|
// TextMarshaler is a configurable text format marshaler.
|
|
type TextMarshaler struct {
|
|
Compact bool // use compact text format (one line).
|
|
ExpandAny bool // expand google.protobuf.Any messages of known types
|
|
}
|
|
|
|
// Marshal writes a given protocol buffer in text format.
|
|
// The only errors returned are from w.
|
|
func (tm *TextMarshaler) Marshal(w io.Writer, pb Message) error {
|
|
val := reflect.ValueOf(pb)
|
|
if pb == nil || val.IsNil() {
|
|
w.Write([]byte("<nil>"))
|
|
return nil
|
|
}
|
|
var bw *bufio.Writer
|
|
ww, ok := w.(writer)
|
|
if !ok {
|
|
bw = bufio.NewWriter(w)
|
|
ww = bw
|
|
}
|
|
aw := &textWriter{
|
|
w: ww,
|
|
complete: true,
|
|
compact: tm.Compact,
|
|
}
|
|
|
|
if etm, ok := pb.(encoding.TextMarshaler); ok {
|
|
text, err := etm.MarshalText()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err = aw.Write(text); err != nil {
|
|
return err
|
|
}
|
|
if bw != nil {
|
|
return bw.Flush()
|
|
}
|
|
return nil
|
|
}
|
|
// Dereference the received pointer so we don't have outer < and >.
|
|
v := reflect.Indirect(val)
|
|
if err := tm.writeStruct(aw, v); err != nil {
|
|
return err
|
|
}
|
|
if bw != nil {
|
|
return bw.Flush()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Text is the same as Marshal, but returns the string directly.
|
|
func (tm *TextMarshaler) Text(pb Message) string {
|
|
var buf bytes.Buffer
|
|
tm.Marshal(&buf, pb)
|
|
return buf.String()
|
|
}
|
|
|
|
var (
|
|
defaultTextMarshaler = TextMarshaler{}
|
|
compactTextMarshaler = TextMarshaler{Compact: true}
|
|
)
|
|
|
|
// TODO: consider removing some of the Marshal functions below.
|
|
|
|
// MarshalText writes a given protocol buffer in text format.
|
|
// The only errors returned are from w.
|
|
func MarshalText(w io.Writer, pb Message) error { return defaultTextMarshaler.Marshal(w, pb) }
|
|
|
|
// MarshalTextString is the same as MarshalText, but returns the string directly.
|
|
func MarshalTextString(pb Message) string { return defaultTextMarshaler.Text(pb) }
|
|
|
|
// CompactText writes a given protocol buffer in compact text format (one line).
|
|
func CompactText(w io.Writer, pb Message) error { return compactTextMarshaler.Marshal(w, pb) }
|
|
|
|
// CompactTextString is the same as CompactText, but returns the string directly.
|
|
func CompactTextString(pb Message) string { return compactTextMarshaler.Text(pb) }
|