//go:build windows

// Shim for the Host Network Service (HNS) to manage networking for
// Windows Server containers and Hyper-V containers. This module
// is a basic libnetwork driver that passes all the calls to HNS
// It implements the 4 networking modes supported by HNS L2Bridge,
// L2Tunnel, NAT and Transparent(DHCP)
//
// The network are stored in memory and docker daemon ensures discovering
// and loading these networks on startup

package windows

import (
	"context"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"github.com/Microsoft/hcsshim"
	"github.com/containerd/log"
	"github.com/docker/docker/libnetwork/datastore"
	"github.com/docker/docker/libnetwork/driverapi"
	"github.com/docker/docker/libnetwork/netlabel"
	"github.com/docker/docker/libnetwork/portmapper"
	"github.com/docker/docker/libnetwork/scope"
	"github.com/docker/docker/libnetwork/types"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
)

// networkConfiguration for network specific configuration
type networkConfiguration struct {
	ID                    string
	Type                  string
	Name                  string
	HnsID                 string
	RDID                  string
	VLAN                  uint
	VSID                  uint
	DNSServers            string
	MacPools              []hcsshim.MacPool
	DNSSuffix             string
	SourceMac             string
	NetworkAdapterName    string
	dbIndex               uint64
	dbExists              bool
	DisableGatewayDNS     bool
	EnableOutboundNat     bool
	OutboundNatExceptions []string
}

// endpointConfiguration represents the user specified configuration for the sandbox endpoint
type endpointOption struct {
	MacAddress  net.HardwareAddr
	QosPolicies []types.QosPolicy
	DNSServers  []string
	DisableDNS  bool
	DisableICC  bool
}

// EndpointConnectivity stores the port bindings and exposed ports that the user has specified in epOptions.
type EndpointConnectivity struct {
	PortBindings []types.PortBinding
	ExposedPorts []types.TransportPort
}

type hnsEndpoint struct {
	id        string
	nid       string
	profileID string
	Type      string
	// Note: Currently, the sandboxID is the same as the containerID since windows does
	// not expose the sandboxID.
	// In the future, windows will support a proper sandboxID that is different
	// than the containerID.
	// Therefore, we are using sandboxID now, so that we won't have to change this code
	// when windows properly supports a sandboxID.
	sandboxID      string
	macAddress     net.HardwareAddr
	epOption       *endpointOption       // User specified parameters
	epConnectivity *EndpointConnectivity // User specified parameters
	portMapping    []types.PortBinding   // Operation port bindings
	addr           *net.IPNet
	gateway        net.IP
	dbIndex        uint64
	dbExists       bool
}

type hnsNetwork struct {
	id         string
	created    bool
	config     *networkConfiguration
	endpoints  map[string]*hnsEndpoint // key: endpoint id
	driver     *driver                 // The network's driver
	portMapper *portmapper.PortMapper
	sync.Mutex
}

type driver struct {
	name     string
	networks map[string]*hnsNetwork
	store    *datastore.Store
	sync.Mutex
}

const (
	errNotFound = "HNS failed with error : The object identifier does not represent a valid object. "
)

var builtinLocalDrivers = map[string]struct{}{
	"transparent": {},
	"l2bridge":    {},
	"l2tunnel":    {},
	"nat":         {},
	"internal":    {},
	"private":     {},
	"ics":         {},
}

// IsBuiltinLocalDriver validates if network-type is a builtin local-scoped driver
func IsBuiltinLocalDriver(networkType string) bool {
	_, ok := builtinLocalDrivers[networkType]
	return ok
}

