<commit_before>package core

import (
	"sync"
	"time"
)

//CronJobs provides a cron job engine for golang function callbacks to schedule events to execute at specific times of the day and week.
var CronJobs cronJobs

//RecurringType defines a type of cron job.
type RecurringType int

var recurringJobs recurringJobsSync

const (
	CRON_TOP_OF_MINUTE RecurringType = iota
	CRON_TOP_OF_HOUR
	CRON_TOP_OF_DAY
)

type cronJobs struct {
}

type onDemandJobsSync struct {
	sync.RWMutex
	items []OnDemandEvent
}

type recurringJobsSync struct {
	sync.RWMutex
	items []recurringEvent
}

//CronJob entity provides details of the cron job to be executed.
type CronJob struct {
}

//CronJobEvent is used as the callback function for the event.
type OnDemandEvent func(id string, eventTime time.Time, context interface{})

//CronEvent is a callback function called by the cron job engine.
type RecurringEvent func(eventDate time.Time)

type recurringEvent struct {
	Type  RecurringType
	Event RecurringEvent
}

//Starts the cron job engine.
func (jobs *cronJobs) Start() {

	ticker := time.NewTicker(time.Millisecond * 100)
	go func() {

		callTopMinute := true
		callTopHour := true
		callTopDay := true

		for t := range ticker.C {
			tm := t
			hour, min, sec := t.Clock()
			if sec == 0 { //Top of the Minute
				if callTopMinute {
					go callRecurringEvents(CRON_TOP_OF_MINUTE, tm)
					callTopMinute = false
				}
			}
			if sec == 0 && min == 0 { //Top of the Hour
				if callTopHour {
					go callRecurringEvents(CRON_TOP_OF_HOUR, tm)
					callTopHour = false
				}
			}
			if sec == 0 && min == 0 && hour == 0 { //Top of the Day
				if callTopDay {
					go callRecurringEvents(CRON_TOP_OF_DAY, tm)
					callTopDay = false
				}
			}
			if sec == 1 {
				callTopMinute = true
				callTopHour = true
				callTopDay = true
			}
		}
	}()

}

//Register provides a method to register for a callback that is called at the start of the cron job engine and 5 seconds before each day occures.
func (jobs *cronJobs) RegisterRecurring(t RecurringType, callback RecurringEvent) {
	recurringJobs.Lock()
	var re recurringEvent
	re.Event = callback
	re.Type = t
	recurringJobs.items = append(recurringJobs.items, re)
	recurringJobs.Unlock()
}

func processRecurringTick(tm time.Time) {

}

func callRecurringEvents(t RecurringType, tm time.Time) {
	recurringJobs.RLock()
	for _, item := range recurringJobs.items {
		i := item
		if i.Type == t {
			go func(e RecurringEvent) {
				e(tm)
			}(i.Event)
		}
	}
	recurringJobs.RUnlock()
}
<commit_msg>Added 30 Second Cron Jobs.<commit_after>package core

import (
	"sync"
	"time"
)

//CronJobs provides a cron job engine for golang function callbacks to schedule events to execute at specific times of the day and week.
var CronJobs cronJobs

//RecurringType defines a type of cron job.
type RecurringType int

var recurringJobs recurringJobsSync

const (
	CRON_TOP_OF_MINUTE RecurringType = iota
	CRON_TOP_OF_HOUR
	CRON_TOP_OF_DAY
	CRON_TOP_OF_30_SECONDS
)

type cronJobs struct {
}

type onDemandJobsSync struct {
	sync.RWMutex
	items []OnDemandEvent
}

type recurringJobsSync struct {
	sync.RWMutex
	items []recurringEvent
}

//CronJob entity provides details of the cron job to be executed.
type CronJob struct {
}

//CronJobEvent is used as the callback function for the event.
type OnDemandEvent func(id string, eventTime time.Time, context interface{})

//CronEvent is a callback function called by the cron job engine.
type RecurringEvent func(eventDate time.Time)

type recurringEvent struct {
	Type  RecurringType
	Event RecurringEvent
}

//Starts the cron job engine.
func (jobs *cronJobs) Start() {

	ticker := time.NewTicker(time.Millisecond * 100)
	go func() {

		callTopMinute := true
		callTopHour := true
		callTopDay := true
		callTop30Seconds := true

		for t := range ticker.C {
			tm := t
			hour, min, sec := t.Clock()
			if sec == 0 { //Top of the Minute && Top of 30 Seconds
				if callTopMinute {
					go callRecurringEvents(CRON_TOP_OF_MINUTE, tm)
					callTopMinute = false
				}

				if callTop30Seconds {
					go callRecurringEvents(CRON_TOP_OF_30_SECONDS, tm)
					callTop30Seconds = false
				}
			}

			if sec == 30 { //Top of the Minute && Top of 30 Seconds
				if callTop30Seconds {
					go callRecurringEvents(CRON_TOP_OF_30_SECONDS, tm)
					callTop30Seconds = false
				}
			}

			if sec == 0 && min == 0 { //Top of the Hour
				if callTopHour {
					go callRecurringEvents(CRON_TOP_OF_HOUR, tm)
					callTopHour = false
				}
			}
			if sec == 0 && min == 0 && hour == 0 { //Top of the Day
				if callTopDay {
					go callRecurringEvents(CRON_TOP_OF_DAY, tm)
					callTopDay = false
				}
			}
			if sec == 1 {
				callTopMinute = true
				callTopHour = true
				callTopDay = true
				callTop30Seconds = true
			}
			if sec == 31 {
				callTop30Seconds = true
			}
		}
	}()

}

//Register provides a method to register for a callback that is called at the start of the cron job engine and 5 seconds before each day occures.
func (jobs *cronJobs) RegisterRecurring(t RecurringType, callback RecurringEvent) {
	recurringJobs.Lock()
	var re recurringEvent
	re.Event = callback
	re.Type = t
	recurringJobs.items = append(recurringJobs.items, re)
	recurringJobs.Unlock()
}

func processRecurringTick(tm time.Time) {

}

func callRecurringEvents(t RecurringType, tm time.Time) {
	recurringJobs.RLock()
	for _, item := range recurringJobs.items {
		i := item
		if i.Type == t {
			go func(e RecurringEvent) {
				e(tm)
			}(i.Event)
		}
	}
	recurringJobs.RUnlock()
}
<|endoftext|>


<commit_before>// Copyright 2022 Google LLC
//
// 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 gnmi

import (
	"context"
	"io"
	"strconv"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/peer"

	gpb "github.com/openconfig/gnmi/proto/gnmi"
)

// Mode indicates the mode in which the gNMI service operates.
type Mode string

const (
	// GNMIModeMetadataKey is the context metadata key used to specify the
	// mode in which the gNMI server should operate.
	GNMIModeMetadataKey = "gnmi-mode"
	// ConfigMode indicates that the gNMI service will allow updates to
	// intended configuration, but not operational state values.
	ConfigMode Mode = "config"
	// StateMode indicates that the gNMI service will allow updates to
	// operational state, but not intended configuration values.
	StateMode Mode = "state"

	// TimestampMetadataKey is the context metadata key used to specify a
	// custom timestamp for the values in the SetRequest instead of using
	// the time at which the SetRequest is received by the server.
	TimestampMetadataKey = "gnmi-timestamp"
)

// AddTimestampMetadata adds a gNMI timestamp metadata to the context.
//
// - ctx is the context to be used for accessing lemming's internal datastore.
// - timestamp is the number of nanoseconds since Epoch.
//
// NOTE: The output of this function should only be used to call into the
// internal lemming gNMI server. This is because it adds an incoming rather
// than an outgoing context metadata to skip regular protobuf handling.
func AddTimestampMetadata(ctx context.Context, timestamp int64) context.Context {
	return metadata.NewIncomingContext(ctx, metadata.Pairs(TimestampMetadataKey, strconv.FormatInt(timestamp, 10)))
}

// new creates a state-based gNMI client for the gNMI cache.
// The client calls the server gRPC implementation with a custom streaming gRPC implementation
// in order to bypass the regular gRPC wire marshalling/unmarshalling handling.
func newLocalClient(srv gpb.GNMIServer) gpb.GNMIClient {
	return &localClient{
		gnmiMode: StateMode,
		srv:      srv,
	}
}

// localClient is a gNMI client talks directly to a server, without sending messages over the wire.
type localClient struct {
	gpb.GNMIClient
	gnmiMode Mode
	srv      gpb.GNMIServer
}

// Set uses the datastore client for Set, instead of the public cache endpoint.
func (c *localClient) Set(ctx context.Context, in *gpb.SetRequest, _ ...grpc.CallOption) (*gpb.SetResponse, error) {
	return c.srv.Set(metadata.NewIncomingContext(ctx, metadata.Pairs(GNMIModeMetadataKey, string(c.gnmiMode))), in)
}

// Subscribe implements gNMI Subscribe, by calling a gNMI server directly.
func (c *localClient) Subscribe(ctx context.Context, _ ...grpc.CallOption) (gpb.GNMI_SubscribeClient, error) {
	errCh := make(chan error)
	respCh := make(chan *gpb.SubscribeResponse, 10)
	reqCh := make(chan *gpb.SubscribeRequest)

	sub := &subServer{
		respCh: respCh,
		reqCh:  reqCh,
		ctx:    peer.NewContext(ctx, &peer.Peer{}), // Add empty Peer, since the cache expects to be set.
	}
	client := &subClient{
		errCh:  errCh,
		respCh: respCh,
		reqCh:  reqCh,
	}

	go func() {
		err := c.srv.Subscribe(sub)
		errCh <- err
	}()
	return client, nil
}

// subClient is an implementation of GNMI_SubscribeClient that use channels to pass messages.
type subClient struct {
	gpb.GNMI_SubscribeClient
	errCh  chan error
	respCh chan *gpb.SubscribeResponse
	reqCh  chan *gpb.SubscribeRequest
}

func (sc *subClient) CloseSend() error {
	close(sc.reqCh)
	return nil
}

func (sc *subClient) Send(req *gpb.SubscribeRequest) error {
	sc.reqCh <- req
	return nil
}

func (sc *subClient) Recv() (*gpb.SubscribeResponse, error) {
	for {
		select {
		case resp := <-sc.respCh:
			return resp, nil
		case err := <-sc.errCh:
			return nil, err
		}
	}
}

// subServer is an implementation of GNMI_SubscribeServer that use channels to pass messages.
type subServer struct {
	gpb.GNMI_SubscribeServer
	respCh chan *gpb.SubscribeResponse
	reqCh  chan *gpb.SubscribeRequest
	ctx    context.Context
}

func (ss *subServer) Context() context.Context {
	return ss.ctx
}

func (ss *subServer) Send(resp *gpb.SubscribeResponse) error {
	ss.respCh <- resp
	return nil
}

