<commit_before>package envoy

import (
	"context"
	"fmt"
	envoyv2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/listener"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
	accesslogv2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/accesslog/v2"
	"github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2"
	"github.com/envoyproxy/go-control-plane/pkg/cache"
	xds "github.com/envoyproxy/go-control-plane/pkg/server"
	"github.com/envoyproxy/go-control-plane/pkg/util"
	"github.com/gogo/protobuf/types"
	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
	"google.golang.org/grpc"
	kubev1 "k8s.io/api/core/v1"
	v1alpha12 "knative.dev/serving/pkg/apis/networking/v1alpha1"
	"kourier/pkg/kubernetes"
	"net"
	"net/http"
	"strconv"
	"time"
)

const (
	grpcMaxConcurrentStreams = 1000000
)

type EnvoyXdsServer struct {
	gatewayPort    uint
	managementPort uint
	kubeClient     kubernetes.KubernetesClient // TODO: let's try to remove this coupling later
	ctx            context.Context
	server         xds.Server
	snapshotCache  cache.SnapshotCache
}

// Hasher returns node ID as an ID
type Hasher struct {
}

func (h Hasher) ID(node *core.Node) string {
	if node == nil {
		return "unknown"
	}
	return node.Id
}

func NewEnvoyXdsServer(gatewayPort uint, managementPort uint, kubeClient kubernetes.KubernetesClient) EnvoyXdsServer {
	ctx := context.Background()
	snapshotCache := cache.NewSnapshotCache(true, Hasher{}, nil)
	srv := xds.NewServer(snapshotCache, nil)

	return EnvoyXdsServer{
		gatewayPort:    gatewayPort,
		managementPort: managementPort,
		kubeClient:     kubeClient,
		ctx:            ctx,
		server:         srv,
		snapshotCache:  snapshotCache,
	}
}

// RunManagementServer starts an xDS server at the given Port.
func (envoyXdsServer *EnvoyXdsServer) RunManagementServer() {
	port := envoyXdsServer.managementPort
	server := envoyXdsServer.server

	var grpcOptions []grpc.ServerOption
	grpcOptions = append(grpcOptions, grpc.MaxConcurrentStreams(grpcMaxConcurrentStreams))
	grpcServer := grpc.NewServer(grpcOptions...)
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		log.Error("Failed to listen")
	}

	// register services
	discovery.RegisterAggregatedDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterEndpointDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterClusterDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterRouteDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterListenerDiscoveryServiceServer(grpcServer, server)

	log.Printf("Starting Management Server on Port %d\n", port)
	go func() {
		if err = grpcServer.Serve(lis); err != nil {
			log.Errorf("%s", err)
		}
	}()
	<-envoyXdsServer.ctx.Done()
	grpcServer.GracefulStop()
}

// RunManagementGateway starts an HTTP gateway to an xDS server.
func (envoyXdsServer *EnvoyXdsServer) RunGateway() {
	port := envoyXdsServer.gatewayPort
	server := envoyXdsServer.server
	ctx := envoyXdsServer.ctx

	log.Printf("Starting HTTP/1.1 gateway on Port %d\n", port)
	httpServer := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: &xds.HTTPGateway{Server: server}}
	go func() {
		if err := httpServer.ListenAndServe(); err != nil {
			panic(err)
		}
	}()

	<-ctx.Done()
	if err := httpServer.Shutdown(ctx); err != nil {
		panic(err)
	}
}

func (envoyXdsServer *EnvoyXdsServer) SetSnapshotForClusterIngresses(nodeId string, Ingresses []v1alpha12.IngressAccessor) {
	var virtualHosts []*route.VirtualHost
	var routeCache []cache.Resource
	var clusterCache []cache.Resource

	for i, ingress := range Ingresses {

		routeName := getRouteName(ingress)
		routeNamespace := getRouteNamespace(ingress)

		log.WithFields(log.Fields{"name": routeName, "namespace": routeNamespace}).Info("Knative Ingress found")

		for _, rule := range ingress.GetSpec().Rules {

			var ruleRoute []*route.Route
			domains := rule.Hosts

			for _, httpPath := range rule.HTTP.Paths {

				path := "/"
				if httpPath.Path != "" {
					path = httpPath.Path
				}

				headers := httpPath.AppendHeaders

				var wrs []*route.WeightedCluster_ClusterWeight

				for _, split := range httpPath.Splits {

					endpointList, err := envoyXdsServer.kubeClient.EndpointsForRevision(split.ServiceNamespace, split.ServiceName)

					if err != nil {
						log.Errorf("%s", err)
						break
					}

					service, err := envoyXdsServer.kubeClient.ServiceForRevision(split.ServiceNamespace, split.ServiceName)

					if err != nil {
						log.Errorf("%s", err)
						break
					}

					var targetPort int32
					http2 := false
					for _, port := range service.Spec.Ports {
						if port.Port == split.ServicePort.IntVal || port.Name == split.ServicePort.StrVal {
							targetPort = port.TargetPort.IntVal
							http2 = port.Name == "http2" || port.Name == "h2c"
						}
					}

					lbEndpoints := lbEndpointsForKubeEndpoints(endpointList, targetPort)

					connectTimeout := 5 * time.Second
					cluster := clusterForRevision(split.ServiceName, connectTimeout, lbEndpoints, http2, path)
					clusterCache = append(clusterCache, &cluster)

					weightedCluster := weightedCluster(split.ServiceName, uint32(split.Percent), path, headers)

					wrs = append(wrs, &weightedCluster)

				}

				attempts := 0
				var perTryTimeout time.Duration
				if httpPath.Retries != nil {
					attempts = httpPath.Retries.Attempts

					if httpPath.Retries.PerTryTimeout != nil {
						perTryTimeout = httpPath.Retries.PerTryTimeout.Duration
					}
				}

				r := createRouteForRevision(routeName, i, path, wrs, attempts, perTryTimeout)

				ruleRoute = append(ruleRoute, &r)
				routeCache = append(routeCache, &r)

			}

			virtualHost := route.VirtualHost{
				Name:    routeName,
				Domains: domains,
				Routes:  ruleRoute,
			}

			virtualHosts = append(virtualHosts, &virtualHost)
		}

	}

	manager := httpConnectionManager(virtualHosts)
	l := envoyListener(&manager)
	listenerCache := []cache.Resource{&l}

	snapshotVersion, errUUID := uuid.NewUUID()
	if errUUID != nil {
		log.Error(errUUID)
		return
	}

	snapshot := cache.NewSnapshot(snapshotVersion.String(), nil, clusterCache, routeCache, listenerCache)

	err := envoyXdsServer.snapshotCache.SetSnapshot(nodeId, snapshot)
	if err != nil {
		log.Error(err)
	}
}

func createRouteForRevision(routeName string, i int, path string, wrs []*route.WeightedCluster_ClusterWeight, attempts int, perTryTimeout time.Duration) route.Route {
	r := route.Route{
		Name: routeName + "_" + strconv.Itoa(i),
		Match: &route.RouteMatch{
			PathSpecifier: &route.RouteMatch_Prefix{
				Prefix: path,
			},
		},
		Action: &route.Route_Route{Route: &route.RouteAction{
			ClusterSpecifier: &route.RouteAction_WeightedClusters{
				WeightedClusters: &route.WeightedCluster{
					Clusters: wrs,
				},
			},
			RetryPolicy: createRetryPolicy(attempts, perTryTimeout),
		}},
	}

	return r
}

func createRetryPolicy(attempts int, perTryTimeout time.Duration) *route.RetryPolicy {
	if attempts > 0 {
		return &route.RetryPolicy{
			RetryOn: "5xx",
			NumRetries: &types.UInt32Value{
				Value: uint32(attempts),
			},
			PerTryTimeout: &perTryTimeout,
		}
	} else {
		return nil
	}
}

func getRouteNamespace(ingress v1alpha12.IngressAccessor) string {
	return ingress.GetLabels()["serving.knative.dev/routeNamespace"]
}

func getRouteName(ingress v1alpha12.IngressAccessor) string {
	return ingress.GetLabels()["serving.knative.dev/route"]
}

func lbEndpointsForKubeEndpoints(kubeEndpoints *kubev1.EndpointsList, targetPort int32) []*endpoint.LbEndpoint {
	var result []*endpoint.LbEndpoint

	for _, kubeEndpoint := range kubeEndpoints.Items {
		for _, subset := range kubeEndpoint.Subsets {

			for _, address := range subset.Addresses {

				serviceEndpoint := &core.Address{
					Address: &core.Address_SocketAddress{
						SocketAddress: &core.SocketAddress{
							Protocol: core.TCP,
							Address:  address.IP,
							PortSpecifier: &core.SocketAddress_PortValue{
								PortValue: uint32(targetPort),
							},
							Ipv4Compat: true,
						},
					},
				}

				lbEndpoint := endpoint.LbEndpoint{
					HostIdentifier: &endpoint.LbEndpoint_Endpoint{
						Endpoint: &endpoint.Endpoint{
							Address: serviceEndpoint,
						},
					},
				}
				result = append(result, &lbEndpoint)
			}
		}
	}

	return result
}

func clusterForRevision(revisionName string, connectTimeout time.Duration, lbEndpoints []*endpoint.LbEndpoint, http2 bool, path string) envoyv2.Cluster {

	cluster := envoyv2.Cluster{
		Name: revisionName + path,
		ClusterDiscoveryType: &envoyv2.Cluster_Type{
			Type: envoyv2.Cluster_STRICT_DNS,
		},
		ConnectTimeout: &connectTimeout,
		LoadAssignment: &envoyv2.ClusterLoadAssignment{
			ClusterName: revisionName + path,
			Endpoints: []*endpoint.LocalityLbEndpoints{
				{
					LbEndpoints: lbEndpoints,
				},
			},
		},
	}

	if http2 {
		cluster.Http2ProtocolOptions = &core.Http2ProtocolOptions{}
	}

	return cluster
}

func weightedCluster(revisionName string, trafficPerc uint32, path string, headersToAdd map[string]string) route.WeightedCluster_ClusterWeight {

	var headers []*core.HeaderValueOption

	for k, v := range headersToAdd {

		header := core.HeaderValueOption{
			Header: &core.HeaderValue{
				Key:   k,
				Value: v,
			},
			Append: &types.BoolValue{
				Value: true,
			},
		}

		headers = append(headers, &header)

	}

	return route.WeightedCluster_ClusterWeight{
		Name: revisionName + path,
		Weight: &types.UInt32Value{
			Value: trafficPerc,
		},
		RequestHeadersToAdd: headers,
	}
}

func httpConnectionManager(virtualHosts []*route.VirtualHost) v2.HttpConnectionManager {
	return v2.HttpConnectionManager{
		CodecType:  v2.AUTO,
		StatPrefix: "ingress_http",
		RouteSpecifier: &v2.HttpConnectionManager_RouteConfig{
			RouteConfig: &envoyv2.RouteConfiguration{
				Name:         "local_route",
				VirtualHosts: virtualHosts,
			},
		},
		HttpFilters: []*v2.HttpFilter{
			{
				Name: util.Router,
			},
		},

		AccessLog: accessLogs(),
	}
}

// Outputs to /dev/stdout using the default format
func accessLogs() []*accesslogv2.AccessLog {
	accessLogConfigFields := make(map[string]*types.Value)
	accessLogConfigFields["path"] = &types.Value{
		Kind: &types.Value_StringValue{
			StringValue: "/dev/stdout",
		},
	}

	return []*accesslogv2.AccessLog{
		{
			Name: "envoy.file_access_log",
			ConfigType: &accesslogv2.AccessLog_Config{
				Config: &types.Struct{Fields: accessLogConfigFields},
			},
		},
	}
}

func envoyListener(httpConnectionManager *v2.HttpConnectionManager) envoyv2.Listener {
	pbst, err := util.MessageToStruct(httpConnectionManager)
	if err != nil {
		panic(err)
	}

	return envoyv2.Listener{
		Name: "listener_0",
		Address: &core.Address{
			Address: &core.Address_SocketAddress{
				SocketAddress: &core.SocketAddress{
					Protocol: core.TCP,
					Address:  "0.0.0.0",
					PortSpecifier: &core.SocketAddress_PortValue{
						PortValue: uint32(8080),
					},
				},
			},
		},
		FilterChains: []*listener.FilterChain{{
			Filters: []*listener.Filter{{
				Name:       util.HTTPConnectionManager,
				ConfigType: &listener.Filter_Config{Config: pbst},
			}},
		}},
	}
}
<commit_msg>Handle ingress defined headers properly<commit_after>package envoy

import (
	"context"
	"fmt"
	envoyv2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/endpoint"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/listener"
	"github.com/envoyproxy/go-control-plane/envoy/api/v2/route"
	accesslogv2 "github.com/envoyproxy/go-control-plane/envoy/config/filter/accesslog/v2"
	"github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2"
	"github.com/envoyproxy/go-control-plane/pkg/cache"
	xds "github.com/envoyproxy/go-control-plane/pkg/server"
	"github.com/envoyproxy/go-control-plane/pkg/util"
	"github.com/gogo/protobuf/types"
	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
	"google.golang.org/grpc"
	kubev1 "k8s.io/api/core/v1"
	v1alpha12 "knative.dev/serving/pkg/apis/networking/v1alpha1"
	"kourier/pkg/kubernetes"
	"net"
	"net/http"
	"strconv"
	"time"
)