var unadoptableNetworkTypes = map[string]struct{}{
	// "internal" and "private" are included here to preserve the workarounds added
	// in commits b91fd26 ("Ignore HNS networks with type Private") and 6a1a4f9 ("Fix
	// long startup on windows, with non-hns governed Hyper-V networks").
	//
	// That long delay was caused by trying to load network driver plugins named
	// "internal" and "private". The workaround doesn't seem necessary now, because
	// those network types are both associated with the windows [driver] by the
	// entries in [builtinLocalDrivers]. (The code has been refactored, but those
	// network types were included at the time the workaround was added. Something
	// else must have changed in the meantime.)
	//
	// TODO(robmry) - remove internal/private from this map? ...
	//
	// On startup the daemon tries to adopt all existing HNS networks by creating
	// corresponding Docker networks. (HNS is the source of truth for Windows, not
	// Docker's network store.)
	//
	// So, removing internal/private from this map would have two consequences
	// (removing restrictions that were probably introduced unintentionally by the
	// workarounds mentioned above):
	// - internal/private networks created on the host would appear in Docker's list
	//   of networks, and it'd be possible to create containers in those networks.
	//   That would match the behaviour for other network types (including the other
	//   currently-undocumented type "ics").
	// - On startup, if an internal/private network originally created by Docker has
	//   been reconfigured outside Docker, the network definition in Docker's store
	//   will be updated to match the current HNS configuration (as it is for other
	//   network types).
	"internal": {},
	"private":  {},

	// "mirrored" is not currently in the list of HNS network types understood by
	// Docker [builtinLocalDrivers]. So, it's not possible for Docker to create a
	// network of this type. And, if the host has "mirrored" networks, Docker
	// should not spend time on startup trying to load a plugin called "mirrored".
	"mirrored": {},
}

// IsAdoptableNetworkType returns true if HNS networks of this type can be
// adopted on startup when searching for networks created outside Docker
// (these networks can be added to the network store).
func IsAdoptableNetworkType(networkType string) bool {
	_, ok := unadoptableNetworkTypes[strings.ToLower(networkType)]
	return !ok
}

func newDriver(networkType string, store *datastore.Store) (*driver, error) {
	d := &driver{
		name:     networkType,
		store:    store,
		networks: map[string]*hnsNetwork{},
	}
	err := d.initStore()
	if err != nil {
		return nil, fmt.Errorf("failed to initialize %q driver: %w", networkType, err)
	}
	return d, nil
}

// RegisterBuiltinLocalDrivers registers the builtin local drivers.
func RegisterBuiltinLocalDrivers(r driverapi.Registerer, store *datastore.Store) error {
	for networkType := range builtinLocalDrivers {
		d, err := newDriver(networkType, store)
		if err != nil {
			return err
		}
		err = r.RegisterDriver(networkType, d, driverapi.Capability{
			DataScope:         scope.Local,
			ConnectivityScope: scope.Local,
		})
		if err != nil {
			return fmt.Errorf("failed to register %q driver: %w", networkType, err)
		}
	}
	return nil
}

func (d *driver) getNetwork(id string) (*hnsNetwork, error) {
	d.Lock()
	defer d.Unlock()

	if nw, ok := d.networks[id]; ok {
		return nw, nil
	}

	return nil, types.NotFoundErrorf("network not found: %s", id)
}

func (n *hnsNetwork) getEndpoint(eid string) (*hnsEndpoint, error) {
	n.Lock()
	defer n.Unlock()

	if ep, ok := n.endpoints[eid]; ok {
		return ep, nil
	}

	return nil, types.NotFoundErrorf("Endpoint not found: %s", eid)
}

func (d *driver) parseNetworkOptions(id string, genericOptions map[string]string) (*networkConfiguration, error) {
	config := &networkConfiguration{Type: d.name}

	for label, value := range genericOptions {
		switch label {
		case NetworkName:
			config.Name = value
		case HNSID:
			config.HnsID = value
		case RoutingDomain:
			config.RDID = value
		case Interface:
			config.NetworkAdapterName = value
		case DNSSuffix:
			config.DNSSuffix = value
		case DNSServers:
			config.DNSServers = value
		case DisableGatewayDNS:
			b, err := strconv.ParseBool(value)
			if err != nil {
				return nil, err
			}
			config.DisableGatewayDNS = b
		case MacPool:
			config.MacPools = make([]hcsshim.MacPool, 0)
			s := strings.Split(value, ",")
			if len(s)%2 != 0 {
				return nil, types.InvalidParameterErrorf("invalid mac pool. You must specify both a start range and an end range")
			}
			for i := 0; i < len(s)-1; i += 2 {
				config.MacPools = append(config.MacPools, hcsshim.MacPool{
					StartMacAddress: s[i],
					EndMacAddress:   s[i+1],
				})
			}
		case VLAN:
			vlan, err := strconv.ParseUint(value, 10, 32)
			if err != nil {
				return nil, err
			}
			config.VLAN = uint(vlan)
		case VSID:
			vsid, err := strconv.ParseUint(value, 10, 32)
			if err != nil {
				return nil, err
			}
			config.VSID = uint(vsid)
		case EnableOutboundNat:
			b, err := strconv.ParseBool(value)
			if err != nil {
				return nil, err
			}
			config.EnableOutboundNat = b
		case OutboundNatExceptions:
			s := strings.Split(value, ",")
			config.OutboundNatExceptions = s
		}
	}

	config.ID = id
	config.Type = d.name
	return config, nil
}