func (ss *subServer) Recv() (*gpb.SubscribeRequest, error) {
	req, ok := <-ss.reqCh
	if !ok {
		return nil, io.EOF
	}
	return req, nil
}
<commit_msg>fix metadata<commit_after>// Copyright 2022 Google LLC
//
// 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 gnmi

import (
	"context"
	"io"
	"strconv"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/peer"

	gpb "github.com/openconfig/gnmi/proto/gnmi"
)

// Mode indicates the mode in which the gNMI service operates.
type Mode string

const (
	// GNMIModeMetadataKey is the context metadata key used to specify the
	// mode in which the gNMI server should operate.
	GNMIModeMetadataKey = "gnmi-mode"
	// ConfigMode indicates that the gNMI service will allow updates to
	// intended configuration, but not operational state values.
	ConfigMode Mode = "config"
	// StateMode indicates that the gNMI service will allow updates to
	// operational state, but not intended configuration values.
	StateMode Mode = "state"

	// TimestampMetadataKey is the context metadata key used to specify a
	// custom timestamp for the values in the SetRequest instead of using
	// the time at which the SetRequest is received by the server.
	TimestampMetadataKey = "gnmi-timestamp"
)

// appendToIncomingContext returns a new context with the provided kv merged
// with any existing metadata in the context. Please refer to the documentation
// of Pairs for a description of kv.
func appendToIncomingContext(ctx context.Context, kv ...string) context.Context {
	md, _ := metadata.FromIncomingContext(ctx)
	return metadata.NewIncomingContext(ctx, metadata.Join(md, metadata.Pairs(kv...)))
}

// AddTimestampMetadata adds a gNMI timestamp metadata to the context.
//
// - ctx is the context to be used for accessing lemming's internal datastore.
// - timestamp is the number of nanoseconds since Epoch.
//
// NOTE: The output of this function should only be used to call into the
// internal lemming gNMI server. This is because it adds an incoming rather
// than an outgoing context metadata to skip regular protobuf handling.
func AddTimestampMetadata(ctx context.Context, timestamp int64) context.Context {
	return appendToIncomingContext(ctx, TimestampMetadataKey, strconv.FormatInt(timestamp, 10))
}

// new creates a state-based gNMI client for the gNMI cache.
// The client calls the server gRPC implementation with a custom streaming gRPC implementation
// in order to bypass the regular gRPC wire marshalling/unmarshalling handling.
func newLocalClient(srv gpb.GNMIServer) gpb.GNMIClient {
	return &localClient{
		gnmiMode: StateMode,
		srv:      srv,
	}
}

// localClient is a gNMI client talks directly to a server, without sending messages over the wire.
type localClient struct {
	gpb.GNMIClient
	gnmiMode Mode
	srv      gpb.GNMIServer
}

// Set uses the datastore client for Set, instead of the public cache endpoint.
func (c *localClient) Set(ctx context.Context, in *gpb.SetRequest, _ ...grpc.CallOption) (*gpb.SetResponse, error) {
	return c.srv.Set(appendToIncomingContext(ctx, GNMIModeMetadataKey, string(c.gnmiMode)), in)
}

// Subscribe implements gNMI Subscribe, by calling a gNMI server directly.
func (c *localClient) Subscribe(ctx context.Context, _ ...grpc.CallOption) (gpb.GNMI_SubscribeClient, error) {
	errCh := make(chan error)
	respCh := make(chan *gpb.SubscribeResponse, 10)
	reqCh := make(chan *gpb.SubscribeRequest)

	sub := &subServer{
		respCh: respCh,
		reqCh:  reqCh,
		ctx:    peer.NewContext(ctx, &peer.Peer{}), // Add empty Peer, since the cache expects to be set.
	}
	client := &subClient{
		errCh:  errCh,
		respCh: respCh,
		reqCh:  reqCh,
	}

	go func() {
		err := c.srv.Subscribe(sub)
		errCh <- err
	}()
	return client, nil
}

// subClient is an implementation of GNMI_SubscribeClient that use channels to pass messages.
type subClient struct {
	gpb.GNMI_SubscribeClient
	errCh  chan error
	respCh chan *gpb.SubscribeResponse
	reqCh  chan *gpb.SubscribeRequest
}

func (sc *subClient) CloseSend() error {
	close(sc.reqCh)
	return nil
}

func (sc *subClient) Send(req *gpb.SubscribeRequest) error {
	sc.reqCh <- req
	return nil
}

func (sc *subClient) Recv() (*gpb.SubscribeResponse, error) {
	for {
		select {
		case resp := <-sc.respCh:
			return resp, nil
		case err := <-sc.errCh:
			return nil, err
		}
	}
}

// subServer is an implementation of GNMI_SubscribeServer that use channels to pass messages.
type subServer struct {
	gpb.GNMI_SubscribeServer
	respCh chan *gpb.SubscribeResponse
	reqCh  chan *gpb.SubscribeRequest
	ctx    context.Context
}

func (ss *subServer) Context() context.Context {
	return ss.ctx
}

func (ss *subServer) Send(resp *gpb.SubscribeResponse) error {
	ss.respCh <- resp
	return nil
}

func (ss *subServer) Recv() (*gpb.SubscribeRequest, error) {
	req, ok := <-ss.reqCh
	if !ok {
		return nil, io.EOF
	}
	return req, nil
}
<|endoftext|>


<commit_before>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package ssa defines a representation of the elements of Go programs
// (packages, types, functions, variables and constants) using a
// static single-assignment (SSA) form intermediate representation
// (IR) for the bodies of functions.
//
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
//
// For an introduction to SSA form, see
// http://en.wikipedia.org/wiki/Static_single_assignment_form.
// This page provides a broader reading list:
// http://www.dcs.gla.ac.uk/~jsinger/ssa.html.
//
// The level of abstraction of the SSA form is intentionally close to
// the source language to facilitate construction of source analysis
// tools.  It is not intended for machine code generation.
//
// All looping, branching and switching constructs are replaced with
// unstructured control flow.  Higher-level control flow constructs
// such as multi-way branch can be reconstructed as needed; see
// ssautil.Switches() for an example.
//
// The simplest way to create the SSA representation of a package is
// to load typed syntax trees using golang.org/x/tools/go/packages, then
// invoke the ssautil.Packages helper function. See ExampleLoadPackages
// and ExampleWholeProgram for examples.
// The resulting ssa.Program contains all the packages and their
// members, but SSA code is not created for function bodies until a
// subsequent call to (*Package).Build or (*Program).Build.
//
// The builder initially builds a naive SSA form in which all local
// variables are addresses of stack locations with explicit loads and
// stores.  Registerisation of eligible locals and φ-node insertion
// using dominance and dataflow are then performed as a second pass
// called "lifting" to improve the accuracy and performance of
// subsequent analyses; this pass can be skipped by setting the
// NaiveForm builder flag.
//
// The primary interfaces of this package are:
//
//    - Member: a named member of a Go package.
//    - Value: an expression that yields a value.
//    - Instruction: a statement that consumes values and performs computation.
//    - Node: a Value or Instruction (emphasizing its membership in the SSA value graph)
//
// A computation that yields a result implements both the Value and
// Instruction interfaces.  The following table shows for each
// concrete type which of these interfaces it implements.
//
//                      Value?          Instruction?      Member?
//   *Alloc                ✔               ✔
//   *BinOp                ✔               ✔
//   *Builtin              ✔
//   *Call                 ✔               ✔
//   *ChangeInterface      ✔               ✔
//   *ChangeType           ✔               ✔
//   *Const                ✔
//   *Convert              ✔               ✔
//   *SliceToArrayPointer  ✔               ✔
//   *DebugRef                             ✔
//   *Defer                                ✔
//   *Extract              ✔               ✔
//   *Field                ✔               ✔
//   *FieldAddr            ✔               ✔
//   *FreeVar              ✔
//   *Function             ✔                               ✔ (func)
//   *Global               ✔                               ✔ (var)
//   *Go                                   ✔
//   *If                                   ✔
//   *Index                ✔               ✔
//   *IndexAddr            ✔               ✔
//   *Jump                                 ✔
//   *Lookup               ✔               ✔
//   *MakeChan             ✔               ✔
//   *MakeClosure          ✔               ✔
//   *MakeInterface        ✔               ✔
//   *MakeMap              ✔               ✔
//   *MakeSlice            ✔               ✔
//   *MapUpdate                            ✔
//   *NamedConst                                           ✔ (const)
//   *Next                 ✔               ✔
//   *Panic                                ✔
//   *Parameter            ✔
//   *Phi                  ✔               ✔
//   *Range                ✔               ✔
//   *Return                               ✔
//   *RunDefers                            ✔
//   *Select               ✔               ✔
//   *Send                                 ✔
//   *Slice                ✔               ✔
//   *Store                                ✔
//   *Type                                                 ✔ (type)
//   *TypeAssert           ✔               ✔
//   *UnOp                 ✔               ✔
//
// Other key types in this package include: Program, Package, Function
// and BasicBlock.
//
// The program representation constructed by this package is fully
// resolved internally, i.e. it does not rely on the names of Values,
// Packages, Functions, Types or BasicBlocks for the correct
// interpretation of the program.  Only the identities of objects and
// the topology of the SSA and type graphs are semantically
// significant.  (There is one exception: Ids, used to identify field
// and method names, contain strings.)  Avoidance of name-based
// operations simplifies the implementation of subsequent passes and
// can make them very efficient.  Many objects are nonetheless named
// to aid in debugging, but it is not essential that the names be
// either accurate or unambiguous.  The public API exposes a number of
// name-based maps for client convenience.
//
// The ssa/ssautil package provides various utilities that depend only
// on the public API of this package.
//
// TODO(adonovan): Consider the exceptional control-flow implications
// of defer and recover().
//
// TODO(adonovan): write a how-to document for all the various cases
// of trying to determine corresponding elements across the four
// domains of source locations, ast.Nodes, types.Objects,
// ssa.Values/Instructions.
//
package ssa // import "golang.org/x/tools/go/ssa"
<commit_msg>go/ssa: keep the ops doc sorted alphabetically<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package ssa defines a representation of the elements of Go programs
// (packages, types, functions, variables and constants) using a
// static single-assignment (SSA) form intermediate representation
// (IR) for the bodies of functions.
//
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
//
// For an introduction to SSA form, see
// http://en.wikipedia.org/wiki/Static_single_assignment_form.
// This page provides a broader reading list:
// http://www.dcs.gla.ac.uk/~jsinger/ssa.html.
//
// The level of abstraction of the SSA form is intentionally close to
// the source language to facilitate construction of source analysis
// tools.  It is not intended for machine code generation.
//
// All looping, branching and switching constructs are replaced with
// unstructured control flow.  Higher-level control flow constructs
// such as multi-way branch can be reconstructed as needed; see
// ssautil.Switches() for an example.
//
// The simplest way to create the SSA representation of a package is
// to load typed syntax trees using golang.org/x/tools/go/packages, then
// invoke the ssautil.Packages helper function. See ExampleLoadPackages
// and ExampleWholeProgram for examples.
// The resulting ssa.Program contains all the packages and their
// members, but SSA code is not created for function bodies until a
// subsequent call to (*Package).Build or (*Program).Build.
//
// The builder initially builds a naive SSA form in which all local
// variables are addresses of stack locations with explicit loads and
// stores.  Registerisation of eligible locals and φ-node insertion
// using dominance and dataflow are then performed as a second pass
// called "lifting" to improve the accuracy and performance of
// subsequent analyses; this pass can be skipped by setting the
// NaiveForm builder flag.
//
// The primary interfaces of this package are:
//
//    - Member: a named member of a Go package.
//    - Value: an expression that yields a value.
//    - Instruction: a statement that consumes values and performs computation.
//    - Node: a Value or Instruction (emphasizing its membership in the SSA value graph)
//
// A computation that yields a result implements both the Value and
// Instruction interfaces.  The following table shows for each
// concrete type which of these interfaces it implements.
//
//                      Value?          Instruction?      Member?
//   *Alloc                ✔               ✔
//   *BinOp                ✔               ✔
//   *Builtin              ✔
//   *Call                 ✔               ✔
//   *ChangeInterface      ✔               ✔
//   *ChangeType           ✔               ✔
//   *Const                ✔
//   *Convert              ✔               ✔
//   *DebugRef                             ✔
//   *Defer                                ✔
//   *Extract              ✔               ✔
//   *Field                ✔               ✔
//   *FieldAddr            ✔               ✔
//   *FreeVar              ✔
//   *Function             ✔                               ✔ (func)
//   *Global               ✔                               ✔ (var)
//   *Go                                   ✔
//   *If                                   ✔
//   *Index                ✔               ✔
//   *IndexAddr            ✔               ✔
//   *Jump                                 ✔
//   *Lookup               ✔               ✔
//   *MakeChan             ✔               ✔
//   *MakeClosure          ✔               ✔
//   *MakeInterface        ✔               ✔
//   *MakeMap              ✔               ✔
//   *MakeSlice            ✔               ✔
//   *MapUpdate                            ✔
//   *NamedConst                                           ✔ (const)
//   *Next                 ✔               ✔
//   *Panic                                ✔
//   *Parameter            ✔
//   *Phi                  ✔               ✔
//   *Range                ✔               ✔
//   *Return                               ✔
//   *RunDefers                            ✔
//   *Select               ✔               ✔
//   *Send                                 ✔
//   *Slice                ✔               ✔
//   *SliceToArrayPointer  ✔               ✔
//   *Store                                ✔
//   *Type                                                 ✔ (type)
//   *TypeAssert           ✔               ✔
//   *UnOp                 ✔               ✔
//
// Other key types in this package include: Program, Package, Function
// and BasicBlock.
//
// The program representation constructed by this package is fully
// resolved internally, i.e. it does not rely on the names of Values,
// Packages, Functions, Types or BasicBlocks for the correct
// interpretation of the program.  Only the identities of objects and
// the topology of the SSA and type graphs are semantically
// significant.  (There is one exception: Ids, used to identify field
// and method names, contain strings.)  Avoidance of name-based
// operations simplifies the implementation of subsequent passes and
// can make them very efficient.  Many objects are nonetheless named
// to aid in debugging, but it is not essential that the names be
// either accurate or unambiguous.  The public API exposes a number of
// name-based maps for client convenience.
//
// The ssa/ssautil package provides various utilities that depend only
// on the public API of this package.
//
// TODO(adonovan): Consider the exceptional control-flow implications
// of defer and recover().
//
// TODO(adonovan): write a how-to document for all the various cases
// of trying to determine corresponding elements across the four
// domains of source locations, ast.Nodes, types.Objects,
// ssa.Values/Instructions.
//
package ssa // import "golang.org/x/tools/go/ssa"
<|endoftext|>


