Commit 7c2f7e5a authored by Muhammad Umair Khan's avatar Muhammad Umair Khan
Browse files

fix issue #12: correct AefProfile.InterfaceDescription mapping per ETSI CAPIF spec

parent a25c01de
Loading
Loading
Loading
Loading
+24 −65
Original line number Diff line number Diff line
@@ -1227,13 +1227,9 @@ components:
              - apiVersion
        interfaceDescriptions:
          description: This type represents information about a transport endpoint
          oneOf:
          - $ref: '#/components/schemas/EndPointInfo.Uris'
          - $ref: '#/components/schemas/EndPointInfo.Fqdn'
          - $ref: '#/components/schemas/EndPointInfo.Addresses'
          - $ref: '#/components/schemas/EndPointInfo.Alternative'
          x-etsi-notes: "NOTE:\tExactly one of \"uris\", \"fqdn\", \"addresses\" or\
            \ \"alternative\" shall be present."
          type: array
          items:
            $ref: '#/components/schemas/InterfaceDescription'
        vendorSpecific-urn:etsi:mec:capifext:transport-info :
          $ref: '#/components/schemas/MecTransportInfoCapifExt'
          description: "Additional attribute of data type MecTransportInfoCapifExt for MEC-specific CAPIF extensions related to alternative transports."
@@ -1331,69 +1327,32 @@ components:
      - RPC
      - RPC_STREAMING
      - WEBSOCKET
    EndPointInfo.Alternative:
      title: EndPointInfo.Alternative
      required:
      - alternative
      type: object
      properties:
        alternative:
          type: object
          description: "Entry point information of the service in a format defined\
            \ by an implementation, or in an external specification. See note."
      description: This type represents information about a transport endpoint.
    EndPointInfo.Address:
      title: EndPointInfo.Address
      required:
      - host
      - port
    InterfaceDescription:
      type: object
      description: 3GPP-compliant InterfaceDescription (TS 29.222 Table 8.2.4.2.3-1)
      properties:
        host:
        ipv4Addr:
          type: string
          description: Host portion of the address
          example: "[\"192.0.2.0\"]"
        port:
          type: integer
          description: Port portion of the address
      description: A IP address and port pair
    EndPointInfo.Addresses:
      title: EndPointInfo.Addresses
      required:
      - addresses
      type: object
      properties:
        addresses:
          type: array
          description: Entry point information of the service as one or more pairs
            of IP address and port. See note.
          items:
            $ref: '#/components/schemas/EndPointInfo.Address'
    EndPointInfo.Fqdn:
      title: EndPointInfo.Fqdn
      required:
      - fqdn
      type: object
      properties:
          format: ipv4
          description: IPv4 address
        ipv6Addr:
          type: string
          format: ipv6
          description: IPv6 address
        fqdn:
          type: array
          description: Fully Qualified Domain Name of the service. See note.
          items:
          type: string
      description: 'This type represents information about a transport endpoint. '
    EndPointInfo.Uris:
      title: EndPointInfo.Uris
      required:
      - uris
      type: object
      properties:
        uris:
          type: array
          description: "Entry point information of the service as string, formatted\
            \ according to URI syntax"
          items:
          description: Fully Qualified Domain Name
        port:
          type: integer
          description: TCP port number
        apiPrefix:
          type: string
      description: This type represents information about a transport endpoint.
          description: Optional API prefix path (starts with '/')
      oneOf:
        - required: [ipv4Addr]
        - required: [ipv6Addr]
        - required: [fqdn]
      additionalProperties: false
  responses:
    "400":
      description: Bad Request. It is used to indicate that incorrect parameters were
+1 −1
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@ type AefProfile struct {

	Versions []Version `json:"versions"`
	// This type represents information about a transport endpoint
	InterfaceDescriptions *OneOfTransportInfoEndpoint `json:"interfaceDescriptions,omitempty"`
	InterfaceDescriptions []InterfaceDescription `json:"interfaceDescriptions,omitempty"`

	VendorSpecificUrnetsimeccapifexttransportInfo *MecTransportInfoCapifExt `json:"vendorSpecific-urn:etsi:mec:capifext:transport-info,omitempty"`
}
+26 −0
Original line number Diff line number Diff line
/*
 * MEC Service Management API
 *
 * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI
 *
 * API version: 3.1.1
 * Contact: cti_support@etsi.org
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */

package server

// Address represents a (host, port) pair
type Address struct {
	Host string `json:"host"`
	Port int    `json:"port"`
}

// EndPointInfo represents transport endpoint information.
// Exactly one of uris, fqdn, addresses, or alternative must be present.
type EndPointInfo struct {
	Uris        []string    `json:"uris,omitempty"`
	Fqdn        []string    `json:"fqdn,omitempty"`
	Addresses   []Address   `json:"addresses,omitempty"`
	Alternative interface{} `json:"alternative,omitempty"` // flexible placeholder
}
+18 −0
Original line number Diff line number Diff line
/*
 * MEC Service Management API
 *
 * The ETSI MEC ISG MEC011 MEC Service Management API described using OpenAPI
 *
 * API version: 3.1.1
 * Contact: cti_support@etsi.org
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */
package server

