<commit_before>// Copyright 2017 ETH Zurich
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package sciond queries local SCIOND servers for information.
//
// To query a SCIOND server, a connection must be established to one using Connect. The returned
// structure can then be queried for information about Paths, ASes, available SCION
// services and interface IDs of border routers.
//
// API calls return the entire answer of SCIOND.
//
// Fields prefixed with Raw (e.g., RawErrorCode) contain data in the format received from SCIOND.
// These are used internally, and the accessors without the prefix (e.g., ErrorCode()) should be
// used instead.
//
// TODO: Revocation notifications are not implemented yet.
package sciond

import (
	"math/rand"
	"net"
	"strconv"
	"sync/atomic"
	"time"

	"github.com/patrickmn/go-cache"
	"zombiezen.com/go/capnproto2"
	"zombiezen.com/go/capnproto2/pogs"

	"github.com/netsec-ethz/scion/go/lib/addr"
	"github.com/netsec-ethz/scion/go/lib/sock/reliable"
	"github.com/netsec-ethz/scion/go/proto"
)

// Time to live for cache entries
const (
	ASInfoTTL  = time.Hour
	IFInfoTTL  = time.Hour
	SVCInfoTTL = 10 * time.Second
)

// A Connector is used to query SCIOND. The Connector maintains an internal
// cache for interface, service and AS information. All Connector methods block until either
// an error occurs, or the method successfully returns.
type Connector struct {
	conn      net.Conn
	requestID uint64

	asInfos  *cache.Cache
	ifInfos  *cache.Cache
	svcInfos *cache.Cache
}

// Connect connects to a SCIOND server listening on socketName. Future method calls on the
// returned Connector request information from SCIOND. The information is not
// guaranteed to be fresh, as the returned connector caches ASInfo replies for ASInfoTTL time,
// IFInfo replies for IFInfoTTL time and SVCInfo for SVCInfoTTL time.
func Connect(socketName string) (*Connector, error) {
	conn, err := reliable.Dial(socketName)
	if err != nil {
		return nil, err
	}
	rand.Seed(time.Now().UnixNano())
	c := &Connector{conn: conn, requestID: uint64(rand.Uint32())}

	cleanupInterval := time.Minute
	c.asInfos = cache.New(ASInfoTTL, cleanupInterval)
	c.ifInfos = cache.New(IFInfoTTL, cleanupInterval)
	c.svcInfos = cache.New(SVCInfoTTL, cleanupInterval)
	return c, nil
}

// Self incrementing atomic counter for request IDs
func (c *Connector) nextID() uint64 {
	return atomic.AddUint64(&c.requestID, 1)
}

func (c *Connector) send(request *SCIONDMsg) error {
	message, arena, err := capnp.NewMessage(capnp.SingleSegment(nil))
	if err != nil {
		return err
	}
	root, err := proto.NewRootSCIONDMsg(arena)
	if err != nil {
		return err
	}
	err = pogs.Insert(proto.SCIONDMsg_TypeID, root.Struct, request)
	if err != nil {
		return err
	}
	packedMsg, err := message.MarshalPacked()
	if err != nil {
		return err
	}
	_, err = c.conn.Write(packedMsg)
	if err != nil {
		return err
	}
	return nil
}

func (c *Connector) receive() (*SCIONDMsg, error) {
	reply, err := capnp.NewPackedDecoder(c.conn).Decode()
	if err != nil {
		return nil, err
	}
	rootPtr, err := reply.RootPtr()
	if err != nil {
		return nil, err
	}
	message := &SCIONDMsg{}
	err = pogs.Extract(message, proto.SCIONDMsg_TypeID, rootPtr.Struct())
	if err != nil {
		return nil, err
	}
	return message, nil
}

// Paths requests from SCIOND a set of end to end paths between src and dst. max specifices the
// maximum number of paths returned.
func (c *Connector) Paths(dst, src *addr.ISD_AS, max uint16, f PathReqFlags) (*PathReply, error) {
	request := &SCIONDMsg{Id: c.nextID(), Which: proto.SCIONDMsg_Which_pathReq}
	request.PathReq.Dst = dst.Uint32()
	request.PathReq.Src = src.Uint32()
	request.PathReq.MaxPaths = max
	request.PathReq.Flags = f

	err := c.send(request)
	if err != nil {
		return nil, err
	}
	reply, err := c.receive()
	if err != nil {
		return nil, err
	}

	return &reply.PathReply, nil
}