<commit_before>package gohex

import (
	"testing"
)

func TestConstructor(t *testing.T) {
	m := NewMemory()
	if m.GetStartAddress() != 0 {
		t.Error("incorrect initial start address")
	}
	if len(m.GetDataSegments()) != 0 {
		t.Error("incorrect initial data segments")
	}
	if m.currentAddress != 0 {
		t.Error("incorrect initial data segments")
	}
}

func assertParseError(t *testing.T, m *Memory, input string, et ParseErrorType, err string) {
	if e := m.ParseIntelHex(input); e != nil {
		perr, ok := e.(*ParseError)
		if ok == true {
			if perr.ErrorType != et {
				t.Error(perr.Error())
				t.Error(err)
			}
		} else {
			t.Error(err)
		}
	} else {
		t.Error(err)
	}
}

func TestSyntaxError(t *testing.T) {
	m := NewMemory()
	assertParseError(t, m, "00000001FF\n", SYNTAX_ERROR, "no colon error")
	assertParseError(t, m, ":qw00000001FF\n", SYNTAX_ERROR, "no ascii hex error")
	assertParseError(t, m, ":0000001FF\n", SYNTAX_ERROR, "no odd/even hex error")
}

func TestDataError(t *testing.T) {
	m := NewMemory()
	assertParseError(t, m, ":000000FF\n", DATA_ERROR, "no line length error")
	assertParseError(t, m, ":02000000FE\n", DATA_ERROR, "no data length error")
	assertParseError(t, m, "\n", DATA_ERROR, "no end of file line error")
	assertParseError(t, m, ":000000FF01\n", DATA_ERROR, "no end of file line error")
	assertParseError(t, m, ":0400000501000000F6\n", DATA_ERROR, "no end of file line error")
	assertParseError(t, m, ":0400000501000000F6\n:0400000502000000F5\n:00000001FF\n", DATA_ERROR, "no multiple start address lines error")
}

func TestChecksumError(t *testing.T) {
	m := NewMemory()
	assertParseError(t, m, ":00000101FF\n", CHECKSUM_ERROR, "no checksum error")
	assertParseError(t, m, ":00000001FE\n", CHECKSUM_ERROR, "no checksum error")
	assertParseError(t, m, ":0000000001\n", CHECKSUM_ERROR, "no checksum error")
	assertParseError(t, m, ":000000FF02\n", CHECKSUM_ERROR, "no checksum error")
}

func TestRecordsError(t *testing.T) {
	m := NewMemory()
	assertParseError(t, m, ":00000101FE\n", RECORD_ERROR, "no eof record error")
	assertParseError(t, m, ":00010001FE\n", RECORD_ERROR, "no eof record error")
	assertParseError(t, m, ":0100000100FE\n", RECORD_ERROR, "no eof record error")
	assertParseError(t, m, ":020001040101F7\n", RECORD_ERROR, "no extended address record error")
	assertParseError(t, m, ":020100040101F7\n", RECORD_ERROR, "no extended address record error")
	assertParseError(t, m, ":03000004010100F7\n", RECORD_ERROR, "no extended address record error")
	assertParseError(t, m, ":0400010501010101F2\n", RECORD_ERROR, "no start address record error")
	assertParseError(t, m, ":0401000501010101F2\n", RECORD_ERROR, "no start address record error")
	assertParseError(t, m, ":050000050101010100F2\n", RECORD_ERROR, "no start address record error")
}

func TestAddress(t *testing.T) {
	m := NewMemory()
	err := m.ParseIntelHex(":020000041234B4\n:0400000501020304ED\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if m.lineNum != 3 {
		t.Error("incorrect lines number")
	}
	if m.currentAddress != 0x12340000 {
		t.Errorf("incorrect extended address: %08X", m.currentAddress)
	}
	if m.startAddress != 0x01020304 {
		t.Errorf("incorrect start address: %08X", m.startAddress)
	}
	if len(m.GetDataSegments()) != 0 {
		t.Error("incorrect data segments")
	}
	if m.eofFlag != true {
		t.Error("incorrect eof flag state")
	}
	if m.startFlag != true {
		t.Error("incorrect start flag state")
	}
	err = m.ParseIntelHex(":020000049ABCA4\n:0400000591929394AD\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if m.currentAddress != 0x9ABC0000 {
		t.Errorf("incorrect extended address: %08X", m.currentAddress)
	}
	if m.startAddress != 0x91929394 {
		t.Errorf("incorrect start address: %08X", m.startAddress)
	}
	m.Clear()
	if m.lineNum != 0 {
		t.Error("incorrect lines number")
	}
	if len(m.GetDataSegments()) != 0 {
		t.Error("incorrect data segments")
	}
	if m.currentAddress != 0 {
		t.Errorf("incorrect extended address: %08X", m.currentAddress)
	}
	if m.startAddress != 0 {
		t.Errorf("incorrect start address: %08X", m.currentAddress)
	}
	if m.eofFlag != false {
		t.Error("incorrect eof flag state")
	}
	if m.startFlag != false {
		t.Error("incorrect start flag state")
	}
	err = m.ParseIntelHex(":020000041234B4\n:02000004234592\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if m.currentAddress != 0x23450000 {
		t.Errorf("incorrect extended address: %08X", m.currentAddress)
	}
}

func TestDataSegments(t *testing.T) {
	//m := NewMemory()
	//err := m.ParseIntelHex(":020000041234B4\n:00000001FF\n")
}

<commit_msg>more tests (data records)<commit_after>package gohex

import (
	"testing"
	"reflect"
)

func TestConstructor(t *testing.T) {
	m := NewMemory()
	if m.GetStartAddress() != 0 {
		t.Error("incorrect initial start address")
	}
	if len(m.GetDataSegments()) != 0 {
		t.Error("incorrect initial data segments")
	}
	if m.extendedAddress != 0 {
		t.Error("incorrect initial data segments")
	}
}

func assertParseError(t *testing.T, m *Memory, input string, et ParseErrorType, err string) {
	if e := m.ParseIntelHex(input); e != nil {
		perr, ok := e.(*ParseError)
		if ok == true {
			if perr.ErrorType != et {
				t.Error(perr.Error())
				t.Error(err)
			}
		} else {
			t.Error(err)
		}
	} else {
		t.Error(err)
	}
}

func TestSyntaxError(t *testing.T) {
	m := NewMemory()
	assertParseError(t, m, "00000001FF\n", SYNTAX_ERROR, "no colon error")
	assertParseError(t, m, ":qw00000001FF\n", SYNTAX_ERROR, "no ascii hex error")
	assertParseError(t, m, ":0000001FF\n", SYNTAX_ERROR, "no odd/even hex error")
}