func (ncfg *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []driverapi.IPAMData) error {
	if len(ipamV6Data) > 0 {
		return types.ForbiddenErrorf("windowsshim driver doesn't support v6 subnets")
	}

	if len(ipamV4Data) == 0 {
		return types.InvalidParameterErrorf("network %s requires ipv4 configuration", id)
	}

	return nil
}

func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
}

func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
	return "", nil
}

func (d *driver) createNetwork(config *networkConfiguration) *hnsNetwork {
	network := &hnsNetwork{
		id:         config.ID,
		endpoints:  make(map[string]*hnsEndpoint),
		config:     config,
		driver:     d,
		portMapper: portmapper.New(),
	}

	d.Lock()
	d.networks[config.ID] = network
	d.Unlock()

	return network
}

// Create a new network
func (d *driver) CreateNetwork(ctx context.Context, id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
	if _, err := d.getNetwork(id); err == nil {
		return types.ForbiddenErrorf("network %s exists", id)
	}

	genData, ok := option[netlabel.GenericData].(map[string]string)
	if !ok {
		return fmt.Errorf("Unknown generic data option")
	}

	if v, ok := option[netlabel.EnableIPv4]; ok {
		if enable_IPv4, ok := v.(bool); ok && !enable_IPv4 {
			return types.InvalidParameterErrorf("IPv4 cannot be disabled on Windows")
		}
	}

	// Parse and validate the config. It should not conflict with existing networks' config
	config, err := d.parseNetworkOptions(id, genData)
	if err != nil {
		return err
	}

	err = config.processIPAM(id, ipV4Data, ipV6Data)
	if err != nil {
		return err
	}

	n := d.createNetwork(config)

	// A non blank hnsid indicates that the network was discovered
	// from HNS. No need to call HNS if this network was discovered
	// from HNS
	if config.HnsID == "" {
		subnets := []hcsshim.Subnet{}

		for _, ipData := range ipV4Data {
			subnet := hcsshim.Subnet{
				AddressPrefix: ipData.Pool.String(),
			}

			if ipData.Gateway != nil {
				subnet.GatewayAddress = ipData.Gateway.IP.String()
			}

			subnets = append(subnets, subnet)
		}

		network := &hcsshim.HNSNetwork{
			Name:               config.Name,
			Type:               d.name,
			Subnets:            subnets,
			DNSServerList:      config.DNSServers,
			DNSSuffix:          config.DNSSuffix,
			MacPools:           config.MacPools,
			SourceMac:          config.SourceMac,
			NetworkAdapterName: config.NetworkAdapterName,
		}

		if config.VLAN != 0 {
			vlanPolicy, err := json.Marshal(hcsshim.VlanPolicy{
				Type: "VLAN",
				VLAN: config.VLAN,
			})
			if err != nil {
				return err
			}
			network.Policies = append(network.Policies, vlanPolicy)
		}

		if config.VSID != 0 {
			vsidPolicy, err := json.Marshal(hcsshim.VsidPolicy{
				Type: "VSID",
				VSID: config.VSID,
			})
			if err != nil {
				return err
			}
			network.Policies = append(network.Policies, vsidPolicy)
		}

		if network.Name == "" {
			network.Name = id
		}

		configurationb, err := json.Marshal(network)
		if err != nil {
			return err
		}

		configuration := string(configurationb)
		log.G(ctx).Debugf("HNSNetwork Request =%v Address Space=%v", configuration, subnets)

		hnsresponse, err := hcsshim.HNSNetworkRequest(http.MethodPost, "", configuration)
		if err != nil {
			return err
		}

		config.HnsID = hnsresponse.Id
		genData[HNSID] = config.HnsID
		n.created = true

		defer func() {
			if err != nil {
				d.DeleteNetwork(n.id)
			}
		}()

		hnsIPv4Data := make([]driverapi.IPAMData, len(hnsresponse.Subnets))

		for i, subnet := range hnsresponse.Subnets {
			var gwIP, subnetIP *net.IPNet

			// The gateway returned from HNS is an IPAddress.
			// We need to convert it to an IPNet to use as the Gateway of driverapi.IPAMData struct
			gwCIDR := subnet.GatewayAddress + "/32"
			_, gwIP, err = net.ParseCIDR(gwCIDR)
			if err != nil {
				return err
			}

			hnsIPv4Data[i].Gateway = gwIP
			_, subnetIP, err = net.ParseCIDR(subnet.AddressPrefix)
			if err != nil {
				return err
			}
			hnsIPv4Data[i].Pool = subnetIP
		}

		nInfo.UpdateIpamConfig(hnsIPv4Data)

	} else {
		// Delete any stale HNS endpoints for this network.
		if endpoints, err := hcsshim.HNSListEndpointRequest(); err == nil {
			for _, ep := range endpoints {
				if ep.VirtualNetwork == config.HnsID {
					log.G(ctx).Infof("Removing stale HNS endpoint %s", ep.Id)
					_, err = hcsshim.HNSEndpointRequest(http.MethodDelete, ep.Id, "")
					if err != nil {
						log.G(ctx).Warnf("Error removing HNS endpoint %s", ep.Id)
					}
				}
			}
		} else {
			log.G(ctx).Warnf("Error listing HNS endpoints for network %s", config.HnsID)
		}

		n.created = true
	}

	return d.storeUpdate(config)
}