// ASInfo requests from SCIOND information about AS ia.
func (c *Connector) ASInfo(ia *addr.ISD_AS) (*ASInfoReply, error) {
	// Check if information for this ISD-AS is cached
	key := ia.String()
	if value, found := c.asInfos.Get(key); found {
		return value.(*ASInfoReply), nil
	}

	// Value not in cache, so we ask SCIOND
	request := &SCIONDMsg{Id: c.nextID(), Which: proto.SCIONDMsg_Which_asInfoReq}
	request.AsInfoReq.Isdas = ia.Uint32()
	err := c.send(request)
	if err != nil {
		return nil, err
	}
	reply, err := c.receive()
	if err != nil {
		return nil, err
	}

	// Cache result
	c.asInfos.SetDefault(key, &reply.AsInfoReply)
	return &reply.AsInfoReply, nil
}

// IFInfo requests from SCIOND addresses and ports of interfaces.
// Slice ifs contains interface IDs of BRs. If empty, a fresh (i.e., uncached) answer containing
// all interfaces is returned.
func (c *Connector) IFInfo(ifs []uint64) (*IFInfoReply, error) {
	// Store uncached interface IDs
	uncachedIfs := make([]uint64, 0, len(ifs))
	cachedEntries := make([]IFInfoReplyEntry, 0, len(ifs))
	for _, iface := range ifs {
		key := strconv.FormatUint(iface, 10)
		if value, found := c.ifInfos.Get(key); found {
			cachedEntries = append(cachedEntries, value.(IFInfoReplyEntry))
		} else {
			uncachedIfs = append(uncachedIfs, iface)
		}
	}

	if len(uncachedIfs) == 0 && len(ifs) != 0 {
		return &IFInfoReply{RawEntries: cachedEntries}, nil
	}

	// Some values were not in the cache, so we ask SCIOND for them
	request := &SCIONDMsg{Id: c.nextID(), Which: proto.SCIONDMsg_Which_ifInfoRequest}
	request.IfInfoRequest.IfIDs = uncachedIfs
	err := c.send(request)
	if err != nil {
		return nil, err
	}
	reply, err := c.receive()
	if err != nil {
		return nil, err
	}

	// Add new information to cache
	// If SCIOND does not find HostInfo for a requested IFID, the
	// null answer is not added to the cache.
	for _, entry := range reply.IfInfoReply.RawEntries {
		key := strconv.FormatUint(entry.IfID, 10)
		c.ifInfos.SetDefault(key, entry)
	}

	// Append old cached entries to our reply
	reply.IfInfoReply.RawEntries = append(reply.IfInfoReply.RawEntries, cachedEntries...)
	return &reply.IfInfoReply, nil
}

