package services import ( "crypto/tls" "encoding/json" "fmt" "io/ioutil" "log" "math/rand" "os" "strconv" "sync" "time" appConfig "bitbucket.org/outfrontmedia/rolling-stock-display-simulation-tool/config" "github.com/twinj/uuid" "gosrc.io/xmpp" "gosrc.io/xmpp/stanza" ) const rolingStockPresenceMessage = `ROLLING STOCK PRESENCE MESSAGE { "serial": "79101X02Y0029450001CJ2000", "model": "VEN032FSNWM00", "displayOrientation": "left", "macAddress": "1c:c0:e1:42:5a:c4", "node": "v8.16.2", "nodeJS": "v8.16.2", "fbi": "2.10-2ubuntu1", "libgif7": "5.1.4-2ubuntu0.1", "bsEdgeServer": "13.3.9-1", "initopciVersion": "0.4~24", "bspInstallVersion": "1.0~36", "awsConfigsVersion": "2021.21.26-e7c5738", "outGuard": "2021.36.23-dev-028972a", "val": "1.1.11~129", "broadSignPlayer": "13.3.9-1", "displayUnitExternalID": "L-0141-M7-7281-Right-3SM-G02-02-79101X02Y0020040003EJ2001", "privateIPaddress": "10.1.1.94", "linux": { "os": "Ubuntu 18.04.2 LTS", "kernel": "4.18.20+10+opci", "architecture": "x86-64" }, "digiID": "digi-00000000-00000000-0040FFFF-FF801D30" }` const commandMessageExample = `{"device_jid":"device1@rolling-stock-sandbox2-openfire.onsmartengineering.com", "message_id":"df26acfa-bdd4-40f4-a980-0638bbdb096e-1632473349400", "api_version":1, "from_id":"agent@rolling-stock-sandbox2-openfire.onsmartengineering.com", "commands":[{"name":"demo_command","uuid":"0388ffd0-3f69-4600-b545-ed19f6378a0a", "params":{"user_id":"21","api_version":1,"command":"demo_command","player_id":"8","arg":"senad.uka@toptal.com help"}}]}` const commandResponse = `{"api_version": 1, "message_id": "766318f3-d1d3-4359-bf2d-e69d508733f4-1632410186320", "responses": [{"params": {"message": "Unsupported command. Type help for the list of supported commands", "response_code": "SUCCESS"}, "uuid": "5a041416-4184-4529-a7d3-7bfdd9fc1739", "name": "demo_command"}], "player_id": "3"}` // CommandMessage - type CommandMessage struct { APIVersion int `json:"api_version"` MessageID string `json:"message_id"` Commands []Command `json:"commands"` } // Command - type Command struct { Params CommandParams `json:"params"` UUID string `json:"uuid"` Name string `json:"name"` } // CommandParams - type CommandParams struct { Command string `json:"message"` PlayerID string `json:"player_id"` } // CommandMessageResponse - type CommandMessageResponse struct { APIVersion int `json:"api_version"` MessageID string `json:"message_id"` Responses []Response `json:"responses"` PlayerID string `json:"player_id"` } // Response - type Response struct { Params ResponseParams `json:"params"` UUID string `json:"uuid"` Name string `json:"name"` } // ResponseParams - type ResponseParams struct { Message string `json:"message"` ResponseCode string `json:"response_code"` } // XMPPService - struct containing type XMPPService struct { Router *xmpp.Router XMPPClients []XMPPClient } // XMPPClient - struct containing client and client configuration type XMPPClient struct { Config xmpp.Config Client *xmpp.Client } // ClientCredentials - credentials info for client configuration type ClientCredentials struct { Jid string `json:"username"` Credential string `json:"password"` Host string `json:"host"` Port int `json:"port"` Ping int `json:"ping"` } const format = "2006/01/02 15:04:05" // Instance - get instance of xmpp service func Instance() *XMPPService { return &xmppService } var xmppService XMPPService // Init Initialise XMPP servie, and router func Init() { xmppService = XMPPService{ Router: xmpp.NewRouter(), } xmppService.Router.HandleFunc("message", handleMessage) //Get Client credentials from file clientCredentials, err := getClientConfigsFromFile(appConfig.AppConfig.Credentials.CredentialsFileLocation) if err != nil { log.Fatalf("Unable to load credentials from file path %v, ERROR : %v TERMINATING", appConfig.AppConfig.Credentials.CredentialsFileLocation, err) } log.Printf("Client credentials length %v", len(clientCredentials)) xmppService.XMPPClients = make([]XMPPClient, 0) var wg sync.WaitGroup for _, credential := range clientCredentials { wg.Add(1) log.Printf("Host %v", credential.Host+":"+strconv.Itoa(credential.Port)) log.Printf("Jid %v", credential.Jid+"@"+credential.Host) log.Printf("Port %v", credential.Port) log.Printf("Credential %v", credential.Credential) xmppClient := XMPPClient{ Config: xmpp.Config{ TransportConfiguration: xmpp.TransportConfiguration{ Address: credential.Host + ":" + strconv.Itoa(credential.Port), Domain: credential.Host, TLSConfig: &tls.Config{InsecureSkipVerify: true}, }, Jid: credential.Jid + "@" + credential.Host, Credential: xmpp.Password(credential.Credential), StreamLogger: os.Stdout, Insecure: true, }, } go func() { defer wg.Done() statusMesageDelay := int64(rand.Intn(appConfig.AppConfig.GeneralOptions.CommandReplyDelay)) log.Printf("Delaying client connection for: %v secconds", statusMesageDelay) // Delay For two seccond to allow all clients to connect time.Sleep(time.Duration(statusMesageDelay * 1000000000)) client, err := xmpp.NewClient(&xmppClient.Config, xmppService.Router, errorHandler) // Client connection if err := client.Connect(); err != nil { msg := fmt.Sprintf("XMPP connection failed: %v", err) fmt.Printf("Failed to connect to server. Exiting... %v", msg) return } if err != nil { log.Printf("Unable to initialise client for %v", xmppClient.Config.Jid) } xmppClient.Client = client xmppService.XMPPClients = append(xmppService.XMPPClients, xmppClient) }() } // Wait untill all client connections are open sucessfuly wg.Wait() } // SendOnlinePresenceStanza - send online presence stnza with data, to server func SendOnlinePresenceStanza(client *xmpp.Client, jid string) error { statusMesageDelay := int64(rand.Intn(appConfig.AppConfig.GeneralOptions.CommandReplyDelay)) log.Printf("Delaying online stanza presence message for: %v, secconds", statusMesageDelay*1000000000) // Delay For two seccond to allow all clients to connect time.Sleep(time.Duration(statusMesageDelay * 1000000000)) onlinePresencePacket := stanza.NewPresence(stanza.Attrs{From: jid, Type: stanza.StanzaType(stanza.PresenceShowChat)}) onlinePresencePacket.Status = rolingStockPresenceMessage err := client.Send(onlinePresencePacket) if err != nil { return err } log.Printf("<%v> sent online status", jid) return nil } // RunOnlinePresenceStatusIndefinetly - keep sending online presence status messages indefinetly for all clients with delay func (xmppService *XMPPService) RunOnlinePresenceStatusIndefinetly() { for { for _, xmppClient := range xmppService.XMPPClients { // Send online presence stanza in go rutines go func(xmppClient XMPPClient) { err := SendOnlinePresenceStanza(xmppClient.Client, xmppClient.Config.Jid) if err != nil { log.Printf("There was an error while sending online presence stanza %v", err) return } }(xmppClient) } // Delay before sending another message time.Sleep(time.Duration(appConfig.AppConfig.GeneralOptions.PresenceStatusDelay * 1000000000)) } } // ExecuteSingleStatusMessage - Wait for all clients to send online presence stanza messages just once func (xmppService *XMPPService) ExecuteSingleStatusMessage() { var wg sync.WaitGroup for _, xmppClient := range xmppService.XMPPClients { wg.Add(1) // Send online presence stanza in go rutines go func(xmppClient XMPPClient) { defer wg.Done() err := SendOnlinePresenceStanza(xmppClient.Client, xmppClient.Config.Jid) if err != nil { log.Printf("There was an error while sending online presence stanza %v", err) return } }(xmppClient) } // Wait until all clients sent their online prezence staza messages wg.Wait() } func getClientConfigsFromFile(filePath string) ([]ClientCredentials, error) { var clientCredentials []ClientCredentials f, err := os.Open(filePath) if err != nil { return clientCredentials, err } defer f.Close() byteValue, err := ioutil.ReadAll(f) if err != nil { return clientCredentials, err } err = json.Unmarshal(byteValue, &clientCredentials) if err != nil { return clientCredentials, err } return clientCredentials, nil } func handleMessage(s xmpp.Sender, p stanza.Packet) { commandMessageResponse := CommandMessageResponse{} err := json.Unmarshal([]byte(commandResponse), &commandMessageResponse) if err != nil { log.Printf("unable to unmarshal Command Meassage Response: %v", commandResponse) } msg, ok := p.(stanza.Message) if !ok { log.Printf(" message not OK") _, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p) return } if len(msg.Body) == 0 { log.Printf("<%v> received empty message, and will not respond", msg.To) return } commandMessage := CommandMessage{} err = json.Unmarshal([]byte(msg.Body), &commandMessage) if err != nil { log.Printf("<%v> unable to unmarshal Command Meassage: %v", msg.To, msg.Body) } if len(commandMessageResponse.Responses) > 0 || len(commandMessage.Commands) > 0 { // messageIDUUID := uuid.NewV4().String() commandMessageUUID := uuid.NewV4().String() commandMessageResponse.Responses[0].UUID = commandMessageUUID commandMessageResponse.PlayerID = commandMessage.Commands[0].Params.PlayerID commandMessageResponse.MessageID = commandMessage.MessageID } else { log.Printf("<%v> There are no commands inside command message : %v", msg.To, commandMessage) } m, err := json.Marshal(commandMessageResponse) if err != nil { log.Printf("<%v> unable to marshal Command Meassage response : %v", msg.To, commandMessageResponse) } log.Printf("<%v> received following message: %v", msg.To, msg.Body) commandReplyDelay := int64(rand.Intn(appConfig.AppConfig.GeneralOptions.CommandReplyDelay)) log.Printf("Waiting FOR: %v", commandReplyDelay*1000000000) // Delay For two seccond to allow all clients to connect time.Sleep(time.Duration(commandReplyDelay * 1000000000)) reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: string(m)} err = s.Send(reply) if err != nil { log.Printf("Error sending to %v message %v", msg.From, err) } } func errorHandler(err error) { fmt.Println(err.Error()) }