func TestDataError(t *testing.T) {
	m := NewMemory()
	assertParseError(t, m, ":000000FF\n", DATA_ERROR, "no line length error")
	assertParseError(t, m, ":02000000FE\n", DATA_ERROR, "no data length error")
	assertParseError(t, m, "\n", DATA_ERROR, "no end of file line error")
	assertParseError(t, m, ":000000FF01\n", DATA_ERROR, "no end of file line error")
	assertParseError(t, m, ":0400000501000000F6\n", DATA_ERROR, "no end of file line error")
	assertParseError(t, m, ":0400000501000000F6\n:0400000502000000F5\n:00000001FF\n", DATA_ERROR, "no multiple start address lines error")
	assertParseError(t, m, ":048000000102030472\n:04800300050607085F\n:00000001FF\n", DATA_ERROR, "no segments overlap error")
	assertParseError(t, m, ":048000000102030472\n:047FFD000506070866\n:00000001FF\n", DATA_ERROR, "no segments overlap error")
}

func TestChecksumError(t *testing.T) {
	m := NewMemory()
	assertParseError(t, m, ":00000101FF\n", CHECKSUM_ERROR, "no checksum error")
	assertParseError(t, m, ":00000001FE\n", CHECKSUM_ERROR, "no checksum error")
	assertParseError(t, m, ":0000000001\n", CHECKSUM_ERROR, "no checksum error")
	assertParseError(t, m, ":000000FF02\n", CHECKSUM_ERROR, "no checksum error")
}

func TestRecordsError(t *testing.T) {
	m := NewMemory()
	assertParseError(t, m, ":00000101FE\n", RECORD_ERROR, "no eof record error")
	assertParseError(t, m, ":00010001FE\n", RECORD_ERROR, "no eof record error")
	assertParseError(t, m, ":0100000100FE\n", RECORD_ERROR, "no eof record error")
	assertParseError(t, m, ":020001040101F7\n", RECORD_ERROR, "no extended address record error")
	assertParseError(t, m, ":020100040101F7\n", RECORD_ERROR, "no extended address record error")
	assertParseError(t, m, ":03000004010100F7\n", RECORD_ERROR, "no extended address record error")
	assertParseError(t, m, ":0400010501010101F2\n", RECORD_ERROR, "no start address record error")
	assertParseError(t, m, ":0401000501010101F2\n", RECORD_ERROR, "no start address record error")
	assertParseError(t, m, ":050000050101010100F2\n", RECORD_ERROR, "no start address record error")
}

func TestAddress(t *testing.T) {
	m := NewMemory()
	err := m.ParseIntelHex(":020000041234B4\n:0400000501020304ED\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if m.lineNum != 3 {
		t.Error("incorrect lines number")
	}
	if m.extendedAddress != 0x12340000 {
		t.Errorf("incorrect extended address: %08X", m.extendedAddress)
	}
	if m.startAddress != 0x01020304 {
		t.Errorf("incorrect start address: %08X", m.startAddress)
	}
	if len(m.GetDataSegments()) != 0 {
		t.Error("incorrect data segments")
	}
	if m.eofFlag != true {
		t.Error("incorrect eof flag state")
	}
	if m.startFlag != true {
		t.Error("incorrect start flag state")
	}
	err = m.ParseIntelHex(":020000049ABCA4\n:0400000591929394AD\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if m.extendedAddress != 0x9ABC0000 {
		t.Errorf("incorrect extended address: %08X", m.extendedAddress)
	}
	if m.startAddress != 0x91929394 {
		t.Errorf("incorrect start address: %08X", m.startAddress)
	}
	m.Clear()
	if m.lineNum != 0 {
		t.Error("incorrect lines number")
	}
	if len(m.GetDataSegments()) != 0 {
		t.Error("incorrect data segments")
	}
	if m.extendedAddress != 0 {
		t.Errorf("incorrect extended address: %08X", m.extendedAddress)
	}
	if m.startAddress != 0 {
		t.Errorf("incorrect start address: %08X", m.extendedAddress)
	}
	if m.eofFlag != false {
		t.Error("incorrect eof flag state")
	}
	if m.startFlag != false {
		t.Error("incorrect start flag state")
	}
	err = m.ParseIntelHex(":020000041234B4\n:02000004234592\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if m.extendedAddress != 0x23450000 {
		t.Errorf("incorrect extended address: %08X", m.extendedAddress)
	}
}

func TestDataSegments(t *testing.T) {
	m := NewMemory()
	err := m.ParseIntelHex(":048000000102030472\n:04800400050607085E\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if len(m.GetDataSegments()) != 1 {
		t.Errorf("incorrect number of data segments: %v", len(m.GetDataSegments()))
	}
	seg := m.GetDataSegments()[0]
	p := DataSegment{address: 0x8000, data: []byte{1,2,3,4,5,6,7,8}}
	if reflect.DeepEqual(*seg, p) == false {
		t.Errorf("incorrect segment: %v != %v", *seg, p)
	}

	err = m.ParseIntelHex(":048000000102030472\n:047FFC000506070867\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if len(m.GetDataSegments()) != 1 {
		t.Errorf("incorrect number of data segments: %v", len(m.GetDataSegments()))
	}
	seg = m.GetDataSegments()[0]
	p = DataSegment{address: 0x7FFC, data: []byte{5,6,7,8,1,2,3,4}}
	if reflect.DeepEqual(*seg, p) == false {
		t.Errorf("incorrect segment: %v != %v", *seg, p)
	}

	err = m.ParseIntelHex(":048000000102030472\n:04800800050607085A\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if len(m.GetDataSegments()) != 2 {
		t.Errorf("incorrect number of data segments: %v", len(m.GetDataSegments()))
	}
	seg = m.GetDataSegments()[0]
	p = DataSegment{address: 0x8000, data: []byte{1,2,3,4}}
	if reflect.DeepEqual(*seg, p) == false {
		t.Errorf("incorrect segment: %v != %v", *seg, p)
	}
	seg = m.GetDataSegments()[1]
	p = DataSegment{address: 0x8008, data: []byte{5,6,7,8}}
	if reflect.DeepEqual(*seg, p) == false {
		t.Errorf("incorrect segment: %v != %v", *seg, p)
	}

	err = m.ParseIntelHex(":04800800050607085A\n:048000000102030472\n\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if len(m.GetDataSegments()) != 2 {
		t.Errorf("incorrect number of data segments: %v", len(m.GetDataSegments()))
	}
	seg = m.GetDataSegments()[0]
	p = DataSegment{address: 0x8008, data: []byte{5,6,7,8}}
	if reflect.DeepEqual(*seg, p) == false {
		t.Errorf("incorrect segment: %v != %v", *seg, p)
	}
	seg = m.GetDataSegments()[1]
	p = DataSegment{address: 0x8000, data: []byte{1,2,3,4}}
	if reflect.DeepEqual(*seg, p) == false {
		t.Errorf("incorrect segment: %v != %v", *seg, p)
	}

	err = m.ParseIntelHex(":020000041000EA\n:048000000102030472\n:04800800050607085A\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if len(m.GetDataSegments()) != 2 {
		t.Errorf("incorrect number of data segments: %v", len(m.GetDataSegments()))
	}
	seg = m.GetDataSegments()[0]
	p = DataSegment{address: 0x10008000, data: []byte{1,2,3,4}}
	if reflect.DeepEqual(*seg, p) == false {
		t.Errorf("incorrect segment: %v != %v", *seg, p)
	}
	seg = m.GetDataSegments()[1]
	p = DataSegment{address: 0x10008008, data: []byte{5,6,7,8}}
	if reflect.DeepEqual(*seg, p) == false {
		t.Errorf("incorrect segment: %v != %v", *seg, p)
	}

	err = m.ParseIntelHex(":020000041000EA\n:048000000102030472\n:020000042000DA\n:048000000506070862\n:00000001FF\n")
	if err != nil {
		t.Error("unexpected error: ", err.Error())
	}
	if len(m.GetDataSegments()) != 2 {
		t.Errorf("incorrect number of data segments: %v", len(m.GetDataSegments()))
	}
	seg = m.GetDataSegments()[0]
	p = DataSegment{address: 0x10008000, data: []byte{1,2,3,4}}
	if reflect.DeepEqual(*seg, p) == false {
		t.Errorf("incorrect segment: %v != %v", *seg, p)
	}
	seg = m.GetDataSegments()[1]
	p = DataSegment{address: 0x20008000, data: []byte{5,6,7,8}}
	if reflect.DeepEqual(*seg, p) == false {
		t.Errorf("incorrect segment: %v != %v", *seg, p)
	}

}

<|endoftext|>


<commit_before>package dynamicip

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/ava-labs/avalanchego/utils"
	"github.com/ava-labs/avalanchego/utils/logging"
)

type DynamicResolver interface {
	Resolve() (string, error)
	IsResolver() bool
}

type NoResolver struct {
}

func (r *NoResolver) IsResolver() bool {
	return false
}

func (r *NoResolver) Resolve() (string, error) {
	return "", errors.New("invalid resolver")
}

type OpenDNSResolver struct {
	*net.Resolver
}

func NewOpenDNSResolver() *OpenDNSResolver {
	return &OpenDNSResolver{&net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			d := net.Dialer{
				Timeout: time.Millisecond * time.Duration(10000),
			}
			return d.DialContext(ctx, "udp", "resolver1.opendns.com:53")
		},
	}}
}

func (r *OpenDNSResolver) IsResolver() bool {
	return true
}

func (r *OpenDNSResolver) Resolve() (string, error) {
	ip, err := r.Resolver.LookupHost(context.Background(), "myip.opendns.com")
	if err != nil {
		return "", err
	}
	if len(ip) == 0 {
		return "", errors.New(fmt.Sprintf("opendns returned no ip"))
	}
	return ip[0], nil
}

type IFConfigResolver struct {
}

func (r *IFConfigResolver) IsResolver() bool {
	return true
}

func (r *IFConfigResolver) Resolve() (string, error) {
	url := "http://ifconfig.co"
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	ip, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	ipstr := string(ip)
	ipstr = strings.Replace(ipstr, "\r\n", "", -1)
	ipstr = strings.Replace(ipstr, "\r", "", -1)
	ipstr = strings.Replace(ipstr, "\n", "", -1)
	return ipstr, nil
}

func NewDynamicResolver(opt string) DynamicResolver {
	switch opt {
	case "opendns":
		return NewOpenDNSResolver()
	case "ifconfig":
		return &IFConfigResolver{}
	default:
		return &NoResolver{}
	}
}

func FetchExternalIP(dynamicResolver DynamicResolver) (string, error) {
	return dynamicResolver.Resolve()
}

type DynamicIPManager interface {
	Stop()
}

type NoDynamicIP struct {
}

func (noDynamicIP *NoDynamicIP) Stop() {
}

type DynamicIP struct {
	tickerCloser    chan struct{}
	log             logging.Logger
	ip              *utils.DynamicIPDesc
	updateTimeout   time.Duration
	dynamicResolver DynamicResolver
}