func (d *driver) DeleteNetwork(nid string) error {
	n, err := d.getNetwork(nid)
	if err != nil {
		return types.InternalMaskableErrorf("%v", err)
	}

	n.Lock()
	config := n.config
	n.Unlock()

	if n.created {
		_, err = hcsshim.HNSNetworkRequest(http.MethodDelete, config.HnsID, "")
		if err != nil && !strings.EqualFold(err.Error(), errNotFound) {
			return types.ForbiddenErrorf("%v", err)
		}
	}

	d.Lock()
	delete(d.networks, nid)
	d.Unlock()

	// delete endpoints belong to this network
	for _, ep := range n.endpoints {
		if err := d.storeDelete(ep); err != nil {
			log.G(context.TODO()).Warnf("Failed to remove bridge endpoint %.7s from store: %v", ep.id, err)
		}
	}

	return d.storeDelete(config)
}

func convertQosPolicies(qosPolicies []types.QosPolicy) ([]json.RawMessage, error) {
	var qps []json.RawMessage

	// Enumerate through the qos policies specified by the user and convert
	// them into the internal structure matching the JSON blob that can be
	// understood by the HCS.
	for _, elem := range qosPolicies {
		encodedPolicy, err := json.Marshal(hcsshim.QosPolicy{
			Type:                            "QOS",
			MaximumOutgoingBandwidthInBytes: elem.MaxEgressBandwidth,
		})
		if err != nil {
			return nil, err
		}
		qps = append(qps, encodedPolicy)
	}
	return qps, nil
}

// ConvertPortBindings converts PortBindings to JSON for HNS request
func ConvertPortBindings(portBindings []types.PortBinding) ([]json.RawMessage, error) {
	var pbs []json.RawMessage

	// Enumerate through the port bindings specified by the user and convert
	// them into the internal structure matching the JSON blob that can be
	// understood by the HCS.
	for _, elem := range portBindings {
		proto := strings.ToUpper(elem.Proto.String())
		if proto != "TCP" && proto != "UDP" {
			return nil, fmt.Errorf("invalid protocol %s", elem.Proto.String())
		}

		if elem.HostPort != elem.HostPortEnd {
			return nil, fmt.Errorf("Windows does not support more than one host port in NAT settings")
		}

		if len(elem.HostIP) != 0 && !elem.HostIP.IsUnspecified() {
			return nil, fmt.Errorf("Windows does not support host IP addresses in NAT settings")
		}

		encodedPolicy, err := json.Marshal(hcsshim.NatPolicy{
			Type:                 "NAT",
			ExternalPort:         elem.HostPort,
			InternalPort:         elem.Port,
			Protocol:             elem.Proto.String(),
			ExternalPortReserved: true,
		})
		if err != nil {
			return nil, err
		}
		pbs = append(pbs, encodedPolicy)
	}
	return pbs, nil
}

