Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 104 additions & 49 deletions providers/fanvil.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"sync"
"time"

"github.com/divan/gorilla-xmlrpc/xml"

Expand All @@ -47,17 +48,76 @@ type FanvilDevice struct {
Url string
}

var fanvilPassword string
type fanvilDeviceLock struct {
sync.Mutex
users int
}

var (
fanvilPassword string
fanvilPasswordMutex sync.Mutex
fanvilHTTPClient = &http.Client{Timeout: 30 * time.Second}
fanvilDeviceLocks = struct {
sync.Mutex
devices map[string]*fanvilDeviceLock
}{devices: make(map[string]*fanvilDeviceLock)}
)

// fanvilLockDevice prevents requests for the same phone from interleaving.
func fanvilLockDevice(mac string) func() {
fanvilDeviceLocks.Lock()
lock := fanvilDeviceLocks.devices[mac]
if lock == nil {
lock = &fanvilDeviceLock{}
fanvilDeviceLocks.devices[mac] = lock
}
lock.users++
fanvilDeviceLocks.Unlock()

lock.Lock()
return func() {
lock.Unlock()
fanvilDeviceLocks.Lock()
defer fanvilDeviceLocks.Unlock()
lock.users--
if lock.users == 0 {
delete(fanvilDeviceLocks.devices, mac)
}
}
}

func (d FanvilDevice) Register() error {
Url, err := url.Parse(d.Url)
if err != nil || Url.Hostname() == "" {
return errors.New("malformed_url")
}

var UrlScheme string
switch Url.Scheme {
case "ftp":
UrlScheme = "1"
case "tftp":
UrlScheme = "2"
case "http":
UrlScheme = "4"
case "https":
UrlScheme = "5"
default:
return errors.New("malformed_url")
}

unlock := fanvilLockDevice(d.Mac)
defer unlock()

fanvilPasswordMutex.Lock()
password := fanvilGetPassword()
fanvilPasswordMutex.Unlock()

//Delete old server
buf, _ := xml.EncodeClientRequest("redirect.deleteServer",
// Fanvil refuses to delete a server while a device is still registered to it.
buf, _ := xml.EncodeClientRequest("redirect.deRegisterDevice",
&struct {
GroupName string
}{GroupName: d.Mac})
Mac string
}{Mac: d.Mac})