func NewDynamicIPManager(dynamicResolver DynamicResolver, updateTimeout time.Duration, log logging.Logger, ip *utils.DynamicIPDesc) DynamicIPManager {
	if dynamicResolver.IsResolver() {
		updater := &DynamicIP{tickerCloser: make(chan struct{}), log: log, ip: ip, updateTimeout: updateTimeout, dynamicResolver: dynamicResolver}
		go updater.UpdateExternalIP()
		return updater
	}
	return &NoDynamicIP{}
}

func (dynamicIP *DynamicIP) Stop() {
	close(dynamicIP.tickerCloser)
}

func (dynamicIP *DynamicIP) UpdateExternalIP() {
	timer := time.NewTimer(dynamicIP.updateTimeout)
	defer timer.Stop()

	for {
		select {
		case <-timer.C:
			dynamicIP.updateIP(dynamicIP.dynamicResolver)
			timer.Reset(dynamicIP.updateTimeout)
		case <-dynamicIP.tickerCloser:
			return
		}
	}
}

func (dynamicIP *DynamicIP) updateIP(dynamicResolver DynamicResolver) {
	ipstr, err := FetchExternalIP(dynamicResolver)
	if err != nil {
		dynamicIP.log.Warn("Fetch external IP failed %s", err)
		return
	}
	newIp := net.ParseIP(ipstr)
	if newIp == nil {
		dynamicIP.log.Warn("Fetched external IP failed to parse %s", ipstr)
		return
	}
	oldIp := dynamicIP.ip.Ip().IP
	dynamicIP.ip.UpdateIP(newIp)
	if !oldIp.Equal(newIp) {
		dynamicIP.log.Info("ExternalIP updated to %s", newIp)
	}
}
<commit_msg>reformat<commit_after>package dynamicip

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/ava-labs/avalanchego/utils"
	"github.com/ava-labs/avalanchego/utils/logging"
)

type DynamicResolver interface {
	Resolve() (string, error)
	IsResolver() bool
}

type NoResolver struct {
}

func (r *NoResolver) IsResolver() bool {
	return false
}

func (r *NoResolver) Resolve() (string, error) {
	return "", errors.New("invalid resolver")
}

type OpenDNSResolver struct {
	*net.Resolver
}

func NewOpenDNSResolver() *OpenDNSResolver {
	return &OpenDNSResolver{&net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			d := net.Dialer{
				Timeout: time.Millisecond * time.Duration(10000),
			}
			return d.DialContext(ctx, "udp", "resolver1.opendns.com:53")
		},
	}}
}

func (r *OpenDNSResolver) IsResolver() bool {
	return true
}

func (r *OpenDNSResolver) Resolve() (string, error) {
	ip, err := r.Resolver.LookupHost(context.Background(), "myip.opendns.com")
	if err != nil {
		return "", err
	}
	if len(ip) == 0 {
		return "", errors.New(fmt.Sprintf("opendns returned no ip"))
	}
	return ip[0], nil
}

type IFConfigResolver struct {
}

func (r *IFConfigResolver) IsResolver() bool {
	return true
}

func (r *IFConfigResolver) Resolve() (string, error) {
	url := "http://ifconfig.co"
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	ip, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	ipstr := string(ip)
	ipstr = strings.Replace(ipstr, "\r\n", "", -1)
	ipstr = strings.Replace(ipstr, "\r", "", -1)
	ipstr = strings.Replace(ipstr, "\n", "", -1)
	return ipstr, nil
}

func NewDynamicResolver(opt string) DynamicResolver {
	switch opt {
	case "opendns":
		return NewOpenDNSResolver()
	case "ifconfig":
		return &IFConfigResolver{}
	default:
		return &NoResolver{}
	}
}

func FetchExternalIP(dynamicResolver DynamicResolver) (string, error) {
	return dynamicResolver.Resolve()
}

type DynamicIPManager interface {
	Stop()
}

type NoDynamicIP struct {
}

func (noDynamicIP *NoDynamicIP) Stop() {
}

type DynamicIP struct {
	tickerCloser    chan struct{}
	log             logging.Logger
	ip              *utils.DynamicIPDesc
	updateTimeout   time.Duration
	dynamicResolver DynamicResolver
}

func NewDynamicIPManager(dynamicResolver DynamicResolver, updateTimeout time.Duration, log logging.Logger, ip *utils.DynamicIPDesc) DynamicIPManager {
	if dynamicResolver.IsResolver() {
		updater := &DynamicIP{
			tickerCloser:    make(chan struct{}),
			log:             log,
			ip:              ip,
			updateTimeout:   updateTimeout,
			dynamicResolver: dynamicResolver}
		go updater.UpdateExternalIP()
		return updater
	}
	return &NoDynamicIP{}
}

func (dynamicIP *DynamicIP) Stop() {
	close(dynamicIP.tickerCloser)
}

func (dynamicIP *DynamicIP) UpdateExternalIP() {
	timer := time.NewTimer(dynamicIP.updateTimeout)
	defer timer.Stop()

	for {
		select {
		case <-timer.C:
			dynamicIP.updateIP(dynamicIP.dynamicResolver)
			timer.Reset(dynamicIP.updateTimeout)
		case <-dynamicIP.tickerCloser:
			return
		}
	}
}

func (dynamicIP *DynamicIP) updateIP(dynamicResolver DynamicResolver) {
	ipstr, err := FetchExternalIP(dynamicResolver)
	if err != nil {
		dynamicIP.log.Warn("Fetch external IP failed %s", err)
		return
	}
	newIp := net.ParseIP(ipstr)
	if newIp == nil {
		dynamicIP.log.Warn("Fetched external IP failed to parse %s", ipstr)
		return
	}
	oldIp := dynamicIP.ip.Ip().IP
	dynamicIP.ip.UpdateIP(newIp)
	if !oldIp.Equal(newIp) {
		dynamicIP.log.Info("ExternalIP updated to %s", newIp)
	}
}
<|endoftext|>


<commit_before>// Copyright 2011, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

/*
*  File: gonew_main.go
*  Created: Sat Jul  2 19:17:53 PDT 2011
 */
import (
	"github.com/bmatsuo/gonew/config"
	"github.com/bmatsuo/gonew/project"
	"github.com/bmatsuo/gonew/templates"

	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"
	"syscall"
	"text/template"
	"time"
	"unicode"
)

// The directory containing Gonew's source code.
var GonewRoot string // The Gonew source directory
func FindGonew() error {
	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		return fmt.Errorf("GOPATH is not set")
	}
	gopath = strings.SplitN(gopath, ":", 2)[0]
	GonewRoot = filepath.Join(gopath, "src", "github.com", "bmatsuo", "gonew")
	stat, err := os.Stat(GonewRoot)
	if err == nil && !stat.IsDir() {
		err = fmt.Errorf("file is not a directory: %s", GonewRoot)
	}
	return err
}

func check(err error, v ...interface{}) error {
	if err != nil {
		if len(v) == 0 {
			fmt.Println(err)
		} else {
			fmt.Printf("%s: %v", fmt.Sprint(v...), err)
		}
	}
	return err
}

func checkFatal(err error, v ...interface{}) {
	if check(err, v...) != nil {
		os.Exit(1)
	}
}

func logJson(v ...interface{}) {
	w := make([]interface{}, 0, len(v))
	w = append(w, v[:len(v)-1]...)
	p, _ := json.MarshalIndent(v[len(v)-1], " ", "\t")
	w = append(w, string(p))
	fmt.Println(w...)
}

func executeHooks(ts templates.Interface, tenv templates.Environment, hooks ...*config.HookConfig) {
	for _, hook := range hooks {
		cwd, err := tenv.RenderTextAsString(ts, "cwd_", hook.Cwd)
		checkFatal(err, "hook cwd template")
		// fmt.Println("cd", cwd)
		for _, _cmd := range hook.Commands {
			cmd, err := tenv.RenderTextAsString(ts, "cmd_", _cmd)
			checkFatal(err, "hook template")
			// fmt.Println("bash", "-c", cmd)
			shell := exec.Command("bash", "-c", cmd)
			shell.Dir = cwd
			shell.Stdin = os.Stdin
			shell.Stdout = os.Stdout
			shell.Stderr = os.Stderr
			checkFatal(shell.Run(), "hook") // TODO clean exit
		}
	}
}

type File struct {
	path    string
	content []byte
}

func funcs(env *config.Environment) template.FuncMap {
	return template.FuncMap{
		"name":  func() string { return env.User.Name },
		"email": func() string { return env.User.Email },

		"year": func() string { return time.Now().Format("2006") },
		"time": func(format ...string) string {
			if len(format) == 0 {
				format = append(format, time.RFC1123)
			}
			return time.Now().Format(format[0])
		},
		"date": func(format ...string) string {
			if len(format) == 0 {
				format = append(format, "Jan 02, 2006")
			}
			return time.Now().Format(format[0])
		},

		"import": func(pkgs ...string) string {
			if len(pkgs) == 0 {
				return `import ()`
			}
			if len(pkgs) == 1 {
				return `import "` + pkgs[0] + `"`
			}
			s := "import (\n"
			for _, pkg := range pkgs {
				s += "\t" + pkg + "\n"
			}
			s += ")"
			return s
		},
		"equal": func(v1, v2 interface{}) bool {
			return reflect.DeepEqual(reflect.ValueOf(v1), reflect.ValueOf(v2))
		},
	}
}

type options struct {
	env     string
	project string
	target  string
	pkg     string
	config  string
}

func parseOptions() *options {
	opts := new(options)
	fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	fs.StringVar(&opts.env, "env", "", "specify a user environment")
	fs.StringVar(&opts.pkg, "pkg", "", "specify a package name")
	fs.StringVar(&opts.config, "config", "", "specify config path")
	fs.Parse(os.Args[1:])

	args := fs.Args()
	if len(args) < 1 {
		fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "[options] [project] target")
		os.Exit(1)
	}
	if len(args) == 1 {
		opts.target = args[0]
	} else {
		opts.project, opts.target = args[0], args[1]
	}
	if opts.pkg == "" {
		opts.pkg = opts.target
	}

	return opts
}

func readLine(r *bufio.Reader, prompt string) (string, error) {
	fmt.Print(prompt)
	p, _, err := r.ReadLine()
	line := strings.TrimFunc(string(p), unicode.IsSpace)
	return line, err
}

