protobufConverter.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. package server
  2. import (
  3. "fmt"
  4. "reflect"
  5. "strconv"
  6. "strings"
  7. "time"
  8. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  9. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/inproxy"
  10. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  11. pb "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/psiphond"
  12. pbr "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/pb/router"
  13. "google.golang.org/protobuf/proto"
  14. "google.golang.org/protobuf/types/known/timestamppb"
  15. )
  16. // protobufFieldGroupConfig defines which field groups each message needs
  17. type protobufFieldGroupConfig struct {
  18. baseParams bool
  19. dialParams bool
  20. inproxyDialParams bool
  21. destParams bool
  22. }
  23. // protobufMessageFieldGroups defines field group requirements for each message type
  24. var protobufMessageFieldGroups = map[string]protobufFieldGroupConfig{
  25. "server_tunnel": {
  26. baseParams: true,
  27. dialParams: true,
  28. inproxyDialParams: true,
  29. },
  30. "unique_user": {
  31. baseParams: true,
  32. },
  33. "asn_dest_bytes": {
  34. destParams: true,
  35. },
  36. "domain_dest_bytes": {
  37. destParams: true,
  38. },
  39. "server_blocklist_hit": {
  40. baseParams: true,
  41. },
  42. "server_load": {},
  43. "server_load_protocol": {},
  44. "server_load_dns": {},
  45. "irregular_tunnel": {
  46. baseParams: true,
  47. },
  48. "failed_tunnel": {
  49. baseParams: true,
  50. dialParams: true,
  51. inproxyDialParams: true,
  52. },
  53. "remote_server_list": {
  54. baseParams: true,
  55. dialParams: true,
  56. },
  57. "tactics": {
  58. baseParams: true,
  59. dialParams: true,
  60. },
  61. "inproxy_broker": {
  62. baseParams: true,
  63. },
  64. "dsl_relay_get_server_entries": {
  65. baseParams: true,
  66. },
  67. }
  68. // NewProtobufRoutedMessage returns a populated Router protobuf message.
  69. func NewProtobufRoutedMessage(
  70. destinationPrefix string, msg proto.Message) (*pbr.Router, error) {
  71. md := msg.ProtoReflect().Descriptor()
  72. metric := md.Oneofs().ByName("metric")
  73. if metric == nil {
  74. return nil, errors.TraceNew("cannot find oneof field: metric")
  75. }
  76. messageType := string(md.FullName())
  77. metricType := msg.ProtoReflect().WhichOneof(metric).TextName()
  78. destination := strings.ToLower(strings.ReplaceAll(
  79. fmt.Sprintf("%s-%s-%s", destinationPrefix, md.Name(), metricType), "_", "-"))
  80. serialized, err := proto.Marshal(msg)
  81. if err != nil {
  82. return nil, errors.Trace(err)
  83. }
  84. return &pbr.Router{
  85. Destination: &destination,
  86. MessageType: &messageType,
  87. Key: []byte(logHostID),
  88. Value: serialized,
  89. }, nil
  90. }
  91. // newProtobufRoutedMessage returns a new pointer to a populated Router
  92. // protobuf message. The error paths in this function should never be
  93. // reached, but in rare cases where they do, instead of returning an error,
  94. // we panic, and allow the existing recovery and logging message to capture
  95. // the error.
  96. func newProtobufRoutedMessage(msg proto.Message) *pbr.Router {
  97. routedMsg, err := NewProtobufRoutedMessage(logDestinationPrefix, msg)
  98. if err != nil {
  99. panic(err.Error())
  100. }
  101. return routedMsg
  102. }
  103. // newPsiphondProtobufMessageWrapper returns a new pointer to a Psiphond
  104. // protobuf message with the common fields populated.
  105. func newPsiphondProtobufMessageWrapper(ts *timestamppb.Timestamp, hostType string) *pb.Psiphond {
  106. wrapper := &pb.Psiphond{}
  107. // Set timestamp (current time if not provided)
  108. if ts == nil {
  109. ts = timestamppb.Now()
  110. }
  111. wrapper.Timestamp = ts
  112. wrapper.HostId = logHostID
  113. wrapper.HostBuildRev = logBuildRev
  114. wrapper.Provider = logHostProvider
  115. wrapper.HostType = hostType
  116. return wrapper
  117. }
  118. // logFieldsToProtobuf converts a LogFields map to a Psiphond wrapper message.
  119. func logFieldsToProtobuf(logFields LogFields) []*pbr.Router {
  120. eventName, ok := logFields["event_name"].(string)
  121. if !ok {
  122. return nil
  123. }
  124. out := []*pbr.Router{}
  125. // Set timestamp (current time if not provided).
  126. var pbTimestamp *timestamppb.Timestamp
  127. if timestampStr, exists := logFields["timestamp"].(string); exists {
  128. if t, err := time.Parse(time.RFC3339, timestampStr); err == nil {
  129. pbTimestamp = timestamppb.New(t)
  130. }
  131. }
  132. // Set host_type from logFields if available.
  133. hostType, exists := logFields["host_type"].(string)
  134. if !exists {
  135. hostType = "psiphond"
  136. }
  137. psiphondWrapped := newPsiphondProtobufMessageWrapper(pbTimestamp, hostType)
  138. // Create and populate the specific metric message.
  139. switch eventName {
  140. case "server_tunnel":
  141. msg := &pb.ServerTunnel{}
  142. protobufPopulateMessage(logFields, msg, eventName)
  143. psiphondWrapped.Metric = &pb.Psiphond_ServerTunnel{ServerTunnel: msg}
  144. case "unique_user":
  145. msg := &pb.UniqueUser{}
  146. protobufPopulateMessage(logFields, msg, eventName)
  147. psiphondWrapped.Metric = &pb.Psiphond_UniqueUser{UniqueUser: msg}
  148. case "asn_dest_bytes":
  149. msg := &pb.AsnDestBytes{}
  150. protobufPopulateMessage(logFields, msg, eventName)
  151. psiphondWrapped.Metric = &pb.Psiphond_AsnDestBytes{AsnDestBytes: msg}
  152. case "domain_dest_bytes":
  153. msg := &pb.DomainDestBytes{}
  154. protobufPopulateMessage(logFields, msg, eventName)
  155. psiphondWrapped.Metric = &pb.Psiphond_DomainDestBytes{DomainDestBytes: msg}
  156. case "server_load":
  157. if region, hasRegion := logFields["region"]; hasRegion {
  158. for _, proto := range append(protocol.SupportedTunnelProtocols, "ALL") {
  159. if _, exists := logFields[proto]; exists {
  160. protoStats := logFields[proto].(map[string]any)
  161. regionString := region.(string)
  162. msg := &pb.ServerLoadProtocol{
  163. Protocol: &proto,
  164. Region: &regionString,
  165. }
  166. if value, exists := protoStats["server_entry_tag"].(string); exists {
  167. msg.ServerEntryTag = &value
  168. }
  169. if value, exists := protoStats["accepted_clients"].(int64); exists {
  170. msg.AcceptedClients = &value
  171. }
  172. if value, exists := protoStats["established_clients"].(int64); exists {
  173. msg.EstablishedClients = &value
  174. }
  175. if psiphondWrapped == nil {
  176. psiphondWrapped = newPsiphondProtobufMessageWrapper(pbTimestamp, hostType)
  177. }
  178. psiphondWrapped.Metric = &pb.Psiphond_ServerLoadProtocol{ServerLoadProtocol: msg}
  179. out = append(out, newProtobufRoutedMessage(psiphondWrapped))
  180. psiphondWrapped = nil
  181. }
  182. }
  183. } else {
  184. msg := &pb.ServerLoad{}
  185. protobufPopulateMessage(logFields, msg, eventName)
  186. if psiphondWrapped == nil {
  187. psiphondWrapped = newPsiphondProtobufMessageWrapper(pbTimestamp, hostType)
  188. }
  189. psiphondWrapped.Metric = &pb.Psiphond_ServerLoad{ServerLoad: msg}
  190. out = append(out, newProtobufRoutedMessage(psiphondWrapped))
  191. psiphondWrapped = nil
  192. }
  193. if dnsCount, hasDNSCount := logFields["dns_count"]; hasDNSCount {
  194. for dns, count := range dnsCount.(map[string]int64) {
  195. dns = strings.ReplaceAll(dns, "-", ".")
  196. msg := &pb.ServerLoadDNS{
  197. DnsServer: &dns,
  198. DnsCount: &count,
  199. }
  200. if value, exists := logFields["server_entry_tag"].(string); exists {
  201. msg.ServerEntryTag = &value
  202. }
  203. if value, exists := logFields["dns_failed_count"].(map[string]int64)[dns]; exists {
  204. msg.DnsFailedCount = &value
  205. }
  206. if value, exists := logFields["dns_duration"].(map[string]int64)[dns]; exists {
  207. msg.DnsDuration = &value
  208. }
  209. if value, exists := logFields["dns_failed_duration"].(map[string]int64)[dns]; exists {
  210. msg.DnsFailedDuration = &value
  211. }
  212. if psiphondWrapped == nil {
  213. psiphondWrapped = newPsiphondProtobufMessageWrapper(pbTimestamp, hostType)
  214. }
  215. psiphondWrapped.Metric = &pb.Psiphond_ServerLoadDns{ServerLoadDns: msg}
  216. out = append(out, newProtobufRoutedMessage(psiphondWrapped))
  217. psiphondWrapped = nil
  218. }
  219. }
  220. // Return early with the slice of wrapped messages here to skip
  221. // extra append attempts at the end of this switch, since we've
  222. // manually appended all of the wrapper messages ourselves.
  223. return out
  224. case "irregular_tunnel":
  225. msg := &pb.IrregularTunnel{}
  226. protobufPopulateMessage(logFields, msg, eventName)
  227. psiphondWrapped.Metric = &pb.Psiphond_IrregularTunnel{IrregularTunnel: msg}
  228. case "failed_tunnel":
  229. msg := &pb.FailedTunnel{}
  230. protobufPopulateMessage(logFields, msg, eventName)
  231. psiphondWrapped.Metric = &pb.Psiphond_FailedTunnel{FailedTunnel: msg}
  232. case "remote_server_list":
  233. msg := &pb.RemoteServerList{}
  234. protobufPopulateMessage(logFields, msg, eventName)
  235. psiphondWrapped.Metric = &pb.Psiphond_RemoteServerList{RemoteServerList: msg}
  236. case "panic":
  237. msg := &pb.ServerPanic{}
  238. protobufPopulateMessage(logFields, msg, eventName)
  239. psiphondWrapped.Metric = &pb.Psiphond_ServerPanic{ServerPanic: msg}
  240. case "tactics":
  241. msg := &pb.Tactics{}
  242. protobufPopulateMessage(logFields, msg, eventName)
  243. psiphondWrapped.Metric = &pb.Psiphond_Tactics{Tactics: msg}
  244. case "inproxy_broker":
  245. msg := &pb.InproxyBroker{}
  246. protobufPopulateMessage(logFields, msg, eventName)
  247. psiphondWrapped.Metric = &pb.Psiphond_InproxyBroker{InproxyBroker: msg}
  248. case "server_blocklist_hit":
  249. msg := &pb.ServerBlocklistHit{}
  250. protobufPopulateMessage(logFields, msg, eventName)
  251. psiphondWrapped.Metric = &pb.Psiphond_ServerBlocklist{ServerBlocklist: msg}
  252. case "dsl_relay_get_server_entries":
  253. msg := &pb.DslRelayGetServerEntries{}
  254. protobufPopulateMessage(logFields, msg, eventName)
  255. psiphondWrapped.Metric = &pb.Psiphond_DslRelayGetServerEntries{DslRelayGetServerEntries: msg}
  256. }
  257. // Single append for all non-special cases.
  258. if psiphondWrapped != nil {
  259. out = append(out, newProtobufRoutedMessage(psiphondWrapped))
  260. }
  261. return out
  262. }
  263. // protobufPopulateBaseParams populates BaseParams from LogFields.
  264. func protobufPopulateBaseParams(logFields LogFields) *pb.BaseParams {
  265. msg := &pb.BaseParams{}
  266. protobufPopulateMessageFromFields(logFields, msg)
  267. return msg
  268. }
  269. // protobufPopulateDialParams populates DialParams from LogFields.
  270. func protobufPopulateDialParams(logFields LogFields) *pb.DialParams {
  271. msg := &pb.DialParams{}
  272. protobufPopulateMessageFromFields(logFields, msg)
  273. return msg
  274. }
  275. // protobufPopulateInproxyDialParams populates InproxyDialParams from LogFields.
  276. func protobufPopulateInproxyDialParams(logFields LogFields) *pb.InproxyDialParams {
  277. msg := &pb.InproxyDialParams{}
  278. protobufPopulateMessageFromFields(logFields, msg)
  279. return msg
  280. }
  281. // protobufPopulateDestParams populates DestParams from LogFields.
  282. func protobufPopulateDestParams(logFields LogFields) *pb.DestParams {
  283. msg := &pb.DestParams{}
  284. protobufPopulateMessageFromFields(logFields, msg)
  285. return msg
  286. }
  287. // protobufPopulateMessage is the single function that handles all protobuf message types.
  288. func protobufPopulateMessage(logFields LogFields, msg proto.Message, eventName string) {
  289. config, exists := protobufMessageFieldGroups[eventName]
  290. if !exists {
  291. // Fallback to reflection-only population.
  292. protobufPopulateMessageFromFields(logFields, msg)
  293. return
  294. }
  295. // Populate field groups based on configuration.
  296. protobufPopulateFieldGroups(logFields, msg, config)
  297. // Populate remaining fields using reflection.
  298. protobufPopulateMessageFromFields(logFields, msg)
  299. }
  300. // protobufPopulateFieldGroups uses reflection to set field group sub-messages based on configuration.
  301. func protobufPopulateFieldGroups(logFields LogFields, msg proto.Message, config protobufFieldGroupConfig) {
  302. msgReflectValue := reflect.ValueOf(msg)
  303. if msgReflectValue.Kind() != reflect.Pointer || msgReflectValue.IsNil() {
  304. return
  305. }
  306. msgValue := msgReflectValue.Elem()
  307. msgType := msgValue.Type()
  308. // Iterate through message fields to find and populate metadata fields.
  309. for i := 0; i < msgValue.NumField(); i++ {
  310. field := msgValue.Field(i)
  311. fieldType := msgType.Field(i)
  312. if !field.CanSet() {
  313. continue
  314. }
  315. switch fieldType.Name {
  316. case "BaseParams":
  317. if config.baseParams {
  318. field.Set(reflect.ValueOf(protobufPopulateBaseParams(logFields)))
  319. }
  320. case "DialParams":
  321. if config.dialParams {
  322. field.Set(reflect.ValueOf(protobufPopulateDialParams(logFields)))
  323. }
  324. case "InproxyDialParams":
  325. if config.inproxyDialParams {
  326. field.Set(reflect.ValueOf(protobufPopulateInproxyDialParams(logFields)))
  327. }
  328. case "DestParams":
  329. if config.destParams {
  330. field.Set(reflect.ValueOf(protobufPopulateDestParams(logFields)))
  331. }
  332. }
  333. }
  334. }
  335. // protobufPopulateMessageFromFields uses reflection to populate protobuf message fields from LogFields.
  336. func protobufPopulateMessageFromFields(logFields LogFields, msg proto.Message) {
  337. msgReflectValue := reflect.ValueOf(msg)
  338. if msgReflectValue.Kind() != reflect.Pointer || msgReflectValue.IsNil() {
  339. return
  340. }
  341. msgValue := msgReflectValue.Elem()
  342. msgType := msgValue.Type()
  343. for i := 0; i < msgValue.NumField(); i++ {
  344. field := msgValue.Field(i)
  345. fieldType := msgType.Field(i)
  346. if !field.CanSet() {
  347. continue
  348. }
  349. protoTag := fieldType.Tag.Get("protobuf")
  350. if protoTag == "" {
  351. continue
  352. }
  353. fieldName := getProtobufFieldName(protoTag)
  354. if fieldName == "" {
  355. continue
  356. }
  357. logValue, exists := logFields[fieldName]
  358. if !exists {
  359. continue
  360. }
  361. // Handle special field names that might be mapped differently.
  362. if err := setProtobufFieldValue(field, fieldType, logValue); err != nil {
  363. panic(errors.Tracef("failed to set field value: %w", err))
  364. }
  365. }
  366. }
  367. // getProtobufFieldName extracts the field name from protobuf struct tag.
  368. //
  369. // Example:
  370. // - in: "bytes,1,opt,name=host_metadata,json=hostMetadata,proto3"
  371. // - out: "host_metadata"
  372. func getProtobufFieldName(protoTag string) string {
  373. n := len(protoTag)
  374. // Process the input byte-by-byte to avoid allocations.
  375. for i := 0; i < n; {
  376. // Find the end of this comma-delimited part of the tag.
  377. j := i
  378. for j < n && protoTag[j] != ',' {
  379. j++
  380. }
  381. // Check for "name=" at the start of this part.
  382. if j-i >= 5 &&
  383. protoTag[i] == 'n' &&
  384. protoTag[i+1] == 'a' &&
  385. protoTag[i+2] == 'm' &&
  386. protoTag[i+3] == 'e' &&
  387. protoTag[i+4] == '=' {
  388. // Return the slice after "name=".
  389. return protoTag[i+5 : j]
  390. }
  391. // Skip to the start of next part of the tag.
  392. i = j + 1
  393. }
  394. return ""
  395. }
  396. // setProtobufFieldValue sets a protobuf field value from a LogFields value.
  397. func setProtobufFieldValue(field reflect.Value, fieldType reflect.StructField, logValue any) error {
  398. if logValue == nil {
  399. return nil // Don't set anything for nil values
  400. }
  401. var err error
  402. // Handle pointers by creating a new instance and setting recursively
  403. if field.Kind() == reflect.Ptr {
  404. err = setProtobufPointerField(field, fieldType, logValue)
  405. } else {
  406. err = setProtobufPrimitiveField(field, fieldType, logValue)
  407. }
  408. if err != nil {
  409. err = errors.Tracef(
  410. "failed to convert field %s value `%v` type %T to %s : %w",
  411. fieldType.Name,
  412. logValue,
  413. logValue,
  414. fieldType.Type.String(),
  415. errors.Trace(err))
  416. }
  417. return nil
  418. }
  419. // setProtobufPointerField handles pointer fields by creating new instances
  420. func setProtobufPointerField(field reflect.Value, fieldType reflect.StructField, logValue any) error {
  421. elemType := field.Type().Elem()
  422. // Special handling for timestamppb.Timestamp
  423. if elemType == reflect.TypeOf(timestamppb.Timestamp{}) {
  424. ts, err := protobufConvertToTimestamp(logValue)
  425. if err != nil {
  426. return errors.Trace(err)
  427. }
  428. if ts != nil {
  429. field.Set(reflect.ValueOf(ts))
  430. }
  431. return nil
  432. }
  433. // For primitive pointer types, create a new instance and set it
  434. newVal := reflect.New(elemType)
  435. err := setProtobufPrimitiveField(newVal.Elem(), fieldType, logValue)
  436. if err != nil {
  437. return errors.Trace(err)
  438. }
  439. field.Set(newVal)
  440. return nil
  441. }
  442. // setProtobufPrimitiveField handles non-pointer fields
  443. func setProtobufPrimitiveField(field reflect.Value, fieldType reflect.StructField, logValue any) error {
  444. var err error
  445. switch field.Kind() {
  446. case reflect.String:
  447. err = setProtobufStringField(field, fieldType, logValue)
  448. case reflect.Int, reflect.Int32, reflect.Int64:
  449. err = setProtobufIntField(field, fieldType, logValue)
  450. case reflect.Uint, reflect.Uint32, reflect.Uint64:
  451. err = setProtobufUintField(field, fieldType, logValue)
  452. case reflect.Float64:
  453. err = setProtobufFloat64Field(field, fieldType, logValue)
  454. case reflect.Bool:
  455. err = setProtobufBoolField(field, fieldType, logValue)
  456. case reflect.Map:
  457. err = setProtobufMapField(field, fieldType, logValue)
  458. case reflect.Slice:
  459. err = setProtobufSliceField(field, fieldType, logValue)
  460. default:
  461. err = errors.TraceNew("unsupported field kind")
  462. }
  463. return errors.Trace(err)
  464. }
  465. func setProtobufStringField(field reflect.Value, fieldType reflect.StructField, logValue any) error {
  466. str, err := protobufConvertToString(logValue)
  467. if err != nil {
  468. return errors.Trace(err)
  469. }
  470. // Handle special cases for string fields
  471. switch fieldType.Name {
  472. case "UpstreamProxyType":
  473. field.SetString(strings.ToLower(str))
  474. default:
  475. field.SetString(str)
  476. }
  477. return nil
  478. }
  479. func setProtobufIntField(field reflect.Value, fieldType reflect.StructField, logValue any) error {
  480. // Because we extensively run on 64-bit architectures and protobuf
  481. // doesn't have the architecture switching int type, for consistency,
  482. // we always use int64 in our protos to represent int in go.
  483. val, err := protobufConvertToInt64(logValue)
  484. if err != nil {
  485. return errors.Trace(err)
  486. }
  487. field.SetInt(val)
  488. return nil
  489. }
  490. func setProtobufUintField(field reflect.Value, fieldType reflect.StructField, logValue any) error {
  491. // Because we extensively run on 64-bit architectures and protobuf
  492. // doesn't have the architecture switching int type, for consistency,
  493. // we always use uint64 in our protos to represent uint in go.
  494. val, err := protobufConvertToUint64(logValue)
  495. if err != nil {
  496. return errors.Trace(err)
  497. }
  498. field.SetUint(val)
  499. return nil
  500. }
  501. func setProtobufFloat64Field(field reflect.Value, fieldType reflect.StructField, logValue any) error {
  502. val, err := protobufConvertToFloat64(logValue)
  503. if err != nil {
  504. return errors.Trace(err)
  505. }
  506. field.SetFloat(val)
  507. return nil
  508. }
  509. func setProtobufBoolField(field reflect.Value, fieldType reflect.StructField, logValue any) error {
  510. val, err := protobufConvertToBool(logValue)
  511. if err != nil {
  512. return errors.Trace(err)
  513. }
  514. field.SetBool(val)
  515. return nil
  516. }
  517. func setProtobufMapField(field reflect.Value, fieldType reflect.StructField, logValue any) error {
  518. mapValue, ok := logValue.(map[string]int64)
  519. if !ok {
  520. return errors.TraceNew("expected map[string]int64")
  521. }
  522. newMap := reflect.MakeMap(field.Type())
  523. for k, v := range mapValue {
  524. newMap.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
  525. }
  526. field.Set(newMap)
  527. return nil
  528. }
  529. func setProtobufSliceField(field reflect.Value, fieldType reflect.StructField, logValue any) error {
  530. switch sliceValue := logValue.(type) {
  531. case []any:
  532. newSlice := make([]string, 0, len(sliceValue))
  533. for i, elem := range sliceValue {
  534. str, ok := elem.(string)
  535. if !ok {
  536. return errors.Tracef("slice element at index %d is not a string", i)
  537. }
  538. newSlice = append(newSlice, str)
  539. }
  540. field.Set(reflect.ValueOf(newSlice))
  541. case []string:
  542. field.Set(reflect.ValueOf(sliceValue))
  543. case inproxy.PortMappingTypes:
  544. newSlice := make([]string, 0, len(sliceValue))
  545. for _, elem := range sliceValue {
  546. newSlice = append(newSlice, inproxy.PortMappingType(elem).String())
  547. }
  548. field.Set(reflect.ValueOf(newSlice))
  549. case inproxy.ICECandidateTypes:
  550. newSlice := make([]string, 0, len(sliceValue))
  551. for _, elem := range sliceValue {
  552. newSlice = append(newSlice, inproxy.PortMappingType(elem).String())
  553. }
  554. field.Set(reflect.ValueOf(newSlice))
  555. default:
  556. return errors.TraceNew("unexpected slice type")
  557. }
  558. return nil
  559. }
  560. func protobufConvertToString(value any) (string, error) {
  561. var s string
  562. switch v := value.(type) {
  563. case string:
  564. s = v
  565. case fmt.Stringer:
  566. s = v.String()
  567. default:
  568. return "", errors.Tracef("cannot convert %T to string", value)
  569. }
  570. // Ensure the string is UTF-8, as required by proto.Marshal.
  571. return strings.ToValidUTF8(s, "\uFFFD"), nil
  572. }
  573. func protobufConvertToInt64(value any) (int64, error) {
  574. switch v := value.(type) {
  575. case int64:
  576. return v, nil
  577. case int:
  578. return int64(v), nil
  579. case int32:
  580. return int64(v), nil
  581. case string:
  582. if v == "" {
  583. return 0, errors.TraceNew("cannot convert empty string to int64")
  584. }
  585. return strconv.ParseInt(v, 10, 64)
  586. case float64:
  587. // Only allow conversion if it's a whole number
  588. if v == float64(int64(v)) {
  589. return int64(v), nil
  590. }
  591. return 0, errors.Tracef("float64 %f is not a whole number", v)
  592. case time.Duration:
  593. return int64(v), nil
  594. default:
  595. return 0, errors.Tracef("cannot convert %T to int64", value)
  596. }
  597. }
  598. func protobufConvertToUint64(value any) (uint64, error) {
  599. switch v := value.(type) {
  600. case uint64:
  601. return v, nil
  602. case uint:
  603. return uint64(v), nil
  604. case uint32:
  605. return uint64(v), nil
  606. case int:
  607. if v < 0 {
  608. return 0, errors.Tracef("cannot convert negative int %d to uint64", v)
  609. }
  610. return uint64(v), nil
  611. case int64:
  612. if v < 0 {
  613. return 0, errors.Tracef("cannot convert negative int64 %d to uint64", v)
  614. }
  615. return uint64(v), nil
  616. case string:
  617. if v == "" {
  618. return 0, errors.TraceNew("cannot convert empty string to uint64")
  619. }
  620. return strconv.ParseUint(v, 10, 64)
  621. default:
  622. return 0, errors.Tracef("cannot convert %T to uint64", value)
  623. }
  624. }
  625. func protobufConvertToFloat64(value any) (float64, error) {
  626. switch v := value.(type) {
  627. case float64:
  628. return v, nil
  629. case float32:
  630. return float64(v), nil
  631. case int:
  632. return float64(v), nil
  633. case int64:
  634. return float64(v), nil
  635. case string:
  636. if v == "" {
  637. return 0, errors.TraceNew("cannot convert empty string to float64")
  638. }
  639. return strconv.ParseFloat(v, 64)
  640. default:
  641. return 0, errors.Tracef("cannot convert %T to float64", value)
  642. }
  643. }
  644. func protobufConvertToBool(value any) (bool, error) {
  645. switch v := value.(type) {
  646. case bool:
  647. return v, nil
  648. case string:
  649. switch strings.ToLower(strings.TrimSpace(v)) {
  650. case "true", "1", "yes", "on":
  651. return true, nil
  652. case "false", "0", "no", "off", "":
  653. return false, nil
  654. default:
  655. return false, errors.Tracef("cannot convert string %q to bool", v)
  656. }
  657. case int:
  658. return v != 0, nil
  659. case int64:
  660. return v != 0, nil
  661. default:
  662. return false, fmt.Errorf("cannot convert %T to bool", value)
  663. }
  664. }
  665. func protobufConvertToTimestamp(value any) (*timestamppb.Timestamp, error) {
  666. switch v := value.(type) {
  667. case string:
  668. if v == "" || v == "None" {
  669. return nil, nil
  670. }
  671. var err error
  672. var t time.Time
  673. for _, format := range []string{
  674. time.RFC3339Nano,
  675. iso8601Date,
  676. } {
  677. if t, err = time.Parse(format, v); err == nil {
  678. break
  679. }
  680. }
  681. if err != nil {
  682. return nil, errors.Tracef("cannot parse timestamp string %q", v)
  683. }
  684. return timestamppb.New(t), nil
  685. case time.Time:
  686. if v.IsZero() {
  687. return nil, nil
  688. }
  689. return timestamppb.New(v), nil
  690. case *time.Time:
  691. if v == nil || v.IsZero() {
  692. return nil, nil
  693. }
  694. return timestamppb.New(*v), nil
  695. default:
  696. return nil, errors.Tracef("cannot convert %T to timestamp", value)
  697. }
  698. }