const (
	grpcMaxConcurrentStreams = 1000000
)

type EnvoyXdsServer struct {
	gatewayPort    uint
	managementPort uint
	kubeClient     kubernetes.KubernetesClient // TODO: let's try to remove this coupling later
	ctx            context.Context
	server         xds.Server
	snapshotCache  cache.SnapshotCache
}

// Hasher returns node ID as an ID
type Hasher struct {
}

func (h Hasher) ID(node *core.Node) string {
	if node == nil {
		return "unknown"
	}
	return node.Id
}

func NewEnvoyXdsServer(gatewayPort uint, managementPort uint, kubeClient kubernetes.KubernetesClient) EnvoyXdsServer {
	ctx := context.Background()
	snapshotCache := cache.NewSnapshotCache(true, Hasher{}, nil)
	srv := xds.NewServer(snapshotCache, nil)

	return EnvoyXdsServer{
		gatewayPort:    gatewayPort,
		managementPort: managementPort,
		kubeClient:     kubeClient,
		ctx:            ctx,
		server:         srv,
		snapshotCache:  snapshotCache,
	}
}

// RunManagementServer starts an xDS server at the given Port.
func (envoyXdsServer *EnvoyXdsServer) RunManagementServer() {
	port := envoyXdsServer.managementPort
	server := envoyXdsServer.server

	var grpcOptions []grpc.ServerOption
	grpcOptions = append(grpcOptions, grpc.MaxConcurrentStreams(grpcMaxConcurrentStreams))
	grpcServer := grpc.NewServer(grpcOptions...)
	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		log.Error("Failed to listen")
	}

	// register services
	discovery.RegisterAggregatedDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterEndpointDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterClusterDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterRouteDiscoveryServiceServer(grpcServer, server)
	envoyv2.RegisterListenerDiscoveryServiceServer(grpcServer, server)

	log.Printf("Starting Management Server on Port %d\n", port)
	go func() {
		if err = grpcServer.Serve(lis); err != nil {
			log.Errorf("%s", err)
		}
	}()
	<-envoyXdsServer.ctx.Done()
	grpcServer.GracefulStop()
}

// RunManagementGateway starts an HTTP gateway to an xDS server.
func (envoyXdsServer *EnvoyXdsServer) RunGateway() {
	port := envoyXdsServer.gatewayPort
	server := envoyXdsServer.server
	ctx := envoyXdsServer.ctx

	log.Printf("Starting HTTP/1.1 gateway on Port %d\n", port)
	httpServer := &http.Server{Addr: fmt.Sprintf(":%d", port), Handler: &xds.HTTPGateway{Server: server}}
	go func() {
		if err := httpServer.ListenAndServe(); err != nil {
			panic(err)
		}
	}()

	<-ctx.Done()
	if err := httpServer.Shutdown(ctx); err != nil {
		panic(err)
	}
}

func (envoyXdsServer *EnvoyXdsServer) SetSnapshotForClusterIngresses(nodeId string, Ingresses []v1alpha12.IngressAccessor) {
	var virtualHosts []*route.VirtualHost
	var routeCache []cache.Resource
	var clusterCache []cache.Resource

	for i, ingress := range Ingresses {

		routeName := getRouteName(ingress)
		routeNamespace := getRouteNamespace(ingress)

		log.WithFields(log.Fields{"name": routeName, "namespace": routeNamespace}).Info("Knative Ingress found")

		for _, rule := range ingress.GetSpec().Rules {

			var ruleRoute []*route.Route
			domains := rule.Hosts

			for _, httpPath := range rule.HTTP.Paths {

				path := "/"
				if httpPath.Path != "" {
					path = httpPath.Path
				}

				headersRev := httpPath.AppendHeaders

				var wrs []*route.WeightedCluster_ClusterWeight

				for _, split := range httpPath.Splits {

					headersSplit := split.AppendHeaders

					endpointList, err := envoyXdsServer.kubeClient.EndpointsForRevision(split.ServiceNamespace, split.ServiceName)

					if err != nil {
						log.Errorf("%s", err)
						break
					}

					service, err := envoyXdsServer.kubeClient.ServiceForRevision(split.ServiceNamespace, split.ServiceName)

					if err != nil {
						log.Errorf("%s", err)
						break
					}

					var targetPort int32
					http2 := false
					for _, port := range service.Spec.Ports {
						if port.Port == split.ServicePort.IntVal || port.Name == split.ServicePort.StrVal {
							targetPort = port.TargetPort.IntVal
							http2 = port.Name == "http2" || port.Name == "h2c"
						}
					}

					lbEndpoints := lbEndpointsForKubeEndpoints(endpointList, targetPort)

					connectTimeout := 5 * time.Second
					cluster := clusterForRevision(split.ServiceName, connectTimeout, lbEndpoints, http2, path)
					clusterCache = append(clusterCache, &cluster)

					weightedCluster := weightedCluster(split.ServiceName, uint32(split.Percent), path, headersSplit)

					wrs = append(wrs, &weightedCluster)

				}

				attempts := 0
				var perTryTimeout time.Duration
				if httpPath.Retries != nil {
					attempts = httpPath.Retries.Attempts

					if httpPath.Retries.PerTryTimeout != nil {
						perTryTimeout = httpPath.Retries.PerTryTimeout.Duration
					}
				}

				r := createRouteForRevision(routeName, i, path, wrs, attempts, perTryTimeout, headersRev)

				ruleRoute = append(ruleRoute, &r)
				routeCache = append(routeCache, &r)

			}

			virtualHost := route.VirtualHost{
				Name:    routeName,
				Domains: domains,
				Routes:  ruleRoute,
			}

			virtualHosts = append(virtualHosts, &virtualHost)
		}

	}

	manager := httpConnectionManager(virtualHosts)
	l := envoyListener(&manager)
	listenerCache := []cache.Resource{&l}

	snapshotVersion, errUUID := uuid.NewUUID()
	if errUUID != nil {
		log.Error(errUUID)
		return
	}

	snapshot := cache.NewSnapshot(snapshotVersion.String(), nil, clusterCache, routeCache, listenerCache)

	err := envoyXdsServer.snapshotCache.SetSnapshot(nodeId, snapshot)
	if err != nil {
		log.Error(err)
	}
}

func createRouteForRevision(routeName string, i int, path string, wrs []*route.WeightedCluster_ClusterWeight, attempts int, perTryTimeout time.Duration, headersToAdd map[string]string) route.Route {

	// TODO: extract header generation
	var headers []*core.HeaderValueOption

	for k, v := range headersToAdd {

		header := core.HeaderValueOption{
			Header: &core.HeaderValue{
				Key:   k,
				Value: v,
			},
			Append: &types.BoolValue{
				Value: true,
			},
		}

		headers = append(headers, &header)

	}

	r := route.Route{
		Name: routeName + "_" + strconv.Itoa(i),
		Match: &route.RouteMatch{
			PathSpecifier: &route.RouteMatch_Prefix{
				Prefix: path,
			},
		},
		Action: &route.Route_Route{Route: &route.RouteAction{
			ClusterSpecifier: &route.RouteAction_WeightedClusters{
				WeightedClusters: &route.WeightedCluster{
					Clusters: wrs,
				},
			},
			RetryPolicy: createRetryPolicy(attempts, perTryTimeout),
		}},
		RequestHeadersToAdd:     headers,
	}

	return r
}

func createRetryPolicy(attempts int, perTryTimeout time.Duration) *route.RetryPolicy {
	if attempts > 0 {
		return &route.RetryPolicy{
			RetryOn: "5xx",
			NumRetries: &types.UInt32Value{
				Value: uint32(attempts),
			},
			PerTryTimeout: &perTryTimeout,
		}
	} else {
		return nil
	}
}

func getRouteNamespace(ingress v1alpha12.IngressAccessor) string {
	return ingress.GetLabels()["serving.knative.dev/routeNamespace"]
}

func getRouteName(ingress v1alpha12.IngressAccessor) string {
	return ingress.GetLabels()["serving.knative.dev/route"]
}

func lbEndpointsForKubeEndpoints(kubeEndpoints *kubev1.EndpointsList, targetPort int32) []*endpoint.LbEndpoint {
	var result []*endpoint.LbEndpoint

	for _, kubeEndpoint := range kubeEndpoints.Items {
		for _, subset := range kubeEndpoint.Subsets {

			for _, address := range subset.Addresses {

				serviceEndpoint := &core.Address{
					Address: &core.Address_SocketAddress{
						SocketAddress: &core.SocketAddress{
							Protocol: core.TCP,
							Address:  address.IP,
							PortSpecifier: &core.SocketAddress_PortValue{
								PortValue: uint32(targetPort),
							},
							Ipv4Compat: true,
						},
					},
				}

				lbEndpoint := endpoint.LbEndpoint{
					HostIdentifier: &endpoint.LbEndpoint_Endpoint{
						Endpoint: &endpoint.Endpoint{
							Address: serviceEndpoint,
						},
					},
				}
				result = append(result, &lbEndpoint)
			}
		}
	}

	return result
}

func clusterForRevision(revisionName string, connectTimeout time.Duration, lbEndpoints []*endpoint.LbEndpoint, http2 bool, path string) envoyv2.Cluster {

	cluster := envoyv2.Cluster{
		Name: revisionName + path,
		ClusterDiscoveryType: &envoyv2.Cluster_Type{
			Type: envoyv2.Cluster_STRICT_DNS,
		},
		ConnectTimeout: &connectTimeout,
		LoadAssignment: &envoyv2.ClusterLoadAssignment{
			ClusterName: revisionName + path,
			Endpoints: []*endpoint.LocalityLbEndpoints{
				{
					LbEndpoints: lbEndpoints,
				},
			},
		},
	}

	if http2 {
		cluster.Http2ProtocolOptions = &core.Http2ProtocolOptions{}
	}

	return cluster
}

func weightedCluster(revisionName string, trafficPerc uint32, path string, headersToAdd map[string]string) route.WeightedCluster_ClusterWeight {

	var headers []*core.HeaderValueOption

	for k, v := range headersToAdd {

		header := core.HeaderValueOption{
			Header: &core.HeaderValue{
				Key:   k,
				Value: v,
			},
			Append: &types.BoolValue{
				Value: true,
			},
		}

		headers = append(headers, &header)

	}

	return route.WeightedCluster_ClusterWeight{
		Name: revisionName + path,
		Weight: &types.UInt32Value{
			Value: trafficPerc,
		},
		RequestHeadersToAdd: headers,
	}
}

func httpConnectionManager(virtualHosts []*route.VirtualHost) v2.HttpConnectionManager {
	return v2.HttpConnectionManager{
		CodecType:  v2.AUTO,
		StatPrefix: "ingress_http",
		RouteSpecifier: &v2.HttpConnectionManager_RouteConfig{
			RouteConfig: &envoyv2.RouteConfiguration{
				Name:         "local_route",
				VirtualHosts: virtualHosts,
			},
		},
		HttpFilters: []*v2.HttpFilter{
			{
				Name: util.Router,
			},
		},

		AccessLog: accessLogs(),
	}
}

// Outputs to /dev/stdout using the default format
func accessLogs() []*accesslogv2.AccessLog {
	accessLogConfigFields := make(map[string]*types.Value)
	accessLogConfigFields["path"] = &types.Value{
		Kind: &types.Value_StringValue{
			StringValue: "/dev/stdout",
		},
	}

	return []*accesslogv2.AccessLog{
		{
			Name: "envoy.file_access_log",
			ConfigType: &accesslogv2.AccessLog_Config{
				Config: &types.Struct{Fields: accessLogConfigFields},
			},
		},
	}
}

func envoyListener(httpConnectionManager *v2.HttpConnectionManager) envoyv2.Listener {
	pbst, err := util.MessageToStruct(httpConnectionManager)
	if err != nil {
		panic(err)
	}

	return envoyv2.Listener{
		Name: "listener_0",
		Address: &core.Address{
			Address: &core.Address_SocketAddress{
				SocketAddress: &core.SocketAddress{
					Protocol: core.TCP,
					Address:  "0.0.0.0",
					PortSpecifier: &core.SocketAddress_PortValue{
						PortValue: uint32(8080),
					},
				},
			},
		},
		FilterChains: []*listener.FilterChain{{
			Filters: []*listener.Filter{{
				Name:       util.HTTPConnectionManager,
				ConfigType: &listener.Filter_Config{Config: pbst},
			}},
		}},
	}
}
<|endoftext|>


<commit_before>package rpc

import (
	"encoding/json"
	"net/http"
	"sync"
	"time"

	"github.com/go-chi/chi"
	"github.com/kardianos/service"
	cache "github.com/patrickmn/go-cache"
	"github.com/zerowidth/gh-shorthand/pkg/config"
)

const (
	resultTTL     = 10 * time.Minute // how long to keep successful results
	errorTTL      = 10 * time.Second // how long to keep errors
	sweepInterval = 10 * time.Minute // how often to sweep the cache
)

// Handler is a set of RPC http handlers
type Handler struct {
	cache   *cache.Cache
	github  *GitHubClient
	logger  service.Logger
	m       sync.Mutex
	pending map[string]struct{}
}

type rpcCall func(result *Result, query string) error