func initConfig(path string) (conf *config.Gonew, err error) {
	if path == "" {
		home := os.Getenv("HOME")
		path = filepath.Join(home, ".config", "gonew.json")
	}
	conf = new(config.Gonew)
	err = conf.UnmarshalFileJSON(path)
	if err == nil {
		return
	}
	switch perr, ok := err.(*os.PathError); {
	case !ok:
		return
	case perr.Err == syscall.ENOENT || perr.Err == os.ErrNotExist:
		fmt.Fprintf(os.Stderr, "configuration not found at %q\n", path)
		fmt.Fprintln(os.Stderr)
		fmt.Fprintf(os.Stderr, "if you are migrating from an older version of Gonew check out the migration guide\n")
		fmt.Fprintf(os.Stderr, "\thttps://github.com/bmatsuo/gonew/blob/v2/MIGRATION.md\n")
		fmt.Fprintln(os.Stderr)
		fmt.Fprintf(os.Stderr, "otherwise, please take a moment to fill in the user information below\n")
		fmt.Fprintln(os.Stderr)

		var name string
		var email string
		var baseImportPath string
		bufr := bufio.NewReader(os.Stdin)
		name, err = readLine(bufr, "Your name: ")
		checkFatal(err)
		email, err = readLine(bufr, "Your email: ")
		checkFatal(err)
		baseImportPath, err = readLine(bufr, "Base import path (e.g. github.com/bmatsuo): ")
		checkFatal(err)

		examplePath := filepath.Join(GonewRoot, "gonew.json.example")
		checkFatal(conf.UnmarshalFileJSON(examplePath), "example config")
		conf.Environments = config.Environments{
			"default": &config.Environment{
				BaseImportPath: baseImportPath,
				User: &config.EnvironmentUserConfig{
					Name:  name,
					Email: email,
				},
			},
		}
		conf.Default.Environment = "default"
		err = conf.MarshalFileJSON(path)
	}
	return
}

func main() {
	checkFatal(FindGonew(), "root not found")

	// parse command line options/args
	opts := parseOptions()
	// read the config file
	conf, err := initConfig(opts.config)
	checkFatal(err, "config")

	// project metadata
	projectName := opts.target
	packageName := opts.pkg
	envName := opts.env
	if envName == "" {
		envName = conf.Default.Environment
	}
	projType := opts.project
	if projType == "" {
		projType = conf.Default.Project
	}

	// initialize project
	env, err := conf.Environment(envName)
	checkFatal(err)
	project.BaseImportPath = env.BaseImportPath
	proj := project.New(projectName, packageName, env)
	projContext := project.Context("", "", proj)
	projTemplEnv := templates.Env(projContext)
	projConfig, err := conf.Project(projType)
	checkFatal(err)

	// initialize template environment
	ts := templates.New(".t2")
	checkFatal(ts.Funcs(funcs(env)), "templates")

	// read templates
	src := templates.SourceDirectory(filepath.Join(GonewRoot, "templates"))
	checkFatal(ts.Source(src), "templates")
	for i := len(conf.ExternalTemplates) - 1; i >= 0; i-- {
		src := templates.SourceDirectory(conf.ExternalTemplates[i])
		checkFatal(ts.Source(src), "external templates")
	}

	if projConfig.Hooks != nil {
		executeHooks(ts, projTemplEnv, projConfig.Hooks.Pre...)
	}

	// generate files. buffer all output then write.
	files := make([]*File, 0, len(projConfig.Files))
	for name, file := range projConfig.Files {
		_relpath, err := projTemplEnv.RenderTextAsString(ts, "pre_", file.Path)
		checkFatal(err, name)
		relpath := string(_relpath)
		filename := filepath.Base(relpath)
		filetype := file.Type

		fileContext := project.Context(filename, filetype, proj)
		fileTemplEnv := templates.Env(fileContext)
		fileBuf := new(bytes.Buffer)
		for _, t := range file.Templates {
			if nil != check(fileTemplEnv.Render(fileBuf, ts, t)) {
				fileBuf = nil
				break
			}
		}

		if fileBuf != nil {
			f := &File{relpath, fileBuf.Bytes()}
			files = append(files, f)
		} else {
			// TODO clean exit
		}
	}
	for _, file := range files {
		dir := filepath.Dir(file.path)

		// fmt.Println("mkdir", "-p", dir)
		err := os.MkdirAll(dir, 0755|os.ModeDir)
		checkFatal(err, file) // TODO clean exit

		// fmt.Println("cat", ">", file.path)
		// fmt.Println(string(file.content))
		writeMode := os.O_WRONLY | os.O_CREATE | os.O_EXCL // must create
		handle, err := os.OpenFile(file.path, writeMode, 0644)
		checkFatal(err, file) // TODO clean exit
		_, err = handle.Write(file.content)
		checkFatal(err, file) // TODO clean exit
		err = handle.Close()
	}

	if projConfig.Hooks != nil {
		// fmt.Println("POST")
		executeHooks(ts, projTemplEnv, projConfig.Hooks.Post...)
	}
}
<commit_msg>move godoc into gonew_main.go<commit_after>// Copyright 2011, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Gonew generates new Go projects. The produced projects contain stub files and
can optionally initialize repositories and add files to them.

Usage

    gonew [options] project target

Arguments

	project: The type of project to generate
	target: The name from which filenames are based

Options

	-config="": specify config path
	-env="": specify a user environment
	-pkg="": specify a package name

Examples

    gonew pkg go-mp3lib
    gonew -pkg mp3lib lib decode
    gonew cmdtest goplay

Configuration

Gonew is configured via a JSON file stored in ~/.config/gonew.json. An example
can be found in gonew.json.example The configuration file specifies
environments, projects, and the locations of externally defined templates. An
environment holds information used in template rendering like user metadata and
import paths for created projects. A project configuration describes the files
contained in a project and script hooks to execute on file creation.
Environments can inherit/override other environments and projects can
inherit/override from other projects.

Custom Templates

Users can define their own set of custom templates. This is done by adding
entries to the ExternalTemplates array in the configuration file. Templates
can make use of the standard gonew templates (in the "templates" directory).
Templates must have the .t2 file extension to be recognized by Gonew.

Template Functions

Templates in Gonew have acces to a small library of helper functions Here is
list of all available template functions.

	name: the user's name specified in the environment
	email: the user's email specified in the environment
	year: the year in 4-digit format
*/
package main

/*
*  File: gonew_main.go
*  Created: Sat Jul  2 19:17:53 PDT 2011
 */
import (
	"github.com/bmatsuo/gonew/config"
	"github.com/bmatsuo/gonew/project"
	"github.com/bmatsuo/gonew/templates"

	"bufio"
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"
	"syscall"
	"text/template"
	"time"
	"unicode"
)

// The directory containing Gonew's source code.
var GonewRoot string // The Gonew source directory
func FindGonew() error {
	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		return fmt.Errorf("GOPATH is not set")
	}
	gopath = strings.SplitN(gopath, ":", 2)[0]
	GonewRoot = filepath.Join(gopath, "src", "github.com", "bmatsuo", "gonew")
	stat, err := os.Stat(GonewRoot)
	if err == nil && !stat.IsDir() {
		err = fmt.Errorf("file is not a directory: %s", GonewRoot)
	}
	return err
}

func check(err error, v ...interface{}) error {
	if err != nil {
		if len(v) == 0 {
			fmt.Println(err)
		} else {
			fmt.Printf("%s: %v", fmt.Sprint(v...), err)
		}
	}
	return err
}

func checkFatal(err error, v ...interface{}) {
	if check(err, v...) != nil {
		os.Exit(1)
	}
}

func logJson(v ...interface{}) {
	w := make([]interface{}, 0, len(v))
	w = append(w, v[:len(v)-1]...)
	p, _ := json.MarshalIndent(v[len(v)-1], " ", "\t")
	w = append(w, string(p))
	fmt.Println(w...)
}

func executeHooks(ts templates.Interface, tenv templates.Environment, hooks ...*config.HookConfig) {
	for _, hook := range hooks {
		cwd, err := tenv.RenderTextAsString(ts, "cwd_", hook.Cwd)
		checkFatal(err, "hook cwd template")
		// fmt.Println("cd", cwd)
		for _, _cmd := range hook.Commands {
			cmd, err := tenv.RenderTextAsString(ts, "cmd_", _cmd)
			checkFatal(err, "hook template")
			// fmt.Println("bash", "-c", cmd)
			shell := exec.Command("bash", "-c", cmd)
			shell.Dir = cwd
			shell.Stdin = os.Stdin
			shell.Stdout = os.Stdout
			shell.Stderr = os.Stderr
			checkFatal(shell.Run(), "hook") // TODO clean exit
		}
	}
}

type File struct {
	path    string
	content []byte
}

func funcs(env *config.Environment) template.FuncMap {
	return template.FuncMap{
		"name":  func() string { return env.User.Name },
		"email": func() string { return env.User.Email },

		"year": func() string { return time.Now().Format("2006") },
		"time": func(format ...string) string {
			if len(format) == 0 {
				format = append(format, time.RFC1123)
			}
			return time.Now().Format(format[0])
		},
		"date": func(format ...string) string {
			if len(format) == 0 {
				format = append(format, "Jan 02, 2006")
			}
			return time.Now().Format(format[0])
		},

		"import": func(pkgs ...string) string {
			if len(pkgs) == 0 {
				return `import ()`
			}
			if len(pkgs) == 1 {
				return `import "` + pkgs[0] + `"`
			}
			s := "import (\n"
			for _, pkg := range pkgs {
				s += "\t" + pkg + "\n"
			}
			s += ")"
			return s
		},
		"equal": func(v1, v2 interface{}) bool {
			return reflect.DeepEqual(reflect.ValueOf(v1), reflect.ValueOf(v2))
		},
	}
}

type options struct {
	env     string
	project string
	target  string
	pkg     string
	config  string
}

func parseOptions() *options {
	opts := new(options)
	fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	fs.StringVar(&opts.env, "env", "", "specify a user environment")
	fs.StringVar(&opts.pkg, "pkg", "", "specify a package name")
	fs.StringVar(&opts.config, "config", "", "specify config path")
	fs.Parse(os.Args[1:])

	args := fs.Args()
	if len(args) < 1 {
		fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "[options] [project] target")
		os.Exit(1)
	}
	if len(args) == 1 {
		opts.target = args[0]
	} else {
		opts.project, opts.target = args[0], args[1]
	}
	if opts.pkg == "" {
		opts.pkg = opts.target
	}

	return opts
}

func readLine(r *bufio.Reader, prompt string) (string, error) {
	fmt.Print(prompt)
	p, _, err := r.ReadLine()
	line := strings.TrimFunc(string(p), unicode.IsSpace)
	return line, err
}