type InterfaceDescription struct {
	Ipv4Addr  *string `json:"ipv4Addr,omitempty"`
	Ipv6Addr  *string `json:"ipv6Addr,omitempty"`
	Fqdn      *string `json:"fqdn,omitempty"`
	Port      *int    `json:"port,omitempty"`
	ApiPrefix *string `json:"apiPrefix,omitempty"`
}
+202 −6
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import (
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strconv"
@@ -323,6 +324,11 @@ func appServicesPOST(w http.ResponseWriter, r *http.Request) {
		errHandlerProblemDetails(w, errStr, http.StatusBadRequest)
		return
	}
	if err := validateInterfaceDescriptions(sInfoPost.AefProfiles[0].InterfaceDescriptions); err != nil {
		log.Error(err.Error())
		errHandlerProblemDetails(w, err.Error(), http.StatusBadRequest)
		return
	}
	if len(sInfoPost.AefProfiles) == 0 || sInfoPost.AefProfiles[0].AefId == "" {
		errStr := "Mandatory AefProfiles (AefId) not present"
		log.Error(errStr)
@@ -374,7 +380,7 @@ func appServicesPOST(w http.ResponseWriter, r *http.Request) {
		Type_:    sInfoPost.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Type_,
		Protocol: sInfoPost.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Protocol,
		Version:  sInfoPost.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Version,
		Endpoint: sInfoPost.AefProfiles[0].InterfaceDescriptions,
		Endpoint: ConvertInterfaceDescriptionsToEndpoint(sInfoPost.AefProfiles[0].InterfaceDescriptions),
		Security: sInfoPost.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Security,
	}

@@ -510,7 +516,7 @@ func appServicesByIdPUT(w http.ResponseWriter, r *http.Request) {
		Type_:    sInfo.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Type_,
		Protocol: sInfo.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Protocol,
		Version:  sInfo.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Version,
		Endpoint: sInfo.AefProfiles[0].InterfaceDescriptions,
		Endpoint: ConvertInterfaceDescriptionsToEndpoint(sInfo.AefProfiles[0].InterfaceDescriptions),
		Security: sInfo.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Security,
	}

@@ -669,13 +675,14 @@ func appServicesByIdPATCH(w http.ResponseWriter, r *http.Request) {
			Category:          sInfo.VendorSpecificUrnetsimeccapifextserviceInfo.Category,
		},
	}

	transportInfo_ := TransportInfo{
		Id:       sInfo.AefProfiles[0].AefId,
		Name:     sInfo.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Name,
		Type_:    sInfo.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Type_,
		Protocol: sInfo.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Protocol,
		Version:  sInfo.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Version,
		Endpoint: sInfo.AefProfiles[0].InterfaceDescriptions,
		Endpoint: ConvertInterfaceDescriptionsToEndpoint(sInfo.AefProfiles[0].InterfaceDescriptions),
		Security: sInfo.AefProfiles[0].VendorSpecificUrnetsimeccapifexttransportInfo.Security,
	}
	// Create Service
@@ -1784,7 +1791,7 @@ func getServices(w http.ResponseWriter, r *http.Request, appId string) {
		aefProfile := AefProfile{
			AefId:                 service.TransportInfo.Id,
			Versions:              []Version{{APIVersion: service.Version}},
			InterfaceDescriptions: service.TransportInfo.Endpoint,
			InterfaceDescriptions: ConvertEndpointToInterfaceDescriptions(service.TransportInfo.Endpoint),
			VendorSpecificUrnetsimeccapifexttransportInfo: &MecTransportInfoCapifExt{
				Name:     service.TransportInfo.Name,
				Type_:    service.TransportInfo.Type_,
@@ -1861,7 +1868,7 @@ func getService(w http.ResponseWriter, r *http.Request, appId string, serviceId
		aefProfile := AefProfile{
			AefId:                 service.TransportInfo.Id,
			Versions:              []Version{{APIVersion: service.Version}},
			InterfaceDescriptions: service.TransportInfo.Endpoint,
			InterfaceDescriptions: ConvertEndpointToInterfaceDescriptions(service.TransportInfo.Endpoint),
			VendorSpecificUrnetsimeccapifexttransportInfo: &MecTransportInfoCapifExt{
				Name:     service.TransportInfo.Name,
				Type_:    service.TransportInfo.Type_,
@@ -2091,7 +2098,7 @@ func checkSerAvailNotification(sInfo *ServiceInfo, mep string, changeType CapifE
		aefProfile := &AefProfile{
			AefId:                 sInfo.TransportInfo.Id,
			Versions:              []Version{{APIVersion: sInfo.Version}},
			InterfaceDescriptions: sInfo.TransportInfo.Endpoint,
			InterfaceDescriptions: ConvertEndpointToInterfaceDescriptions(sInfo.TransportInfo.Endpoint),
			VendorSpecificUrnetsimeccapifexttransportInfo: &MecTransportInfoCapifExt{
				Name:     sInfo.TransportInfo.Name,
				Type_:    sInfo.TransportInfo.Type_,
@@ -2312,3 +2319,192 @@ func errHandlerProblemDetails(w http.ResponseWriter, error string, code int) {
	w.WriteHeader(code)
	fmt.Fprint(w, jsonResponse)
}

func validateInterfaceDescriptions(descs []InterfaceDescription) error {
	for i, desc := range descs {
		hasAddr := desc.Ipv4Addr != nil || desc.Ipv6Addr != nil
		hasFqdn := desc.Fqdn != nil

		if (hasAddr && hasFqdn) || (!hasAddr && !hasFqdn) {
			return fmt.Errorf("InterfaceDescription[%d]: exactly one of ipv4Addr/ipv6Addr or fqdn must be set", i)
		}
	}
	return nil
}

func ConvertInterfaceDescriptionsToEndpoint(descs []InterfaceDescription) *OneOfTransportInfoEndpoint {
	if len(descs) == 0 {
		return nil
	}

	// 1. Try to construct URI(s) when all required fields are available
	var uris []string
	allHaveHost := true
	for _, desc := range descs {
		var host string
		switch {
		case desc.Ipv4Addr != nil:
			host = *desc.Ipv4Addr
		case desc.Ipv6Addr != nil:
			host = "[" + *desc.Ipv6Addr + "]"
		case desc.Fqdn != nil:
			host = *desc.Fqdn
		default:
			allHaveHost = false
		}

		if host == "" {
			allHaveHost = false
			continue
		}

		uri := "http://" + host
		if desc.Port != nil {
			uri += ":" + fmt.Sprintf("%d", *desc.Port)
		}
		if desc.ApiPrefix != nil {
			uri += *desc.ApiPrefix
		}
		uris = append(uris, uri)
	}

	if allHaveHost && len(uris) > 0 {
		return &OneOfTransportInfoEndpoint{
			EndPointInfoUris: EndPointInfoUris{
				Uris: uris,
			},
		}
	}

	// 2. Try FQDN (without port)
	var fqdns []string
	for _, desc := range descs {
		if desc.Fqdn != nil && desc.Port == nil {
			fqdns = append(fqdns, *desc.Fqdn)
		}
	}
	if len(fqdns) > 0 {
		return &OneOfTransportInfoEndpoint{
			EndPointInfoFqdn: EndPointInfoFqdn{
				Fqdn: fqdns,
			},
		}
	}

	// 3. Fallback to addresses (host + port)
	var addresses []EndPointInfoAddress
	for _, desc := range descs {
		if desc.Port == nil {
			continue
		}

		var host string
		switch {
		case desc.Ipv4Addr != nil:
			host = *desc.Ipv4Addr
		case desc.Ipv6Addr != nil:
			host = *desc.Ipv6Addr
		case desc.Fqdn != nil:
			host = *desc.Fqdn
		}

		if host != "" {
			addresses = append(addresses, EndPointInfoAddress{
				Host: host,
				Port: int32(*desc.Port),
			})
		}
	}

	if len(addresses) > 0 {
		return &OneOfTransportInfoEndpoint{
			EndPointInfoAddresses: EndPointInfoAddresses{
				Addresses: addresses,
			},
		}
	}

	// 4. None matched: treat as bad input
	return nil
}

func ConvertEndpointToInterfaceDescriptions(endpoint *OneOfTransportInfoEndpoint) []InterfaceDescription {
	if endpoint == nil {
		return nil
	}

	var descs []InterfaceDescription

	// 1. uris → try to extract apiPrefix and host/port info
	if len(endpoint.Uris) > 0 {
		for _, uriStr := range endpoint.Uris {
			u, err := url.Parse(uriStr)
			if err != nil || u.Host == "" {
				continue
			}

			host, portStr, _ := net.SplitHostPort(u.Host)
			var port *int
			if portStr != "" {
				p, err := strconv.Atoi(portStr)
				if err == nil {
					port = &p
				}
			} else {
				host = u.Host
			}

			desc := InterfaceDescription{
				ApiPrefix: &u.Path,
				Port:      port,
			}

			if ip := net.ParseIP(host); ip != nil {
				if ip.To4() != nil {
					desc.Ipv4Addr = &host
				} else {
					desc.Ipv6Addr = &host
				}
			} else {
				desc.Fqdn = &host
			}

			descs = append(descs, desc)
		}
		return descs
	}

	// 2. fqdn (no port)
	if len(endpoint.Fqdn) > 0 {
		for _, fqdn := range endpoint.Fqdn {
			desc := InterfaceDescription{
				Fqdn: &fqdn,
			}
			descs = append(descs, desc)
		}
		return descs
	}

	// 3. addresses
	if len(endpoint.Addresses) > 0 {
		for _, addr := range endpoint.Addresses {
			port := int(addr.Port)
			desc := InterfaceDescription{
				Port: &port,
			}
			if ip := net.ParseIP(addr.Host); ip != nil {
				if ip.To4() != nil {
					desc.Ipv4Addr = &addr.Host
				} else {
					desc.Ipv6Addr = &addr.Host
				}
			} else {
				desc.Fqdn = &addr.Host
			}
			descs = append(descs, desc)
		}
		return descs
	}

	return nil
}
Loading