req, _ := http.NewRequest("POST", configuration.Config.Providers.Fanvil.RpcUrl,
bytes.NewReader(buf))
Expand All @@ -67,14 +127,13 @@ func (d FanvilDevice) Register() error {
req.Header.Set("Content-Type", "text/xml")
req.Header.Set("User-Agent", " Falconieri/1")

resp, err := http.DefaultClient.Do(req)
resp, err := fanvilHTTPClient.Do(req)

if err != nil {
return models.ProviderError{
Message: "connection_to_remote_provider_failed",
WrappedError: err,
}

}

defer resp.Body.Close()
Expand All @@ -84,41 +143,19 @@ func (d FanvilDevice) Register() error {
}

err = fanvilParseResponse(resp.Body)

if (err != nil) && (errors.Unwrap(err).Error() != "Error:server_not_exist") {
return err
}

//Create Server
var Url *url.URL
var UrlScheme string

Url, err = url.Parse(d.Url)

switch Url.Scheme {
case "ftp":
UrlScheme = "1"
case "tftp":
UrlScheme = "2"
case "http":
UrlScheme = "4"
case "https":
UrlScheme = "5"
if err != nil {
cause := errors.Unwrap(err)
// A device that has never been registered needs no cleanup.
if cause == nil || cause.Error() != "Error:device_not_exist" {
return err
}
}

/* Server/Group configuration:
* - Disable DHCP provision: cfgDhcpOpt=false
* - Disable PnP provision: cfgPnpEnable=false
* - Apply the configuration after the reboot: cfgPfMode=1
*/

buf, _ = xml.EncodeClientRequest("redirect.addMaterialServer",
//Delete old server
buf, _ = xml.EncodeClientRequest("redirect.deleteServer",
&struct {
ServerConfigs []string
}{ServerConfigs: []string{"cfgName=" + d.Mac, "cfgPfMode=1",
"cfgDhcpOpt=false", "cfgPnpEnable=false",
"cfgPfSrv=" + Url.Host, "cfgPfName=" + strings.TrimPrefix(Url.Path, "/"),
"cfgPfProt=" + UrlScheme}})
GroupName string
}{GroupName: d.Mac})

req, _ = http.NewRequest("POST", configuration.Config.Providers.Fanvil.RpcUrl,
bytes.NewReader(buf))
Expand All @@ -128,7 +165,7 @@ func (d FanvilDevice) Register() error {
req.Header.Set("Content-Type", "text/xml")
req.Header.Set("User-Agent", " Falconieri/1")

resp, err = http.DefaultClient.Do(req)
resp, err = fanvilHTTPClient.Do(req)

if err != nil {
return models.ProviderError{
Expand All @@ -147,14 +184,26 @@ func (d FanvilDevice) Register() error {
err = fanvilParseResponse(resp.Body)

if err != nil {
return err
cause := errors.Unwrap(err)
// An absent server is expected when registering a device for the first time.
if cause == nil || (cause.Error() != "Error:server_not_exist" && cause.Error() != "Error:server_not_existed") {
return err
}
}

//Deregister the device
buf, _ = xml.EncodeClientRequest("redirect.deRegisterDevice",
/* Server/Group configuration:
* - Disable DHCP provision: cfgDhcpOpt=false
* - Disable PnP provision: cfgPnpEnable=false
* - Apply the configuration after the reboot: cfgPfMode=1
*/

buf, _ = xml.EncodeClientRequest("redirect.addMaterialServer",
&struct {
Mac string
}{Mac: d.Mac})
ServerConfigs []string
}{ServerConfigs: []string{"cfgName=" + d.Mac, "cfgPfMode=1",
"cfgDhcpOpt=false", "cfgPnpEnable=false",
"cfgPfSrv=" + Url.Host, "cfgPfName=" + strings.TrimPrefix(Url.Path, "/"),
"cfgPfProt=" + UrlScheme}})

req, _ = http.NewRequest("POST", configuration.Config.Providers.Fanvil.RpcUrl,
bytes.NewReader(buf))
Expand All @@ -164,7 +213,7 @@ func (d FanvilDevice) Register() error {
req.Header.Set("Content-Type", "text/xml")
req.Header.Set("User-Agent", " Falconieri/1")

resp, err = http.DefaultClient.Do(req)
resp, err = fanvilHTTPClient.Do(req)

if err != nil {
return models.ProviderError{
Expand All @@ -180,6 +229,12 @@ func (d FanvilDevice) Register() error {
return errors.New("provider_remote_call_failed")
}

err = fanvilParseResponse(resp.Body)

if err != nil {
return err
}

//Register the device
buf, _ = xml.EncodeClientRequest("redirect.registerDevice",
&struct {
Expand All @@ -195,7 +250,7 @@ func (d FanvilDevice) Register() error {
req.Header.Set("Content-Type", "text/xml")
req.Header.Set("User-Agent", " Falconieri/1")

resp, err = http.DefaultClient.Do(req)
resp, err = fanvilHTTPClient.Do(req)

if err != nil {
return models.ProviderError{
Expand Down Expand Up @@ -239,7 +294,7 @@ func fanvilParseResponse(body io.ReadCloser) error {

var response_regexp = `(?s).*<boolean>(.*)</boolean>.*<string>(.*)</string>.*`

respBytes, err := ioutil.ReadAll(body)
respBytes, err := io.ReadAll(body)

if err != nil {
return errors.New("read_remote_response_failed")
Expand Down