func initConfig(path string) (conf *config.Gonew, err error) {
	if path == "" {
		home := os.Getenv("HOME")
		path = filepath.Join(home, ".config", "gonew.json")
	}
	conf = new(config.Gonew)
	err = conf.UnmarshalFileJSON(path)
	if err == nil {
		return
	}
	switch perr, ok := err.(*os.PathError); {
	case !ok:
		return
	case perr.Err == syscall.ENOENT || perr.Err == os.ErrNotExist:
		fmt.Fprintf(os.Stderr, "configuration not found at %q\n", path)
		fmt.Fprintln(os.Stderr)
		fmt.Fprintf(os.Stderr, "if you are migrating from an older version of Gonew check out the migration guide\n")
		fmt.Fprintf(os.Stderr, "\thttps://github.com/bmatsuo/gonew/blob/v2/MIGRATION.md\n")
		fmt.Fprintln(os.Stderr)
		fmt.Fprintf(os.Stderr, "otherwise, please take a moment to fill in the user information below\n")
		fmt.Fprintln(os.Stderr)

		var name string
		var email string
		var baseImportPath string
		bufr := bufio.NewReader(os.Stdin)
		name, err = readLine(bufr, "Your name: ")
		checkFatal(err)
		email, err = readLine(bufr, "Your email: ")
		checkFatal(err)
		baseImportPath, err = readLine(bufr, "Base import path (e.g. github.com/bmatsuo): ")
		checkFatal(err)

		examplePath := filepath.Join(GonewRoot, "gonew.json.example")
		checkFatal(conf.UnmarshalFileJSON(examplePath), "example config")
		conf.Environments = config.Environments{
			"default": &config.Environment{
				BaseImportPath: baseImportPath,
				User: &config.EnvironmentUserConfig{
					Name:  name,
					Email: email,
				},
			},
		}
		conf.Default.Environment = "default"
		err = conf.MarshalFileJSON(path)
	}
	return
}

func main() {
	checkFatal(FindGonew(), "root not found")

	// parse command line options/args
	opts := parseOptions()
	// read the config file
	conf, err := initConfig(opts.config)
	checkFatal(err, "config")

	// project metadata
	projectName := opts.target
	packageName := opts.pkg
	envName := opts.env
	if envName == "" {
		envName = conf.Default.Environment
	}
	projType := opts.project
	if projType == "" {
		projType = conf.Default.Project
	}

	// initialize project
	env, err := conf.Environment(envName)
	checkFatal(err)
	project.BaseImportPath = env.BaseImportPath
	proj := project.New(projectName, packageName, env)
	projContext := project.Context("", "", proj)
	projTemplEnv := templates.Env(projContext)
	projConfig, err := conf.Project(projType)
	checkFatal(err)

	// initialize template environment
	ts := templates.New(".t2")
	checkFatal(ts.Funcs(funcs(env)), "templates")

	// read templates
	src := templates.SourceDirectory(filepath.Join(GonewRoot, "templates"))
	checkFatal(ts.Source(src), "templates")
	for i := len(conf.ExternalTemplates) - 1; i >= 0; i-- {
		src := templates.SourceDirectory(conf.ExternalTemplates[i])
		checkFatal(ts.Source(src), "external templates")
	}

	if projConfig.Hooks != nil {
		executeHooks(ts, projTemplEnv, projConfig.Hooks.Pre...)
	}

	// generate files. buffer all output then write.
	files := make([]*File, 0, len(projConfig.Files))
	for name, file := range projConfig.Files {
		_relpath, err := projTemplEnv.RenderTextAsString(ts, "pre_", file.Path)
		checkFatal(err, name)
		relpath := string(_relpath)
		filename := filepath.Base(relpath)
		filetype := file.Type

		fileContext := project.Context(filename, filetype, proj)
		fileTemplEnv := templates.Env(fileContext)
		fileBuf := new(bytes.Buffer)
		for _, t := range file.Templates {
			if nil != check(fileTemplEnv.Render(fileBuf, ts, t)) {
				fileBuf = nil
				break
			}
		}

		if fileBuf != nil {
			f := &File{relpath, fileBuf.Bytes()}
			files = append(files, f)
		} else {
			// TODO clean exit
		}
	}
	for _, file := range files {
		dir := filepath.Dir(file.path)

		// fmt.Println("mkdir", "-p", dir)
		err := os.MkdirAll(dir, 0755|os.ModeDir)
		checkFatal(err, file) // TODO clean exit

		// fmt.Println("cat", ">", file.path)
		// fmt.Println(string(file.content))
		writeMode := os.O_WRONLY | os.O_CREATE | os.O_EXCL // must create
		handle, err := os.OpenFile(file.path, writeMode, 0644)
		checkFatal(err, file) // TODO clean exit
		_, err = handle.Write(file.content)
		checkFatal(err, file) // TODO clean exit
		err = handle.Close()
	}

	if projConfig.Hooks != nil {
		// fmt.Println("POST")
		executeHooks(ts, projTemplEnv, projConfig.Hooks.Post...)
	}
}
<|endoftext|>


<commit_before>package ipfilter

import (
	"bytes"
	"errors"
	"io"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/mholt/caddy/caddy/setup"
	"github.com/mholt/caddy/middleware"
	"github.com/oschwald/maxminddb-golang"
)

// IPFilter is a middleware for filtering clients based on their ip or country's ISO code;
type IPFilter struct {
	Next   middleware.Handler
	Config IPFConfig
}

// IPFConfig holds the configuration for the ipfilter middleware
type IPFConfig struct {
	PathScopes   []string
	Rule         string
	BlockPage    string
	CountryCodes []string
	Ranges       []Range

	DBHandler *maxminddb.Reader // Database's handler if it gets opened
}

// to ease if-statments, and not over-use len()
var (
	hasCountryCodes bool
	hasRanges       bool
	isBlock         bool // true if the rule is 'block'
	strict          bool
)

// Range is a pair of two 'net.IP'
type Range struct {
	start net.IP
	end   net.IP
}

// InRange is a method of 'Range' takes a pointer to net.IP, returns true if in range, false otherwise
func (rng Range) InRange(ip *net.IP) bool {
	if bytes.Compare(*ip, rng.start) >= 0 && bytes.Compare(*ip, rng.end) <= 0 {
		return true
	}
	return false
}

// OnlyCountry is used to fetch only the country's code from 'mmdb'
type OnlyCountry struct {
	Country struct {
		ISOCode string `maxminddb:"iso_code"`
	} `maxminddb:"country"`
}

// status is used to keep track of the status of the request
type Status struct {
	countryMatch, inRange bool
}

// method of Status, returns 'true' if any of the two is true
func (s *Status) Any() bool {
	return s.countryMatch || s.inRange
}

// block will take care of blocking
func block(blockPage string, w *http.ResponseWriter) (int, error) {
	if blockPage != "" {
		bp, err := os.Open(blockPage)
		if err != nil {
			return http.StatusInternalServerError, err
		}
		defer bp.Close()

		if _, err := io.Copy(*w, bp); err != nil {
			return http.StatusInternalServerError, err
		}
		// we wrote the blockpage, return OK
		return http.StatusOK, nil
	}

	// if we don't have blockpage, return forbidden
	return http.StatusForbidden, nil
}

// Setup parses the ipfilter configuration and returns the middleware handler
func Setup(c *setup.Controller) (middleware.Middleware, error) {
	ifconfig, err := ipfilterParse(c)
	if err != nil {
		return nil, err
	}

	return func(next middleware.Handler) middleware.Handler {
		return &IPFilter{
			Next:   next,
			Config: ifconfig,
		}
	}, nil
}

func getClientIP(r *http.Request) (net.IP, error) {
	var ip string

	// Use the client ip from the 'X-Forwarded-For' header, if available
	if fwdFor := r.Header.Get("X-Forwarded-For"); fwdFor != "" && !strict {
		ip = fwdFor
	} else {
		// Otherwise, get the client ip from the request remote address
		var err error
		ip, _, err = net.SplitHostPort(r.RemoteAddr)
		if err != nil {
			return nil, err
		}
	}

	// Parse the ip address string into a net.IP
	parsedIP := net.ParseIP(ip)
	if parsedIP == nil {
		return nil, errors.New("unable to parse address")
	}

	return parsedIP, nil
}

func (ipf IPFilter) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	// check if we are in one of our scopes
	for _, scope := range ipf.Config.PathScopes {
		if middleware.Path(r.URL.Path).Matches(scope) {
			// extract the client's IP and parse it
			clientIP, err := getClientIP(r)
			if err != nil {
				return http.StatusInternalServerError, err
			}

			// request status
			var rs Status

			if hasCountryCodes {
				// do the lookup
				var result OnlyCountry
				if err = ipf.Config.DBHandler.Lookup(clientIP, &result); err != nil {
					return http.StatusInternalServerError, err
				}

				// get only the ISOCode out of the lookup results
				clientCountry := result.Country.ISOCode
				for _, c := range ipf.Config.CountryCodes {
					if clientCountry == c {
						rs.countryMatch = true
						break
					}
				}
			}

			if hasRanges {
				for _, rng := range ipf.Config.Ranges {
					if rng.InRange(&clientIP) {
						rs.inRange = true
						break
					}
				}
			}

			if rs.Any() {
				if isBlock { // if the rule is block and we have a true in our status, block
					return block(ipf.Config.BlockPage, &w)
				}
				// the rule is allow, and we have a true in our status, allow
				return ipf.Next.ServeHTTP(w, r)
			}
			if isBlock { // the rule is block and we have no trues in status, allow
				return ipf.Next.ServeHTTP(w, r)
			}
			// the rule is allow, and we have no trues in status, block
			return block(ipf.Config.BlockPage, &w)
		}
	}
	// no scope match, pass-thru
	return ipf.Next.ServeHTTP(w, r)
}

