func (cl joinServerHTTPClient) GetAppSKey(ctx context.Context, asID string, req *ttnpb.SessionKeyRequest) (*ttnpb.AppSKeyResponse, error) {
	interopAns := &AppSKeyAns{}
	if err := cl.exchange(ctx, req.JoinEui, jsRPCPaths.appSKey, &AppSKeyReq{
		AsJsMessageHeader: AsJsMessageHeader{
			MessageHeader: MessageHeader{
				ProtocolVersion: cl.Protocol,
				MessageType:     MessageTypeAppSKeyReq,
			},
			SenderID:   asID,
			ReceiverID: EUI64(req.JoinEui),
		},
		DevEUI:       EUI64(req.DevEui),
		SessionKeyID: Buffer(req.SessionKeyId),
	}, interopAns); err != nil {
		return nil, err
	}
	if err := parseResult(interopAns.Result); err != nil {
		return nil, err
	}
	return &ttnpb.AppSKeyResponse{
		AppSKey: (*ttnpb.KeyEnvelope)(&interopAns.AppSKey),
	}, nil
}
GetAppSKey performs AppSKey request according to LoRaWAN Backend Interfaces specification.
package interop
import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"path/filepath"
	"sort"
	"strings"
	"time"
	"github.com/oklog/ulid/v2"
	"go.thethings.network/lorawan-stack/v3/pkg/config"
	"go.thethings.network/lorawan-stack/v3/pkg/encoding/lorawan"
	"go.thethings.network/lorawan-stack/v3/pkg/errors"
	"go.thethings.network/lorawan-stack/v3/pkg/fetch"
	"go.thethings.network/lorawan-stack/v3/pkg/log"
	"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
	"go.thethings.network/lorawan-stack/v3/pkg/types"
	yaml "gopkg.in/yaml.v2"
)
const (
	loRaAllianceDomain = "lora-alliance.org"
	LoRaAllianceJoinEUIDomain = "joineuis." + loRaAllianceDomain
	LoRaAllianceNetIDDomain = "netids." + loRaAllianceDomain
	defaultHTTPSPort = 443
)
type jsRPCPaths struct {
	Join    string `yaml:"join"`
	Rejoin  string `yaml:"rejoin"`
	AppSKey string `yaml:"app-s-key"`
	HomeNS  string `yaml:"home-ns"`
}
func (p jsRPCPaths) join() string    { return p.Join }
func (p jsRPCPaths) rejoin() string  { return p.Rejoin }
func (p jsRPCPaths) appSKey() string { return p.AppSKey }
func (p jsRPCPaths) homeNS() string  { return p.HomeNS }
func serverURL(scheme, fqdn, path string, port uint32) string {
	if scheme == "" {
		scheme = "https"
	}
	if port == 0 {
		port = defaultHTTPSPort
	}
	if path != "" {
		path = fmt.Sprintf("/%s", path)
	}
	return fmt.Sprintf("%s://%s:%d%s", scheme, fqdn, port, path)
}
func newHTTPRequest(url string, pld interface{}, headers map[string]string) (*http.Request, error) {
	buf := &bytes.Buffer{}
	if err := json.NewEncoder(buf).Encode(pld); err != nil {
		return nil, err
	}
	req, err := http.NewRequest(http.MethodPost, url, buf)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	for k, v := range headers {
		req.Header.Set(k, v)
	}
	return req, nil
}
func JoinServerFQDN(eui types.EUI64, domain string) string {
	if domain == "" {
		domain = LoRaAllianceJoinEUIDomain
	}
	return fmt.Sprintf(
		"%01x.%01x.%01x.%01x.%01x.%01x.%01x.%01x.%01x.%01x.%01x.%01x.%01x.%01x.%01x.%01x.%s",
		eui[7]&0x0f, eui[7]>>4,
		eui[6]&0x0f, eui[6]>>4,
		eui[5]&0x0f, eui[5]>>4,
		eui[4]&0x0f, eui[4]>>4,
		eui[3]&0x0f, eui[3]>>4,
		eui[2]&0x0f, eui[2]>>4,
		eui[1]&0x0f, eui[1]>>4,
		eui[0]&0x0f, eui[0]>>4,
		domain,
	)
}
func httpExchange(ctx context.Context, httpReq *http.Request, res interface{}, do func(*http.Request) (*http.Response, error)) error {
	logger := log.FromContext(ctx).WithField("url", httpReq.URL)
	logger.Debug("Send interop HTTP request")
	httpRes, err := do(httpReq)
	if err != nil {
		return err
	}
	defer httpRes.Body.Close()
	logger = logger.WithField("http_code", httpRes.StatusCode)
	logger.Debug("Receive interop HTTP response")
	b, err := ioutil.ReadAll(httpRes.Body)
	if err != nil {
		if err == io.EOF && res == nil {
			return nil
		}
		logger.WithError(err).Warn("Failed to read HTTP response body")
		return errors.FromHTTPStatusCode(httpRes.StatusCode)
	}
	if err := json.Unmarshal(b, res); err != nil {
		logger.WithError(err).Warn("Failed to decode HTTP response body")
		return errors.FromHTTPStatusCode(httpRes.StatusCode)
	}
	return nil
}
type joinServerHTTPClient struct {
	Client         http.Client
	NewRequestFunc func(types.EUI64, func(jsRPCPaths) string, interface{}) (*http.Request, error)
	Protocol       ProtocolVersion
}
func (cl joinServerHTTPClient) exchange(ctx context.Context, joinEUI types.EUI64, pathFunc func(jsRPCPaths) string, req, res interface{}) error {
	httpReq, err := cl.NewRequestFunc(joinEUI, pathFunc, req)
	if err != nil {
		return err
	}
	return httpExchange(ctx, httpReq.WithContext(ctx), res, cl.Client.Do)
}
func parseResult(r Result) error {
	if r.ResultCode == ResultSuccess {
		return nil
	}
	err, ok := resultErrors[r.ResultCode]
	if ok {
		return err
	}
	return errUnexpectedResult.WithAttributes("code", r.ResultCode)
}