Merge branch 'dev' into maximilien

This commit is contained in:
Maximilien LEDOUX 2022-03-10 17:18:06 +01:00
commit 4975c4e454
21 changed files with 383 additions and 94 deletions

View File

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<module version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ StorBackEnd.iml" filepath="$PROJECT_DIR$/.idea/ StorBackEnd.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/StoreBackEnd.iml" filepath="$PROJECT_DIR$/.idea/StoreBackEnd.iml" />
</modules>
</component>
</project>

View File

@ -1,47 +1,76 @@
package main
import (
"StorBackEnd/pkg/config"
"StorBackEnd/pkg/network"
"StorBackEnd/pkg/protocol/managers"
"StorBackEnd/pkg/protocol/repository"
"StorBackEnd/pkg/protocol/rules/readers"
"StorBackEnd/pkg/protocol/rules/writers"
"StoreBackEnd/pkg/config"
"StoreBackEnd/pkg/network"
"StoreBackEnd/pkg/protocol/managers"
"StoreBackEnd/pkg/protocol/repository"
"StoreBackEnd/pkg/protocol/rules/readers"
"StoreBackEnd/pkg/protocol/rules/writers"
"StoreBackEnd/pkg/utils"
"time"
)
const (
FILE_PATH = "resources/AppConfig.json"
FilePath = "resources/AppConfig.json"
)
func main() {
println("StorBackEnd started !")
utils.NetworkLister() // TODO REMOVE
println("StoreBackEnd started !")
// Loading App config
appConfig, err := config.Read(FILE_PATH)
appConfig, err := config.Read(FilePath)
if err != nil {
println("Impossible de charger la configuration du server : " + err.Error())
return
}
println("Adresse multicast : " + appConfig.MulticastAddress)
println(" - Multicast Network Interface : " + appConfig.MulticastNetworkInterface)
println(" - Multicast Address : " + appConfig.MulticastAddress)
println(" - StoreBacked Domain : " + appConfig.Domain)
protocolRepository := repository.CreateProtocolRepository()
// Création des Writers
/**
===== Init all Write here =====
*/
// Creation of the HelloRule
helloRule := writers.CreateHelloRule("^HELLO ([A-Za-z0-9]{5,20}) ([0-9]{1,5})\r\n$")
protocolRepository.AddWriter(&helloRule)
// Création des Readers
// Creation of the SendOkRule
sendOkRule := writers.CreateSendOkRule("^SEND_OK\r\n$")
protocolRepository.AddWriter(&sendOkRule)
// Creation of the SendErrorRule
sendErrorRule := writers.CreateSendOkRule("^SEND_ERROR\r\n$")
protocolRepository.AddWriter(&sendErrorRule)
/**
===== Init all Reader here =====
*/
// Creation of the EraseFileRule
eraseFileRule := readers.CreateEraseFileRule("^ERASEFILE ([A-Za-z0-9.]{50,200})\r\n$")
protocolRepository.AddReader(&eraseFileRule)
multicast := network.CreateClientMulticast(appConfig.MulticastAddress, appConfig.Domain, appConfig.UnicastPort, time.Duration(appConfig.MulticastSecond), protocolRepository)
// Creation of the SendFileRule // TODO reset to 50,200
sendFileRule := readers.CreateSendFileRule("^SENDFILE ([A-Za-z0-9.]{1,200}) ([0-9]{1,10}) ([A-Za-z0-9.]{50,200})\r\n$")
protocolRepository.AddReader(&sendFileRule)
// Create a Multicast Client & run it
multicast := network.CreateClientMulticast(
appConfig.MulticastNetworkInterface, appConfig.MulticastAddress, appConfig.Domain,
appConfig.UnicastPort, time.Duration(appConfig.MulticastSecond), protocolRepository)
go multicast.Run()
requestManager := managers.RequestManager{Repository: protocolRepository}
server := network.ServerUnicast{Network: "tcp", Port: appConfig.UnicastPort, ReqManager: &requestManager}
server.Run()
server.Run() // TODO : -> pourquoi ne pas partir dans un thread ici.
//reader := bufio.NewReader(os.Stdin) TODO ne pas oublier ici de mettre en place un point de sortie pour le programme.
//fmt.Print("Type Enter to quite: ")
//cmd, _ := reader.ReadString('\n')
//println(cmd)
}