// SVCInfo requests from SCIOND information about addresses and ports of infrastructure services.
// Slice svcTypes contains a list of desired service types. If unset, a fresh (i.e., uncached)
// answer containing all service types is returned.
func (c *Connector) SVCInfo(svcTypes []ServiceType) (*ServiceInfoReply, error) {
	// Store uncached SVC Types
	uncachedSVCs := make([]ServiceType, 0, len(svcTypes))
	cachedEntries := make([]ServiceInfoReplyEntry, 0, len(svcTypes))
	for _, svcType := range svcTypes {
		key := strconv.FormatUint(uint64(svcType), 10)
		if value, found := c.svcInfos.Get(key); found {
			cachedEntries = append(cachedEntries, value.(ServiceInfoReplyEntry))
		} else {
			uncachedSVCs = append(uncachedSVCs, svcType)
		}
	}

	if len(uncachedSVCs) == 0 && len(svcTypes) != 0 {<commit_msg>Make the SCIOND Go library thread-safe (#1223)<commit_after>// Copyright 2017 ETH Zurich
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package sciond queries local SCIOND servers for information.
//
// To query a SCIOND server, a connection must be established to one using Connect. The returned
// structure can then be queried for information about Paths, ASes, available SCION
// services and interface IDs of border routers.
//
// API calls return the entire answer of SCIOND.
//
// Fields prefixed with Raw (e.g., RawErrorCode) contain data in the format received from SCIOND.
// These are used internally, and the accessors without the prefix (e.g., ErrorCode()) should be
// used instead.
//
// TODO: Revocation notifications are not implemented yet.
package sciond

import (
	"math/rand"
	"net"
	"strconv"
	"sync"
	"sync/atomic"
	"time"

	"github.com/patrickmn/go-cache"
	"zombiezen.com/go/capnproto2"
	"zombiezen.com/go/capnproto2/pogs"

	"github.com/netsec-ethz/scion/go/lib/addr"
	"github.com/netsec-ethz/scion/go/lib/sock/reliable"
	"github.com/netsec-ethz/scion/go/proto"
)

// Time to live for cache entries
const (
	ASInfoTTL  = time.Hour
	IFInfoTTL  = time.Hour
	SVCInfoTTL = 10 * time.Second
)

// A Connector is used to query SCIOND. The Connector maintains an internal
// cache for interface, service and AS information. All Connector methods block until either
// an error occurs, or the method successfully returns.
type Connector struct {
	sync.Mutex
	conn      net.Conn
	requestID uint64

	asInfos  *cache.Cache
	ifInfos  *cache.Cache
	svcInfos *cache.Cache
}

// Connect connects to a SCIOND server listening on socketName. Future method calls on the
// returned Connector request information from SCIOND. The information is not
// guaranteed to be fresh, as the returned connector caches ASInfo replies for ASInfoTTL time,
// IFInfo replies for IFInfoTTL time and SVCInfo for SVCInfoTTL time.
func Connect(socketName string) (*Connector, error) {
	conn, err := reliable.Dial(socketName)
	if err != nil {
		return nil, err
	}
	rand.Seed(time.Now().UnixNano())
	c := &Connector{conn: conn, requestID: uint64(rand.Uint32())}

	cleanupInterval := time.Minute
	c.asInfos = cache.New(ASInfoTTL, cleanupInterval)
	c.ifInfos = cache.New(IFInfoTTL, cleanupInterval)
	c.svcInfos = cache.New(SVCInfoTTL, cleanupInterval)
	return c, nil
}

// Self incrementing atomic counter for request IDs
func (c *Connector) nextID() uint64 {
	return atomic.AddUint64(&c.requestID, 1)
}

func (c *Connector) send(request *SCIONDMsg) error {
	message, arena, err := capnp.NewMessage(capnp.SingleSegment(nil))
	if err != nil {
		return err
	}
	root, err := proto.NewRootSCIONDMsg(arena)
	if err != nil {
		return err
	}
	err = pogs.Insert(proto.SCIONDMsg_TypeID, root.Struct, request)
	if err != nil {
		return err
	}
	packedMsg, err := message.MarshalPacked()
	if err != nil {
		return err
	}
	_, err = c.conn.Write(packedMsg)
	if err != nil {
		return err
	}
	return nil
}

func (c *Connector) receive() (*SCIONDMsg, error) {
	reply, err := capnp.NewPackedDecoder(c.conn).Decode()
	if err != nil {
		return nil, err
	}
	rootPtr, err := reply.RootPtr()
	if err != nil {
		return nil, err
	}
	message := &SCIONDMsg{}
	err = pogs.Extract(message, proto.SCIONDMsg_TypeID, rootPtr.Struct())
	if err != nil {
		return nil, err
	}
	return message, nil
}

// Paths requests from SCIOND a set of end to end paths between src and dst. max specifices the
// maximum number of paths returned.
func (c *Connector) Paths(dst, src *addr.ISD_AS, max uint16, f PathReqFlags) (*PathReply, error) {
	c.Lock()
	defer c.Unlock()

	request := &SCIONDMsg{Id: c.nextID(), Which: proto.SCIONDMsg_Which_pathReq}
	request.PathReq.Dst = dst.Uint32()
	request.PathReq.Src = src.Uint32()
	request.PathReq.MaxPaths = max
	request.PathReq.Flags = f

	err := c.send(request)
	if err != nil {
		return nil, err
	}
	reply, err := c.receive()
	if err != nil {
		return nil, err
	}

	return &reply.PathReply, nil
}

// ASInfo requests from SCIOND information about AS ia.
func (c *Connector) ASInfo(ia *addr.ISD_AS) (*ASInfoReply, error) {
	c.Lock()
	defer c.Unlock()

	// Check if information for this ISD-AS is cached
	key := ia.String()
	if value, found := c.asInfos.Get(key); found {
		return value.(*ASInfoReply), nil
	}

	// Value not in cache, so we ask SCIOND
	request := &SCIONDMsg{Id: c.nextID(), Which: proto.SCIONDMsg_Which_asInfoReq}
	request.AsInfoReq.Isdas = ia.Uint32()
	err := c.send(request)
	if err != nil {
		return nil, err
	}
	reply, err := c.receive()
	if err != nil {
		return nil, err
	}

	// Cache result
	c.asInfos.SetDefault(key, &reply.AsInfoReply)
	return &reply.AsInfoReply, nil
}

// IFInfo requests from SCIOND addresses and ports of interfaces.
// Slice ifs contains interface IDs of BRs. If empty, a fresh (i.e., uncached) answer containing
// all interfaces is returned.
func (c *Connector) IFInfo(ifs []uint64) (*IFInfoReply, error) {
	c.Lock()
	defer c.Unlock()

	// Store uncached interface IDs
	uncachedIfs := make([]uint64, 0, len(ifs))
	cachedEntries := make([]IFInfoReplyEntry, 0, len(ifs))
	for _, iface := range ifs {
		key := strconv.FormatUint(iface, 10)
		if value, found := c.ifInfos.Get(key); found {
			cachedEntries = append(cachedEntries, value.(IFInfoReplyEntry))
		} else {
			uncachedIfs = append(uncachedIfs, iface)
		}
	}

	if len(uncachedIfs) == 0 && len(ifs) != 0 {
		return &IFInfoReply{RawEntries: cachedEntries}, nil
	}

	// Some values were not in the cache, so we ask SCIOND for them
	request := &SCIONDMsg{Id: c.nextID(), Which: proto.SCIONDMsg_Which_ifInfoRequest}
	request.IfInfoRequest.IfIDs = uncachedIfs
	err := c.send(request)
	if err != nil {
		return nil, err
	}
	reply, err := c.receive()
	if err != nil {
		return nil, err
	}

	// Add new information to cache
	// If SCIOND does not find HostInfo for a requested IFID, the
	// null answer is not added to the cache.
	for _, entry := range reply.IfInfoReply.RawEntries {
		key := strconv.FormatUint(entry.IfID, 10)
		c.ifInfos.SetDefault(key, entry)
	}

	// Append old cached entries to our reply
	reply.IfInfoReply.RawEntries = append(reply.IfInfoReply.RawEntries, cachedEntries...)
	return &reply.IfInfoReply, nil
}

// SVCInfo requests from SCIOND information about addresses and ports of infrastructure services.
// Slice svcTypes contains a list of desired service types. If unset, a fresh (i.e., uncached)
// answer containing all service types is returned.
func (c *Connector) SVCInfo(svcTypes []ServiceType) (*ServiceInfoReply, error) {
	c.Lock()
	defer c.Unlock()

	// Store uncached SVC Types
	uncachedSVCs := make([]ServiceType, 0, len(svcTypes))
	cachedEntries := make([]ServiceInfoReplyEntry, 0, len(svcTypes))
	for _, svcType := range svcTypes {
		key := strconv.FormatUint(uint64(svcType), 10)
		if value, found := c.svcInfos.Get(key); found {
			cachedEntries = append(cachedEntries, value.(ServiceInfoReplyEntry))
		} else {
			uncachedSVCs = append(uncachedSVCs, svcType)
		}
	}

	if len(uncachedSVCs) == 0 && len(svcTypes) != 0 {
{'fraction_non_alphanumeric': 0.11633514165159735, 'fraction_numerical': 0.007099323555019758, 'mean_word_length': 3.8877250409165303, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1323050', 'n_tokens_mistral': 5195, 'n_tokens_neox': 5005, 'n_words': 1630}