func ipfilterParse(c *setup.Controller) (IPFConfig, error) {
	var config IPFConfig

	for c.Next() {

		// get the PathScopes
		config.PathScopes = c.RemainingArgs()
		if len(config.PathScopes) == 0 {
			return config, c.ArgErr()
		}

		for c.NextBlock() {
			value := c.Val()

			switch value {
			case "rule":
				if !c.NextArg() {
					return config, c.ArgErr()
				}
				config.Rule = c.Val()

				if config.Rule == "block" {
					isBlock = true
				} else if config.Rule != "allow" {
					return config, c.Err("ipfilter: Rule should be 'block' or 'allow'")
				}

			case "database":
				if !c.NextArg() {
					return config, c.ArgErr()
				}
				database := c.Val()

				// open the database
				var err error
				config.DBHandler, err = maxminddb.Open(database)
				if err != nil {
					return config, c.Err("ipfilter: Can't open database: " + database)
				}

			case "blockpage":
				if !c.NextArg() {
					return config, c.ArgErr()
				}

				// check if blockpage exists
				blockpage := c.Val()
				if _, err := os.Stat(blockpage); os.IsNotExist(err) {
					return config, c.Err("ipfilter: No such file: " + blockpage)
				}
				config.BlockPage = blockpage

			case "country":
				config.CountryCodes = c.RemainingArgs()
				if len(config.CountryCodes) == 0 {
					return config, c.ArgErr()
				}
				hasCountryCodes = true

			case "ip":
				ips := c.RemainingArgs()
				if len(ips) == 0 {
					return config, c.ArgErr()
				}

				for _, ip := range ips {
					// try to split on '-' to see if it is a range of ips e.g. 1.1.1.1-10
					splitted := strings.Split(ip, "-")
					if len(splitted) > 1 { // if more than one, then we got a range e.g. ["1.1.1.1", "10"]
						start := net.ParseIP(splitted[0])
						// make sure that we got a valid IPv4 IP
						if start.To4() == nil {
							return config, c.Err("ipfilter: Can't parse IPv4 address")
						}

						// split the start of the range on "." and switch the last field with splitted[1], e.g 1.1.1.1 -> 1.1.1.10
						fields := strings.Split(start.String(), ".")
						fields[3] = splitted[1]
						end := net.ParseIP(strings.Join(fields, "."))

						// parse the end range
						if end.To4() == nil {
							return config, c.Err("ipfilter: Can't parse IPv4 address")
						}

						// append to ranges, continue the loop
						config.Ranges = append(config.Ranges, Range{start, end})
						hasRanges = true
						continue

					}

					// the IP is not a range
					parsedIP := net.ParseIP(ip)
					if parsedIP.To4() == nil {
						return config, c.Err("ipfilter: Can't parse IPv4 address")
					}
					// append singular IPs as a range e.g Range{192.168.1.100, 192.168.1.100}
					config.Ranges = append(config.Ranges, Range{parsedIP, parsedIP})
					hasRanges = true
				}

			case "strict":
				strict = true
			}
		}
	}

	// having a databse is mandatory if you are blocking by country codes
	if hasCountryCodes && config.DBHandler == nil {
		return config, c.Err("ipfilter: Database is required to block/allow by country")
	}

	// needs atleast one of the three
	if !hasCountryCodes && !hasRanges {
		return config, c.Err("ipfilter: No IPs or Country codes has been provided")
	}
	return config, nil
}
<commit_msg>Adds support for open ranges<commit_after>package ipfilter

import (
	"bytes"
	"errors"
	"io"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/mholt/caddy/caddy/setup"
	"github.com/mholt/caddy/middleware"
	"github.com/oschwald/maxminddb-golang"
)

// IPFilter is a middleware for filtering clients based on their ip or country's ISO code;
type IPFilter struct {
	Next   middleware.Handler
	Config IPFConfig
}

// IPFConfig holds the configuration for the ipfilter middleware
type IPFConfig struct {
	PathScopes   []string
	Rule         string
	BlockPage    string
	CountryCodes []string
	Ranges       []Range

	DBHandler *maxminddb.Reader // Database's handler if it gets opened
}

// to ease if-statments, and not over-use len()
var (
	hasCountryCodes bool
	hasRanges       bool
	isBlock         bool // true if the rule is 'block'
	strict          bool
)

// Range is a pair of two 'net.IP'
type Range struct {
	start net.IP
	end   net.IP
}

// InRange is a method of 'Range' takes a pointer to net.IP, returns true if in range, false otherwise
func (rng Range) InRange(ip *net.IP) bool {
	if bytes.Compare(*ip, rng.start) >= 0 && bytes.Compare(*ip, rng.end) <= 0 {
		return true
	}
	return false
}

// OnlyCountry is used to fetch only the country's code from 'mmdb'
type OnlyCountry struct {
	Country struct {
		ISOCode string `maxminddb:"iso_code"`
	} `maxminddb:"country"`
}

// status is used to keep track of the status of the request
type Status struct {
	countryMatch, inRange bool
}

// method of Status, returns 'true' if any of the two is true
func (s *Status) Any() bool {
	return s.countryMatch || s.inRange
}

// block will take care of blocking
func block(blockPage string, w *http.ResponseWriter) (int, error) {
	if blockPage != "" {
		bp, err := os.Open(blockPage)
		if err != nil {
			return http.StatusInternalServerError, err
		}
		defer bp.Close()

		if _, err := io.Copy(*w, bp); err != nil {
			return http.StatusInternalServerError, err
		}
		// we wrote the blockpage, return OK
		return http.StatusOK, nil
	}

	// if we don't have blockpage, return forbidden
	return http.StatusForbidden, nil
}

// Setup parses the ipfilter configuration and returns the middleware handler
func Setup(c *setup.Controller) (middleware.Middleware, error) {
	ifconfig, err := ipfilterParse(c)
	if err != nil {
		return nil, err
	}

	return func(next middleware.Handler) middleware.Handler {
		return &IPFilter{
			Next:   next,
			Config: ifconfig,
		}
	}, nil
}

func getClientIP(r *http.Request) (net.IP, error) {
	var ip string

	// Use the client ip from the 'X-Forwarded-For' header, if available
	if fwdFor := r.Header.Get("X-Forwarded-For"); fwdFor != "" && !strict {
		ip = fwdFor
	} else {
		// Otherwise, get the client ip from the request remote address
		var err error
		ip, _, err = net.SplitHostPort(r.RemoteAddr)
		if err != nil {
			return nil, err
		}
	}

	// Parse the ip address string into a net.IP
	parsedIP := net.ParseIP(ip)
	if parsedIP == nil {
		return nil, errors.New("unable to parse address")
	}

	return parsedIP, nil
}

func (ipf IPFilter) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	// check if we are in one of our scopes
	for _, scope := range ipf.Config.PathScopes {
		if middleware.Path(r.URL.Path).Matches(scope) {
			// extract the client's IP and parse it
			clientIP, err := getClientIP(r)
			if err != nil {
				return http.StatusInternalServerError, err
			}

			// request status
			var rs Status

			if hasCountryCodes {
				// do the lookup
				var result OnlyCountry
				if err = ipf.Config.DBHandler.Lookup(clientIP, &result); err != nil {
					return http.StatusInternalServerError, err
				}

				// get only the ISOCode out of the lookup results
				clientCountry := result.Country.ISOCode
				for _, c := range ipf.Config.CountryCodes {
					if clientCountry == c {
						rs.countryMatch = true
						break
					}
				}
			}

			if hasRanges {
				for _, rng := range ipf.Config.Ranges {
					if rng.InRange(&clientIP) {
						rs.inRange = true
						break
					}
				}
			}

			if rs.Any() {
				if isBlock { // if the rule is block and we have a true in our status, block
					return block(ipf.Config.BlockPage, &w)
				}
				// the rule is allow, and we have a true in our status, allow
				return ipf.Next.ServeHTTP(w, r)
			}
			if isBlock { // the rule is block and we have no trues in status, allow
				return ipf.Next.ServeHTTP(w, r)
			}
			// the rule is allow, and we have no trues in status, block
			return block(ipf.Config.BlockPage, &w)
		}
	}
	// no scope match, pass-thru
	return ipf.Next.ServeHTTP(w, r)
}

func ipfilterParse(c *setup.Controller) (IPFConfig, error) {
	var config IPFConfig

	for c.Next() {

		// get the PathScopes
		config.PathScopes = c.RemainingArgs()
		if len(config.PathScopes) == 0 {
			return config, c.ArgErr()
		}

		for c.NextBlock() {
			value := c.Val()

			switch value {
			case "rule":
				if !c.NextArg() {
					return config, c.ArgErr()
				}
				config.Rule = c.Val()

				if config.Rule == "block" {
					isBlock = true
				} else if config.Rule != "allow" {
					return config, c.Err("ipfilter: Rule should be 'block' or 'allow'")
				}

			case "database":
				if !c.NextArg() {
					return config, c.ArgErr()
				}
				database := c.Val()

				// open the database
				var err error
				config.DBHandler, err = maxminddb.Open(database)
				if err != nil {
					return config, c.Err("ipfilter: Can't open database: " + database)
				}

			case "blockpage":
				if !c.NextArg() {
					return config, c.ArgErr()
				}

				// check if blockpage exists
				blockpage := c.Val()
				if _, err := os.Stat(blockpage); os.IsNotExist(err) {
					return config, c.Err("ipfilter: No such file: " + blockpage)
				}
				config.BlockPage = blockpage

			case "country":
				config.CountryCodes = c.RemainingArgs()
				if len(config.CountryCodes) == 0 {
					return config, c.ArgErr()
				}
				hasCountryCodes = true

			case "ip":
				ips := c.RemainingArgs()
				if len(ips) == 0 {
					return config, c.ArgErr()
				}

				for _, ip := range ips {
					// check if the ip isn't complete;
					// e.g. 192.168 -> Range{"192.168.0.0", "192.168.255.255"}
					dotSplit := strings.Split(ip, ".")
					if len(dotSplit) < 4 {
						startR := make([]string, len(dotSplit), 4)
						copy(startR, dotSplit)
						for len(dotSplit) < 4 {
							startR = append(startR, "0")
							dotSplit = append(dotSplit, "255")
						}
						start := net.ParseIP(strings.Join(startR, "."))
						end := net.ParseIP(strings.Join(dotSplit, "."))
						if start.To4() == nil || end.To4() == nil {
							return config, c.Err("ipfilter: Can't parse IPv4 address")
						}
						config.Ranges = append(config.Ranges, Range{start, end})
						hasRanges = true
						continue
					}

					// try to split on '-' to see if it is a range of ips e.g. 1.1.1.1-10
					splitted := strings.Split(ip, "-")
					if len(splitted) > 1 { // if more than one, then we got a range e.g. ["1.1.1.1", "10"]
						start := net.ParseIP(splitted[0])
						// make sure that we got a valid IPv4 IP
						if start.To4() == nil {
							return config, c.Err("ipfilter: Can't parse IPv4 address")
						}

						// split the start of the range on "." and switch the last field with splitted[1], e.g 1.1.1.1 -> 1.1.1.10
						fields := strings.Split(start.String(), ".")
						fields[3] = splitted[1]
						end := net.ParseIP(strings.Join(fields, "."))

						// parse the end range
						if end.To4() == nil {
							return config, c.Err("ipfilter: Can't parse IPv4 address")
						}

						// append to ranges, continue the loop
						config.Ranges = append(config.Ranges, Range{start, end})
						hasRanges = true
						continue

					}

					// the IP is not a range
					parsedIP := net.ParseIP(ip)
					if parsedIP.To4() == nil {
						return config, c.Err("ipfilter: Can't parse IPv4 address")
					}
					// append singular IPs as a range e.g Range{192.168.1.100, 192.168.1.100}
					config.Ranges = append(config.Ranges, Range{parsedIP, parsedIP})
					hasRanges = true
				}

			case "strict":
				strict = true
			}
		}
	}

	// having a databse is mandatory if you are blocking by country codes
	if hasCountryCodes && config.DBHandler == nil {
		return config, c.Err("ipfilter: Database is required to block/allow by country")
	}

	// needs atleast one of the three
	if !hasCountryCodes && !hasRanges {
		return config, c.Err("ipfilter: No IPs or Country codes has been provided")
	}
	return config, nil
}
<|endoftext|>