// NewHandler creates a new RPC handler with the given config
func NewHandler(cfg config.Config, lg service.Logger) *Handler {
	handler := Handler{
		cache:   cache.New(resultTTL, sweepInterval),
		pending: make(map[string]struct{}),
		github:  NewGitHubClient(cfg),
		logger:  lg,
	}
	return &handler
}

// Mount routes the RPC handlers on a mux
func (h *Handler) Mount(mux *chi.Mux) {
	mux.Get("/repo", h.rpcHandler("repo", h.github.GetRepo))
	mux.Get("/issue", h.rpcHandler("issue", h.github.GetIssue))
	mux.Get("/issues", h.rpcHandler("issues", h.github.GetIssues))
	mux.Get("/project", h.rpcHandler("project", h.github.GetProject))
	mux.Get("/projects", h.rpcHandler("projects", h.github.GetProjects))
}

// rpcHandler creates an http handler func to wrap a GitHub API call with
// asynchronous execution and caching of its result.
func (h *Handler) rpcHandler(action string, rpc rpcCall) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if err := r.ParseForm(); err != nil {
			http.Error(w, http.StatusText(400), 400)
			return
		}

		query := r.Form.Get("q")
		if len(query) == 0 {
			return
		}

		// Now that basic checks are done, lock the cache and pending map to see
		// if the request is already in flight. If not, kick it off.
		h.m.Lock()
		defer h.m.Unlock()
		var res Result

		key := action + ":" + query
		if _, pending := h.pending[key]; !pending {
			if cr, ok := h.cache.Get(key); ok {
				res = cr.(Result)
			} else {
				// this will wait on the mutex immediately, but we're returning soon anyway
				go h.makeRequest(rpc, query, key)
			}
		}

		if err := json.NewEncoder(w).Encode(res); err != nil {
			_ = h.logger.Error("encoding error", err)
		}
	}
}

func (h *Handler) makeRequest(rpc rpcCall, query, key string) {
	h.m.Lock()
	h.pending[key] = struct{}{}
	h.m.Unlock()

	var res Result
	ttl := resultTTL

	_ = h.logger.Infof("RPC request: %s", key)
	err := rpc(&res, query)
	if err != nil {
		res.Error = err.Error()
		ttl = errorTTL
	}
	res.Complete = true
	_ = h.logger.Infof("RPC result: %s %+v\n", key, res)

	h.m.Lock()
	delete(h.pending, key)
	h.cache.Set(key, res, ttl)
	h.m.Unlock()
}
<commit_msg>remove unnecessary alias<commit_after>package rpc

import (
	"encoding/json"
	"net/http"
	"sync"
	"time"

	"github.com/go-chi/chi"
	"github.com/kardianos/service"
	"github.com/patrickmn/go-cache"
	"github.com/zerowidth/gh-shorthand/pkg/config"
)

const (
	resultTTL     = 10 * time.Minute // how long to keep successful results
	errorTTL      = 10 * time.Second // how long to keep errors
	sweepInterval = 10 * time.Minute // how often to sweep the cache
)

// Handler is a set of RPC http handlers
type Handler struct {
	cache   *cache.Cache
	github  *GitHubClient
	logger  service.Logger
	m       sync.Mutex
	pending map[string]struct{}
}

type rpcCall func(result *Result, query string) error

// NewHandler creates a new RPC handler with the given config
func NewHandler(cfg config.Config, lg service.Logger) *Handler {
	handler := Handler{
		cache:   cache.New(resultTTL, sweepInterval),
		pending: make(map[string]struct{}),
		github:  NewGitHubClient(cfg),
		logger:  lg,
	}
	return &handler
}

// Mount routes the RPC handlers on a mux
func (h *Handler) Mount(mux *chi.Mux) {
	mux.Get("/repo", h.rpcHandler("repo", h.github.GetRepo))
	mux.Get("/issue", h.rpcHandler("issue", h.github.GetIssue))
	mux.Get("/issues", h.rpcHandler("issues", h.github.GetIssues))
	mux.Get("/project", h.rpcHandler("project", h.github.GetProject))
	mux.Get("/projects", h.rpcHandler("projects", h.github.GetProjects))
}

// rpcHandler creates an http handler func to wrap a GitHub API call with
// asynchronous execution and caching of its result.
func (h *Handler) rpcHandler(action string, rpc rpcCall) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if err := r.ParseForm(); err != nil {
			http.Error(w, http.StatusText(400), 400)
			return
		}

		query := r.Form.Get("q")
		if len(query) == 0 {
			return
		}

		// Now that basic checks are done, lock the cache and pending map to see
		// if the request is already in flight. If not, kick it off.
		h.m.Lock()
		defer h.m.Unlock()
		var res Result

		key := action + ":" + query
		if _, pending := h.pending[key]; !pending {
			if cr, ok := h.cache.Get(key); ok {
				res = cr.(Result)
			} else {
				// this will wait on the mutex immediately, but we're returning soon anyway
				go h.makeRequest(rpc, query, key)
			}
		}

		if err := json.NewEncoder(w).Encode(res); err != nil {
			_ = h.logger.Error("encoding error", err)
		}
	}
}

func (h *Handler) makeRequest(rpc rpcCall, query, key string) {
	h.m.Lock()
	h.pending[key] = struct{}{}
	h.m.Unlock()

	var res Result
	ttl := resultTTL

	_ = h.logger.Infof("RPC request: %s", key)
	err := rpc(&res, query)
	if err != nil {
		res.Error = err.Error()
		ttl = errorTTL
	}
	res.Complete = true
	_ = h.logger.Infof("RPC result: %s %+v\n", key, res)

	h.m.Lock()
	delete(h.pending, key)
	h.cache.Set(key, res, ttl)
	h.m.Unlock()
}
<|endoftext|>


<commit_before>package negroni

import (
	"bufio"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

type closeNotifyingRecorder struct {
	*httptest.ResponseRecorder
	closed chan bool
}

func newCloseNotifyingRecorder() *closeNotifyingRecorder {
	return &closeNotifyingRecorder{
		httptest.NewRecorder(),
		make(chan bool, 1),
	}
}

func (c *closeNotifyingRecorder) close() {
	c.closed <- true
}

func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
	return c.closed
}

type hijackableResponse struct {
	Hijacked bool
}

func newHijackableResponse() *hijackableResponse {
	return &hijackableResponse{}
}

func (h *hijackableResponse) Header() http.Header           { return nil }
func (h *hijackableResponse) Write(buf []byte) (int, error) { return 0, nil }
func (h *hijackableResponse) WriteHeader(code int)          {}
func (h *hijackableResponse) Flush()                        {}
func (h *hijackableResponse) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	h.Hijacked = true
	return nil, nil, nil
}

func Test_ResponseWriter_WritingString(t *testing.T) {
	rec := httptest.NewRecorder()
	rw := NewResponseWriter(rec)

	rw.Write([]byte("Hello world"))

	expect(t, rec.Code, rw.Status())
	expect(t, rec.Body.String(), "Hello world")
	expect(t, rw.Status(), http.StatusOK)
	expect(t, rw.Size(), 11)
	expect(t, rw.Written(), true)
}

func Test_ResponseWriter_WritingStrings(t *testing.T) {
	rec := httptest.NewRecorder()
	rw := NewResponseWriter(rec)

	rw.Write([]byte("Hello world"))
	rw.Write([]byte("foo bar bat baz"))

	expect(t, rec.Code, rw.Status())
	expect(t, rec.Body.String(), "Hello worldfoo bar bat baz")
	expect(t, rw.Status(), http.StatusOK)
	expect(t, rw.Size(), 26)
}

func Test_ResponseWriter_WritingHeader(t *testing.T) {
	rec := httptest.NewRecorder()
	rw := NewResponseWriter(rec)

	rw.WriteHeader(http.StatusNotFound)

	expect(t, rec.Code, rw.Status())
	expect(t, rec.Body.String(), "")
	expect(t, rw.Status(), http.StatusNotFound)
	expect(t, rw.Size(), 0)
}

func Test_ResponseWriter_Before(t *testing.T) {
	rec := httptest.NewRecorder()
	rw := NewResponseWriter(rec)
	result := ""

	rw.Before(func(ResponseWriter) {
		result += "foo"
	})
	rw.Before(func(ResponseWriter) {
		result += "bar"
	})

	rw.WriteHeader(http.StatusNotFound)

	expect(t, rec.Code, rw.Status())
	expect(t, rec.Body.String(), "")
	expect(t, rw.Status(), http.StatusNotFound)
	expect(t, rw.Size(), 0)
	expect(t, result, "barfoo")
}

func Test_ResponseWriter_Hijack(t *testing.T) {
	hijackable := newHijackableResponse()
	rw := NewResponseWriter(hijackable)
	hijacker, ok := rw.(http.Hijacker)
	expect(t, ok, true)
	_, _, err := hijacker.Hijack()
	if err != nil {
		t.Error(err)
	}
	expect(t, hijackable.Hijacked, true)
}

func Test_ResponseWrite_Hijack_NotOK(t *testing.T) {
	hijackable := new(http.ResponseWriter)
	rw := NewResponseWriter(*hijackable)
	hijacker, ok := rw.(http.Hijacker)
	expect(t, ok, true)
	_, _, err := hijacker.Hijack()

	refute(t, err, nil)
}

func Test_ResponseWriter_CloseNotify(t *testing.T) {
	rec := newCloseNotifyingRecorder()
	rw := NewResponseWriter(rec)
	closed := false
	notifier := rw.(http.CloseNotifier).CloseNotify()
	rec.close()
	select {
	case <-notifier:
		closed = true
	case <-time.After(time.Second):
	}
	expect(t, closed, true)
}

func Test_ResponseWriter_Flusher(t *testing.T) {

	rec := httptest.NewRecorder()
	rw := NewResponseWriter(rec)

	_, ok := rw.(http.Flusher)
	expect(t, ok, true)
}
<commit_msg>Whitespace<commit_after>package negroni

import (
	"bufio"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

type closeNotifyingRecorder struct {
	*httptest.ResponseRecorder
	closed chan bool
}

func newCloseNotifyingRecorder() *closeNotifyingRecorder {
	return &closeNotifyingRecorder{
		httptest.NewRecorder(),
		make(chan bool, 1),
	}
}

func (c *closeNotifyingRecorder) close() {
	c.closed <- true
}

func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
	return c.closed
}

type hijackableResponse struct {
	Hijacked bool
}

func newHijackableResponse() *hijackableResponse {
	return &hijackableResponse{}
}

func (h *hijackableResponse) Header() http.Header           { return nil }
func (h *hijackableResponse) Write(buf []byte) (int, error) { return 0, nil }
func (h *hijackableResponse) WriteHeader(code int)          {}
func (h *hijackableResponse) Flush()                        {}
func (h *hijackableResponse) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	h.Hijacked = true
	return nil, nil, nil
}

func Test_ResponseWriter_WritingString(t *testing.T) {
	rec := httptest.NewRecorder()
	rw := NewResponseWriter(rec)

	rw.Write([]byte("Hello world"))

	expect(t, rec.Code, rw.Status())
	expect(t, rec.Body.String(), "Hello world")
	expect(t, rw.Status(), http.StatusOK)
	expect(t, rw.Size(), 11)
	expect(t, rw.Written(), true)
}

func Test_ResponseWriter_WritingStrings(t *testing.T) {
	rec := httptest.NewRecorder()
	rw := NewResponseWriter(rec)

	rw.Write([]byte("Hello world"))
	rw.Write([]byte("foo bar bat baz"))

	expect(t, rec.Code, rw.Status())
	expect(t, rec.Body.String(), "Hello worldfoo bar bat baz")
	expect(t, rw.Status(), http.StatusOK)
	expect(t, rw.Size(), 26)
}

func Test_ResponseWriter_WritingHeader(t *testing.T) {
	rec := httptest.NewRecorder()
	rw := NewResponseWriter(rec)

	rw.WriteHeader(http.StatusNotFound)

	expect(t, rec.Code, rw.Status())
	expect(t, rec.Body.String(), "")
	expect(t, rw.Status(), http.StatusNotFound)
	expect(t, rw.Size(), 0)
}

func Test_ResponseWriter_Before(t *testing.T) {
	rec := httptest.NewRecorder()
	rw := NewResponseWriter(rec)
	result := ""

	rw.Before(func(ResponseWriter) {
		result += "foo"
	})
	rw.Before(func(ResponseWriter) {
		result += "bar"
	})

	rw.WriteHeader(http.StatusNotFound)

	expect(t, rec.Code, rw.Status())
	expect(t, rec.Body.String(), "")
	expect(t, rw.Status(), http.StatusNotFound)
	expect(t, rw.Size(), 0)
	expect(t, result, "barfoo")
}

func Test_ResponseWriter_Hijack(t *testing.T) {
	hijackable := newHijackableResponse()
	rw := NewResponseWriter(hijackable)
	hijacker, ok := rw.(http.Hijacker)
	expect(t, ok, true)
	_, _, err := hijacker.Hijack()
	if err != nil {
		t.Error(err)
	}
	expect(t, hijackable.Hijacked, true)
}

func Test_ResponseWrite_Hijack_NotOK(t *testing.T) {
	hijackable := new(http.ResponseWriter)
	rw := NewResponseWriter(*hijackable)
	hijacker, ok := rw.(http.Hijacker)
	expect(t, ok, true)
	_, _, err := hijacker.Hijack()

	refute(t, err, nil)
}

