diff --git a/Device.go b/Device.go index 339b0c1..705b31b 100644 --- a/Device.go +++ b/Device.go @@ -6,17 +6,17 @@ import ( "github.com/beevik/etree" "github.com/yakovlevdmv/gosoap" "strconv" + "net/http" + "io/ioutil" "github.com/yakovlevdmv/WS-Discovery" - "github.com/yakovlevdmv/goonvif/networking" - "reflect" "strings" "github.com/yakovlevdmv/goonvif/Device" "errors" - "net/http" - "io/ioutil" + "reflect" + "github.com/yakovlevdmv/goonvif/networking" ) -var xlmns = map[string]string { +var Xlmns = map[string]string { "onvif":"http://www.onvif.org/ver10/schema", "tds":"http://www.onvif.org/ver10/device/wsdl", "trt":"http://www.onvif.org/ver10/media/wsdl", @@ -197,6 +197,10 @@ func (dev *device) Authenticate(username, password string) { dev.password = password } +func (dev *device) GetEndpoint(name string) string { + return dev.endpoints[name] +} + func buildMethodSOAP(msg string) (gosoap.SoapMessage, error) { doc := etree.NewDocument() if err := doc.ReadFromString(msg); err != nil { @@ -231,8 +235,6 @@ func (dev device) CallMethod(method interface{}) (*http.Response, error) { case "PTZ": endpoint = dev.endpoints["PTZ"] } - ////fmt.Println("endpoint", endpoint) - //TODO: Get endpoint automatically if dev.login != "" && dev.password != "" { resp, err := dev.callAuthorizedMethod(endpoint, method) @@ -273,7 +275,7 @@ func (dev device) callNonAuthorizedMethod(endpoint string, method interface{}) ( return nil, err } - soap.AddRootNamespaces(xlmns) + soap.AddRootNamespaces(Xlmns) /* Sending request and returns the response @@ -303,8 +305,9 @@ func (dev device) callAuthorizedMethod(endpoint string, method interface{}) (*ht soap.AddWSSecurity(dev.login, dev.password) + /* Sending request and returns the response */ return networking.SendSoap(endpoint, soap.String()) -} +} \ No newline at end of file diff --git a/PTZ/types.go b/PTZ/types.go index d281283..8fd3a33 100644 --- a/PTZ/types.go +++ b/PTZ/types.go @@ -1,7 +1,9 @@ package PTZ -import "github.com/yakovlevdmv/goonvif/xsd" -import "github.com/yakovlevdmv/goonvif/xsd/onvif" +import ( + "github.com/yakovlevdmv/goonvif/xsd" + "github.com/yakovlevdmv/goonvif/xsd/onvif" +) type Capabilities struct { EFlip xsd.Boolean `xml:"EFlip,attr"` diff --git a/api/api.go b/api/api.go new file mode 100644 index 0000000..2616a2f --- /dev/null +++ b/api/api.go @@ -0,0 +1,326 @@ +package api + +import ( + "fmt" + "reflect" + "regexp" + "errors" + "strings" + "github.com/beevik/etree" + "github.com/gin-gonic/gin" + "github.com/yakovlevdmv/gosoap" + "github.com/yakovlevdmv/goonvif" + "github.com/yakovlevdmv/goonvif/networking" + "log" + "net/http" + "io/ioutil" + "encoding/xml" +) + +func RunApi () { + router := gin.Default() + + router.POST("/:service/:method", func(c *gin.Context) { + serviceName := c.Param("service") + methodName := c.Param("method") + acceptedData, err := c.GetRawData() + if err != nil { + fmt.Println(err) + } + message := callNecessaryMethod(serviceName, methodName, string(acceptedData), "192.168.13.12") + c.XML(http.StatusOK, message) + }) + router.Run() +} + +//func soapHandling(tp interface{}, tags* map[string]string) { +// ifaceValue := reflect.ValueOf(tp).Elem() +// typeOfStruct := ifaceValue.Type() +// if ifaceValue.Kind() != reflect.Struct { +// return +// } +// for i := 0; i < ifaceValue.NumField(); i++ { +// field := ifaceValue.Field(i) +// tg, err := typeOfStruct.FieldByName(typeOfStruct.Field(i).Name) +// if err == false { +// fmt.Println(err) +// } +// (*tags)[typeOfStruct.Field(i).Name] = string(tg.Tag) +// +// subStruct := reflect.New(reflect.TypeOf( field.Interface() )) +// soapHandling(subStruct.Interface(), tags) +// } +//} + + +func callNecessaryMethod(serviceName string, methodName string, acceptedData string, deviceXaddr string) string { + var methodStruct interface{} + var err error + switch serviceName { + case "Device", "device": + methodStruct, err = GetDeviceStructByName(methodName) + case "PTZ", "ptz": + methodStruct, err = GetPTZStructByName(methodName) + } + if err != nil { + //todo: нормально написать + } + resp, err := xmlAnalize(methodStruct, &acceptedData) + if err != nil { + //todo: нормально написать + } + + soap := gosoap.NewEmptySOAP() + soap.AddStringBodyContent(*resp) + soap.AddRootNamespaces(goonvif.Xlmns) + + /* + Getting an WS-Security struct representation + */ + auth := goonvif.NewSecurity("admin", "Supervisor") + + /* + Adding WS-Security namespaces to root element of SOAP message + */ + soap.AddRootNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext1.0.xsd") + soap.AddRootNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility1.0.xsd") + + soapReq, err := xml.MarshalIndent(auth, "", " ") + + /* + Adding WS-Security struct to SOAP header + */ + soap.AddStringHeaderContent(string(soapReq)) + + endpoint:= getEndpoint(serviceName, deviceXaddr) + servResp, srvErr := networking.SendSoap(endpoint, soap.String()) + if srvErr != nil { + panic(srvErr) + //todo: нормально написать + } + + rsp, err := ioutil.ReadAll(servResp.Body) + if err != nil { + log.Println(err) + return "" + } + return string(rsp) +} + +func getEndpoint(service, xaddr string) string { + dev, err := goonvif.NewDevice(xaddr) + if err != nil { + log.Println(err) + return "" + } + pkg := strings.ToLower(service) + + var endpoint string + switch pkg { + case "device": endpoint = dev.GetEndpoint("Device") + case "event": endpoint = dev.GetEndpoint("Event") + case "imaging": endpoint = dev.GetEndpoint("Imaging") + case "media": endpoint = dev.GetEndpoint("Media") + case "ptz": endpoint = dev.GetEndpoint("PTZ") + } + return endpoint +} + +/* +NEW + */ + +func xmlAnalize(methodStruct interface{}, acceptedData* string) (*string, error) { + test := make([]map[string]string, 0) //tags + testunMarshal := make([][]interface{}, 0) //data + //tmp, err := GetPTZStructByName("Stop") + //if err != nil { + // fmt.Println(err) + //} + + soapHandling(methodStruct, &test) + test = mapProcessing(test) + + doc := etree.NewDocument() + if err := doc.ReadFromString(*acceptedData); err != nil { + fmt.Println(err) + return nil, nil //todo: нормально написать + } + etr := doc.FindElements("./*") + xmlUnmarshal(etr, &testunMarshal) + + etr_ := doc.FindElements("./*") + var mas []string //idnt + getPos(etr_, &mas) + ident(&mas) + + //todo: может возникнуть проблема с типами данных без тегов. От таких типов надо избавляться + //todo: попробовать всунуть все вызовы (soapHandling, xmlUnmarshal)сюда + + + document:= etree.NewDocument() + var el *etree.Element + var idntIndex = 0 + + for lstIndex := 0; lstIndex < len(testunMarshal); { + lst := (testunMarshal)[lstIndex] + elemName, attr, value:= xmlMaker(&lst, &test, lstIndex) + + if mas[lstIndex] == "Push" && lstIndex == 0 { //done + el = document.CreateElement(elemName) + el.SetText(value) + if len(attr) != 0 { + for key, value := range attr { + el.CreateAttr(key, value) + } + } + } else if mas[idntIndex] == "Push" { + pushTmp := etree.NewElement(elemName) + pushTmp.SetText(value) + if len(attr) != 0 { + for key, value := range attr { + pushTmp.CreateAttr(key, value) + } + } + el.AddChild(pushTmp) + el = pushTmp + } else if mas[idntIndex] == "PushPop" { //done + popTmp := etree.NewElement(elemName) + popTmp.SetText(value) + if len(attr) != 0 { + for key, value := range attr { + popTmp.CreateAttr(key, value) + } + } + el.AddChild(popTmp) + } else if mas[idntIndex] == "Pop" { + el = el.Parent() + lstIndex -= 1 + } + idntIndex += 1 + lstIndex += 1 + } + //document.Indent(2) + //fmt.Println(document.WriteToString()) + resp, err := document.WriteToString() + if err != nil { + //todo нормально расписать + } + return &resp, err +} + +func xmlMaker(lst* []interface{}, tags* []map[string]string, lstIndex int) (string, map[string]string, string) { + var elemName, value string + attr := make(map[string]string) + for tgIndx, tg := range *tags { + if tgIndx == lstIndex { + for index, elem := range *lst { + if reflect.TypeOf(elem).String() == "[]etree.Attr" { + conversion := elem.([]etree.Attr) + for _, i := range conversion { + attr[i.Key] = i.Value + } + } else { + conversion := elem.(string) + if index == 0 && lstIndex == 0 { + res, err := xmlProcessing(tg["XMLName"]) + if err != nil { + fmt.Println(err) + } + elemName = res + } else if index == 0 { + res, err := xmlProcessing(tg[conversion]) + if err != nil { + fmt.Println(err) + } + elemName = res + } else { + value = conversion + } + } + } + } + } + return elemName, attr, value +} + +func xmlProcessing (tg string) (string, error) { + r, _ := regexp.Compile(`\"(.*?)\"`) + str := r.FindStringSubmatch(tg) + if len(str) == 0 { + return "", errors.New("out of range") + } + attr := strings.Index(str[1], ",attr") + omit := strings.Index(str[1], ",omitempty") + if attr == -1 && omit == -1 { //todo: проработать вариант, когдв omitempty и attr вместе + return str[1], nil + } else if omit > -1 { + return str[1][0:omit], nil + }else { + return str[0:attr][0], nil + } + return "", errors.New("something went wrong") +} + +func mapProcessing(mapVar []map[string]string) []map[string]string { + for indx := 0; indx < len(mapVar); indx++ { + element := mapVar[indx] + for _, value := range element { + if value == "" { + mapVar = append(mapVar[:indx], mapVar[indx+1:]...) + indx-- + } + if strings.Index(value, ",attr") != -1 { + mapVar = append(mapVar[:indx], mapVar[indx+1:]...) + indx-- + } + } + } + return mapVar +} + +func soapHandling(tp interface{}, tags* []map[string]string) { + s := reflect.ValueOf(tp).Elem() + typeOfT := s.Type() + if s.Kind() != reflect.Struct { + return + } + for i := 0; i < s.NumField(); i++ { + f := s.Field(i) + tmp, err := typeOfT.FieldByName(typeOfT.Field(i).Name) + if err == false { + fmt.Println(err) + } + *tags = append(*tags, map[string]string{typeOfT.Field(i).Name : string(tmp.Tag)}) + subStruct := reflect.New(reflect.TypeOf( f.Interface() )) + soapHandling(subStruct.Interface(), tags) + } +} + +//todo: передавать в него отступы +func xmlUnmarshal(elems []*etree.Element, data* [][]interface{}) { + for _, elem := range elems { + *data = append(*data, []interface{}{elem.Tag,elem.Attr,elem.Text()}) + xmlUnmarshal(elem.FindElements("./*"), data) + } +} + + +//todo лишняя функция, засунуть в xmlUnmarshal +func getPos(elems []*etree.Element, mas* []string) { + for _, elem := range elems { + *mas = append(*mas, "Push") + getPos(elem.FindElements("./*"), mas) + *mas = append(*mas, "Pop") + } +} + +func ident(mas* []string) { + var buffer string + for _, j := range *mas { + buffer += j + " " + } + buffer = strings.Replace(buffer, "Push Pop ", "PushPop ", -1) + buffer = strings.TrimSpace(buffer) + *mas = strings.Split(buffer, " ") +} \ No newline at end of file diff --git a/api/get_structs.go b/api/get_structs.go new file mode 100644 index 0000000..8463ad8 --- /dev/null +++ b/api/get_structs.go @@ -0,0 +1,79 @@ +package api + +import ( + "github.com/yakovlevdmv/goonvif/PTZ" + "errors" + "github.com/yakovlevdmv/goonvif/Device" +) + +func GetPTZStructByName(name string) (interface{}, error) { + switch name { + case "GetServiceCapabilities": + return &PTZ.GetServiceCapabilities{}, nil + case "GetNodes": + return &PTZ.GetNodes{}, nil + case "GetNode": + return &PTZ.GetNode{}, nil + case "GetConfiguration": + return &PTZ.GetConfiguration{}, nil + case "GetConfigurations": + return &PTZ.GetConfigurations{}, nil + case "SetConfiguration": + return &PTZ.SetConfiguration{}, nil + case "GetConfigurationOptions": + return &PTZ.GetConfigurationOptions{}, nil + case "SendAuxiliaryCommand": + return &PTZ.SendAuxiliaryCommand{}, nil + case "GetPresets": + return &PTZ.GetPresets{}, nil + case "SetPreset": + return &PTZ.SetPreset{}, nil + case "RemovePreset": + return &PTZ.RemovePreset{}, nil + case "GotoPreset": + return &PTZ.GotoPreset{}, nil + case "GotoHomePosition": + return &PTZ.GotoHomePosition{}, nil + case "SetHomePosition": + return &PTZ.SetHomePosition{}, nil + case "ContinuousMove": + return &PTZ.ContinuousMove{}, nil + case "RelativeMove": + return &PTZ.RelativeMove{}, nil + case "GetStatus": + return &PTZ.GetStatus{}, nil + case "AbsoluteMove": + return &PTZ.AbsoluteMove{}, nil + case "GeoMove": + return &PTZ.GeoMove{}, nil + case "Stop": + return &PTZ.Stop{}, nil + case "GetPresetTours": + return &PTZ.GetPresetTours{}, nil + case "GetPresetTour": + return &PTZ.GetPresetTour{}, nil + case "GetPresetTourOptions": + return &PTZ.GetPresetTourOptions{}, nil + case "CreatePresetTour": + return &PTZ.CreatePresetTour{}, nil + case "ModifyPresetTour": + return &PTZ.ModifyPresetTour{}, nil + case "OperatePresetTour": + return &PTZ.OperatePresetTour{}, nil + case "RemovePresetTour": + return &PTZ.RemovePresetTour{}, nil + case "GetCompatibleConfigurations": + return &PTZ.GetCompatibleConfigurations{}, nil + default: + return nil, errors.New("invalid structure") + } +} + +func GetDeviceStructByName(name string) (interface{}, error) { + switch name { + case "CreateUsers": + return &Device.CreateUsers{}, nil + default: + return nil, errors.New("invalid structure") + } +} \ No newline at end of file