2
go.mod
View File

@ -1 +1 @@
module StorBackEnd
module StoreBackEnd

View File

@ -2,15 +2,18 @@ package config
// AppConfig Contient toute la configuration du server
type AppConfig struct {
// multicastAddress Contient l'adresse multicast du FileFrontEnd
// MulticastNetworkInterface
MulticastNetworkInterface string `json:"multicastNetworkInterface"`
// MulticastAddress Contient l'adresse multicast du FileFrontEnd
MulticastAddress string `json:"multicastAddress"`
// multicastSecond Contient le nombre de seconde entre chaque annonce
// MulticastSecond Contient le nombre de seconde entre chaque annonce
MulticastSecond int `json:"multicastSecond"`
// domain Domain du StorBackEnd
// Domain du StoreBackEnd
Domain string `json:"domain"`
// unicastPort Contient le port unicast auquel le FileFrontEnd se connecte
// UnicastPort Contient le port unicast auquel le FileFrontEnd se connecte
UnicastPort int `json:"unicastPort"`
}

View File

@ -5,6 +5,8 @@ import (
"io/ioutil"
)
// Read Méthode permettant de lire le fichier de donfiguration
// return AppConfig
func Read(filePath string) (*AppConfig, error) {
config := AppConfig{}
file, err := ioutil.ReadFile(filePath)
@ -16,6 +18,5 @@ func Read(filePath string) (*AppConfig, error) {
if errJson != nil {
return nil, errJson
}
return &config, nil
}

View File

@ -1,16 +1,17 @@
package network
import (
"StorBackEnd/pkg/protocol/repository"
"StorBackEnd/pkg/protocol/rules/writers"
"StoreBackEnd/pkg/protocol/repository"
"StoreBackEnd/pkg/protocol/rules/writers"
"fmt"
"net"
"time"
)
// CreateClientMulticast Méthode de construction d'un instance de la stuct ClientMulticast
func CreateClientMulticast(address string, domain string, port int, second time.Duration, repository *repository.ProtocolRepository) ClientMulticast {
func CreateClientMulticast(netInterface string, address string, domain string, port int, second time.Duration, repository *repository.ProtocolRepository) ClientMulticast {
return ClientMulticast{
netInter: netInterface,
address: address,
domain: domain,
port: port,
@ -22,62 +23,102 @@ func CreateClientMulticast(address string, domain string, port int, second time.
// ClientMulticast Cette structure représente une communication en multicast.
// TODO : Prévoir une fermeture de la connection (con.Close())
type ClientMulticast struct {
// netInter Interface réseaux multicast
netInter string
// address Adresse de multicast
address string
// address Domain de du StorBackEnd
// address Domain de du StoreBackEnd
domain string
// port Port de connexion en unicast
port int
// second Temps en seconde entre chaque ping
second time.Duration
// Repository de protocol permettant de
// repository de protocol permettant de
repository *repository.ProtocolRepository
}
// Run Cette méthode démarre une commmunication multicast
func (cMult ClientMulticast) Run() {
addr, done := cMult.ResolveAddr()
if done {
func (client ClientMulticast) Run() {
// Resolve multicast addr
rAddr, failedRA := client.ResolveAddr()
if failedRA {
return
}
con, done2 := cMult.DialUdp(addr)
if done2 {
// Resolve interface addr
lAddr, failedRIA := ResolveInterfaceAddr(client.netInter)
if failedRIA {
println("finish")
return
}
cmd, correct := cMult.repository.ExecuteWriter(writers.HelloRuleName, cMult.domain, fmt.Sprintf("%d", cMult.port))
// Init UDP server flux
con, failedDU := client.DialUdp(lAddr, rAddr)
if failedDU {
return
}
cmd, correct := client.repository.ExecuteWriter(writers.HelloRulePrefix, client.domain, fmt.Sprintf("%d", client.port))
if !correct {
println("[ClientMulticast] Hello rule isn't correct (" + cmd + ")")
return
}
for {
con.Write([]byte(cmd))
time.Sleep(time.Second * cMult.second)
_, _ = con.Write([]byte(cmd))
time.Sleep(time.Second * client.second)
}
}
// ResolveAddr Permet de résoude l'addresse
func (cMult ClientMulticast) ResolveAddr() (*net.UDPAddr, bool) {
addr, errResUdp := net.ResolveUDPAddr("udp", cMult.address)
if errResUdp != nil {
println(errResUdp.Error())
// ResolveAddr Permet de résoude l'addresse multicast
func (client ClientMulticast) ResolveAddr() (*net.UDPAddr, bool) {
addr, err := net.ResolveUDPAddr("udp", client.address)
if err != nil {
println(err.Error())
return nil, true
}
return addr, false
}
// ResolveInterfaceAddr Resolves the network interface address.
func ResolveInterfaceAddr(inter string) (*net.UDPAddr, bool) {
//i, err := net.InterfaceByName(inter)
//addrs, err := i.Addrs()
//println(addrs[0])
//addr, err := net.ResolveUDPAddr("udp", addrs[0].String())
//if err != nil {
// println(err.Error())
// return nil, true
//}
return nil, false
//var ipv4Addr net.IP
//ief, err := net.InterfaceByName(inter)
//if err != nil {
// println(err.Error())
// return nil, true
//}
//addrs, err := ief.Addrs()
//if err != nil {
// println(err.Error())
// return nil, true
//}
//for _, addr := range addrs {
// if ipv4Addr = addr.(*net.IPNet).IP; ipv4Addr != nil {
// break
// }
//}
//println(ipv4Addr.String())
//addr, err := net.ResolveUDPAddr("udp", ipv4Addr.String())
//if err != nil {
// println(err.Error())
// return nil, true
//}
//return addr, false
}
// DialUdp Ouvre une connection UDP
func (cMult ClientMulticast) DialUdp(addr *net.UDPAddr) (*net.UDPConn, bool) {
con, errDial := net.DialUDP("udp", nil, addr)
func (client ClientMulticast) DialUdp(lAddr *net.UDPAddr, rAddr *net.UDPAddr) (*net.UDPConn, bool) {
con, errDial := net.DialUDP("udp", nil, rAddr)
if errDial != nil {
println(errDial.Error())
return nil, true

View File

@ -1,7 +1,7 @@
package network
import (
"StorBackEnd/pkg/protocol/managers"
"StoreBackEnd/pkg/protocol/managers"
"bufio"
"fmt"
"net"
@ -14,7 +14,6 @@ type ServerUnicast struct {
}
func (server ServerUnicast) Run() {
listen, err := net.Listen(server.Network, fmt.Sprintf("0.0.0.0:%d", server.Port)) // "tcp", "0.0.0.0:58000"
if err != nil {
@ -24,20 +23,22 @@ func (server ServerUnicast) Run() {
// Attente connexion du FileFrontEnd
con, err := listen.Accept()
print("connection ok")
if err != nil {
fmt.Printf("Error while accepting client : %s\n", err)
return
} else {
for { // TODO : Extraire cette partie de code
line, err := bufio.NewReader(con).ReadString('\n')
reader := bufio.NewReader(con)
line, err := reader.ReadString('\n')
println("[REQUEST] " + line)
if err != nil {
return
}
result := server.ReqManager.Execute(line)
con.Write(append([]byte(result), '\n')) // TODO : ATTENTION laisser les \n
result := server.ReqManager.Execute(line, reader)
println("[RESPONSE] : ", result)
_, _ = con.Write(append([]byte(result), '\n')) // TODO : ATTENTION laisser les \n
}
}
}

View File

@ -1,12 +1,16 @@
package protocol
import (
"bufio"
)
// IProtocolReader Représentation abstraite d'un protocol
type IProtocolReader interface {
// GetCmd Permet de récupérer le nom de la commande
GetCmd() string
// Execute Permet d'exécuter l'action implémentée par une règle. Retourne le message (rule) de retour et bool pour savoir si tout s'est bien passé ou non
Execute(data string) (string, bool)
Execute(data string) (string, bool, func(reader *bufio.Reader) (string, bool))
// Match Permet de vérifier la validité d'une donnée censée suivre les règles d'un protocol
Match(data string) bool

View File

@ -1,10 +1,10 @@
package protocol
// IProtocolReader Représentation abstraite d'un protocol
// IProtocolWriter Représentation abstraite d'un protocol
type IProtocolWriter interface {
// GetCmd Permet de récupérer le nom de la commande
GetCmd() string
// Execute Permet de vérifier la validité d'une donnée censée suivre les règles d'un protocol
// Execute Permet de créer une règle à envoyer
Execute(argsData ...string) (string, bool)
}

View File

@ -8,7 +8,6 @@ func CreateRegexMatcher(pattern string) *RegexMatcher {
if err != nil {
return nil
}
return &RegexMatcher{matcher: compile}
}

View File

@ -1,20 +1,28 @@
package managers
import "StorBackEnd/pkg/protocol/repository"
import (
"StoreBackEnd/pkg/protocol/repository"
"bufio"
)
type RequestManager struct {
Repository *repository.ProtocolRepository
}
func (receiver RequestManager) Execute(request string) string {
func (receiver RequestManager) Execute(request string, reader *bufio.Reader) string {
// On lis ce que l'on reçoit
result, executed := receiver.Repository.ExecuteReader(request)
result, executed, readCb := receiver.Repository.ExecuteReader(request)
// On renvoie la réponse (Comment pour fichier ?)
if executed {
if readCb != nil {
cbResult, _ := readCb(reader)
if cbResult != "" {
result = cbResult
}
}
return result
} else {
// TODO : Renvoyer qu'une erreur est survenue
return "Error occured while execute command"
return "Error occurred while execute command"
}
}

View File

@ -1,6 +1,9 @@
package repository
import "StorBackEnd/pkg/protocol"
import (
"StoreBackEnd/pkg/protocol"
"bufio"
)
func CreateProtocolRepository() *ProtocolRepository {
return &ProtocolRepository{
@ -17,7 +20,6 @@ type ProtocolRepository struct {
// AddReader Permet d'ajouter une règle servant à lire une commande
func (repo ProtocolRepository) AddReader(reader *protocol.IProtocolReader) {
cmd := (*reader).GetCmd()
if cmd != "" && !repo.containsReader(cmd) {
repo.protocolReaders[cmd] = reader
}
@ -26,7 +28,6 @@ func (repo ProtocolRepository) AddReader(reader *protocol.IProtocolReader) {
// AddWriter Permet d'ajouter une règle servant à construire une commande
func (repo ProtocolRepository) AddWriter(writer *protocol.IProtocolWriter) {
cmd := (*writer).GetCmd()
if cmd != "" && !repo.containsWriter(cmd) {
repo.protocolWriters[cmd] = writer
}
@ -35,25 +36,24 @@ func (repo ProtocolRepository) AddWriter(writer *protocol.IProtocolWriter) {
/*
ExecuteReader Permet d'exécuter un Reader et de récupérer la commande à renvoyer. La deuxième valeur de retour permet de savoir si une commande a put être exécuté
*/
func (repo ProtocolRepository) ExecuteReader(data string) (string, bool) {
func (repo ProtocolRepository) ExecuteReader(data string) (string, bool, func(reader *bufio.Reader) (string, bool)) {
for _, reader := range repo.protocolReaders {
if (*reader).Match(data) { // Exécuter si match
// Récupérer résultat à renvoyer (Donc donner ProtocolRepository aux reader ?!)
return (*reader).Execute(data)
}
}
return "", false
return "", false, nil
}
/*
ExecuteReader Permet d'exécuter un Wrier et de récupérer la commande construite avec nos argument et un indicateur de réussite.
*/
func (repo ProtocolRepository) ExecuteWriter(ruleName string, data ...string) (string, bool) {
protWriter, has := repo.protocolWriters[ruleName]
protocolWriter, has := repo.protocolWriters[ruleName]
if has {
println("OHOH")
return (*protWriter).Execute(data...)
return (*protocolWriter).Execute(data...)
} else {
return "", false
}

View File

@ -1,9 +1,12 @@
package readers
import "StorBackEnd/pkg/protocol"
import (
"StoreBackEnd/pkg/protocol"
"bufio"
)
// EraseFileRuleName Identifiant de cette règle
const EraseFileRuleName = "ffe_erasefile"
// EraseFileRulePrefix Identifiant de cette règle
const EraseFileRulePrefix = "ERASEFILE"
// EraseFileRule Demande de suppression d'un fichier
type EraseFileRule struct {
@ -17,7 +20,7 @@ type EraseFileRule struct {
// CreateEraseFileRule Création d'une instance de EraseFileRule
func CreateEraseFileRule(pattern string) protocol.IProtocolReader {
return &EraseFileRule{
Cmd: EraseFileRuleName,
Cmd: EraseFileRulePrefix,
matcher: protocol.CreateRegexMatcher(pattern),
}
}
@ -26,14 +29,13 @@ func (rule EraseFileRule) GetCmd() string {
return rule.Cmd
}
func (rule EraseFileRule) Execute(data string) (string, bool) {
func (rule EraseFileRule) Execute(data string) (string, bool, func(r *bufio.Reader) (string, bool)) {
if rule.Match(data) {
values := rule.matcher.Parse(data)
println(values[1], " est le hash du fichier à supprimer")
return "Parsing : Fichier avec le hash " + values[1] + " supprimé", true
return "Parsing : Fichier avec le hash " + values[1] + " supprimé", true, nil
} else {
return "Parsing : EraseFileRule command incorrecte", false
return "Parsing : EraseFileRule command incorrecte", false, nil
}
}

View File

@ -0,0 +1,79 @@
package readers
import (
"StoreBackEnd/pkg/protocol"
"StoreBackEnd/pkg/utils"
"bufio"
"strconv"
)
// SendFileRulePrefix Rule command prefix
const SendFileRulePrefix = "ffe_sendfile"
// SendFileRule Storage structure for the SendFileRule
type SendFileRule struct {
// cmd Rule name
cmd string
// matcher Allow to make a regex match
matcher *protocol.RegexMatcher
}
// CreateSendFileRule Creating an instance of SendFileRule
func CreateSendFileRule(pattern string) protocol.IProtocolReader {
return &SendFileRule{
cmd: SendFileRulePrefix,
matcher: protocol.CreateRegexMatcher(pattern),
}
}
// GetCmd retrieve the command name.
func (rule SendFileRule) GetCmd() string {
return rule.cmd
}
// Execute the Rule with a string command.
func (rule SendFileRule) Execute(data string) (string, bool, func(reader *bufio.Reader) (string, bool)) {
if rule.Match(data) { // TODO : cloture this command.
values := rule.matcher.Parse(data)
// Values
fileName := values[1]
fileSize, _ := strconv.Atoi(values[2])
// fileContentHash := values[3]
// print received data
println(values[1], " File Name Hash")
println(values[2], " File Size")
println(values[3], " File Content Hash")
// function callback
callback := func(reader *bufio.Reader) (string, bool) {
hasReceive := utils.ReceiveFile(fileName, fileSize, reader)
println("HEY1")
/*
file, _ := os.Create(fmt.Sprintf("/home/benjamin/sbe/%s", fileName))
_, err := io.Copy(file, reader)
println("HEY1")
if err != nil {
println("Can't copy file")
return "SEND_ERROR\r", false
}
*/
if !hasReceive {
return "SEND_ERROR\r", false
}
println("HEY2")
return "SEND_OK\r", true
}
return "SEND_OK\r", true, callback
} else {
println("AHAHAHAHAHAHA")
return "SEND_ERROR\r", false, nil
}
}
// Match make a match with the current Rule and check if it matches.
func (rule SendFileRule) Match(data string) bool {
return rule.matcher.Match(data)
}

View File

@ -1,29 +1,32 @@
package writers
import "StorBackEnd/pkg/protocol"
import "StoreBackEnd/pkg/protocol"
const HelloRuleName = "sbe_hello"
// HelloRulePrefix Rule command prefix
const HelloRulePrefix = "HELLO"
// HelloRule Storage structure for the HelloRule
type HelloRule struct {
// Cmd Nom de la règle
Cmd string
// matcher Permet d'extraire de éléments d'une chaine
// cmd Rule name
cmd string
// matcher Allow to make a regex match
matcher *protocol.RegexMatcher
}
// CreateHelloRule Création d'une instance de HelloRule
// CreateHelloRule Creating an instance of HelloRule
func CreateHelloRule(pattern string) protocol.IProtocolWriter {
return &HelloRule{
Cmd: HelloRuleName,
cmd: HelloRulePrefix,
matcher: protocol.CreateRegexMatcher(pattern),
}
}
// GetCmd retrieve the command name.
func (rule HelloRule) GetCmd() string {
return rule.Cmd
return rule.cmd
}
// Execute the Rule with a string command.
func (rule HelloRule) Execute(argsData ...string) (string, bool) {
return rule.matcher.Build("HELLO", argsData...)
return rule.matcher.Build(HelloRulePrefix, argsData...)
}

View File

@ -0,0 +1,32 @@
package writers
import "StoreBackEnd/pkg/protocol"
// SendErrorRulePrefix Rule command prefix
const SendErrorRulePrefix = "SEND_ERROR"
// SendErrorRule Storage structure for the SendErrorRule
type SendErrorRule struct {
// cmd Rule name
cmd string
// matcher Allow to make a regex match
matcher *protocol.RegexMatcher
}
// CreateSendErrorRule Creating an instance of SendErrorRule
func CreateSendErrorRule(pattern string) protocol.IProtocolWriter {
return &SendErrorRule{
cmd: SendErrorRulePrefix,
matcher: protocol.CreateRegexMatcher(pattern),
}
}
// GetCmd retrieve the command name.
func (rule SendErrorRule) GetCmd() string {
return rule.cmd
}
// Execute the Rule with a string command.
func (rule SendErrorRule) Execute(argsData ...string) (string, bool) {
return rule.matcher.Build(SendErrorRulePrefix, argsData...)
}

View File

@ -0,0 +1,32 @@
package writers
import "StoreBackEnd/pkg/protocol"
// SendOkRulePrefix Rule command prefix
const SendOkRulePrefix = "SEND_OK"
// SendOkRule Storage structure for the SendOkRule
type SendOkRule struct {
// cmd Rule name
cmd string
// matcher Allow to make a regex match
matcher *protocol.RegexMatcher
}
// CreateSendOkRule Creating an instance of SendOkRule
func CreateSendOkRule(pattern string) protocol.IProtocolWriter {
return &SendOkRule{
cmd: SendOkRulePrefix,
matcher: protocol.CreateRegexMatcher(pattern),
}
}
// GetCmd retrieve the command name.
func (rule SendOkRule) GetCmd() string {
return rule.cmd
}
// Execute the Rule with a string command.
func (rule SendOkRule) Execute(argsData ...string) (string, bool) {
return rule.matcher.Build(SendOkRulePrefix, argsData...)
}

31
pkg/utils/FileReceiver.go Normal file
View File

@ -0,0 +1,31 @@
package utils
import (
"bufio"
"fmt"
"os"
)
// ReceiveFile Permet de récupérer un fichier sur un reader
func ReceiveFile(fileName string, fileSize int, reader *bufio.Reader) bool {
file, fileErr := os.Create(fmt.Sprintf("/home/benjamin/sbe/%s", fileName))
if fileErr != nil {
return false
}
defer file.Close()
currentSize := 0
buffer := make([]byte, 1024)
for currentSize < fileSize {
length, err := reader.Read(buffer)
if err != nil {
return false
}
currentSize += length
file.WriteAt(buffer, int64(currentSize))
}
return true
}

View File

@ -0,0 +1,24 @@
package utils
import (
"fmt"
"net"
)
func NetworkLister() {
// Retrieve Interfaces
inter, err := net.Interfaces()
// Process errors
if err != nil {
println("[ERROR] An error occurred : " + err.Error())
return
}
// Display items
println("\n\nNetwork interface list :")
for i, val := range inter {
fmt.Printf("%d. %s\n", i, val.Name)
}
print("\n")
}

View File

@ -1,6 +1,7 @@
{
"multicastAddress" : "226.66.66.1:42500",
"multicastNetworkInterface" : "Wi-Fi",
"multicastAddress" : "226.66.66.1:15502",
"multicastSecond" : 10,
"domain" : "benjamin",
"domain" : "lightcontainerSB01",
"unicastPort" : 58000
}