func Test_ResponseWriter_CloseNotify(t *testing.T) {
	rec := newCloseNotifyingRecorder()
	rw := NewResponseWriter(rec)
	closed := false
	notifier := rw.(http.CloseNotifier).CloseNotify()
	rec.close()
	select {
	case <-notifier:
		closed = true
	case <-time.After(time.Second):
	}
	expect(t, closed, true)
}

func Test_ResponseWriter_Flusher(t *testing.T) {
	rec := httptest.NewRecorder()
	rw := NewResponseWriter(rec)

	_, ok := rw.(http.Flusher)
	expect(t, ok, true)
}
<|endoftext|>


<commit_before>// +build windows

package windows

import (
	"syscall"
	"unsafe"

	"github.com/mackerelio/mackerel-agent/logging"
	"github.com/mackerelio/mackerel-agent/util/windows"
)

// BlockDeviceGenerator XXX
type BlockDeviceGenerator struct {
}

// Key XXX
func (g *BlockDeviceGenerator) Key() string {
	return "block_device"
}

var blockDeviceLogger = logging.GetLogger("spec.block_device")

// Generate XXX
func (g *BlockDeviceGenerator) Generate() (interface{}, error) {
	results := make(map[string]map[string]interface{})

	drivebuf := make([]byte, 256)
	windows.GetLogicalDriveStrings.Call(
		uintptr(len(drivebuf)),
		uintptr(unsafe.Pointer(&drivebuf[0])))

	for _, v := range drivebuf {
		if v >= 65 && v <= 90 {
			drive := string(v)
			removable := false
			r, _, _ := windows.GetDriveType.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(drive + `:\`))))
			if r == windows.DriveRemovable {
				removable = true
			}
			freeBytesAvailable := int64(0)
			totalNumberOfBytes := int64(0)
			totalNumberOfFreeBytes := int64(0)
			r, _, _ = windows.GetDiskFreeSpaceEx.Call(
				uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(drive))),
				uintptr(unsafe.Pointer(&freeBytesAvailable)),
				uintptr(unsafe.Pointer(&totalNumberOfBytes)),
				uintptr(unsafe.Pointer(&totalNumberOfFreeBytes)))
			if r == 0 {
				continue
			}
			results[drive] = map[string]interface{}{
				"size":      totalNumberOfFreeBytes,
				"removable": removable,
			}
		}
	}

	return results, nil
}
edit the error handling to block_device and filesystem. for windows metric.
<commit_msg>add crlf in spec/windows/block_device.go<commit_after>// +build windows

package windows

import (
	"syscall"
	"unsafe"

	"github.com/mackerelio/mackerel-agent/logging"
	"github.com/mackerelio/mackerel-agent/util/windows"
)

// BlockDeviceGenerator XXX
type BlockDeviceGenerator struct {
}

// Key XXX
func (g *BlockDeviceGenerator) Key() string {
	return "block_device"
}

var blockDeviceLogger = logging.GetLogger("spec.block_device")

// Generate XXX
func (g *BlockDeviceGenerator) Generate() (interface{}, error) {
	results := make(map[string]map[string]interface{})

	drivebuf := make([]byte, 256)
	windows.GetLogicalDriveStrings.Call(
		uintptr(len(drivebuf)),
		uintptr(unsafe.Pointer(&drivebuf[0])))

	for _, v := range drivebuf {
		if v >= 65 && v <= 90 {
			drive := string(v)
			removable := false
			r, _, _ := windows.GetDriveType.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(drive + `:\`))))
			if r == windows.DriveRemovable {
				removable = true
			}
			freeBytesAvailable := int64(0)
			totalNumberOfBytes := int64(0)
			totalNumberOfFreeBytes := int64(0)
			r, _, _ = windows.GetDiskFreeSpaceEx.Call(
				uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(drive))),
				uintptr(unsafe.Pointer(&freeBytesAvailable)),
				uintptr(unsafe.Pointer(&totalNumberOfBytes)),
				uintptr(unsafe.Pointer(&totalNumberOfFreeBytes)))
			if r == 0 {
				continue
			}
			results[drive] = map[string]interface{}{
				"size":      totalNumberOfFreeBytes,
				"removable": removable,
			}
		}
	}

	return results, nil
}

<|endoftext|>


<commit_before>// +build windows

package windows

import (
	"syscall"
	"unsafe"

	"github.com/mackerelio/mackerel-agent/logging"
	"github.com/mackerelio/mackerel-agent/util/windows"
)

// BlockDeviceGenerator XXX
type BlockDeviceGenerator struct {
}

// Key XXX
func (g *BlockDeviceGenerator) Key() string {
	return "block_device"
}

var blockDeviceLogger = logging.GetLogger("spec.block_device")

// Generate XXX
func (g *BlockDeviceGenerator) Generate() (interface{}, error) {
	results := make(map[string]map[string]interface{})

	drivebuf := make([]byte, 256)
	r, _, err := windows.GetLogicalDriveStrings.Call(
		uintptr(len(drivebuf)),
		uintptr(unsafe.Pointer(&drivebuf[0])))
	if r == 0 {
		e := windows.GetLastError.Call
		blockDeviceLogger.Errorf("error code is  [%s]", e)
		return nil, err
	}

	for _, v := range drivebuf {
		if v >= 65 && v <= 90 {
			drive := string(v)
			removable := false
			r, _, err = windows.GetDriveType.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(drive + `:\`))))
			if r == windows.DRIVE_REMOVABLE {
				removable = true
			}
			freeBytesAvailable := int64(0)
			totalNumberOfBytes := int64(0)
			totalNumberOfFreeBytes := int64(0)
			r, _, err = windows.GetDiskFreeSpaceEx.Call(
				uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(drive))),
				uintptr(unsafe.Pointer(&freeBytesAvailable)),
				uintptr(unsafe.Pointer(&totalNumberOfBytes)),
				uintptr(unsafe.Pointer(&totalNumberOfFreeBytes)))
			if r == 0 {
				continue
			}
			results[drive] = map[string]interface{}{
				"size":      totalNumberOfFreeBytes,
				"removable": removable,
			}
		}
	}

	return results, nil
}
<commit_msg>GetLastError is not neccessary.<commit_after>// +build windows

package windows

import (
	"syscall"
	"unsafe"

	"github.com/mackerelio/mackerel-agent/logging"
	"github.com/mackerelio/mackerel-agent/util/windows"
)

// BlockDeviceGenerator XXX
type BlockDeviceGenerator struct {
}

// Key XXX
func (g *BlockDeviceGenerator) Key() string {
	return "block_device"
}

var blockDeviceLogger = logging.GetLogger("spec.block_device")

// Generate XXX
func (g *BlockDeviceGenerator) Generate() (interface{}, error) {
	results := make(map[string]map[string]interface{})

	drivebuf := make([]byte, 256)
	r, _, err := windows.GetLogicalDriveStrings.Call(
		uintptr(len(drivebuf)),
		uintptr(unsafe.Pointer(&drivebuf[0])))
	if r == 0 {
		return nil, err
	}

	for _, v := range drivebuf {
		if v >= 65 && v <= 90 {
			drive := string(v)
			removable := false
			r, _, err = windows.GetDriveType.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(drive + `:\`))))
			if r == windows.DRIVE_REMOVABLE {
				removable = true
			}
			freeBytesAvailable := int64(0)
			totalNumberOfBytes := int64(0)
			totalNumberOfFreeBytes := int64(0)
			r, _, err = windows.GetDiskFreeSpaceEx.Call(
				uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(drive))),
				uintptr(unsafe.Pointer(&freeBytesAvailable)),
				uintptr(unsafe.Pointer(&totalNumberOfBytes)),
				uintptr(unsafe.Pointer(&totalNumberOfFreeBytes)))
			if r == 0 {
				continue
			}
			results[drive] = map[string]interface{}{
				"size":      totalNumberOfFreeBytes,
				"removable": removable,
			}
		}
	}

	return results, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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 watch

import (
	"fmt"
	"sync"

	"k8s.io/klog/v2"

	"k8s.io/apimachinery/pkg/runtime"
)

// Interface can be implemented by anything that knows how to watch and report changes.
type Interface interface {
	// Stops watching. Will close the channel returned by ResultChan(). Releases
	// any resources used by the watch.
	Stop()

	// Returns a chan which will receive all the events. If an error occurs
	// or Stop() is called, the implementation will close this channel and
	// release any resources used by the watch.
	ResultChan() <-chan Event
}

// EventType defines the possible types of events.
type EventType string

const (
	Added    EventType = "ADDED"
	Modified EventType = "MODIFIED"
	Deleted  EventType = "DELETED"
	Bookmark EventType = "BOOKMARK"
	Error    EventType = "ERROR"
)

var (
	DefaultChanSize int32 = 100
)

// Event represents a single event to a watched resource.
// +k8s:deepcopy-gen=true
type Event struct {
	Type EventType

	// Object is:
	//  * If Type is Added or Modified: the new state of the object.
	//  * If Type is Deleted: the state of the object immediately before deletion.
	//  * If Type is Bookmark: the object (instance of a type being watched) where
	//    only ResourceVersion field is set. On successful restart of watch from a
	//    bookmark resourceVersion, client is guaranteed to not get repeat event
	//    nor miss any events.
	//  * If Type is Error: *api.Status is recommended; other types may make sense
	//    depending on context.
	Object runtime.Object
}

type emptyWatch chan Event

// NewEmptyWatch returns a watch interface that returns no results and is closed.
// May be used in certain error conditions where no information is available but
// an error is not warranted.
func NewEmptyWatch() Interface {
	ch := make(chan Event)
	close(ch)
	return emptyWatch(ch)
}

// Stop implements Interface
func (w emptyWatch) Stop() {
}

// ResultChan implements Interface
func (w emptyWatch) ResultChan() <-chan Event {
	return chan Event(w)
}

// FakeWatcher lets you test anything that consumes a watch.Interface; threadsafe.
type FakeWatcher struct {
	result  chan Event
	stopped bool
	sync.Mutex
}

func NewFake() *FakeWatcher {
	return &FakeWatcher{
		result: make(chan Event),
	}
}

func NewFakeWithChanSize(size int, blocking bool) *FakeWatcher {
	return &FakeWatcher{
		result: make(chan Event, size),
	}
}

// Stop implements Interface.Stop().
func (f *FakeWatcher) Stop() {
	f.Lock()
	defer f.Unlock()
	if !f.stopped {
		klog.V(4).Infof("Stopping fake watcher.")
		close(f.result)
		f.stopped = true
	}
}

func (f *FakeWatcher) IsStopped() bool {
	f.Lock()
	defer f.Unlock()
	return f.stopped
}

// Reset prepares the watcher to be reused.
func (f *FakeWatcher) Reset() {
	f.Lock()
	defer f.Unlock()
	f.stopped = false
	f.result = make(chan Event)
}

func (f *FakeWatcher) ResultChan() <-chan Event {
	return f.result
}

// Add sends an add event.
func (f *FakeWatcher) Add(obj runtime.Object) {
	f.result <- Event{Added, obj}
}

// Modify sends a modify event.
func (f *FakeWatcher) Modify(obj runtime.Object) {
	f.result <- Event{Modified, obj}
}

// Delete sends a delete event.
func (f *FakeWatcher) Delete(lastValue runtime.Object) {
	f.result <- Event{Deleted, lastValue}
}

// Error sends an Error event.
func (f *FakeWatcher) Error(errValue runtime.Object) {
	f.result <- Event{Error, errValue}
}

// Action sends an event of the requested type, for table-based testing.
func (f *FakeWatcher) Action(action EventType, obj runtime.Object) {
	f.result <- Event{action, obj}
}

// RaceFreeFakeWatcher lets you test anything that consumes a watch.Interface; threadsafe.
type RaceFreeFakeWatcher struct {
	result  chan Event
	Stopped bool
	sync.Mutex
}

func NewRaceFreeFake() *RaceFreeFakeWatcher {
	return &RaceFreeFakeWatcher{
		result: make(chan Event, DefaultChanSize),
	}
}

// Stop implements Interface.Stop().
func (f *RaceFreeFakeWatcher) Stop() {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		klog.V(4).Infof("Stopping fake watcher.")
		close(f.result)
		f.Stopped = true
	}
}

func (f *RaceFreeFakeWatcher) IsStopped() bool {
	f.Lock()
	defer f.Unlock()
	return f.Stopped
}

// Reset prepares the watcher to be reused.
func (f *RaceFreeFakeWatcher) Reset() {
	f.Lock()
	defer f.Unlock()
	f.Stopped = false
	f.result = make(chan Event, DefaultChanSize)
}

func (f *RaceFreeFakeWatcher) ResultChan() <-chan Event {
	f.Lock()
	defer f.Unlock()
	return f.result
}

// Add sends an add event.
func (f *RaceFreeFakeWatcher) Add(obj runtime.Object) {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		select {
		case f.result <- Event{Added, obj}:
			return
		default:
			panic(fmt.Errorf("channel full"))
		}
	}
}

// Modify sends a modify event.
func (f *RaceFreeFakeWatcher) Modify(obj runtime.Object) {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		select {
		case f.result <- Event{Modified, obj}:
			return
		default:
			panic(fmt.Errorf("channel full"))
		}
	}
}

// Delete sends a delete event.
func (f *RaceFreeFakeWatcher) Delete(lastValue runtime.Object) {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		select {
		case f.result <- Event{Deleted, lastValue}:
			return
		default:
			panic(fmt.Errorf("channel full"))
		}
	}
}

// Error sends an Error event.
func (f *RaceFreeFakeWatcher) Error(errValue runtime.Object) {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		select {
		case f.result <- Event{Error, errValue}:
			return
		default:
			panic(fmt.Errorf("channel full"))
		}
	}
}

// Action sends an event of the requested type, for table-based testing.
func (f *RaceFreeFakeWatcher) Action(action EventType, obj runtime.Object) {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		select {
		case f.result <- Event{action, obj}:
			return
		default:
			panic(fmt.Errorf("channel full"))
		}
	}
}

// ProxyWatcher lets you wrap your channel in watch Interface. threadsafe.
type ProxyWatcher struct {
	result chan Event
	stopCh chan struct{}

	mutex   sync.Mutex
	stopped bool
}

var _ Interface = &ProxyWatcher{}

// NewProxyWatcher creates new ProxyWatcher by wrapping a channel
func NewProxyWatcher(ch chan Event) *ProxyWatcher {
	return &ProxyWatcher{
		result:  ch,
		stopCh:  make(chan struct{}),
		stopped: false,
	}
}

// Stop implements Interface
func (pw *ProxyWatcher) Stop() {
	pw.mutex.Lock()
	defer pw.mutex.Unlock()
	if !pw.stopped {
		pw.stopped = true
		close(pw.stopCh)
	}
}

// Stopping returns true if Stop() has been called
func (pw *ProxyWatcher) Stopping() bool {
	pw.mutex.Lock()
	defer pw.mutex.Unlock()
	return pw.stopped
}

// ResultChan implements Interface
func (pw *ProxyWatcher) ResultChan() <-chan Event {
	return pw.result
}

// StopChan returns stop channel
func (pw *ProxyWatcher) StopChan() <-chan struct{} {
	return pw.stopCh
}
<commit_msg>Prepare apiserver for operating on cached objects by not modifying them<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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 watch

import (
	"fmt"
	"sync"

	"k8s.io/klog/v2"

	"k8s.io/apimachinery/pkg/runtime"
)

// Interface can be implemented by anything that knows how to watch and report changes.
type Interface interface {
	// Stop stops watching. Will close the channel returned by ResultChan(). Releases
	// any resources used by the watch.
	Stop()

	// ResultChan returns a chan which will receive all the events. If an error occurs
	// or Stop() is called, the implementation will close this channel and
	// release any resources used by the watch.
	ResultChan() <-chan Event
}

// EventType defines the possible types of events.
type EventType string

const (
	Added    EventType = "ADDED"
	Modified EventType = "MODIFIED"
	Deleted  EventType = "DELETED"
	Bookmark EventType = "BOOKMARK"
	Error    EventType = "ERROR"
)

var (
	DefaultChanSize int32 = 100
)

// Event represents a single event to a watched resource.
// +k8s:deepcopy-gen=true
type Event struct {
	Type EventType

	// Object is:
	//  * If Type is Added or Modified: the new state of the object.
	//  * If Type is Deleted: the state of the object immediately before deletion.
	//  * If Type is Bookmark: the object (instance of a type being watched) where
	//    only ResourceVersion field is set. On successful restart of watch from a
	//    bookmark resourceVersion, client is guaranteed to not get repeat event
	//    nor miss any events.
	//  * If Type is Error: *api.Status is recommended; other types may make sense
	//    depending on context.
	Object runtime.Object
}

type emptyWatch chan Event

// NewEmptyWatch returns a watch interface that returns no results and is closed.
// May be used in certain error conditions where no information is available but
// an error is not warranted.
func NewEmptyWatch() Interface {
	ch := make(chan Event)
	close(ch)
	return emptyWatch(ch)
}

// Stop implements Interface
func (w emptyWatch) Stop() {
}

// ResultChan implements Interface
func (w emptyWatch) ResultChan() <-chan Event {
	return chan Event(w)
}

// FakeWatcher lets you test anything that consumes a watch.Interface; threadsafe.
type FakeWatcher struct {
	result  chan Event
	stopped bool
	sync.Mutex
}

func NewFake() *FakeWatcher {
	return &FakeWatcher{
		result: make(chan Event),
	}
}

func NewFakeWithChanSize(size int, blocking bool) *FakeWatcher {
	return &FakeWatcher{
		result: make(chan Event, size),
	}
}

// Stop implements Interface.Stop().
func (f *FakeWatcher) Stop() {
	f.Lock()
	defer f.Unlock()
	if !f.stopped {
		klog.V(4).Infof("Stopping fake watcher.")
		close(f.result)
		f.stopped = true
	}
}

func (f *FakeWatcher) IsStopped() bool {
	f.Lock()
	defer f.Unlock()
	return f.stopped
}

// Reset prepares the watcher to be reused.
func (f *FakeWatcher) Reset() {
	f.Lock()
	defer f.Unlock()
	f.stopped = false
	f.result = make(chan Event)
}

func (f *FakeWatcher) ResultChan() <-chan Event {
	return f.result
}

// Add sends an add event.
func (f *FakeWatcher) Add(obj runtime.Object) {
	f.result <- Event{Added, obj}
}

// Modify sends a modify event.
func (f *FakeWatcher) Modify(obj runtime.Object) {
	f.result <- Event{Modified, obj}
}

// Delete sends a delete event.
func (f *FakeWatcher) Delete(lastValue runtime.Object) {
	f.result <- Event{Deleted, lastValue}
}

// Error sends an Error event.
func (f *FakeWatcher) Error(errValue runtime.Object) {
	f.result <- Event{Error, errValue}
}

// Action sends an event of the requested type, for table-based testing.
func (f *FakeWatcher) Action(action EventType, obj runtime.Object) {
	f.result <- Event{action, obj}
}

// RaceFreeFakeWatcher lets you test anything that consumes a watch.Interface; threadsafe.
type RaceFreeFakeWatcher struct {
	result  chan Event
	Stopped bool
	sync.Mutex
}

func NewRaceFreeFake() *RaceFreeFakeWatcher {
	return &RaceFreeFakeWatcher{
		result: make(chan Event, DefaultChanSize),
	}
}

// Stop implements Interface.Stop().
func (f *RaceFreeFakeWatcher) Stop() {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		klog.V(4).Infof("Stopping fake watcher.")
		close(f.result)
		f.Stopped = true
	}
}

func (f *RaceFreeFakeWatcher) IsStopped() bool {
	f.Lock()
	defer f.Unlock()
	return f.Stopped
}

// Reset prepares the watcher to be reused.
func (f *RaceFreeFakeWatcher) Reset() {
	f.Lock()
	defer f.Unlock()
	f.Stopped = false
	f.result = make(chan Event, DefaultChanSize)
}

func (f *RaceFreeFakeWatcher) ResultChan() <-chan Event {
	f.Lock()
	defer f.Unlock()
	return f.result
}

// Add sends an add event.
func (f *RaceFreeFakeWatcher) Add(obj runtime.Object) {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		select {
		case f.result <- Event{Added, obj}:
			return
		default:
			panic(fmt.Errorf("channel full"))
		}
	}
}

// Modify sends a modify event.
func (f *RaceFreeFakeWatcher) Modify(obj runtime.Object) {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		select {
		case f.result <- Event{Modified, obj}:
			return
		default:
			panic(fmt.Errorf("channel full"))
		}
	}
}

// Delete sends a delete event.
func (f *RaceFreeFakeWatcher) Delete(lastValue runtime.Object) {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		select {
		case f.result <- Event{Deleted, lastValue}:
			return
		default:
			panic(fmt.Errorf("channel full"))
		}
	}
}

// Error sends an Error event.
func (f *RaceFreeFakeWatcher) Error(errValue runtime.Object) {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		select {
		case f.result <- Event{Error, errValue}:
			return
		default:
			panic(fmt.Errorf("channel full"))
		}
	}
}

// Action sends an event of the requested type, for table-based testing.
func (f *RaceFreeFakeWatcher) Action(action EventType, obj runtime.Object) {
	f.Lock()
	defer f.Unlock()
	if !f.Stopped {
		select {
		case f.result <- Event{action, obj}:
			return
		default:
			panic(fmt.Errorf("channel full"))
		}
	}
}

// ProxyWatcher lets you wrap your channel in watch Interface. threadsafe.
type ProxyWatcher struct {
	result chan Event
	stopCh chan struct{}

	mutex   sync.Mutex
	stopped bool
}

var _ Interface = &ProxyWatcher{}

// NewProxyWatcher creates new ProxyWatcher by wrapping a channel
func NewProxyWatcher(ch chan Event) *ProxyWatcher {
	return &ProxyWatcher{
		result:  ch,
		stopCh:  make(chan struct{}),
		stopped: false,
	}
}

// Stop implements Interface
func (pw *ProxyWatcher) Stop() {
	pw.mutex.Lock()
	defer pw.mutex.Unlock()
	if !pw.stopped {
		pw.stopped = true
		close(pw.stopCh)
	}
}

// Stopping returns true if Stop() has been called
func (pw *ProxyWatcher) Stopping() bool {
	pw.mutex.Lock()
	defer pw.mutex.Unlock()
	return pw.stopped
}

// ResultChan implements Interface
func (pw *ProxyWatcher) ResultChan() <-chan Event {
	return pw.result
}

// StopChan returns stop channel
func (pw *ProxyWatcher) StopChan() <-chan struct{} {
	return pw.stopCh
}
<|endoftext|>


<commit_before>package ilang

import "strings"

func (ic *Compiler) CollectGarbage() {
	//Erm garbage collection???
	for name, variable := range ic.Scope[len(ic.Scope)-1] {
		if strings.Contains(name, "_") {
			var ok = false
			if ic.LastDefinedType.Detail != nil {
				_, ok = ic.LastDefinedType.Detail.Table[strings.Split(name, "_")[0]]
			}
			if variable == Unused && !(ic.GetFlag(InMethod) && ok ) {
				ic.RaiseError("unused variable! ", strings.Split(name, "_")[0])
			}
		}

		if ic.Scope[len(ic.Scope)-1][name+"."] != Protected { //Protected variables
			if ic.GetFlag(InMethod) && name == ic.LastDefinedType.Name {
				continue
			}

			//Possible memory leak, TODO check up on this.
			if _, ok := ic.DefinedTypes[name]; ic.GetFlag(InMethod) && ok {
				continue
			}

			if variable.IsUser() != Undefined && !variable.Empty() {
				ic.Assembly("SHARE ", name)
				ic.Assembly(ic.RunFunction("collect_m_"+variable.Name))
			}
		}
	}
}

func (t Type) Free(pointer string) string {
	if t.IsUser() == Undefined || t.Empty() {
		return ""
	}

	return "IF "+pointer+"\nPUSH "+pointer+
	"\nHEAP\nRUN collect_m_"+t.Name+
	"\nMUL "+pointer+" -1 "+pointer+"\nPUSH "+pointer+"\nHEAP\nEND"
}

func (ic *Compiler) Collect(t Type) {
	if t.IsUser() == Undefined || t.Empty() {
		return
	}

	var scope = ic.Scope
	ic.GainScope()
	ic.Library("FUNCTION collect_m_", t.Name)
	ic.GainScope()
	ic.Library("GRAB variable")
	ic.Library("PLACE variable")

	for i, element := range t.Detail.Elements {
		if element == Text || element.IsUser() != Undefined {
			var tmp = ic.Tmp("gc")
			ic.Library("PUSH ", i)
			ic.Library("GET ", tmp)

			ic.Library("IF ", tmp)
			ic.GainScope()
			ic.Library("MUL %v %v -1", tmp, tmp)
			ic.Library("PUSH ", tmp)
			ic.Library("HEAP")
			ic.Library("MUL %v %v -1", tmp, tmp)
			if element.IsUser() != Undefined {
				ic.Library("PUSH ", tmp)
				ic.Library("HEAP")

				var member = ic.Tmp("member")
				ic.Library("GRAB ", member)
				ic.Library("IF #", member)
					ic.Library("SHARE ", member)
					ic.Library(ic.RunFunction("collect_m_"+element.Name))
				ic.Library("END")
			}
			ic.LoseScope()
			ic.Library("END")
		}
	}
	ic.LoseScope()
	ic.Library("RETURN")
	ic.Scope = scope
}
<commit_msg>Fix garbage collection bug.<commit_after>package ilang

import "strings"

func (ic *Compiler) CollectGarbage() {
	//Erm garbage collection???
	for name, variable := range ic.Scope[len(ic.Scope)-1] {
		if strings.Contains(name, "_used") {
			var ok = false
			if ic.LastDefinedType.Detail != nil {
				_, ok = ic.LastDefinedType.Detail.Table[strings.Split(name, "_")[0]]
			}
			if variable == Unused && !(ic.GetFlag(InMethod) && ok ) {
				ic.RaiseError("unused variable! ", strings.Split(name, "_")[0])
			}
		}

		if ic.Scope[len(ic.Scope)-1][name+"."] != Protected { //Protected variables
			if ic.GetFlag(InMethod) && name == ic.LastDefinedType.Name {
				continue
			}

			//Possible memory leak, TODO check up on this.
			if _, ok := ic.DefinedTypes[name]; ic.GetFlag(InMethod) && ok {
				continue
			}

			if variable.IsUser() != Undefined && !variable.Empty() {
				ic.Assembly("SHARE ", name)
				ic.Assembly(ic.RunFunction("collect_m_"+variable.Name))
			}
		}
	}
}

func (t Type) Free(pointer string) string {
	if t.IsUser() == Undefined || t.Empty() {
		return ""
	}

	return "IF "+pointer+"\nPUSH "+pointer+
	"\nHEAP\nRUN collect_m_"+t.Name+
	"\nMUL "+pointer+" -1 "+pointer+"\nPUSH "+pointer+"\nHEAP\nEND"
}

func (ic *Compiler) Collect(t Type) {
	if t.IsUser() == Undefined || t.Empty() {
		return
	}

	var scope = ic.Scope
	ic.GainScope()
	ic.Library("FUNCTION collect_m_", t.Name)
	ic.GainScope()
	ic.Library("GRAB variable")
	ic.Library("PLACE variable")

	for i, element := range t.Detail.Elements {
		if element == Text || element.IsUser() != Undefined {
			var tmp = ic.Tmp("gc")
			ic.Library("PUSH ", i)
			ic.Library("GET ", tmp)

			ic.Library("IF ", tmp)
			ic.GainScope()
			ic.Library("PUSH ", tmp)
			ic.Library("HEAP")
			ic.Library("MUL %v %v -1", tmp, tmp)
			if element.IsUser() != Undefined {
				ic.Library("PUSH ", tmp)
				ic.Library("HEAP")

				var member = ic.Tmp("member")
				ic.Library("GRAB ", member)
				ic.Library("IF #", member)
					ic.Library("SHARE ", member)
					ic.Library(ic.RunFunction("collect_m_"+element.Name))
				ic.Library("END")
			}
			ic.LoseScope()
			ic.Library("END")
		}
	}
	ic.LoseScope()
	ic.Library("RETURN")
	ic.Scope = scope
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 topology

import (
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"sync"
	"text/template"

	"github.com/gorilla/mux"
	"github.com/hydrogen18/stoppableListener"

	"github.com/redhat-cip/skydive/config"
	"github.com/redhat-cip/skydive/logging"
	"github.com/redhat-cip/skydive/rpc"
	"github.com/redhat-cip/skydive/statics"
	"github.com/redhat-cip/skydive/topology/graph"
)

type Server struct {
	Graph  *graph.Graph
	Router *mux.Router
	Addr   string
	Port   int
	sl     *stoppableListener.StoppableListener
	wg     sync.WaitGroup
}

func (s *Server) serveIndex(w http.ResponseWriter, r *http.Request) {
	html, err := statics.Asset("statics/topology.html")
	if err != nil {
		logging.GetLogger().Panic("Unable to find the topology asset")
	}

	t := template.New("topology template")

	t, err = t.Parse(string(html))
	if err != nil {
		panic(err)
	}

	host, err := os.Hostname()
	if err != nil {
		panic(err)
	}

	var data = &struct {
		Hostname string
		Port     int
	}{
		Hostname: host,
		Port:     s.Port,
	}

	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	t.Execute(w, data)
}

func (s *Server) TopologyIndex(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)

	if err := json.NewEncoder(w).Encode(s.Graph); err != nil {
		panic(err)
	}
}

func (s *Server) RegisterStaticEndpoints() {
	s.Router.HandleFunc("/static/topology", s.serveIndex)
}

func (s *Server) RegisterRpcEndpoints() {
	routes := []rpc.Route{
		{
			"TopologiesIndex",
			"GET",
			"/rpc/topology",
			s.TopologyIndex,
		},
	}

	for _, route := range routes {
		s.Router.
			Methods(route.Method).
			Path(route.Pattern).
			Name(route.Name).
			Handler(route.HandlerFunc)
	}
}

func (s *Server) ListenAndServe() {
	defer s.wg.Done()
	s.wg.Add(1)

	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Addr, s.Port))
	if err != nil {
		logging.GetLogger().Fatalf("Failed to listen on %s:%d: %s", s.Addr, s.Port, err.Error())
	}

	s.sl, err = stoppableListener.New(listener)
	if err != nil {
		logging.GetLogger().Fatalf("Failed to create stoppable listener: %s", err.Error())
	}

	http.Serve(s.sl, s.Router)
}

func (s *Server) Stop() {
	if s.sl != nil {
		s.sl.Stop()
		s.wg.Wait()
	}
}

func NewServer(g *graph.Graph, a string, p int, router *mux.Router) *Server {
	return &Server{
		Graph:  g,
		Router: router,
		Addr:   a,
		Port:   p,
	}
}

func NewServerFromConfig(s string, g *graph.Graph, router *mux.Router) (*Server, error) {
	addr, port, err := config.GetHostPortAttributes(s, "listen")
	if err != nil {
		return nil, errors.New("Configuration error: " + err.Error())
	}

	return NewServer(g, addr, port, router), nil
}
<commit_msg>[topology] avoid data race during Stop()<commit_after>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 topology

import (
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"sync"
	"text/template"

	"github.com/gorilla/mux"
	"github.com/hydrogen18/stoppableListener"

	"github.com/redhat-cip/skydive/config"
	"github.com/redhat-cip/skydive/logging"
	"github.com/redhat-cip/skydive/rpc"
	"github.com/redhat-cip/skydive/statics"
	"github.com/redhat-cip/skydive/topology/graph"
)

type Server struct {
	Graph  *graph.Graph
	Router *mux.Router
	Addr   string
	Port   int
	lock   sync.Mutex
	sl     *stoppableListener.StoppableListener
	wg     sync.WaitGroup
}

func (s *Server) serveIndex(w http.ResponseWriter, r *http.Request) {
	html, err := statics.Asset("statics/topology.html")
	if err != nil {
		logging.GetLogger().Panic("Unable to find the topology asset")
	}

	t := template.New("topology template")

	t, err = t.Parse(string(html))
	if err != nil {
		panic(err)
	}

	host, err := os.Hostname()
	if err != nil {
		panic(err)
	}

	var data = &struct {
		Hostname string
		Port     int
	}{
		Hostname: host,
		Port:     s.Port,
	}

	w.Header().Set("Content-Type", "text/html; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
	t.Execute(w, data)
}

func (s *Server) TopologyIndex(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)

	if err := json.NewEncoder(w).Encode(s.Graph); err != nil {
		panic(err)
	}
}

func (s *Server) RegisterStaticEndpoints() {
	s.Router.HandleFunc("/static/topology", s.serveIndex)
}

func (s *Server) RegisterRpcEndpoints() {
	routes := []rpc.Route{
		{
			"TopologiesIndex",
			"GET",
			"/rpc/topology",
			s.TopologyIndex,
		},
	}

	for _, route := range routes {
		s.Router.
			Methods(route.Method).
			Path(route.Pattern).
			Name(route.Name).
			Handler(route.HandlerFunc)
	}
}

func (s *Server) ListenAndServe() {
	defer s.wg.Done()
	s.wg.Add(1)

	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Addr, s.Port))
	if err != nil {
		logging.GetLogger().Fatalf("Failed to listen on %s:%d: %s", s.Addr, s.Port, err.Error())
	}

	s.lock.Lock()
	s.sl, err = stoppableListener.New(listener)
	if err != nil {
		s.lock.Unlock()
		logging.GetLogger().Fatalf("Failed to create stoppable listener: %s", err.Error())
	}
	s.lock.Unlock()

	http.Serve(s.sl, s.Router)
}

func (s *Server) Stop() {
	s.lock.Lock()
	s.sl.Stop()
	s.lock.Unlock()

	s.wg.Wait()
}

func NewServer(g *graph.Graph, a string, p int, router *mux.Router) *Server {
	return &Server{
		Graph:  g,
		Router: router,
		Addr:   a,
		Port:   p,
	}
}

func NewServerFromConfig(s string, g *graph.Graph, router *mux.Router) (*Server, error) {
	addr, port, err := config.GetHostPortAttributes(s, "listen")
	if err != nil {
		return nil, errors.New("Configuration error: " + err.Error())
	}

	return NewServer(g, addr, port, router), nil
}
<|endoftext|>


<commit_before>package database

import (
	"database/sql"
	"errors"
	_ "github.com/alexbrainman/odbc"
	_ "github.com/denisenkom/go-mssqldb"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/ziutek/mymysql/godrv"
	"runtime"
	"strings"
)

type Database struct {
	inst       *sql.DB
	connString string
	driver     string
}

func (db *Database) Open(driver string, connString string) error {
	db.driver = driver
	db.connString = connString
	runtime.SetFinalizer(db, func(f interface{}) {
		f.(*Database).Close()
	})
	return db.executeOpen()
}

func (db *Database) executeOpen() error {
	var err error
	db.inst, err = sql.Open(db.driver, db.connString)
	if err != nil {
		return err
	}
	return nil
}

func (db *Database) Close() error {
	if db.inst != nil {
		return db.inst.Close()
	}
	return nil
}

type Rows struct {
	inst    *sql.Rows
	isFirst bool
	isNil   bool
	Cols    []string
}

func (db *Database) prepare(queryStr string, retCount int) (*sql.Stmt, error) {
	stmt, err := db.inst.Prepare(queryStr)
	if err != nil {
		db.Close()
		db.executeOpen()
		if retCount > 0 {
			return db.prepare(queryStr, retCount-1)
		}
		return nil, err
	}
	return stmt, err
}

func (db *Database) Query(queryStr string) (*Rows, error) {
	stmt, err := db.prepare(queryStr, 1)
	if stmt != nil {
		defer stmt.Close()
	}
	if err != nil {
		db.Close()
		db.executeOpen()
		return db.TempQuery(queryStr)
	}

	rows := &Rows{nil, true, false, make([]string, 0, 100)}
	rows.inst, err = stmt.Query()

	if err != nil {
		db.Close()
		db.executeOpen()
		return db.TempQuery(queryStr)
	}

	rows.Cols, err = rows.inst.Columns()

	if !rows.inst.Next() {
		rows.Close()
	}

	QUERYSTR := strings.ToUpper(queryStr)
	if strings.HasPrefix(QUERYSTR, "INSERT") && strings.Contains(QUERYSTR, "OUTPUT") && strings.Contains(QUERYSTR, "INSERTED.") {
		if rows.IsNil() {
			return nil, errors.New("insert.fail")
		}
	}

	runtime.SetFinalizer(rows, func(f interface{}) {
		f.(*Rows).Close()
	})

	return rows, nil
}

func (db *Database) TempQuery(queryStr string) (*Rows, error) {
	stmt, err := db.prepare(queryStr, 1)
	if stmt != nil {
		defer stmt.Close()
	}
	if err != nil {
		println("P1 : ", err.Error())
		return nil, err
	}

	rows := &Rows{nil, true, false, make([]string, 0, 100)}
	rows.inst, err = stmt.Query()

	if err != nil && err.Error() != "Stmt did not create a result set" {
		println("P2 : ", err.Error(), "\n", queryStr)
		return nil, err
	}

	rows.Cols, err = rows.inst.Columns()

	if !rows.inst.Next() {
		rows.Close()
	}

	QUERYSTR := strings.ToUpper(queryStr)
	if strings.HasPrefix(QUERYSTR, "INSERT") && strings.Contains(QUERYSTR, "OUTPUT") && strings.Contains(QUERYSTR, "INSERTED.") {
		if rows.IsNil() {
			return nil, errors.New("insert.fail")
		}
	}

	runtime.SetFinalizer(rows, func(f interface{}) {
		f.(*Rows).Close()
	})

	return rows, nil
}

func (rows *Rows) Next() bool {
	if !rows.isNil && rows.isFirst {
		rows.isFirst = false
		return true
	}
	if !rows.inst.Next() {
		rows.Close()
	}
	return !rows.isNil
}

func (rows *Rows) FetchArray() []interface{} {
	if rows.isNil {
		return nil
	}
	cols, err := rows.inst.Columns()
	if err != nil {
		return nil
	}

	rawResult := make([]*interface{}, len(cols))
	result := make([]interface{}, len(cols))

	dest := make([]interface{}, len(cols))
	for i, _ := range rawResult {
		dest[i] = &rawResult[i]
	}
	rows.inst.Scan(dest...)
	for i, raw := range rawResult {
		if raw != nil {
			result[i] = (*raw)
		} else {
			result[i] = nil
		}
	}
	return result
}

func (rows *Rows) FetchHash() map[string]interface{} {
	if rows.isNil {
		return nil
	}
	cols, err := rows.inst.Columns()
	if err != nil {
		return nil
	}

	rawResult := make([]*interface{}, len(cols))
	result := make(map[string]interface{}, len(cols))

	dest := make([]interface{}, len(cols))
	for i, _ := range rawResult {
		dest[i] = &rawResult[i]
	}
	rows.inst.Scan(dest...)
	for i, raw := range rawResult {
		if raw != nil {
			result[cols[i]] = (*raw)
			result[strings.ToUpper(cols[i])] = (*raw)
			result[strings.ToLower(cols[i])] = (*raw)
		} else {
			result[cols[i]] = nil
			result[strings.ToUpper(cols[i])] = nil
			result[strings.ToLower(cols[i])] = nil
		}
	}
	return result
}

func (rows *Rows) Close() error {
	if rows != nil && rows.inst != nil {
		rows.isNil = true
		return rows.inst.Close()
	}
	return nil
}

func (rows *Rows) IsNil() bool {
	return rows.isNil
}
<commit_msg>change<commit_after>package database

import (
	"database/sql"
	"errors"
	_ "github.com/alexbrainman/odbc"
	_ "github.com/denisenkom/go-mssqldb"
	_ "github.com/go-sql-driver/mysql"
	_ "github.com/ziutek/mymysql/godrv"
	"runtime"
	"strings"
)

type Database struct {
	inst       *sql.DB
	connString string
	driver     string
}

func (db *Database) Open(driver string, connString string) error {
	db.driver = driver
	db.connString = connString
	runtime.SetFinalizer(db, func(f interface{}) {
		f.(*Database).Close()
	})
	return db.executeOpen()
}

func (db *Database) executeOpen() error {
	var err error
	db.inst, err = sql.Open(db.driver, db.connString)
	if err != nil {
		return err
	}
	return nil
}

func (db *Database) Close() error {
	if db.inst != nil {
		return db.inst.Close()
	}
	return nil
}

type Rows struct {
	inst    *sql.Rows
	isFirst bool
	isNil   bool
	Cols    []string
}

func (db *Database) prepare(queryStr string, retCount int) (*sql.Stmt, error) {
	stmt, err := db.inst.Prepare(queryStr)
	if err != nil {
		db.Close()
		db.executeOpen()
		if retCount > 0 {
			return db.prepare(queryStr, retCount-1)
		}
		return nil, err
	}
	return stmt, err
}

func (db *Database) Query(queryStr string) (*Rows, error) {
	stmt, err := db.prepare(queryStr, 1)
	if stmt != nil {
		defer stmt.Close()
	}
	if err != nil {
		db.Close()
		db.executeOpen()
		return db.TempQuery(queryStr)
	}

	rows := &Rows{nil, true, false, make([]string, 0, 100)}
	rows.inst, err = stmt.Query()

	if err != nil {
		db.Close()
		db.executeOpen()
		return db.TempQuery(queryStr)
	}

	rows.Cols, err = rows.inst.Columns()

	if !rows.inst.Next() {
		rows.Close()
	}

	QUERYSTR := strings.ToUpper(queryStr)
	if strings.HasPrefix(QUERYSTR, "INSERT") && strings.Contains(QUERYSTR, "OUTPUT") && strings.Contains(QUERYSTR, "INSERTED.") {
		if rows.IsNil() {
			return nil, errors.New("insert.fail")
		}
	}

	runtime.SetFinalizer(rows, func(f interface{}) {
		f.(*Rows).Close()
	})

	return rows, nil
}

func (db *Database) TempQuery(queryStr string) (*Rows, error) {
	stmt, err := db.prepare(queryStr, 1)
	if stmt != nil {
		defer stmt.Close()
	}
	if err != nil {
		println("P1 : ", err.Error())
		return nil, err
	}

	rows := &Rows{nil, true, false, make([]string, 0, 100)}
	rows.inst, err = stmt.Query()

	if err != nil {
		if err.Error() != "Stmt did not create a result set" {
			println("P2 : ", err.Error(), "\n", queryStr)
			return nil, err
		} else {
			runtime.SetFinalizer(rows, func(f interface{}) {
				f.(*Rows).Close()
			})
			return rows, nil
		}
	}

	rows.Cols, err = rows.inst.Columns()

	if !rows.inst.Next() {
		rows.Close()
	} else {
		runtime.SetFinalizer(rows, func(f interface{}) {
			f.(*Rows).Close()
		})
	}

	QUERYSTR := strings.ToUpper(queryStr)
	if strings.HasPrefix(QUERYSTR, "INSERT") && strings.Contains(QUERYSTR, "OUTPUT") && strings.Contains(QUERYSTR, "INSERTED.") {
		if rows.IsNil() {
			return nil, errors.New("insert.fail")
		}
	}

	return rows, nil
}

func (rows *Rows) Next() bool {
	if !rows.isNil && rows.isFirst {
		rows.isFirst = false
		return true
	}
	if !rows.inst.Next() {
		rows.Close()
	}
	return !rows.isNil
}

func (rows *Rows) FetchArray() []interface{} {
	if rows.isNil {
		return nil
	}
	cols, err := rows.inst.Columns()
	if err != nil {
		return nil
	}

	rawResult := make([]*interface{}, len(cols))
	result := make([]interface{}, len(cols))

	dest := make([]interface{}, len(cols))
	for i, _ := range rawResult {
		dest[i] = &rawResult[i]
	}
	rows.inst.Scan(dest...)
	for i, raw := range rawResult {
		if raw != nil {
			result[i] = (*raw)
		} else {
			result[i] = nil
		}
	}
	return result
}

func (rows *Rows) FetchHash() map[string]interface{} {
	if rows.isNil {
		return nil
	}
	cols, err := rows.inst.Columns()
	if err != nil {
		return nil
	}

	rawResult := make([]*interface{}, len(cols))
	result := make(map[string]interface{}, len(cols))

	dest := make([]interface{}, len(cols))
	for i, _ := range rawResult {
		dest[i] = &rawResult[i]
	}
	rows.inst.Scan(dest...)
	for i, raw := range rawResult {
		if raw != nil {
			result[cols[i]] = (*raw)
			result[strings.ToUpper(cols[i])] = (*raw)
			result[strings.ToLower(cols[i])] = (*raw)
		} else {
			result[cols[i]] = nil
			result[strings.ToUpper(cols[i])] = nil
			result[strings.ToLower(cols[i])] = nil
		}
	}
	return result
}

func (rows *Rows) Close() error {
	if rows != nil && rows.inst != nil {
		rows.isNil = true
		return rows.inst.Close()
	}
	return nil
}

func (rows *Rows) IsNil() bool {
	return rows.isNil
}
<|endoftext|>


<commit_before>//
// util.go -- various utilities
//

package srnd

import (
  "github.com/majestrate/srndv2/src/nacl"
  "crypto/sha1"
  "crypto/sha512"
  "encoding/base64"
  "encoding/hex"
  "fmt"
  "io"
  "log"
  "net"
  "os"
  "path/filepath"
  "strconv"
  "strings"
  "time"
)

func DelFile(fname string) {
  if CheckFile(fname) {
    os.Remove(fname)
  }
}

func CheckFile(fname string) bool {
  if _, err := os.Stat(fname) ; os.IsNotExist(err) {
    return false
  }
  return true
}

func IsDir(dirname string) bool {
  stat, err := os.Stat(dirname)
  if err != nil {
    log.Fatal(err)
  }
  return stat.IsDir()
}

// ensure a directory exists
func EnsureDir(dirname string) {
  stat, err := os.Stat(dirname)
  if os.IsNotExist(err) {
    os.Mkdir(dirname, 0755)
  } else if ! stat.IsDir() {
    os.Remove(dirname)
    os.Mkdir(dirname, 0755)
  }
}

// TODO make this work better
func ValidMessageID(id string) bool {
  id_len := len(id)

  if id_len < 5 {
    return false
  }

  at_idx := strings.Index(id, "@")
  if at_idx < 3 {
    return false
  }

  for idx, c := range id {
    if idx == 0 {
      if c == '<' {
        continue
      }
    } else if idx == id_len - 1 {
      if c == '>' {
        continue
      }
    } else {
      if idx == at_idx {
        continue
      }
      if c >= 'a' && c <= 'z' {
        continue
      }
      if c >= 'A' && c <= 'Z' {
        continue
      }
      if c >= '0' && c <= '9' {
        continue
      }
      if c == '.' {
        continue
      }
    }
    log.Printf("bad message ID: len=%d %s , invalid char at %d: %c", id_len, id, idx, c)
    return false
  }
  return true
}

// message id hash
func HashMessageID(msgid string) string {
  return fmt.Sprintf("%x", sha1.Sum([]byte(msgid)))
}
// short message id hash
func ShortHashMessageID(msgid string) string {
  return strings.ToLower(HashMessageID(msgid)[:18])
}

// shorter message id hash
func ShorterHashMessageID(msgid string) string {
  return strings.ToLower(HashMessageID(msgid)[:10])
}

type lineWriter struct {
  io.Writer
  wr io.Writer
  delim []byte
}

func NewLineWriter(wr io.Writer, delim string) io.Writer {
  return lineWriter{wr, wr, []byte(delim)}
}

func (self lineWriter) Write(data []byte) (n int, err error) {
  n, err = self.wr.Write(data)
  self.wr.Write(self.delim)
  return n, err
}

func OpenFileWriter(fname string) (io.WriteCloser, error) {
  return os.Create(fname)
}

// make a random string
func randStr(length int) string {
  return hex.EncodeToString(nacl.RandBytes(length))[length:]
}

// time for right now as int64
func timeNow() int64 {
  return time.Now().Unix()
}

// sanitize data for nntp
func nntpSanitize(data string) string {
  parts := strings.Split(data, "\n.\n")
  return parts[0]
}

type int64Sorter []int64

func (self int64Sorter) Len() int {
  return len(self)
}

func (self int64Sorter) Less(i, j int) bool {
  return self[i] < self[j]
}

func (self int64Sorter) Swap(i, j int) {
  tmp := self[j]
  self[j] = self[i]
  self[i] = tmp
}

// obtain the "real" ip address
func getRealIP(name string) string {
  if len(name) > 0 {
    ip , err := net.ResolveIPAddr("ip", name)
    if err == nil {
      if ip.IP.IsGlobalUnicast() {
      return ip.IP.String()
      }
    }
  }
  return ""
}

// check that we have permission to access this
// fatal on fail
func checkPerms(fname string) {
  fstat, err := os.Stat(fname)
  if err != nil {
    log.Fatalf("Cannot access %s, %s", fname, err)
  }
  // check if we can access this dir
  if fstat.IsDir() {
    tmpfname := filepath.Join(fname, ".test")
    f, err := os.Create(tmpfname)
    if err != nil {
      log.Fatalf("No Write access in %s, %s", fname, err)
    }
    err = f.Close()
    if err != nil {
      log.Fatalf("failed to close test file %s !? %s", tmpfname, err)
    }
    err = os.Remove(tmpfname)
    if err != nil {
      log.Fatalf("failed to remove test file %s, %s", tmpfname, err)
    }
  } else {
    // this isn't a dir, treat it like a regular file
    f, err := os.Open(fname)
    if err != nil {
      log.Fatalf("cannot read file %s, %s", fname, err)
    }
    f.Close()
  }
}

// number of bytes to use in otp
func encAddrBytes() int {
  return 64
}

// length of an encrypted clearnet address
func encAddrLen() int {
  return 88
}

// length of an i2p dest hash
func i2pDestHashLen() int {
  return 44
}

// given an address
// generate a new encryption key for it
// return the encryption key and the encrypted address
func newAddrEnc(addr string) (string, string) {
  key_bytes := nacl.RandBytes(encAddrBytes())
  key := base64.StdEncoding.EncodeToString(key_bytes)
  return key, encAddr(addr, key)
}

// xor address with a one time pad
// if the address isn't long enough it's padded with spaces
func encAddr(addr, key string) string {
  key_bytes, err := base64.StdEncoding.DecodeString(key)

  if err != nil {
    log.Println("encAddr() key base64 decode", err)
    return ""
  }

  if len(addr) > len(key_bytes) {
    log.Println("encAddr() len(addr) > len(key_bytes)")
    return ""
  }

  // pad with spaces
  for len(addr) < len(key_bytes) {
    addr += " "
  }

  addr_bytes := []byte(addr)
  res_bytes := make([]byte, len(addr_bytes))
  for idx, b := range key_bytes {
    res_bytes[idx] = addr_bytes[idx] ^ b
  }

  return base64.StdEncoding.EncodeToString(res_bytes)
}

func checkError(err error) {
  if err != nil {
    log.Fatal(err)
  }
}

// decrypt an address
// strips any whitespaces
func decAddr(encaddr, key string) string {
  encaddr_bytes, err := base64.StdEncoding.DecodeString(encaddr)
  if err != nil {
    log.Println("decAddr() encaddr base64 decode", err)
    return ""
  }
  if len(encaddr_bytes) != len(key) {
    log.Println("decAddr() len(encaddr_bytes) != len(key)")
    return ""
  }
  key_bytes, err := base64.StdEncoding.DecodeString(key)
  if err != nil {
    log.Println("decAddr() key base64 decode", err)
  }
  res_bytes := make([]byte, len(key))
  for idx, b := range key_bytes {
    res_bytes[idx] = encaddr_bytes[idx] ^ b
  }
  res := string(res_bytes)
  return strings.Trim(res, " ")
}

func newsgroupValidFormat(newsgroup string) bool {
  // too long newsgroup
  if len(newsgroup) > 128 {
    return false
  }
  for _, ch := range newsgroup {
    if ch >= 'a' && ch <= 'z' {
      continue
    }
    if ch >= '0' && ch <= '9' {
      continue
    }
    if ch >= 'A' && ch <= 'Z' {
      continue
    }
    if ch == '.' {
      continue
    }
    return false
  }
  return true
}

// generate a new signing keypair
// public, secret
func newSignKeypair() (string, string) {
  kp := nacl.GenSignKeypair()
  defer kp.Free()
  pk := kp.Public()
  sk := kp.Seed()
  return hex.EncodeToString(pk), hex.EncodeToString(sk)
}

// make a utf-8 tripcode
func makeTripcode(pk string) string {
  data, err := hex.DecodeString(pk)
  if err == nil {
    tripcode := ""
    //  here is the python code this is based off of
    //  i do something slightly different but this is the base
    //
    //  for x in range(0, length / 2):
    //    pub_short += '&#%i;' % (9600 + int(full_pubkey_hex[x*2:x*2+2], 16))
    //  length -= length / 2
    //  for x in range(0, length):
    //    pub_short += '&#%i;' % (9600 + int(full_pubkey_hex[-(length*2):][x*2:x*2+2], 16))
    //
    for _, c := range data {
      ch := 9600
      ch += int(c)
      tripcode += fmt.Sprintf("&#%04d;", ch)
    }
    return tripcode
  }
  return "[invalid]"
}

// generate a new message id with base name
func genMessageID(name string) string {
  return fmt.Sprintf("<%s%d@%s>", randStr(5), timeNow(), name)
}

// time now as a string timestamp
func timeNowStr() string {
  return time.Unix(timeNow(), 0).UTC().Format(time.RFC1123Z)
}

// get from a map an int given a key or fall back to a default value
func mapGetInt(m map[string]string, key string, fallback int) int {
  val, ok := m[key]
  if ok {
    i, err := strconv.ParseInt(val, 10, 32)
    if err == nil {
      return int(i)
    }
  }
  return fallback
}

func isSage(str string) bool {
  str = strings.ToLower(str)
  return str == "sage" || strings.HasPrefix(str, "sage ")
}

func unhex(str string) []byte {
  buff, _ := hex.DecodeString(str)
  return buff
}

func hexify(data []byte) string {
  return hex.EncodeToString(data)
}

// extract pubkey from secret key
// return as base32
func getSignPubkey(sk []byte) string {
  return hexify(nacl.GetSignPubkey(sk))
}

// sign data with secret key the fucky srnd way
// return signature as base32
func cryptoSign(data, sk []byte) string {
  // hash
  hash := sha512.Sum512(data)
  log.Printf("hash=%s len=%s", hexify(hash[:]), len(data))
  // sign
  sig := nacl.CryptoSignFucky(hash[:], sk)
  return hexify(sig)
}

// given a tripcode after the #
// make a seed byteslice
func parseTripcodeSecret(str string) []byte {
  // try decoding hex
  raw := unhex(str)
  keylen := nacl.CryptoSignSeedLen()
  if raw == nil || len(raw) != keylen {
    // treat this as a "regular" chan tripcode
    // decode as bytes then pad the rest with 0s if it doesn't fit
    raw = make([]byte, keylen)
    str_bytes := []byte(str)
    if len(str_bytes) > keylen {
      copy(raw, str_bytes[:keylen])
    } else {
      copy(raw, str_bytes)
    }
  }
  return raw
}
<commit_msg>try fixing shorthash backlinks again<commit_after>//
// util.go -- various utilities
//

package srnd

import (
  "github.com/majestrate/srndv2/src/nacl"
  "crypto/sha1"
  "crypto/sha512"
  "encoding/base64"
  "encoding/hex"
  "fmt"
  "io"
  "log"
  "net"
  "os"
  "path/filepath"
  "strconv"
  "strings"
  "time"
)

func DelFile(fname string) {
  if CheckFile(fname) {
    os.Remove(fname)
  }
}

func CheckFile(fname string) bool {
  if _, err := os.Stat(fname) ; os.IsNotExist(err) {
    return false
  }
  return true
}

func IsDir(dirname string) bool {
  stat, err := os.Stat(dirname)
  if err != nil {
    log.Fatal(err)
  }
  return stat.IsDir()
}

// ensure a directory exists
func EnsureDir(dirname string) {
  stat, err := os.Stat(dirname)
  if os.IsNotExist(err) {
    os.Mkdir(dirname, 0755)
  } else if ! stat.IsDir() {
    os.Remove(dirname)
    os.Mkdir(dirname, 0755)
  }
}

// TODO make this work better
func ValidMessageID(id string) bool {
  id_len := len(id)

  if id_len < 5 {
    return false
  }

  at_idx := strings.Index(id, "@")
  if at_idx < 3 {
    return false
  }

  for idx, c := range id {
    if idx == 0 {
      if c == '<' {
        continue
      }
    } else if idx == id_len - 1 {
      if c == '>' {
        continue
      }
    } else {
      if idx == at_idx {
        continue
      }
      if c >= 'a' && c <= 'z' {
        continue
      }
      if c >= 'A' && c <= 'Z' {
        continue
      }
      if c >= '0' && c <= '9' {
        continue
      }
      if c == '.' {
        continue
      }
    }
    log.Printf("bad message ID: len=%d %s , invalid char at %d: %c", id_len, id, idx, c)
    return false
  }
  return true
}

// message id hash
func HashMessageID(msgid string) string {
  return fmt.Sprintf("%x", sha1.Sum([]byte(msgid)))
}
// short message id hash
func ShortHashMessageID(msgid string) string {
  return strings.ToLower(HashMessageID(msgid)[:18])
}

// shorter message id hash
func ShorterHashMessageID(msgid string) string {
  return strings.ToLower(HashMessageID(msgid)[:8])
}

type lineWriter struct {
  io.Writer
  wr io.Writer
  delim []byte
}

func NewLineWriter(wr io.Writer, delim string) io.Writer {
  return lineWriter{wr, wr, []byte(delim)}
}

func (self lineWriter) Write(data []byte) (n int, err error) {
  n, err = self.wr.Write(data)
  self.wr.Write(self.delim)
  return n, err
}

func OpenFileWriter(fname string) (io.WriteCloser, error) {
  return os.Create(fname)
}

// make a random string
func randStr(length int) string {
  return hex.EncodeToString(nacl.RandBytes(length))[length:]
}

// time for right now as int64
func timeNow() int64 {
  return time.Now().Unix()
}

// sanitize data for nntp
func nntpSanitize(data string) string {
  parts := strings.Split(data, "\n.\n")
  return parts[0]
}

type int64Sorter []int64

func (self int64Sorter) Len() int {
  return len(self)
}

func (self int64Sorter) Less(i, j int) bool {
  return self[i] < self[j]
}

func (self int64Sorter) Swap(i, j int) {
  tmp := self[j]
  self[j] = self[i]
  self[i] = tmp
}

// obtain the "real" ip address
func getRealIP(name string) string {
  if len(name) > 0 {
    ip , err := net.ResolveIPAddr("ip", name)
    if err == nil {
      if ip.IP.IsGlobalUnicast() {
      return ip.IP.String()
      }
    }
  }
  return ""
}

// check that we have permission to access this
// fatal on fail
func checkPerms(fname string) {
  fstat, err := os.Stat(fname)
  if err != nil {
    log.Fatalf("Cannot access %s, %s", fname, err)
  }
  // check if we can access this dir
  if fstat.IsDir() {
    tmpfname := filepath.Join(fname, ".test")
    f, err := os.Create(tmpfname)
    if err != nil {
      log.Fatalf("No Write access in %s, %s", fname, err)
    }
    err = f.Close()
    if err != nil {
      log.Fatalf("failed to close test file %s !? %s", tmpfname, err)
    }
    err = os.Remove(tmpfname)
    if err != nil {
      log.Fatalf("failed to remove test file %s, %s", tmpfname, err)
    }
  } else {
    // this isn't a dir, treat it like a regular file
    f, err := os.Open(fname)
    if err != nil {
      log.Fatalf("cannot read file %s, %s", fname, err)
    }
    f.Close()
  }
}

// number of bytes to use in otp
func encAddrBytes() int {
  return 64
}

// length of an encrypted clearnet address
func encAddrLen() int {
  return 88
}

// length of an i2p dest hash
func i2pDestHashLen() int {
  return 44
}

// given an address
// generate a new encryption key for it
// return the encryption key and the encrypted address
func newAddrEnc(addr string) (string, string) {
  key_bytes := nacl.RandBytes(encAddrBytes())
  key := base64.StdEncoding.EncodeToString(key_bytes)
  return key, encAddr(addr, key)
}

// xor address with a one time pad
// if the address isn't long enough it's padded with spaces
func encAddr(addr, key string) string {
  key_bytes, err := base64.StdEncoding.DecodeString(key)

  if err != nil {
    log.Println("encAddr() key base64 decode", err)
    return ""
  }

  if len(addr) > len(key_bytes) {
    log.Println("encAddr() len(addr) > len(key_bytes)")
    return ""
  }

  // pad with spaces
  for len(addr) < len(key_bytes) {
    addr += " "
  }

  addr_bytes := []byte(addr)
  res_bytes := make([]byte, len(addr_bytes))
  for idx, b := range key_bytes {
    res_bytes[idx] = addr_bytes[idx] ^ b
  }

  return base64.StdEncoding.EncodeToString(res_bytes)
}

func checkError(err error) {
  if err != nil {
    log.Fatal(err)
  }
}

// decrypt an address
// strips any whitespaces
func decAddr(encaddr, key string) string {
  encaddr_bytes, err := base64.StdEncoding.DecodeString(encaddr)
  if err != nil {
    log.Println("decAddr() encaddr base64 decode", err)
    return ""
  }
  if len(encaddr_bytes) != len(key) {
    log.Println("decAddr() len(encaddr_bytes) != len(key)")
    return ""
  }
  key_bytes, err := base64.StdEncoding.DecodeString(key)
  if err != nil {
    log.Println("decAddr() key base64 decode", err)
  }
  res_bytes := make([]byte, len(key))
  for idx, b := range key_bytes {
    res_bytes[idx] = encaddr_bytes[idx] ^ b
  }
  res := string(res_bytes)
  return strings.Trim(res, " ")
}

func newsgroupValidFormat(newsgroup string) bool {
  // too long newsgroup
  if len(newsgroup) > 128 {
    return false
  }
  for _, ch := range newsgroup {
    if ch >= 'a' && ch <= 'z' {
      continue
    }
    if ch >= '0' && ch <= '9' {
      continue
    }
    if ch >= 'A' && ch <= 'Z' {
      continue
    }
    if ch == '.' {
      continue
    }
    return false
  }
  return true
}

// generate a new signing keypair
// public, secret
func newSignKeypair() (string, string) {
  kp := nacl.GenSignKeypair()
  defer kp.Free()
  pk := kp.Public()
  sk := kp.Seed()
  return hex.EncodeToString(pk), hex.EncodeToString(sk)
}

// make a utf-8 tripcode
func makeTripcode(pk string) string {
  data, err := hex.DecodeString(pk)
  if err == nil {
    tripcode := ""
    //  here is the python code this is based off of
    //  i do something slightly different but this is the base
    //
    //  for x in range(0, length / 2):
    //    pub_short += '&#%i;' % (9600 + int(full_pubkey_hex[x*2:x*2+2], 16))
    //  length -= length / 2
    //  for x in range(0, length):
    //    pub_short += '&#%i;' % (9600 + int(full_pubkey_hex[-(length*2):][x*2:x*2+2], 16))
    //
    for _, c := range data {
      ch := 9600
      ch += int(c)
      tripcode += fmt.Sprintf("&#%04d;", ch)
    }
    return tripcode
  }
  return "[invalid]"
}

// generate a new message id with base name
func genMessageID(name string) string {
  return fmt.Sprintf("<%s%d@%s>", randStr(5), timeNow(), name)
}

// time now as a string timestamp
func timeNowStr() string {
  return time.Unix(timeNow(), 0).UTC().Format(time.RFC1123Z)
}

// get from a map an int given a key or fall back to a default value
func mapGetInt(m map[string]string, key string, fallback int) int {
  val, ok := m[key]
  if ok {
    i, err := strconv.ParseInt(val, 10, 32)
    if err == nil {
      return int(i)
    }
  }
  return fallback
}

func isSage(str string) bool {
  str = strings.ToLower(str)
  return str == "sage" || strings.HasPrefix(str, "sage ")
}

func unhex(str string) []byte {
  buff, _ := hex.DecodeString(str)
  return buff
}

func hexify(data []byte) string {
  return hex.EncodeToString(data)
}

// extract pubkey from secret key
// return as base32
func getSignPubkey(sk []byte) string {
  return hexify(nacl.GetSignPubkey(sk))
}

// sign data with secret key the fucky srnd way
// return signature as base32
func cryptoSign(data, sk []byte) string {
  // hash
  hash := sha512.Sum512(data)
  log.Printf("hash=%s len=%s", hexify(hash[:]), len(data))
  // sign
  sig := nacl.CryptoSignFucky(hash[:], sk)
  return hexify(sig)
}

// given a tripcode after the #
// make a seed byteslice
func parseTripcodeSecret(str string) []byte {
  // try decoding hex
  raw := unhex(str)
  keylen := nacl.CryptoSignSeedLen()
  if raw == nil || len(raw) != keylen {
    // treat this as a "regular" chan tripcode
    // decode as bytes then pad the rest with 0s if it doesn't fit
    raw = make([]byte, keylen)
    str_bytes := []byte(str)
    if len(str_bytes) > keylen {
      copy(raw, str_bytes[:keylen])
    } else {
      copy(raw, str_bytes)
    }
  }
  return raw
}
<|endoftext|>