// ParsePortBindingPolicies parses HNS endpoint response message to PortBindings
func ParsePortBindingPolicies(policies []json.RawMessage) ([]types.PortBinding, error) {
	var bindings []types.PortBinding
	hcsPolicy := &hcsshim.NatPolicy{}

	for _, elem := range policies {

		if err := json.Unmarshal([]byte(elem), &hcsPolicy); err != nil || hcsPolicy.Type != "NAT" {
			continue
		}

		binding := types.PortBinding{
			HostPort:    hcsPolicy.ExternalPort,
			HostPortEnd: hcsPolicy.ExternalPort,
			Port:        hcsPolicy.InternalPort,
			Proto:       types.ParseProtocol(hcsPolicy.Protocol),
			HostIP:      net.IPv4(0, 0, 0, 0),
		}

		bindings = append(bindings, binding)
	}

	return bindings, nil
}

func parseEndpointOptions(epOptions map[string]interface{}) (*endpointOption, error) {
	if epOptions == nil {
		return nil, nil
	}

	ec := &endpointOption{}

	if opt, ok := epOptions[netlabel.MacAddress]; ok {
		if mac, ok := opt.(net.HardwareAddr); ok {
			ec.MacAddress = mac
		} else {
			return nil, fmt.Errorf("Invalid endpoint configuration")
		}
	}

	if opt, ok := epOptions[QosPolicies]; ok {
		if policies, ok := opt.([]types.QosPolicy); ok {
			ec.QosPolicies = policies
		} else {
			return nil, fmt.Errorf("Invalid endpoint configuration")
		}
	}

	if opt, ok := epOptions[netlabel.DNSServers]; ok {
		if dns, ok := opt.([]string); ok {
			ec.DNSServers = dns
		} else {
			return nil, fmt.Errorf("Invalid endpoint configuration")
		}
	}

	if opt, ok := epOptions[DisableICC]; ok {
		if disableICC, ok := opt.(bool); ok {
			ec.DisableICC = disableICC
		} else {
			return nil, fmt.Errorf("Invalid endpoint configuration")
		}
	}

	if opt, ok := epOptions[DisableDNS]; ok {
		if disableDNS, ok := opt.(bool); ok {
			ec.DisableDNS = disableDNS
		} else {
			return nil, fmt.Errorf("Invalid endpoint configuration")
		}
	}

	return ec, nil
}

// ParseEndpointConnectivity parses options passed to CreateEndpoint, specifically port bindings, and store in a endpointConnectivity object.
func ParseEndpointConnectivity(epOptions map[string]interface{}) (*EndpointConnectivity, error) {
	if epOptions == nil {
		return nil, nil
	}

	ec := &EndpointConnectivity{}

	if opt, ok := epOptions[netlabel.PortMap]; ok {
		if bs, ok := opt.([]types.PortBinding); ok {
			ec.PortBindings = bs
		} else {
			return nil, fmt.Errorf("Invalid endpoint configuration")
		}
	}

	if opt, ok := epOptions[netlabel.ExposedPorts]; ok {
		if ports, ok := opt.([]types.TransportPort); ok {
			ec.ExposedPorts = ports
		} else {
			return nil, fmt.Errorf("Invalid endpoint configuration")
		}
	}
	return ec, nil
}

