amf3.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. package wire
  2. import (
  3. "fmt"
  4. "reflect"
  5. "strings"
  6. "time"
  7. goAMF3 "github.com/breign/goAMF3"
  8. )
  9. // AMF3 stores whole numbers in a signed 29-bit integer. A value outside this
  10. // range is written as a double instead, which is how a Unix timestamp survives
  11. // the trip.
  12. const (
  13. minInt29 = -(1 << 28)
  14. maxInt29 = 1<<28 - 1
  15. )
  16. var (
  17. timeType = reflect.TypeFor[time.Time]()
  18. byteType = reflect.TypeFor[byte]()
  19. )
  20. // MarshalAMF3 returns the AMF3 encoding of v, the format the Flash-based Web AIM
  21. // client reads its fetchEvents payloads in.
  22. //
  23. // Struct fields are named by their amf3 tag, falling back to the json tag, so a
  24. // field carries an amf3 tag only where the two formats disagree. Both spellings
  25. // honor "-" and ",omitempty".
  26. func MarshalAMF3(v any) ([]byte, error) {
  27. norm, err := amf3Value(reflect.ValueOf(v))
  28. if err != nil {
  29. return nil, err
  30. }
  31. if norm == nil {
  32. // A response body is an object even when it carries nothing, because the
  33. // client dereferences it unconditionally.
  34. norm = map[string]any{}
  35. }
  36. return goAMF3.EncodeAMF3(norm), nil
  37. }
  38. // amf3Value reduces v to the values the AMF3 writer encodes correctly: bool,
  39. // string, int32, float64, []byte, time.Time, []any and map[string]any. It is
  40. // given anything else only as an error, because the writer silently emits
  41. // nothing for a value it cannot handle, truncating the enclosing object.
  42. func amf3Value(v reflect.Value) (any, error) {
  43. for v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface {
  44. if v.IsNil() {
  45. return nil, nil
  46. }
  47. v = v.Elem()
  48. }
  49. if !v.IsValid() {
  50. return nil, nil
  51. }
  52. switch v.Kind() {
  53. case reflect.Bool:
  54. return v.Bool(), nil
  55. case reflect.String:
  56. return v.String(), nil
  57. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  58. return amf3Int(v.Int()), nil
  59. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  60. return amf3Uint(v.Uint()), nil
  61. case reflect.Float32, reflect.Float64:
  62. return v.Float(), nil
  63. case reflect.Slice, reflect.Array:
  64. return amf3Slice(v)
  65. case reflect.Map:
  66. return amf3Map(v)
  67. case reflect.Struct:
  68. if v.Type() == timeType {
  69. return v.Interface(), nil
  70. }
  71. return amf3Struct(v)
  72. default:
  73. return nil, fmt.Errorf("amf3: cannot encode %s", v.Type())
  74. }
  75. }
  76. // amf3Int returns the narrowest AMF3 number holding n.
  77. func amf3Int(n int64) any {
  78. if n >= minInt29 && n <= maxInt29 {
  79. return int32(n)
  80. }
  81. return float64(n)
  82. }
  83. // amf3Uint returns the narrowest AMF3 number holding n.
  84. func amf3Uint(n uint64) any {
  85. if n <= maxInt29 {
  86. return int32(n)
  87. }
  88. return float64(n)
  89. }
  90. // amf3Slice returns v's elements as []any, passing a byte slice through so it is
  91. // written as an AMF3 byte array. A nil slice becomes an empty one because the
  92. // client iterates lists such as capabilities unconditionally.
  93. func amf3Slice(v reflect.Value) (any, error) {
  94. if v.Kind() == reflect.Slice && v.Type().Elem() == byteType {
  95. return v.Bytes(), nil
  96. }
  97. out := make([]any, v.Len())
  98. for i := range out {
  99. elem, err := amf3Value(v.Index(i))
  100. if err != nil {
  101. return nil, fmt.Errorf("[%d]: %w", i, err)
  102. }
  103. out[i] = elem
  104. }
  105. return out, nil
  106. }
  107. // amf3Map returns v's entries keyed by the string form of each key, which is the
  108. // only key type an AMF3 object has. A nil value is dropped rather than written as
  109. // null: the client merges each object it receives onto the one it already holds,
  110. // so an absent key leaves the current value alone.
  111. func amf3Map(v reflect.Value) (any, error) {
  112. out := make(map[string]any, v.Len())
  113. for iter := v.MapRange(); iter.Next(); {
  114. val, err := amf3Value(iter.Value())
  115. if err != nil {
  116. return nil, err
  117. }
  118. if val == nil {
  119. continue
  120. }
  121. out[amf3Key(iter.Key())] = val
  122. }
  123. return out, nil
  124. }
  125. // amf3Key renders a map key as an AMF3 object key.
  126. func amf3Key(k reflect.Value) string {
  127. if k.Kind() == reflect.String {
  128. return k.String()
  129. }
  130. return fmt.Sprint(k.Interface())
  131. }
  132. // amf3Struct returns v's exported fields keyed by their tag names.
  133. func amf3Struct(v reflect.Value) (any, error) {
  134. out := map[string]any{}
  135. if err := amf3Fields(v, out); err != nil {
  136. return nil, err
  137. }
  138. return out, nil
  139. }
  140. // amf3Fields writes v's fields into out. An untagged embedded struct contributes
  141. // its own fields to the enclosing object, as it does in JSON, including when its
  142. // type is unexported: reflect still reads the exported fields inside it.
  143. func amf3Fields(v reflect.Value, out map[string]any) error {
  144. t := v.Type()
  145. for i := 0; i < t.NumField(); i++ {
  146. f := t.Field(i)
  147. name, omitEmpty, omitZero, ok := amf3FieldKey(f)
  148. if !ok {
  149. continue
  150. }
  151. fv := v.Field(i)
  152. if f.Anonymous && name == "" {
  153. embedded := reflect.Indirect(fv)
  154. if embedded.Kind() == reflect.Struct && embedded.Type() != timeType {
  155. if err := amf3Fields(embedded, out); err != nil {
  156. return err
  157. }
  158. continue
  159. }
  160. }
  161. if !f.IsExported() {
  162. continue
  163. }
  164. if name == "" {
  165. name = f.Name
  166. }
  167. if omitEmpty && isEmptyValue(fv) {
  168. continue
  169. }
  170. if omitZero && isZeroValue(fv) {
  171. continue
  172. }
  173. val, err := amf3Value(fv)
  174. if err != nil {
  175. return fmt.Errorf("%s.%s: %w", t.Name(), f.Name, err)
  176. }
  177. if val == nil {
  178. if omitEmpty || omitZero {
  179. continue
  180. }
  181. // A field the client dereferences unconditionally, such as a
  182. // response's data or an event's eventData, is an empty object rather
  183. // than an absent key.
  184. val = map[string]any{}
  185. }
  186. out[name] = val
  187. }
  188. return nil
  189. }
  190. // amf3FieldKey returns the AMF3 name for f and whether it is written at all. An
  191. // amf3 tag replaces the json tag outright, so a field that must always be present
  192. // in AMF but is omitempty in JSON just names itself in amf3.
  193. func amf3FieldKey(f reflect.StructField) (name string, omitEmpty, omitZero, ok bool) {
  194. tag, tagged := f.Tag.Lookup("amf3")
  195. if !tagged {
  196. tag = f.Tag.Get("json")
  197. }
  198. if tag == "-" {
  199. return "", false, false, false
  200. }
  201. name, opts, _ := strings.Cut(tag, ",")
  202. return name, hasTagOption(opts, "omitempty"), hasTagOption(opts, "omitzero"), true
  203. }
  204. // hasTagOption reports whether the comma-separated tag options contain want.
  205. func hasTagOption(opts, want string) bool {
  206. for opts != "" {
  207. var opt string
  208. opt, opts, _ = strings.Cut(opts, ",")
  209. if opt == want {
  210. return true
  211. }
  212. }
  213. return false
  214. }
  215. var zeroerType = reflect.TypeOf((*interface{ IsZero() bool })(nil)).Elem()
  216. // isZeroValue reports whether v is the zero value omitzero suppresses, mirroring
  217. // encoding/json including the IsZero override. Unlike omitempty, an empty-but-non-nil
  218. // slice or map is not zero.
  219. func isZeroValue(v reflect.Value) bool {
  220. if v.Type().Implements(zeroerType) {
  221. if v.Kind() == reflect.Pointer && v.IsNil() {
  222. return true
  223. }
  224. return v.Interface().(interface{ IsZero() bool }).IsZero()
  225. }
  226. if v.CanAddr() && reflect.PointerTo(v.Type()).Implements(zeroerType) {
  227. return v.Addr().Interface().(interface{ IsZero() bool }).IsZero()
  228. }
  229. return v.IsZero()
  230. }
  231. // isEmptyValue reports whether v is the zero value that omitempty suppresses.
  232. func isEmptyValue(v reflect.Value) bool {
  233. switch v.Kind() {
  234. case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
  235. return v.Len() == 0
  236. case reflect.Bool:
  237. return !v.Bool()
  238. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  239. return v.Int() == 0
  240. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  241. return v.Uint() == 0
  242. case reflect.Float32, reflect.Float64:
  243. return v.Float() == 0
  244. case reflect.Interface, reflect.Pointer:
  245. return v.IsNil()
  246. case reflect.Struct:
  247. return v.Type() == timeType && v.Interface().(time.Time).IsZero()
  248. }
  249. return false
  250. }