reportingtoken.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. // Copyright (c) 2025 Tulir Asokan
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package whatsmeow
  7. import (
  8. "crypto/hmac"
  9. "crypto/sha256"
  10. _ "embed"
  11. "encoding/binary"
  12. "encoding/json"
  13. "sort"
  14. "sync"
  15. "go.mau.fi/util/exerrors"
  16. "go.mau.fi/util/exstrings"
  17. waBinary "git.bobomao.top/joey/testwh/binary"
  18. "git.bobomao.top/joey/testwh/proto/waE2E"
  19. "git.bobomao.top/joey/testwh/types"
  20. )
  21. //go:embed reportingfields.json
  22. var reportingFieldsJSON string
  23. var getReportingFields = sync.OnceValue(func() (output []reportingField) {
  24. exerrors.PanicIfNotNil(json.Unmarshal(exstrings.UnsafeBytes(reportingFieldsJSON), &output))
  25. return
  26. })
  27. type reportingField struct {
  28. FieldNumber int `json:"f"`
  29. IsMessage bool `json:"m,omitempty"`
  30. Subfields []reportingField `json:"s,omitempty"`
  31. }
  32. func (cli *Client) shouldIncludeReportingToken(message *waE2E.Message) bool {
  33. if !cli.SendReportingTokens {
  34. return false
  35. }
  36. return message.ReactionMessage == nil &&
  37. message.EncReactionMessage == nil &&
  38. message.EncEventResponseMessage == nil &&
  39. message.PollUpdateMessage == nil
  40. }
  41. func (cli *Client) getMessageReportingToken(
  42. msgProtobuf []byte,
  43. msg *waE2E.Message,
  44. senderJID, remoteJID types.JID,
  45. messageID types.MessageID,
  46. ) waBinary.Node {
  47. reportingSecret, _ := generateMsgSecretKey(
  48. EncSecretReportToken, senderJID, messageID, remoteJID,
  49. msg.GetMessageContextInfo().GetMessageSecret(),
  50. )
  51. hasher := hmac.New(sha256.New, reportingSecret)
  52. hasher.Write(getReportingToken(msgProtobuf))
  53. return waBinary.Node{
  54. Tag: "reporting",
  55. Content: []waBinary.Node{{
  56. Tag: "reporting_token",
  57. Attrs: waBinary.Attrs{"v": "2"},
  58. Content: hasher.Sum(nil)[:16],
  59. }},
  60. }
  61. }
  62. func getReportingToken(messageProtobuf []byte) []byte {
  63. return extractReportingTokenContent(messageProtobuf, getReportingFields())
  64. }
  65. // Helper to find config for a field number
  66. func getConfigForField(fields []reportingField, fieldNum int) *reportingField {
  67. for i := range fields {
  68. if fields[i].FieldNumber == fieldNum {
  69. return &fields[i]
  70. }
  71. }
  72. return nil
  73. }
  74. // Protobuf wire types
  75. const (
  76. wireVarint = 0
  77. wire64bit = 1
  78. wireBytes = 2
  79. wire32bit = 5
  80. )
  81. // Extracts the reporting token content recursively
  82. func extractReportingTokenContent(data []byte, config []reportingField) []byte {
  83. type field struct {
  84. Num int
  85. Bytes []byte
  86. }
  87. var fields []field
  88. i := 0
  89. for i < len(data) {
  90. // Read tag (varint)
  91. tag, tagLen := binary.Uvarint(data[i:])
  92. if tagLen <= 0 {
  93. break // malformed
  94. }
  95. fieldNum := int(tag >> 3)
  96. wireType := int(tag & 0x7)
  97. fieldCfg := getConfigForField(config, fieldNum)
  98. fieldStart := i
  99. i += tagLen
  100. if fieldCfg == nil {
  101. // Skip field
  102. switch wireType {
  103. case wireVarint:
  104. _, n := binary.Uvarint(data[i:])
  105. i += n
  106. case wire64bit:
  107. i += 8
  108. case wireBytes:
  109. l, n := binary.Uvarint(data[i:])
  110. i += n + int(l)
  111. case wire32bit:
  112. i += 4
  113. default:
  114. return nil
  115. }
  116. continue
  117. }
  118. switch wireType {
  119. case wireVarint:
  120. _, n := binary.Uvarint(data[i:])
  121. i += n
  122. fields = append(fields, field{Num: fieldNum, Bytes: data[fieldStart:i]})
  123. case wire64bit:
  124. i += 8
  125. fields = append(fields, field{Num: fieldNum, Bytes: data[fieldStart:i]})
  126. case wireBytes:
  127. l, n := binary.Uvarint(data[i:])
  128. valStart := i + n
  129. valEnd := valStart + int(l)
  130. if fieldCfg.IsMessage || len(fieldCfg.Subfields) > 0 {
  131. // Recursively extract subfields
  132. sub := extractReportingTokenContent(data[valStart:valEnd], fieldCfg.Subfields)
  133. if len(sub) > 0 {
  134. // Re-encode tag and length
  135. buf := make([]byte, 0, tagLen+n+len(sub))
  136. tagBuf := make([]byte, binary.MaxVarintLen64)
  137. tagN := binary.PutUvarint(tagBuf, tag)
  138. lenBuf := make([]byte, binary.MaxVarintLen64)
  139. lenN := binary.PutUvarint(lenBuf, uint64(len(sub)))
  140. buf = append(buf, tagBuf[:tagN]...)
  141. buf = append(buf, lenBuf[:lenN]...)
  142. buf = append(buf, sub...)
  143. fields = append(fields, field{Num: fieldNum, Bytes: buf})
  144. }
  145. } else {
  146. fields = append(fields, field{Num: fieldNum, Bytes: data[fieldStart:valEnd]})
  147. }
  148. i = valEnd
  149. case wire32bit:
  150. i += 4
  151. fields = append(fields, field{Num: fieldNum, Bytes: data[fieldStart:i]})
  152. default:
  153. return nil
  154. }
  155. }
  156. // Sort by field number
  157. sort.Slice(fields, func(i, j int) bool { return fields[i].Num < fields[j].Num })
  158. // Concatenate
  159. var out []byte
  160. for _, f := range fields {
  161. out = append(out, f.Bytes...)
  162. }
  163. return out
  164. }