func (d *driver) CreateEndpoint(ctx context.Context, nid, eid string, ifInfo driverapi.InterfaceInfo, epOptions map[string]interface{}) error {
	ctx, span := otel.Tracer("").Start(ctx, fmt.Sprintf("libnetwork.drivers.windows_%s.CreateEndpoint", d.name), trace.WithAttributes(
		attribute.String("nid", nid),
		attribute.String("eid", eid)))
	defer span.End()

	n, err := d.getNetwork(nid)
	if err != nil {
		return err
	}

	// Check if endpoint id is good and retrieve corresponding endpoint
	ep, err := n.getEndpoint(eid)
	if err == nil && ep != nil {
		return driverapi.ErrEndpointExists(eid)
	}

	endpointStruct := &hcsshim.HNSEndpoint{
		VirtualNetwork: n.config.HnsID,
	}

	epOption, err := parseEndpointOptions(epOptions)
	if err != nil {
		return err
	}
	epConnectivity, err := ParseEndpointConnectivity(epOptions)
	if err != nil {
		return err
	}

	macAddress := ifInfo.MacAddress()
	// Use the macaddress if it was provided
	if macAddress != nil {
		endpointStruct.MacAddress = strings.ReplaceAll(macAddress.String(), ":", "-")
	}

	portMapping := epConnectivity.PortBindings

	if n.config.Type == "l2bridge" || n.config.Type == "l2tunnel" {
		ip := net.IPv4(0, 0, 0, 0)
		if ifInfo.Address() != nil {
			ip = ifInfo.Address().IP
		}

		portMapping, err = AllocatePorts(n.portMapper, portMapping, ip)
		if err != nil {
			return err
		}

		defer func() {
			if err != nil {
				ReleasePorts(n.portMapper, portMapping)
			}
		}()
	}

	endpointStruct.Policies, err = ConvertPortBindings(portMapping)
	if err != nil {
		return err
	}

	qosPolicies, err := convertQosPolicies(epOption.QosPolicies)
	if err != nil {
		return err
	}
	endpointStruct.Policies = append(endpointStruct.Policies, qosPolicies...)

	if ifInfo.Address() != nil {
		endpointStruct.IPAddress = ifInfo.Address().IP
	}

	endpointStruct.DNSServerList = strings.Join(epOption.DNSServers, ",")

	// overwrite the ep DisableDNS option if DisableGatewayDNS was set to true during the network creation option
	if n.config.DisableGatewayDNS {
		log.G(ctx).Debugf("n.config.DisableGatewayDNS[%v] overwrites epOption.DisableDNS[%v]", n.config.DisableGatewayDNS, epOption.DisableDNS)
		epOption.DisableDNS = n.config.DisableGatewayDNS
	}

	if n.driver.name == "nat" && !epOption.DisableDNS {
		endpointStruct.EnableInternalDNS = true
		log.G(ctx).Debugf("endpointStruct.EnableInternalDNS =[%v]", endpointStruct.EnableInternalDNS)
	}

	endpointStruct.DisableICC = epOption.DisableICC

	// Inherit OutboundNat policy from the network
	if n.config.EnableOutboundNat {
		outboundNatPolicy, err := json.Marshal(hcsshim.OutboundNatPolicy{
			Policy:     hcsshim.Policy{Type: hcsshim.OutboundNat},
			Exceptions: n.config.OutboundNatExceptions,
		})
		if err != nil {
			return err
		}
		endpointStruct.Policies = append(endpointStruct.Policies, outboundNatPolicy)
	}

	configurationb, err := json.Marshal(endpointStruct)
	if err != nil {
		return err
	}

	hnsresponse, err := hcsshim.HNSEndpointRequest(http.MethodPost, "", string(configurationb))
	if err != nil {
		return err
	}

	mac, err := net.ParseMAC(hnsresponse.MacAddress)
	if err != nil {
		return err
	}

	// TODO For now the ip mask is not in the info generated by HNS
	endpoint := &hnsEndpoint{
		id:         eid,
		nid:        n.id,
		Type:       d.name,
		addr:       &net.IPNet{IP: hnsresponse.IPAddress, Mask: hnsresponse.IPAddress.DefaultMask()},
		macAddress: mac,
	}

	if hnsresponse.GatewayAddress != "" {
		endpoint.gateway = net.ParseIP(hnsresponse.GatewayAddress)
	}

	endpoint.profileID = hnsresponse.Id
	endpoint.epConnectivity = epConnectivity
	endpoint.epOption = epOption
	endpoint.portMapping, err = ParsePortBindingPolicies(hnsresponse.Policies)
	if err != nil {
		hcsshim.HNSEndpointRequest(http.MethodDelete, hnsresponse.Id, "")
		return err
	}

	n.Lock()
	n.endpoints[eid] = endpoint
	n.Unlock()

	if ifInfo.Address() == nil {
		ifInfo.SetIPAddress(endpoint.addr)
	}

	if macAddress == nil {
		ifInfo.SetMacAddress(endpoint.macAddress)
	}

	if err = d.storeUpdate(endpoint); err != nil {
		log.G(ctx).Errorf("Failed to save endpoint %.7s to store: %v", endpoint.id, err)
	}

	return nil
}

