[RICPLT-1528] xApp Mock improvements...
[ric-plt/e2mgr.git] / tools / xappmock / sender / jsonSender.go
1 //
2 // Copyright 2019 AT&T Intellectual Property
3 // Copyright 2019 Nokia
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 //      http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 package sender
18
19 import (
20         "fmt"
21         "github.com/pkg/errors"
22         "os"
23         "reflect"
24         "strconv"
25         "strings"
26         "sync/atomic"
27         "time"
28         "unicode"
29         "xappmock/logger"
30         "xappmock/models"
31         "xappmock/rmr"
32 )
33
34 var counter uint64
35
36 type JsonSender struct {
37         logger *logger.Logger
38 }
39
40 func NewJsonSender(logger *logger.Logger) *JsonSender {
41         return &JsonSender{
42                 logger: logger,
43         }
44 }
45
46 func (s *JsonSender) SendJsonRmrMessage(command models.JsonCommand /*the copy is modified locally*/, xAction *[]byte, r *rmr.Service) error {
47         var payload []byte
48         _, err := fmt.Sscanf(command.PackedPayload, "%x", &payload)
49         if err != nil {
50                 return errors.New(fmt.Sprintf("convert inputPayloadAsStr to payloadAsByte. Error: %v\n", err))
51         }
52         command.PackedPayload = string(payload)
53         command.TransactionId = expandTransactionId(command.TransactionId)
54         if len(command.TransactionId) == 0 {
55                 command.TransactionId = string(*xAction)
56         }
57         command.PayloadHeader = s.expandPayloadHeader(command.PayloadHeader, &command)
58         s.logger.Infof("#JsonSender.SendJsonRmrMessage - command payload header: %s", command.PayloadHeader)
59         rmrMsgId, err := rmr.MessageIdToUint(command.RmrMessageType)
60         if err != nil {
61                 return errors.New(fmt.Sprintf("invalid rmr message id: %s", command.RmrMessageType))
62         }
63
64         msg := append([]byte(command.PayloadHeader), payload...)
65         messageInfo := models.NewMessageInfo(int(rmrMsgId), command.Meid, msg, []byte(command.TransactionId))
66         s.logger.Infof("#JsonSender.SendJsonRmrMessage - going to send message: %s", messageInfo)
67
68         _, err = r.SendMessage(int(rmrMsgId), command.Meid, msg, []byte(command.TransactionId))
69         return err
70 }
71
72 /*
73  * transactionId (xAction): The value may have a fixed value or $ or <prefix>$.
74  * $ is replaced by a value generated at runtime (possibly unique per message sent).
75  * If the tag does not exist, then the mock shall use the value taken from the incoming message.
76  */
77 func expandTransactionId(id string) string {
78         if len(id) == 1 && id[0] == '$' {
79                 return fmt.Sprintf("%d", incAndGetCounter())
80         }
81         if len(id) > 1 && id[len(id)-1] == '$' {
82                 return fmt.Sprintf("%s%d", id[:len(id)-1], incAndGetCounter())
83         }
84         return id
85 }
86
87 /*
88  * payloadHeader: A prefix to combine with the payload that will be the message’s payload. The value may include variables of the format $<name> or #<name> where:
89  *   $<name> expands to the value of <name> if it exists or the empty string if not.
90  *   #<name> expands to the length of the value of <name> if it exists or omitted if not.
91  * The intention is to allow the Mock to construct the payload header required by the setup messages (ranIp|ranPort|ranName|payload len|<payload>).
92  * Example: “payloadHeader”: “$ranIp|$ranPort|$ranName|#packedPayload|”
93  */
94
95 func (s *JsonSender) expandPayloadHeader(header string, command *models.JsonCommand) string {
96         var name strings.Builder
97         var expandedHeader strings.Builder
98
99         r := strings.NewReader(header)
100         ch, err := r.ReadByte()
101         for {
102                 if err != nil {
103                         break
104                 }
105
106                 switch ch {
107                 case '$':
108                         for {
109                                 ch, err = r.ReadByte() //on error ch == 0
110                                 if unicode.IsDigit(rune(ch)) || unicode.IsLetter(rune(ch)) {
111                                         if name.Len() == 0 {
112                                                 name.WriteByte(byte(unicode.ToUpper(rune(ch))))
113                                         } else {
114                                                 name.WriteByte(ch)
115                                         }
116                                 } else {
117                                         if fieldValue := reflect.Indirect(reflect.ValueOf(command)).FieldByName(name.String()); fieldValue.IsValid() {
118                                                 switch fieldValue.Kind() {
119                                                 case reflect.String:
120                                                         expandedHeader.WriteString(fieldValue.String())
121                                                 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
122                                                         expandedHeader.WriteString(strconv.FormatInt(fieldValue.Int(), 10))
123                                                 case reflect.Bool:
124                                                         expandedHeader.WriteString(strconv.FormatBool(fieldValue.Bool()))
125                                                 case reflect.Float64, reflect.Float32:
126                                                         expandedHeader.WriteString(fmt.Sprintf("%g", fieldValue.Float()))
127                                                 default:
128                                                         s.logger.Errorf("#JsonSender.expandPayloadHeader - invalid type for $%s, value must be a string, an int, a bool or a float", name.String())
129                                                         os.Exit(1)
130                                                 }
131                                         }
132                                         name.Reset()
133                                         break
134                                 }
135                         }
136                 case '#':
137                         for {
138                                 ch, err = r.ReadByte() //on error ch == 0
139                                 if unicode.IsDigit(rune(ch)) || unicode.IsLetter(rune(ch)) {
140                                         if name.Len() == 0 {
141                                                 name.WriteByte(byte(unicode.ToUpper(rune(ch))))
142                                         } else {
143                                                 name.WriteByte(ch)
144                                         }
145                                 } else {
146                                         if fieldValue := reflect.Indirect(reflect.ValueOf(command)).FieldByName(name.String()); fieldValue.IsValid() {
147                                                 if fieldValue.Kind() == reflect.String {
148                                                         expandedHeader.WriteString(strconv.FormatInt(int64(len(fieldValue.String())), 10))
149                                                 } else {
150                                                         s.logger.Errorf("#JsonSender.expandPayloadHeader - invalid type for #%s, value must be a string", name.String())
151                                                         os.Exit(1)
152                                                 }
153                                         }
154                                         name.Reset()
155                                         break
156                                 }
157                         }
158                 default:
159                         if unicode.IsPrint(rune(ch)) {
160                                 expandedHeader.WriteByte(ch)
161                         }
162                         ch, err = r.ReadByte()
163                 }
164         }
165         return expandedHeader.String()
166 }
167
168 func incAndGetCounter() uint64 {
169         return atomic.AddUint64(&counter, 1)
170 }
171
172 func init() {
173         counter = uint64(time.Now().Unix() - 1572000000)
174 }