Code Monkey home page Code Monkey logo

Comments (4)

mh-cbon avatar mh-cbon commented on June 12, 2024

I have sketched some changes here, to see.

  • gupnp.localIPv4MCastAddrs is made public
  • ssdp.SSDPRawSearch is modified to de duplicate answers using the client local address as additional uniq identifier parameter. It receives an additional parameter client httpu.ClientInterface.
  • httpu.HTTPUClient is modified to add a new Field Addr string
  • goupnp.DiscoverDevicesCtx and others, are modified to take in a client httpu.ClientInterface
  • httpu.HTTPUClient.Do is modified to set the response header key httpu.LocalAddressHeader to the related httpuClient.Addr instead of httpuClient.conn.LocalAddr

Overall, in this form it is breaking the API. At some point i though that it probably should exist a(nother) Client API onto which functions defined within goupnp.go are defined with more flexibility. Than some default client is defined and those package functions redirect their call to that default client.

Edit: looking further,

  • Discovery services functions defined within goupnp/service.go should also exist within that aforementioned client.
  • the auto generated code would have to make public those definitions that maps a generic client to a dedicated service client. https://github.com/huin/goupnp/blob/main/dcps/internetgateway1/internetgateway1.go#L109
  • Alternatively, maybe, all those dedicated clients like WANEthernetLinkConfig1, LANHostConfigManagement1 etc could share a small API like interface { setGenericClient(goupnp.ServiceClient} and he package goupnp provides a MultiServiceClient []GenericClientSetter API to distribute calls to individual underlying setGenericClient instances, whichever their upnp implementations is. (After further reading, i figured we could define func (client *ServiceClient) setGenericClient(n *ServiceClient){*client=*n}, thus, all dedicated upnp implementations would benefit from it. I don't usually write this kind of method, but that should do it, I guess)
  • that MultiServiceClient Could also provide some filtering/mapping methods to group services by LocationURL/USN/Device ID/Local Address. That would be useful to work when dealing with multi homed setup.
  • So that "client" is something about the discovery process, guessing.

I dont share the sketch because it contains lots of useless dup code that would not exist if i had used a fork directly, i worked around. Although, here is the resulting equivalent invocation of the API, to figure how different that would be

	addrs := []string{"0.0.0.0"}
	addrs = append(addrs, upnp.SomeLocalIPv4MCastAddrs()...)
	log.Println(addrs)
	client, err := upnp.NewHTTPUMultiClient(addrs...)
	if err != nil {
		return err
	}
	rootDevices, err := upnp.DiscoverDevicesCtx(context.Background(), client, ssdp.UPNPRootDevice)
	if err != nil {
		return err
	}

	for _, rtDevice := range rootDevices {
		log.Println(rtDevice.LocalAddr)
		log.Println(rtDevice.Location)
		log.Println(rtDevice.Root.SpecVersion)
		log.Println(rtDevice.Root.Device.String())
	}

from goupnp.

mh-cbon avatar mh-cbon commented on June 12, 2024

I ended up crafting many things.

mh-cbon@f058335

there is a new discovery client like

client := goupnp.Discovery{
		Client: goupnp.AutoDiscover("0.0.0.0"),
	}

A new kind to lookup services and provide concrete implementations such as lookup.ServiceTable map[string]func(goupnp.MultiServiceClient) ServiceImplementations

svcTable := lookup.ServiceTable{
		internetgateway2.URN_WANIPConnection_1: func(services goupnp.MultiServiceClient) (out lookup.ServiceImplementations) {
			for _, v := range internetgateway2.NewWANIPConnection1MultiClients(services) {
				out = append(out, v)
			}
			return
		},

Then it is possible to resolve services to implementation with a call like

	searchTargets := svcTable.Targets() // internetgateway2.URN_WANIPConnection_1, internetgateway2.URN_WANIPConnection_2 etc....
	services, _, err := client.NewServiceClientsCtx(context.Background(), searchTargets...)
	if err != nil {
		return err
	}

	// do some filterings...
	// hostwhatever := services.Filter(goupnp.ServiceHost("192..."))

        impls := svcTable.Implement(services)
        // impls is interface []lookup.ServiceImplementation, which in turn is one of ` internetgateway2.NewWANIPConnection1MultiClients` , ` internetgateway2.NewWANIPConnection2MultiClients` etc... 


	for _, impl := range impls {
		fmt.Printf("%T\n", impl)
		// need type assert
		// impl.(portmapping.PortMapper).AddPortMapping()
	}

The Template is modified to instantiate DCP client for a MultiServiceClient, such as

func NewDeviceProtection1MultiClients(mutiClients goupnp.MultiServiceClient) []*DeviceProtection1 {
	services := mutiClients.Filter(goupnp.ServiceType(URN_DeviceProtection_1)) // The service clients are filtered to keep only those that corresponds to the related service definition
	clients := make([]*DeviceProtection1, len(services))
	for i := range services {
		clients[i] = &DeviceProtection1{services[i]}
	}
	return clients
}

The MultiServiceClient is a list filter that works on the flat list of all services for every local addr.
It defines, func (m MultiServiceClient) Filter(f ServiceClientFilter, andAlso ...ServiceClientFilter) MultiServiceClient and type ServiceClientFilter func(ServiceClient) bool. With some dedicated helpers such as ServiceType(ts ...string) ServiceClientFilter .

Doing so, i was able to define an appropriate IGW service table

var IGW = lookup.ServiceTable{
	internetgateway1.URN_WANIPConnection_1: func(services goupnp.MultiServiceClient) (out lookup.ServiceImplementations) {
		for _, v := range internetgateway1.NewWANIPConnection1MultiClients(services) {
			out = append(out, v)
		}
		return
	},
	internetgateway2.URN_WANIPConnection_2: func(services goupnp.MultiServiceClient) (out lookup.ServiceImplementations) {
		for _, v := range internetgateway2.NewWANIPConnection2MultiClients(services) {
			out = append(out, v)
		}
		return
	},
}

And its accompanying Lookup function to deal with virtual any addresses.

	var out []PortMapperClient

	searchTargets := IGW.Targets()

	services, _, err := client.NewServiceClientsCtx(context.Background(), searchTargets...)
	if err != nil {
		return out, err
	}

	impls := IGW.ExpandAnyAddr(services)
	for _, impl := range impls {
		switch x := impl.(type) {
		case lookup.AnyAddrServiceImplementation:
			out = append(out, PortMapperClient{
				PortMapper:            x.ServiceImplementation.(PortMapper),
				ServiceImplementation: impl,
			})
		case PortMapper:
			out = append(out, PortMapperClient{
				PortMapper:            x,
				ServiceImplementation: impl,
			})
		default:
			log.Printf("not a port mapper %T", x)
		}
	}

in the end it can fetch all clients, resolve the virtual address thing and get the right local address to use for the call to AddPortMapping when it encounters anyAddr.

	for _, impl := range impls {
		switch x := impl.(type) {
		case lookup.AnyAddrServiceImplementation:
			fmt.Printf("%v should port map using %v\n", x.ServiceImplementation.LocalAddr(), impl.LocalAddr())
		}
		_, ok := impl.(portmapping.PortMapper)
		fmt.Printf("%T PortMapper=%v\n", impl, ok)
		printService(*impl.GetServiceClient())
	}

anyway.. Much.... much changes !

from goupnp.

huin avatar huin commented on June 12, 2024

Sorry for the slow reply, I tend to only deal with this repo on weekends.

A few questions/comments:

  • MultiServiceClient - is this something that benefits from being in this library, or might be better placed in a more specific library or application codebase? That is: is its utility general and not special purpose?

  • httpu.HTTPUClient:

    • having a new address field (presumably affecting which address+interface it talks on) - or perhaps receiving the local address as a parameter - this seems possible without breaking compatibility, by either adding a new New* function, or better yet: using functional options. This pattern could be applied elsewhere.

    • httpu.HTTPUClient.Do is modified to set the response header key httpu.LocalAddressHeader to the related httpuClient.Addr instead of httpuClient.conn.LocalAddr

      What if instead the HTTPClient.conn field was created based on the given local address? And then set the header based on that (which should be effectively the same, for specific addresses?)

  • ssdp.SSDPRawSearch:

    • taking a client parameter - it already receives an HTTPU client, perhaps we simply need to broaden it to accepting ClientInterface instead of a concrete type?

    • Changing the dedupe behaviour - also possible to change without breaking compatibility with functional options. Perhaps provide a function that maps from result to deduplication key? (with the default being the current behaviour).

      All that said - isn't this achievable by users of the library calling SSDPRawSearch multiple times (optionally concurrently)?

  • goupnp.DiscoverDevicesCtx and others, are modified to take in a client httpu.ClientInterface

    Seems reasonable - functional options for compatibility?

from goupnp.

mh-cbon avatar mh-cbon commented on June 12, 2024

hi, no worries for the late reply, thanks for the inputs.

About functional options, it surely could do few things, although, this is still breaking the API signature. I dont think this is right for someone that built up some APIs that relies on interfaces, or functions type def.

About httpuClient, the reason I did that is that if you do net.Listen("0.0.0.0").Addr() you dont exactly get back 0.0.0.0, but its ipv6 equivalent notation. It is anything but obvious. So that seem just right to re use the strict input.

About ssdpRawSearch, bad comment on my part, sorry, indeed it already receives one, it just happens I had to rename the variable because it was conflicting with the newly imported httpu package, and misread that. Although, again, such change, take n an hypothetical ClientInterface would break signature APIs.

About MultiServiceClient, I have introduced that new type in order to manipulate found clients before they are instantiated, filtering and select against the multiple upnp gateways. Otherwise, such API leaks into all generated types (

func newLANHostConfigManagement1ClientsFromGenericClients(genericClients []goupnp.ServiceClient) []*LANHostConfigManagement1 {
for example), or it needed an extra step to unbox the clients from their instance implementation, then rebox into some slice type to perform filtering, then later, re instantiate the specific services client, it did not look right at the time of writing.
Although, this is definitely an intentional major shift in regard to the current API design.

Overall, besides the specific implementation details change, like the dedup mechanism, which could be made configurable, one way or another, it is a lot of changes, despite my attempts at being conservative (i tried = ). I feel mixed about it regarding the shift in the design api. Maybe i kept that away and for an eventual V2 this is considered.

from goupnp.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.