func (d *driver) DeleteEndpoint(nid, eid string) error {
	n, err := d.getNetwork(nid)
	if err != nil {
		return types.InternalMaskableErrorf("%v", err)
	}

	ep, err := n.getEndpoint(eid)
	if err != nil {
		return err
	}

	if n.config.Type == "l2bridge" || n.config.Type == "l2tunnel" {
		ReleasePorts(n.portMapper, ep.portMapping)
	}

	n.Lock()
	delete(n.endpoints, eid)
	n.Unlock()

	_, err = hcsshim.HNSEndpointRequest(http.MethodDelete, ep.profileID, "")
	if err != nil && !strings.EqualFold(err.Error(), errNotFound) {
		return err
	}

	if err := d.storeDelete(ep); err != nil {
		log.G(context.TODO()).Warnf("Failed to remove bridge endpoint %.7s from store: %v", ep.id, err)
	}
	return nil
}

func (d *driver) EndpointOperInfo(nid, eid string) (map[string]interface{}, error) {
	network, err := d.getNetwork(nid)
	if err != nil {
		return nil, err
	}

	ep, err := network.getEndpoint(eid)
	if err != nil {
		return nil, err
	}

	data := make(map[string]interface{}, 1)
	if network.driver.name == "nat" {
		data["AllowUnqualifiedDNSQuery"] = true
	}

	data["hnsid"] = ep.profileID
	if ep.epConnectivity.ExposedPorts != nil {
		// Return a copy of the config data
		epc := make([]types.TransportPort, 0, len(ep.epConnectivity.ExposedPorts))
		for _, tp := range ep.epConnectivity.ExposedPorts {
			epc = append(epc, tp.GetCopy())
		}
		data[netlabel.ExposedPorts] = epc
	}

	if ep.portMapping != nil {
		// Return a copy of the operational data
		pmc := make([]types.PortBinding, 0, len(ep.portMapping))
		for _, pm := range ep.portMapping {
			pmc = append(pmc, pm.GetCopy())
		}
		data[netlabel.PortMap] = pmc
	}

	if len(ep.macAddress) != 0 {
		data[netlabel.MacAddress] = ep.macAddress
	}
	return data, nil
}

// Join method is invoked when a Sandbox is attached to an endpoint.
func (d *driver) Join(ctx context.Context, nid, eid string, sboxKey string, jinfo driverapi.JoinInfo, _, options map[string]interface{}) error {
	ctx, span := otel.Tracer("").Start(ctx, fmt.Sprintf("libnetwork.drivers.windows_%s.Join", d.name), trace.WithAttributes(
		attribute.String("nid", nid),
		attribute.String("eid", eid),
		attribute.String("sboxKey", sboxKey)))
	defer span.End()

	network, err := d.getNetwork(nid)
	if err != nil {
		return err
	}

	// Ensure that the endpoint exists
	endpoint, err := network.getEndpoint(eid)
	if err != nil {
		return err
	}

	err = jinfo.SetGateway(endpoint.gateway)
	if err != nil {
		return err
	}

	endpoint.sandboxID = sboxKey

	err = hcsshim.HotAttachEndpoint(endpoint.sandboxID, endpoint.profileID)
	if err != nil {
		// If container doesn't exists in hcs, do not throw error for hot add/remove
		if err != hcsshim.ErrComputeSystemDoesNotExist {
			return err
		}
	}

	jinfo.DisableGatewayService()
	return nil
}

// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid string) error {
	network, err := d.getNetwork(nid)
	if err != nil {
		return types.InternalMaskableErrorf("%v", err)
	}

	// Ensure that the endpoint exists
	endpoint, err := network.getEndpoint(eid)
	if err != nil {
		return err
	}

	err = hcsshim.HotDetachEndpoint(endpoint.sandboxID, endpoint.profileID)
	if err != nil {
		// If container doesn't exists in hcs, do not throw error for hot add/remove
		if err != hcsshim.ErrComputeSystemDoesNotExist {
			return err
		}
	}
	return nil
}

func (d *driver) ProgramExternalConnectivity(_ context.Context, nid, eid string, options map[string]interface{}) error {
	return nil
}

func (d *driver) RevokeExternalConnectivity(nid, eid string) error {
	return nil
}

func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data, ipV6Data []driverapi.IPAMData) (map[string]string, error) {
	return nil, types.NotImplementedErrorf("not implemented")
}

func (d *driver) NetworkFree(id string) error {
	return types.NotImplementedErrorf("not implemented")
}

func (d *driver) Type() string {
	return d.name
}

func (d *driver) IsBuiltIn() bool {
	return true
}
