<commit_before>package main

import (
	"github.com/nfnt/resize"
	_ "golang.org/x/image/tiff"
	"image"
	"image/draw"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"os"
)

// SimpleImage implements IIIFImage for reading non-JP2 image types.  These can
// only handle a basic "read it all, then crop/resize" flow, and thus should
// have very careful load testing.
type SimpleImage struct {
	file         *os.File
	conf         image.Config
	decodeWidth  int
	decodeHeight int
	decodeArea   image.Rectangle
	resize       bool
	crop         bool
}

func NewSimpleImage(filename string) (*SimpleImage, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}

	i := &SimpleImage{file: file}
	i.conf, _, err = image.DecodeConfig(file)
	if err != nil {
		return nil, err
	}
	i.file.Seek(0, 0)

	// Default to full size, no crop
	i.decodeWidth = i.conf.Width
	i.decodeHeight = i.conf.Height
	i.decodeArea = image.Rect(0, 0, i.decodeWidth, i.decodeHeight)
	i.resize = false
	i.crop = false

	return i, nil
}

// SetScale sets the image to scale by the given multiplier, typically a
// percentage from 0 to 1.  This is mutually exclusive with resizing by a set
// width/height value.
func (i *SimpleImage) SetScale(m float64) {
	i.resize = true
	i.decodeWidth = int(float64(i.conf.Width) * m)
	i.decodeHeight = int(float64(i.conf.Height) * m)
}

// SetResizeWH sets the image to scale to the given width and height.  If one
// dimension is 0, the decoded image will preserve the aspect ratio while
// scaling to the non-zero dimension.
func (i *SimpleImage) SetResizeWH(width, height int) {
	i.resize = true
	i.decodeWidth = width
	i.decodeHeight = height
}

func (i *SimpleImage) SetCrop(r image.Rectangle) {
	i.crop = true
	i.decodeArea = r
}

// DecodeImage returns an image.Image that holds the decoded image data,
// resized and cropped if resizing or cropping was requested.  Both cropping
// and resizing happen here due to the nature of openjpeg, so SetScale,
// SetResizeWH, and SetCrop must be called before this function.
func (i *SimpleImage) DecodeImage() (image.Image, error) {
	img, _, err := image.Decode(i.file)
	if err != nil {
		return nil, err
	}

	if i.crop {
		srcB := img.Bounds()
		dstB := i.decodeArea
		dst := image.NewRGBA(image.Rect(0, 0, dstB.Dx(), dstB.Dy()))
		draw.Draw(dst, srcB, img, dstB.Min, draw.Src)
		img = dst
	}

	if i.resize {
		img = resize.Resize(uint(i.decodeWidth), uint(i.decodeHeight), img, resize.Bilinear)
	}

	return img, nil
}

// GetDimensions returns the config data as a rectangle
func (i *SimpleImage) GetDimensions() (image.Rectangle, error) {
	return image.Rect(0, 0, i.conf.Width, i.conf.Height), nil
}
<commit_msg>Fix SimpleImage resize to be *post*-crop<commit_after>package main

import (
	"github.com/nfnt/resize"
	_ "golang.org/x/image/tiff"
	"image"
	"image/draw"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
	"os"
)

// SimpleImage implements IIIFImage for reading non-JP2 image types.  These can
// only handle a basic "read it all, then crop/resize" flow, and thus should
// have very careful load testing.
type SimpleImage struct {
	file            *os.File
	conf            image.Config
	decodeWidth     int
	decodeHeight    int
	scaleFactor     float64
	decodeArea      image.Rectangle
	resizeByPercent bool
	resizeByPixels  bool
	crop            bool
}

func NewSimpleImage(filename string) (*SimpleImage, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}

	i := &SimpleImage{file: file}
	i.conf, _, err = image.DecodeConfig(file)
	if err != nil {
		return nil, err
	}
	i.file.Seek(0, 0)

	// Default to full size, no crop
	i.decodeWidth = i.conf.Width
	i.decodeHeight = i.conf.Height
	i.decodeArea = image.Rect(0, 0, i.decodeWidth, i.decodeHeight)

	return i, nil
}

// SetScale sets the image to scale by the given multiplier, typically a
// percentage from 0 to 1.  This is mutually exclusive with resizing by a set
// width/height value.
func (i *SimpleImage) SetScale(m float64) {
	i.resizeByPixels = false
	i.resizeByPercent = true
	i.scaleFactor = m
}

// SetResizeWH sets the image to scale to the given width and height.  If one
// dimension is 0, the decoded image will preserve the aspect ratio while
// scaling to the non-zero dimension.
func (i *SimpleImage) SetResizeWH(width, height int) {
	i.resizeByPercent = false
	i.resizeByPixels = true
	i.decodeWidth = width
	i.decodeHeight = height
}

func (i *SimpleImage) SetCrop(r image.Rectangle) {
	i.crop = true
	i.decodeArea = r
}

// DecodeImage returns an image.Image that holds the decoded image data,
// resized and cropped if resizing or cropping was requested.  Both cropping
// and resizing happen here due to the nature of openjpeg, so SetScale,
// SetResizeWH, and SetCrop must be called before this function.
func (i *SimpleImage) DecodeImage() (image.Image, error) {
	img, _, err := image.Decode(i.file)
	if err != nil {
		return nil, err
	}

	if i.crop {
		srcB := img.Bounds()
		dstB := i.decodeArea
		dst := image.NewRGBA(image.Rect(0, 0, dstB.Dx(), dstB.Dy()))
		draw.Draw(dst, srcB, img, dstB.Min, draw.Src)
		img = dst
	}

	if i.resizeByPercent {
		i.decodeWidth = int(float64(i.decodeArea.Dx()) * i.scaleFactor)
		i.decodeHeight = int(float64(i.decodeArea.Dy()) * i.scaleFactor)
		i.resizeByPixels = true
	}

	if i.resizeByPixels {
		img = resize.Resize(uint(i.decodeWidth), uint(i.decodeHeight), img, resize.Bilinear)
	}

	return img, nil
}

// GetDimensions returns the config data as a rectangle
func (i *SimpleImage) GetDimensions() (image.Rectangle, error) {
	return image.Rect(0, 0, i.conf.Width, i.conf.Height), nil
}
<|endoftext|>


<commit_before>package txindex

import (
	"context"

	"github.com/tendermint/tendermint/libs/service"
	"github.com/tendermint/tendermint/state/indexer"
	"github.com/tendermint/tendermint/types"
)

// XXX/TODO: These types should be moved to the indexer package.

const (
	subscriber = "IndexerService"
)

// IndexerService connects event bus, transaction and block indexers together in
// order to index transactions and blocks coming from the event bus.
type IndexerService struct {
	service.BaseService

	txIdxr    TxIndexer
	blockIdxr indexer.BlockIndexer
	eventBus  *types.EventBus
}

// NewIndexerService returns a new service instance.
func NewIndexerService(
	txIdxr TxIndexer,
	blockIdxr indexer.BlockIndexer,
	eventBus *types.EventBus,
) *IndexerService {

	is := &IndexerService{txIdxr: txIdxr, blockIdxr: blockIdxr, eventBus: eventBus}
	is.BaseService = *service.NewBaseService(nil, "IndexerService", is)
	return is
}

// OnStart implements service.Service by subscribing for all transactions
// and indexing them by events.
func (is *IndexerService) OnStart() error {
	// Use SubscribeUnbuffered here to ensure both subscriptions does not get
	// cancelled due to not pulling messages fast enough. Cause this might
	// sometimes happen when there are no other subscribers.
	blockHeadersSub, err := is.eventBus.SubscribeUnbuffered(
		context.Background(),
		subscriber,
		types.EventQueryNewBlockHeader)
	if err != nil {
		return err
	}

	txsSub, err := is.eventBus.SubscribeUnbuffered(context.Background(), subscriber, types.EventQueryTx)
	if err != nil {
		return err
	}

	go func() {
		for {
			msg := <-blockHeadersSub.Out()
			eventDataHeader := msg.Data().(types.EventDataNewBlockHeader)
			height := eventDataHeader.Header.Height
			batch := NewBatch(eventDataHeader.NumTxs)

			for i := int64(0); i < eventDataHeader.NumTxs; i++ {
				msg2 := <-txsSub.Out()
				txResult := msg2.Data().(types.EventDataTx).TxResult

				if err = batch.Add(&txResult); err != nil {
					is.Logger.Error(
						"failed to add tx to batch",
						"height", height,
						"index", txResult.Index,
						"err", err,
					)
				}
			}

			if err := is.blockIdxr.Index(eventDataHeader); err != nil {
				is.Logger.Error("failed to index block", "height", height, "err", err)
			} else {
				is.Logger.Error("indexed block", "height", height)
			}

			if err = is.txIdxr.AddBatch(batch); err != nil {
				is.Logger.Error("failed to index block txs", "height", height, "err", err)
			} else {
				is.Logger.Debug("indexed block txs", "height", height, "num_txs", eventDataHeader.NumTxs)
			}
		}
	}()
	return nil
}

// OnStop implements service.Service by unsubscribing from all transactions.
func (is *IndexerService) OnStop() {
	if is.eventBus.IsRunning() {
		_ = is.eventBus.UnsubscribeAll(context.Background(), subscriber)
	}
}
<commit_msg>change index block log to info (#6290) (#6294)<commit_after>package txindex

import (
	"context"

	"github.com/tendermint/tendermint/libs/service"
	"github.com/tendermint/tendermint/state/indexer"
	"github.com/tendermint/tendermint/types"
)

// XXX/TODO: These types should be moved to the indexer package.

const (
	subscriber = "IndexerService"
)

// IndexerService connects event bus, transaction and block indexers together in
// order to index transactions and blocks coming from the event bus.
type IndexerService struct {
	service.BaseService

	txIdxr    TxIndexer
	blockIdxr indexer.BlockIndexer
	eventBus  *types.EventBus
}

// NewIndexerService returns a new service instance.
func NewIndexerService(
	txIdxr TxIndexer,
	blockIdxr indexer.BlockIndexer,
	eventBus *types.EventBus,
) *IndexerService {

	is := &IndexerService{txIdxr: txIdxr, blockIdxr: blockIdxr, eventBus: eventBus}
	is.BaseService = *service.NewBaseService(nil, "IndexerService", is)
	return is
}

// OnStart implements service.Service by subscribing for all transactions
// and indexing them by events.
func (is *IndexerService) OnStart() error {
	// Use SubscribeUnbuffered here to ensure both subscriptions does not get
	// cancelled due to not pulling messages fast enough. Cause this might
	// sometimes happen when there are no other subscribers.
	blockHeadersSub, err := is.eventBus.SubscribeUnbuffered(
		context.Background(),
		subscriber,
		types.EventQueryNewBlockHeader)
	if err != nil {
		return err
	}

	txsSub, err := is.eventBus.SubscribeUnbuffered(context.Background(), subscriber, types.EventQueryTx)
	if err != nil {
		return err
	}

	go func() {
		for {
			msg := <-blockHeadersSub.Out()
			eventDataHeader := msg.Data().(types.EventDataNewBlockHeader)
			height := eventDataHeader.Header.Height
			batch := NewBatch(eventDataHeader.NumTxs)

			for i := int64(0); i < eventDataHeader.NumTxs; i++ {
				msg2 := <-txsSub.Out()
				txResult := msg2.Data().(types.EventDataTx).TxResult

				if err = batch.Add(&txResult); err != nil {
					is.Logger.Error(
						"failed to add tx to batch",
						"height", height,
						"index", txResult.Index,
						"err", err,
					)
				}
			}

			if err := is.blockIdxr.Index(eventDataHeader); err != nil {
				is.Logger.Error("failed to index block", "height", height, "err", err)
			} else {
				is.Logger.Info("indexed block", "height", height)
			}

			if err = is.txIdxr.AddBatch(batch); err != nil {
				is.Logger.Error("failed to index block txs", "height", height, "err", err)
			} else {
				is.Logger.Debug("indexed block txs", "height", height, "num_txs", eventDataHeader.NumTxs)
			}
		}
	}()
	return nil
}

// OnStop implements service.Service by unsubscribing from all transactions.
func (is *IndexerService) OnStop() {
	if is.eventBus.IsRunning() {
		_ = is.eventBus.UnsubscribeAll(context.Background(), subscriber)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2016 Andriy Pylypenko. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package sippy

import (
    "bufio"
    "net"
    "strconv"
    "strings"

    "sippy/net"
    "sippy/types"
)

func NewRtpProxyClient(opts *rtpProxyClientOpts) sippy_types.RtpProxyClient {
    return NewRtp_proxy_client_base(nil, opts)
}

type Rtp_proxy_client_base struct {
    heir            sippy_types.RtpProxyClient
    opts            *rtpProxyClientOpts
    transport       rtp_proxy_transport
    online          bool
    sbind_supported bool
    tnot_supported  bool
    copy_supported  bool
    stat_supported  bool
    wdnt_supported  bool
    caps_done       bool
    shut_down       bool
    active_sessions int64
    sessions_created int64
    active_streams  int64
    preceived       int64
    ptransmitted    int64
}

type rtp_proxy_transport interface {
    address() net.Addr
    get_rtpc_delay() float64
    is_local() bool
    send_command(string, func(string))
    shutdown()
    reconnect(net.Addr, *sippy_net.HostPort)
}

func (self *Rtp_proxy_client_base) IsLocal() bool {
    return self.transport.is_local()
}

func (self *Rtp_proxy_client_base) IsOnline() bool {
    return self.online
}

func (self *Rtp_proxy_client_base) WdntSupported() bool {
    return self.wdnt_supported
}

func (self *Rtp_proxy_client_base) SBindSupported() bool {
    return self.sbind_supported
}

func (self *Rtp_proxy_client_base) TNotSupported() bool {
    return self.tnot_supported
}

func (self *Rtp_proxy_client_base) GetProxyAddress() string {
    return self.opts.proxy_address
}

func (self *Rtp_proxy_client_base) me() sippy_types.RtpProxyClient {
    if self.heir != nil {
        return self.heir
    }
    return self
}

func (self *Rtp_proxy_client_base) Address() net.Addr {
    return self.transport.address()
}

func NewRtp_proxy_client_base(heir sippy_types.RtpProxyClient, opts *rtpProxyClientOpts) *Rtp_proxy_client_base {
    return &Rtp_proxy_client_base{
        heir            : heir,
        caps_done       : false,
        shut_down       : false,
        opts            : opts,
    }
}

func (self *Rtp_proxy_client_base) Start() error {
    var err error

    self.transport, err = self.opts.rtpp_class(self.me(), self.opts.config, self.opts.rtppaddr, self.opts.bind_address)
    if err != nil {
        return err
    }
    if ! self.opts.no_version_check {
        self.version_check()
    } else {
        self.caps_done = true
        self.online = true
    }
    return nil
}

func (self *Rtp_proxy_client_base) SendCommand(cmd string, cb func(string)) {
    self.transport.send_command(cmd, cb)
}

func (self *Rtp_proxy_client_base) Reconnect(addr net.Addr, bind_addr *sippy_net.HostPort) {
    self.transport.reconnect(addr, bind_addr)
}

func (self *Rtp_proxy_client_base) version_check() {
    if self.shut_down {
        return
    }
    self.transport.send_command("V", self.version_check_reply)
}

func (self *Rtp_proxy_client_base) version_check_reply(version string) {
    if self.shut_down {
        return
    }
    if version == "20040107" {
        self.me().GoOnline()
    } else if self.online {
        self.me().GoOffline()
    } else {
        StartTimeoutWithSpread(self.version_check, nil, self.opts.hrtb_retr_ival, 1, self.opts.logger, 0.1)
    }
}

func (self *Rtp_proxy_client_base) heartbeat() {
    //print "heartbeat", self, self.address
    if self.shut_down {
        return
    }
    self.transport.send_command("Ib", self.heartbeat_reply)
}

func (self *Rtp_proxy_client_base) heartbeat_reply(stats string) {
    //print "heartbeat_reply", self.address, stats, self.online
    if self.shut_down || ! self.online {
        return
    }
    if stats == "" {
        self.active_sessions = 0
        self.me().GoOffline()
    } else {
        sessions_created := int64(0)
        active_sessions := int64(0)
        active_streams := int64(0)
        preceived := int64(0)
        ptransmitted := int64(0)
        scanner := bufio.NewScanner(strings.NewReader(stats))
        for scanner.Scan() {
            line_parts := strings.SplitN(scanner.Text(), ":", 2)
            if len(line_parts) != 2 { continue }
            switch line_parts[0] {
            case "sessions created":
                sessions_created, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "active sessions":
                active_sessions, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "active streams":
                active_streams, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "packets received":
                preceived, _ = strconv.ParseInt(line_parts[1], 10, 64)
            case "packets transmitted":
                ptransmitted, _ = strconv.ParseInt(line_parts[1], 10, 64)
            }
        }
        self.UpdateActive(active_sessions, sessions_created, active_streams, preceived, ptransmitted)
    }
    StartTimeoutWithSpread(self.heartbeat, nil, self.opts.hrtb_ival, 1, self.opts.logger, 0.1)
}

func (self *Rtp_proxy_client_base) GoOnline() {
    if self.shut_down {
        return
    }
    if ! self.online {
        if ! self.caps_done {
            newRtppCapsChecker(self)
            return
        }
        self.online = true
        self.heartbeat()
    }
}

func (self *Rtp_proxy_client_base) GoOffline() {
    if self.shut_down {
        return
    }
    //print "go_offline", self.address, self.online
    if self.online {
        self.online = false
        StartTimeoutWithSpread(self.version_check, nil, self.opts.hrtb_retr_ival, 1, self.opts.logger, 0.1)
    }
}

func (self *Rtp_proxy_client_base) UpdateActive(active_sessions, sessions_created, active_streams, preceived, ptransmitted int64) {
    self.sessions_created = sessions_created
    self.active_sessions = active_sessions
    self.active_streams = active_streams
    self.preceived = preceived
    self.ptransmitted = ptransmitted
}

func (self *Rtp_proxy_client_base) GetActiveSessions() int64 {
    return self.active_sessions
}

func (self *Rtp_proxy_client_base) GetActiveStreams() int64 {
    return self.active_streams
}

func (self *Rtp_proxy_client_base) GetPReceived() int64 {
    return self.preceived
}

func (self *Rtp_proxy_client_base) GetSessionsCreated() int64 {
    return self.sessions_created
}

func (self *Rtp_proxy_client_base) GetPTransmitted() int64 {
    return self.ptransmitted
}

func (self *Rtp_proxy_client_base) Shutdown() {
    if self.shut_down { // do not crash when shutdown() called twice
        return
    }
    self.shut_down = true
    self.transport.shutdown()
    self.transport = nil
}

func (self *Rtp_proxy_client_base) IsShutDown() bool {
    return self.shut_down
}

func (self *Rtp_proxy_client_base) GetOpts() sippy_types.RtpProxyClientOpts {
    return self.opts
}

func (self *Rtp_proxy_client_base) GetRtpcDelay() float64 {
    return self.transport.get_rtpc_delay()
}

type rtppCapsChecker struct {
    caps_requested  int
    caps_received   int
    rtpc            *Rtp_proxy_client_base
}

func newRtppCapsChecker(rtpc *Rtp_proxy_client_base) *rtppCapsChecker {
    self := &rtppCapsChecker{
        rtpc    : rtpc,
    }
    rtpc.caps_done = false
    CAPSTABLE := []struct{ vers string; attr *bool }{
        { "20071218", &self.rtpc.copy_supported },
        { "20080403", &self.rtpc.stat_supported },
        { "20081224", &self.rtpc.tnot_supported },
        { "20090810", &self.rtpc.sbind_supported },
        { "20150617", &self.rtpc.wdnt_supported },
    }
    self.caps_requested = len(CAPSTABLE)
    for _, it := range CAPSTABLE {
        attr := it.attr // For some reason the it.attr cannot be passed into the following
                        // function directly - the resulting value is always that of the
                        // last 'it.attr' value.
        rtpc.transport.send_command("VF " + it.vers, func(res string) { self.caps_query_done(res, attr) })
    }
    return self
}

func (self *rtppCapsChecker) caps_query_done(result string, attr *bool) {
    self.caps_received += 1
    if result == "1" {
        *attr = true
    } else {
        *attr = false
    }
    if self.caps_received == self.caps_requested {
        self.rtpc.caps_done = true
        self.rtpc.me().GoOnline()
        self.rtpc = nil
    }
}
<commit_msg>Trim spaces.<commit_after>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2016 Andriy Pylypenko. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package sippy

import (
    "bufio"
    "net"
    "strconv"
    "strings"

    "sippy/net"
    "sippy/types"
)

func NewRtpProxyClient(opts *rtpProxyClientOpts) sippy_types.RtpProxyClient {
    return NewRtp_proxy_client_base(nil, opts)
}

type Rtp_proxy_client_base struct {
    heir            sippy_types.RtpProxyClient
    opts            *rtpProxyClientOpts
    transport       rtp_proxy_transport
    online          bool
    sbind_supported bool
    tnot_supported  bool
    copy_supported  bool
    stat_supported  bool
    wdnt_supported  bool
    caps_done       bool
    shut_down       bool
    active_sessions int64
    sessions_created int64
    active_streams  int64
    preceived       int64
    ptransmitted    int64
}

type rtp_proxy_transport interface {
    address() net.Addr
    get_rtpc_delay() float64
    is_local() bool
    send_command(string, func(string))
    shutdown()
    reconnect(net.Addr, *sippy_net.HostPort)
}

func (self *Rtp_proxy_client_base) IsLocal() bool {
    return self.transport.is_local()
}

func (self *Rtp_proxy_client_base) IsOnline() bool {
    return self.online
}

func (self *Rtp_proxy_client_base) WdntSupported() bool {
    return self.wdnt_supported
}

func (self *Rtp_proxy_client_base) SBindSupported() bool {
    return self.sbind_supported
}

func (self *Rtp_proxy_client_base) TNotSupported() bool {
    return self.tnot_supported
}

func (self *Rtp_proxy_client_base) GetProxyAddress() string {
    return self.opts.proxy_address
}

func (self *Rtp_proxy_client_base) me() sippy_types.RtpProxyClient {
    if self.heir != nil {
        return self.heir
    }
    return self
}

func (self *Rtp_proxy_client_base) Address() net.Addr {
    return self.transport.address()
}

func NewRtp_proxy_client_base(heir sippy_types.RtpProxyClient, opts *rtpProxyClientOpts) *Rtp_proxy_client_base {
    return &Rtp_proxy_client_base{
        heir            : heir,
        caps_done       : false,
        shut_down       : false,
        opts            : opts,
    }
}

func (self *Rtp_proxy_client_base) Start() error {
    var err error

    self.transport, err = self.opts.rtpp_class(self.me(), self.opts.config, self.opts.rtppaddr, self.opts.bind_address)
    if err != nil {
        return err
    }
    if ! self.opts.no_version_check {
        self.version_check()
    } else {
        self.caps_done = true
        self.online = true
    }
    return nil
}

func (self *Rtp_proxy_client_base) SendCommand(cmd string, cb func(string)) {
    self.transport.send_command(cmd, cb)
}

func (self *Rtp_proxy_client_base) Reconnect(addr net.Addr, bind_addr *sippy_net.HostPort) {
    self.transport.reconnect(addr, bind_addr)
}

func (self *Rtp_proxy_client_base) version_check() {
    if self.shut_down {
        return
    }
    self.transport.send_command("V", self.version_check_reply)
}

func (self *Rtp_proxy_client_base) version_check_reply(version string) {
    if self.shut_down {
        return
    }
    if version == "20040107" {
        self.me().GoOnline()
    } else if self.online {
        self.me().GoOffline()
    } else {
        StartTimeoutWithSpread(self.version_check, nil, self.opts.hrtb_retr_ival, 1, self.opts.logger, 0.1)
    }
}

func (self *Rtp_proxy_client_base) heartbeat() {
    //print "heartbeat", self, self.address
    if self.shut_down {
        return
    }
    self.transport.send_command("Ib", self.heartbeat_reply)
}

func (self *Rtp_proxy_client_base) heartbeat_reply(stats string) {
    //print "heartbeat_reply", self.address, stats, self.online
    if self.shut_down || ! self.online {
        return
    }
    if stats == "" {
        self.active_sessions = 0
        self.me().GoOffline()
    } else {
        sessions_created := int64(0)
        active_sessions := int64(0)
        active_streams := int64(0)
        preceived := int64(0)
        ptransmitted := int64(0)
        scanner := bufio.NewScanner(strings.NewReader(stats))
        for scanner.Scan() {
            line_parts := strings.SplitN(scanner.Text(), ":", 2)
            if len(line_parts) != 2 { continue }
            switch line_parts[0] {
            case "sessions created":
                sessions_created, _ = strconv.ParseInt(strings.TrimSpace(line_parts[1]), 10, 64)
            case "active sessions":
                active_sessions, _ = strconv.ParseInt(strings.TrimSpace(line_parts[1]), 10, 64)
            case "active streams":
                active_streams, _ = strconv.ParseInt(strings.TrimSpace(line_parts[1]), 10, 64)
            case "packets received":
                preceived, _ = strconv.ParseInt(strings.TrimSpace(line_parts[1]), 10, 64)
            case "packets transmitted":
                ptransmitted, _ = strconv.ParseInt(strings.TrimSpace(line_parts[1]), 10, 64)
            }
        }
        self.UpdateActive(active_sessions, sessions_created, active_streams, preceived, ptransmitted)
    }
    StartTimeoutWithSpread(self.heartbeat, nil, self.opts.hrtb_ival, 1, self.opts.logger, 0.1)
}

func (self *Rtp_proxy_client_base) GoOnline() {
    if self.shut_down {
        return
    }
    if ! self.online {
        if ! self.caps_done {
            newRtppCapsChecker(self)
            return
        }
        self.online = true
        self.heartbeat()
    }
}

func (self *Rtp_proxy_client_base) GoOffline() {
    if self.shut_down {
        return
    }
    //print "go_offline", self.address, self.online
    if self.online {
        self.online = false
        StartTimeoutWithSpread(self.version_check, nil, self.opts.hrtb_retr_ival, 1, self.opts.logger, 0.1)
    }
}

func (self *Rtp_proxy_client_base) UpdateActive(active_sessions, sessions_created, active_streams, preceived, ptransmitted int64) {
    self.sessions_created = sessions_created
    self.active_sessions = active_sessions
    self.active_streams = active_streams
    self.preceived = preceived
    self.ptransmitted = ptransmitted
}

func (self *Rtp_proxy_client_base) GetActiveSessions() int64 {
    return self.active_sessions
}

func (self *Rtp_proxy_client_base) GetActiveStreams() int64 {
    return self.active_streams
}

func (self *Rtp_proxy_client_base) GetPReceived() int64 {
    return self.preceived
}

func (self *Rtp_proxy_client_base) GetSessionsCreated() int64 {
    return self.sessions_created
}

func (self *Rtp_proxy_client_base) GetPTransmitted() int64 {
    return self.ptransmitted
}

func (self *Rtp_proxy_client_base) Shutdown() {
    if self.shut_down { // do not crash when shutdown() called twice
        return
    }
    self.shut_down = true
    self.transport.shutdown()
    self.transport = nil
}

func (self *Rtp_proxy_client_base) IsShutDown() bool {
    return self.shut_down
}

func (self *Rtp_proxy_client_base) GetOpts() sippy_types.RtpProxyClientOpts {
    return self.opts
}

func (self *Rtp_proxy_client_base) GetRtpcDelay() float64 {
    return self.transport.get_rtpc_delay()
}

type rtppCapsChecker struct {
    caps_requested  int
    caps_received   int
    rtpc            *Rtp_proxy_client_base
}

func newRtppCapsChecker(rtpc *Rtp_proxy_client_base) *rtppCapsChecker {
    self := &rtppCapsChecker{
        rtpc    : rtpc,
    }
    rtpc.caps_done = false
    CAPSTABLE := []struct{ vers string; attr *bool }{
        { "20071218", &self.rtpc.copy_supported },
        { "20080403", &self.rtpc.stat_supported },
        { "20081224", &self.rtpc.tnot_supported },
        { "20090810", &self.rtpc.sbind_supported },
        { "20150617", &self.rtpc.wdnt_supported },
    }
    self.caps_requested = len(CAPSTABLE)
    for _, it := range CAPSTABLE {
        attr := it.attr // For some reason the it.attr cannot be passed into the following
                        // function directly - the resulting value is always that of the
                        // last 'it.attr' value.
        rtpc.transport.send_command("VF " + it.vers, func(res string) { self.caps_query_done(res, attr) })
    }
    return self
}

func (self *rtppCapsChecker) caps_query_done(result string, attr *bool) {
    self.caps_received += 1
    if result == "1" {
        *attr = true
    } else {
        *attr = false
    }
    if self.caps_received == self.caps_requested {
        self.rtpc.caps_done = true
        self.rtpc.me().GoOnline()
        self.rtpc = nil
    }
}
<|endoftext|>


<commit_before>package cmd

import (
	"bufio"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/go-errors/errors"
	irma "github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/server"
	"github.com/privacybydesign/irmago/server/irmaserver"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	prefixed "github.com/x-cray/logrus-prefixed-formatter"
)

var (
	httpServer *http.Server
	irmaServer *irmaserver.Server
	defaulturl string

	logger = logrus.New()
)

// sessionCmd represents the session command
var sessionCmd = &cobra.Command{
	Use:   "session",
	Short: "Perform an IRMA disclosure, issuance or signature session",
	Long: `Perform an IRMA disclosure, issuance or signature session on the command line

Using either the builtin IRMA server library, or an external IRMA server (specify its URL
with --server), an IRMA session is started; the QR is printed in the terminal; and the session
result is printed when the session completes or fails.

A session request can either be constructed using the --disclose, --issue, and --sign together
with --message flags, or it can be specified as JSON to the --request flag.`,
	Example: `irma session --disclose irma-demo.MijnOverheid.root.BSN
irma session --sign irma-demo.MijnOverheid.root.BSN --message message
irma session --issue irma-demo.MijnOverheid.ageLower=yes,yes,yes,no --disclose irma-demo.MijnOverheid.root.BSN
irma session --request '{"type":"disclosing","content":[{"label":"BSN","attributes":["irma-demo.MijnOverheid.root.BSN"]}]}'
irma session --server http://localhost:8088 --authmethod token --key mytoken --disclose irma-demo.MijnOverheid.root.BSN
irma session --server http://localhost:8088 --static mystaticsession
irma session --from-package '{"sessionPtr": ... , "frontendRequest": ...}'`,
	Run: func(cmd *cobra.Command, args []string) {
		var (
			request    irma.RequestorRequest
			irmaconfig *irma.Configuration
			pkg        *server.SessionPackage
			result     *server.SessionResult
			err        error

			flags         = cmd.Flags()
			url, _        = flags.GetString("url")
			serverURL, _  = flags.GetString("server")
			noqr, _       = flags.GetBool("noqr")
			pairing, _    = flags.GetBool("pairing")
			authMethod, _ = flags.GetString("authmethod")
			key, _        = flags.GetString("key")
			jsonPkg, _    = flags.GetString("from-package")
			static, _     = flags.GetString("static")
		)
		if url != defaulturl && serverURL != "" {
			die("Failed to read configuration", errors.New("--url can't be combined with --server"))
		}

		if static != "" {
			if err = staticRequest(serverURL, static, noqr); err != nil {
				die("Failed to handle static session", err)
			}
			// Static sessions are fully handled on the phone.
			return
		}

		if jsonPkg == "" {
			request, irmaconfig, err = configureSession(cmd)
			if err != nil {
				die("", err)
			}
			if serverURL != "" {
				name, _ := flags.GetString("name")
				pkg, err = postRequest(serverURL, "session", request, name, authMethod, key)
				if err != nil {
					die("Session could not be started", err)
				}
			}
		} else {
			pkg = &server.SessionPackage{}
			err = json.Unmarshal([]byte(jsonPkg), pkg)
			if err != nil {
				die("Failed to parse session package", err)
			}
		}

		if pkg == nil {
			port, _ := flags.GetInt("port")
			privatekeysPath, _ := flags.GetString("privkeys")
			verbosity, _ := cmd.Flags().GetCount("verbose")
			result, err = libraryRequest(request, irmaconfig, url, port, privatekeysPath, noqr, verbosity, pairing)
		} else {
			result, err = serverRequest(pkg, noqr, pairing)
		}
		if err != nil {
			die("Session failed", err)
		}

		printSessionResult(result)

		// Done!
		if httpServer != nil {
			_ = httpServer.Close()
		}
	},
}

func libraryRequest(
	request irma.RequestorRequest,
	irmaconfig *irma.Configuration,
	url string,
	port int,
	privatekeysPath string,
	noqr bool,
	verbosity int,
	pairing bool,
) (*server.SessionResult, error) {
	if err := configureSessionServer(url, port, privatekeysPath, irmaconfig, verbosity); err != nil {
		return nil, err
	}
	startServer(port)

	// Start the session
	resultchan := make(chan *server.SessionResult)
	qr, requestorToken, _, err := irmaServer.StartSession(request, func(r *server.SessionResult) {
		resultchan <- r
	})
	if err != nil {
		return nil, errors.WrapPrefix(err, "IRMA session failed", 0)
	}

	// Enable pairing if necessary
	var sessionOptions *irma.SessionOptions
	if pairing {
		optionsRequest := irma.NewFrontendOptionsRequest()
		optionsRequest.PairingMethod = irma.PairingMethodPin
		if sessionOptions, err = irmaServer.SetFrontendOptions(requestorToken, &optionsRequest); err != nil {
			return nil, errors.WrapPrefix(err, "Failed to enable pairing", 0)
		}
	}

	// Print QR code
	if err := printQr(qr, noqr); err != nil {
		return nil, errors.WrapPrefix(err, "Failed to print QR", 0)
	}

	if pairing {
		// Listen for session status
		statuschan, err := irmaServer.SessionStatus(requestorToken)
		if err != nil {
			return nil, errors.WrapPrefix(err, "Failed to start listening for session statuses", 0)
		}

		err = handlePairing(sessionOptions, statuschan, func() error {
			return irmaServer.PairingCompleted(requestorToken)
		})
		if err != nil {
			return nil, errors.WrapPrefix(err, "Failed to handle pairing", 0)
		}
	}

	// Wait for session to finish and then return session result
	return <-resultchan, nil
}

func serverRequest(
	pkg *server.SessionPackage,
	noqr bool,
	pairing bool,
) (*server.SessionResult, error) {
	// Enable pairing if necessary
	var (
		qr              = pkg.SessionPtr
		frontendRequest = pkg.FrontendRequest
		transport       = irma.NewHTTPTransport(qr.URL, false)
		sessionOptions  = &irma.SessionOptions{}
		err             error
	)
	if pairing {
		transport.SetHeader(irma.AuthorizationHeader, string(frontendRequest.Authorization))
		optionsRequest := irma.NewFrontendOptionsRequest()
		optionsRequest.PairingMethod = irma.PairingMethodPin
		err = transport.Post("frontend/options", sessionOptions, optionsRequest)
		if err != nil {
			return nil, errors.WrapPrefix(err, "Failed to enable pairing", 0)
		}
	}

	// Print session QR
	logger.Debug("QR: ", prettyprint(qr))
	if err := printQr(qr, noqr); err != nil {
		return nil, errors.WrapPrefix(err, "Failed to print QR", 0)
	}

	statuschan := make(chan irma.ServerStatus)
	errorchan := make(chan error)
	var wg sync.WaitGroup

	go irma.WaitStatus(transport, irma.ServerStatusInitialized, statuschan, errorchan)
	go func() {
		err := <-errorchan
		if err != nil {
			_ = server.LogFatal(err)
		}
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()

		if pairing {
			err = handlePairing(sessionOptions, statuschan, func() error {
				err = transport.Post("frontend/pairingcompleted", nil, nil)
				if err != nil {
					return errors.WrapPrefix(err, "Failed to complete pairing", 0)
				}
				return nil
			})
			if err != nil {
				err = errors.WrapPrefix(err, "Failed to handle pairing", 0)
				return
			}
		} else {
			// Wait until client connects if pairing is disabled
			status := <-statuschan
			if status != irma.ServerStatusConnected {
				err = errors.Errorf("Unexpected status: %s", status)
				return
			}
		}

		// Wait until client finishes
		status := <-statuschan
		if status != irma.ServerStatusCancelled && status != irma.ServerStatusDone {
			err = errors.Errorf("Unexpected status: %s", status)
			return
		}
	}()

	wg.Wait()

	if err == nil && pkg.Token != "" {
		result := &server.SessionResult{}
		urlParts := strings.Split(pkg.SessionPtr.URL, "/irma/session/")
		if len(urlParts) <= 1 {
			// Custom URL structure is used, so we cannot determine result endpoint location.
			return nil, nil
		}
		path := fmt.Sprintf("session/%s/result", pkg.Token)
		if err = irma.NewHTTPTransport(urlParts[0], false).Get(path, result); err == nil {
			return result, err
		}
	}
	return nil, err
}

func staticRequest(serverURL, name string, noqr bool) error {
	if serverURL == "" {
		return errors.New("--static must be combined with --server")
	}
	qr := &irma.Qr{
		Type: irma.ActionRedirect,
		URL:  fmt.Sprintf("%s/irma/session/%s", serverURL, name),
	}
	return printQr(qr, noqr)
}

func postRequest(serverURL, path string, request irma.RequestorRequest, name, authMethod, key string) (
	*server.SessionPackage, error) {
	var (
		err       error
		pkg       = &server.SessionPackage{}
		transport = irma.NewHTTPTransport(serverURL, false)
	)

	switch authMethod {
	case "token":
		transport.SetHeader("Authorization", key)
		fallthrough
	case "none":
		err = transport.Post(path, pkg, request)
	case "hmac", "rsa":
		var jwtstr string
		jwtstr, err = signRequest(request, name, authMethod, key)
		if err != nil {
			return nil, err
		}
		logger.Debug("Session request JWT: ", jwtstr)
		err = transport.Post(path, pkg, jwtstr)
	default:
		return nil, errors.New("Invalid authentication method (must be none, token, hmac or rsa)")
	}

	if err != nil {
		return nil, err
	}
	return pkg, err
}

func handlePairing(options *irma.SessionOptions, statusChan chan irma.ServerStatus, completePairing func() error) error {
	errorChan := make(chan error)
	pairingStarted := false
	for {
		select {
		case status := <-statusChan:
			if status == irma.ServerStatusInitialized {
				continue
			} else if status == irma.ServerStatusPairing {
				pairingStarted = true
				go requestPairingPermission(options, completePairing, errorChan)
				continue
			} else if status == irma.ServerStatusConnected && !pairingStarted {
				fmt.Println("Pairing is not supported by the connected device.")
			}
			return nil
		case err := <-errorChan:
			return err
		}
	}
}

func requestPairingPermission(options *irma.SessionOptions, completePairing func() error, errorChan chan error) {
	if options.PairingMethod == irma.PairingMethodPin {
		fmt.Println("\nPairing code:", options.PairingCode)
		fmt.Println("Press Enter to confirm your device shows the same pairing code; otherwise press Ctrl-C.")
		_, err := bufio.NewReader(os.Stdin).ReadString('\n')
		if err != nil {
			errorChan <- err
			return
		}
		if err = completePairing(); err != nil {
			errorChan <- err
			return
		}
		fmt.Println("Pairing completed.")
		errorChan <- nil
		return
	}
	errorChan <- errors.Errorf("Pairing method %s is not supported", options.PairingMethod)
}

// Configuration functions

func configureSessionServer(url string, port int, privatekeysPath string, irmaconfig *irma.Configuration, verbosity int) error {
	// Replace "port" in url with actual port
	replace := "$1:" + strconv.Itoa(port)
	url = string(regexp.MustCompile("(https?://[^/]*):port").ReplaceAll([]byte(url), []byte(replace)))

	config := &server.Configuration{
		IrmaConfiguration:    irmaconfig,
		Logger:               logger,
		URL:                  url,
		DisableSchemesUpdate: true,
		Verbose:              verbosity,
	}
	if privatekeysPath != "" {
		config.IssuerPrivateKeysPath = privatekeysPath
	}

	var err error
	irmaServer, err = irmaserver.New(config)
	return err
}

func configureSession(cmd *cobra.Command) (irma.RequestorRequest, *irma.Configuration, error) {
	verbosity, _ := cmd.Flags().GetCount("verbose")
	logger.Level = server.Verbosity(verbosity)
	irma.SetLogger(logger)

	if localIPErr != nil {
		logger.Warn("Could not determine local IP address: ", localIPErr.Error())
	}

	request, irmaconfig, err := configureRequest(cmd)
	if err != nil {
		return nil, nil, err
	}

	// Make sure we always run with latest configuration
	disableUpdate, _ := cmd.Flags().GetBool("disable-schemes-update")
	if !disableUpdate {
		if err = irmaconfig.UpdateSchemes(); err != nil {
			return nil, nil, err
		}
	}

	return request, irmaconfig, nil
}

func init() {
	RootCmd.AddCommand(sessionCmd)

	logger.Formatter = &prefixed.TextFormatter{FullTimestamp: true}

	if localIP != "" {
		defaulturl = "http://" + localIP + ":port"
	}

	flags := sessionCmd.Flags()
	flags.SortFlags = false
	flags.String("server", "", "External IRMA server to post request to (leave blank to use builtin library)")
	flags.StringP("url", "u", defaulturl, "external URL to which IRMA app connects (when not using --server), \":port\" being replaced by --port value")
	flags.IntP("port", "p", 48680, "port to listen at (when not using --server)")
	flags.Bool("noqr", false, "Print JSON instead of draw QR")
	flags.Bool("pairing", false, "Let IRMA app first pair, by entering the pairing code, before it can access the session")
	flags.StringP("request", "r", "", "JSON session request")
	flags.StringP("privkeys", "k", "", "path to private keys")
	flags.Bool("disable-schemes-update", false, "disable scheme updates")

	addRequestFlags(flags)

	flags.String("static", "", "Start a static IRMA session with the given name")
	flags.String("from-package", "", "Start the IRMA session from the given session package")

	flags.CountP("verbose", "v", "verbose (repeatable)")
}
<commit_msg>Use --url option for static sessions instead --serverurl to properly handle localhost sessions<commit_after>package cmd

import (
	"bufio"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/go-errors/errors"
	irma "github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/server"
	"github.com/privacybydesign/irmago/server/irmaserver"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cobra"
	prefixed "github.com/x-cray/logrus-prefixed-formatter"
)

var (
	httpServer *http.Server
	irmaServer *irmaserver.Server
	defaulturl string

	logger = logrus.New()
)

// sessionCmd represents the session command
var sessionCmd = &cobra.Command{
	Use:   "session",
	Short: "Perform an IRMA disclosure, issuance or signature session",
	Long: `Perform an IRMA disclosure, issuance or signature session on the command line

Using either the builtin IRMA server library, or an external IRMA server (specify its URL
with --server), an IRMA session is started; the QR is printed in the terminal; and the session
result is printed when the session completes or fails.

A session request can either be constructed using the --disclose, --issue, and --sign together
with --message flags, or it can be specified as JSON to the --request flag.`,
	Example: `irma session --disclose irma-demo.MijnOverheid.root.BSN
irma session --sign irma-demo.MijnOverheid.root.BSN --message message
irma session --issue irma-demo.MijnOverheid.ageLower=yes,yes,yes,no --disclose irma-demo.MijnOverheid.root.BSN
irma session --request '{"type":"disclosing","content":[{"label":"BSN","attributes":["irma-demo.MijnOverheid.root.BSN"]}]}'
irma session --server http://localhost:8088 --authmethod token --key mytoken --disclose irma-demo.MijnOverheid.root.BSN
irma session --server http://localhost:8088 --static mystaticsession
irma session --from-package '{"sessionPtr": ... , "frontendRequest": ...}'`,
	Run: func(cmd *cobra.Command, args []string) {
		var (
			request    irma.RequestorRequest
			irmaconfig *irma.Configuration
			pkg        *server.SessionPackage
			result     *server.SessionResult
			err        error

			flags        = cmd.Flags()
			url, _       = flags.GetString("url")
			port, _      = flags.GetInt("port")
			serverURL, _ = flags.GetString("server")
			noqr, _      = flags.GetBool("noqr")
			pairing, _   = flags.GetBool("pairing")
			jsonPkg, _   = flags.GetString("from-package")
			static, _    = flags.GetString("static")
		)
		if url != defaulturl && serverURL != "" {
			die("Failed to read configuration", errors.New("--url can't be combined with --server"))
		}

		if static != "" {
			if pairing {
				die("Failed to read configuration", errors.New("--static can't be combined with --pairing"))
			}
			if err = staticRequest(url, port, static, noqr); err != nil {
				die("Failed to handle static session", err)
			}
			// Static sessions are fully handled on the phone.
			return
		}

		if jsonPkg == "" {
			request, irmaconfig, err = configureSession(cmd)
			if err != nil {
				die("", err)
			}
			if serverURL != "" {
				authMethod, _ := flags.GetString("authmethod")
				key, _ := flags.GetString("key")
				name, _ := flags.GetString("name")
				pkg, err = postRequest(serverURL, "session", request, name, authMethod, key)
				if err != nil {
					die("Session could not be started", err)
				}
			}
		} else {
			pkg = &server.SessionPackage{}
			err = json.Unmarshal([]byte(jsonPkg), pkg)
			if err != nil {
				die("Failed to parse session package", err)
			}
		}

		if pkg == nil {
			privatekeysPath, _ := flags.GetString("privkeys")
			verbosity, _ := cmd.Flags().GetCount("verbose")
			result, err = libraryRequest(request, irmaconfig, url, port, privatekeysPath, noqr, verbosity, pairing)
		} else {
			result, err = serverRequest(pkg, noqr, pairing)
		}
		if err != nil {
			die("Session failed", err)
		}

		printSessionResult(result)

		// Done!
		if httpServer != nil {
			_ = httpServer.Close()
		}
	},
}

func libraryRequest(
	request irma.RequestorRequest,
	irmaconfig *irma.Configuration,
	url string,
	port int,
	privatekeysPath string,
	noqr bool,
	verbosity int,
	pairing bool,
) (*server.SessionResult, error) {
	if err := configureSessionServer(url, port, privatekeysPath, irmaconfig, verbosity); err != nil {
		return nil, err
	}
	startServer(port)

	// Start the session
	resultchan := make(chan *server.SessionResult)
	qr, requestorToken, _, err := irmaServer.StartSession(request, func(r *server.SessionResult) {
		resultchan <- r
	})
	if err != nil {
		return nil, errors.WrapPrefix(err, "IRMA session failed", 0)
	}

	// Enable pairing if necessary
	var sessionOptions *irma.SessionOptions
	if pairing {
		optionsRequest := irma.NewFrontendOptionsRequest()
		optionsRequest.PairingMethod = irma.PairingMethodPin
		if sessionOptions, err = irmaServer.SetFrontendOptions(requestorToken, &optionsRequest); err != nil {
			return nil, errors.WrapPrefix(err, "Failed to enable pairing", 0)
		}
	}

	// Print QR code
	if err := printQr(qr, noqr); err != nil {
		return nil, errors.WrapPrefix(err, "Failed to print QR", 0)
	}

	if pairing {
		// Listen for session status
		statuschan, err := irmaServer.SessionStatus(requestorToken)
		if err != nil {
			return nil, errors.WrapPrefix(err, "Failed to start listening for session statuses", 0)
		}

		err = handlePairing(sessionOptions, statuschan, func() error {
			return irmaServer.PairingCompleted(requestorToken)
		})
		if err != nil {
			return nil, errors.WrapPrefix(err, "Failed to handle pairing", 0)
		}
	}

	// Wait for session to finish and then return session result
	return <-resultchan, nil
}

func serverRequest(
	pkg *server.SessionPackage,
	noqr bool,
	pairing bool,
) (*server.SessionResult, error) {
	// Enable pairing if necessary
	var (
		qr              = pkg.SessionPtr
		frontendRequest = pkg.FrontendRequest
		transport       = irma.NewHTTPTransport(qr.URL, false)
		sessionOptions  = &irma.SessionOptions{}
		err             error
	)
	if pairing {
		transport.SetHeader(irma.AuthorizationHeader, string(frontendRequest.Authorization))
		optionsRequest := irma.NewFrontendOptionsRequest()
		optionsRequest.PairingMethod = irma.PairingMethodPin
		err = transport.Post("frontend/options", sessionOptions, optionsRequest)
		if err != nil {
			return nil, errors.WrapPrefix(err, "Failed to enable pairing", 0)
		}
	}

	// Print session QR
	logger.Debug("QR: ", prettyprint(qr))
	if err := printQr(qr, noqr); err != nil {
		return nil, errors.WrapPrefix(err, "Failed to print QR", 0)
	}

	statuschan := make(chan irma.ServerStatus)
	errorchan := make(chan error)
	var wg sync.WaitGroup

	go irma.WaitStatus(transport, irma.ServerStatusInitialized, statuschan, errorchan)
	go func() {
		err := <-errorchan
		if err != nil {
			_ = server.LogFatal(err)
		}
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()

		if pairing {
			err = handlePairing(sessionOptions, statuschan, func() error {
				err = transport.Post("frontend/pairingcompleted", nil, nil)
				if err != nil {
					return errors.WrapPrefix(err, "Failed to complete pairing", 0)
				}
				return nil
			})
			if err != nil {
				err = errors.WrapPrefix(err, "Failed to handle pairing", 0)
				return
			}
		} else {
			// Wait until client connects if pairing is disabled
			status := <-statuschan
			if status != irma.ServerStatusConnected {
				err = errors.Errorf("Unexpected status: %s", status)
				return
			}
		}

		// Wait until client finishes
		status := <-statuschan
		if status != irma.ServerStatusCancelled && status != irma.ServerStatusDone {
			err = errors.Errorf("Unexpected status: %s", status)
			return
		}
	}()

	wg.Wait()

	if err == nil && pkg.Token != "" {
		result := &server.SessionResult{}
		urlParts := strings.Split(pkg.SessionPtr.URL, "/irma/session/")
		if len(urlParts) <= 1 {
			// Custom URL structure is used, so we cannot determine result endpoint location.
			return nil, nil
		}
		path := fmt.Sprintf("session/%s/result", pkg.Token)
		if err = irma.NewHTTPTransport(urlParts[0], false).Get(path, result); err == nil {
			return result, err
		}
	}
	return nil, err
}

func staticRequest(url string, port int, name string, noqr bool) error {
	url = configureURL(url, port)
	fmt.Println("Server URL:", url)
	qr := &irma.Qr{
		Type: irma.ActionRedirect,
		URL:  fmt.Sprintf("%s/irma/session/%s", url, name),
	}
	return printQr(qr, noqr)
}

func postRequest(serverURL, path string, request irma.RequestorRequest, name, authMethod, key string) (
	*server.SessionPackage, error) {
	var (
		err       error
		pkg       = &server.SessionPackage{}
		transport = irma.NewHTTPTransport(serverURL, false)
	)

	switch authMethod {
	case "token":
		transport.SetHeader("Authorization", key)
		fallthrough
	case "none":
		err = transport.Post(path, pkg, request)
	case "hmac", "rsa":
		var jwtstr string
		jwtstr, err = signRequest(request, name, authMethod, key)
		if err != nil {
			return nil, err
		}
		logger.Debug("Session request JWT: ", jwtstr)
		err = transport.Post(path, pkg, jwtstr)
	default:
		return nil, errors.New("Invalid authentication method (must be none, token, hmac or rsa)")
	}

	if err != nil {
		return nil, err
	}
	return pkg, err
}

func handlePairing(options *irma.SessionOptions, statusChan chan irma.ServerStatus, completePairing func() error) error {
	errorChan := make(chan error)
	pairingStarted := false
	for {
		select {
		case status := <-statusChan:
			if status == irma.ServerStatusInitialized {
				continue
			} else if status == irma.ServerStatusPairing {
				pairingStarted = true
				go requestPairingPermission(options, completePairing, errorChan)
				continue
			} else if status == irma.ServerStatusConnected && !pairingStarted {
				fmt.Println("Pairing is not supported by the connected device.")
			}
			return nil
		case err := <-errorChan:
			return err
		}
	}
}

func requestPairingPermission(options *irma.SessionOptions, completePairing func() error, errorChan chan error) {
	if options.PairingMethod == irma.PairingMethodPin {
		fmt.Println("\nPairing code:", options.PairingCode)
		fmt.Println("Press Enter to confirm your device shows the same pairing code; otherwise press Ctrl-C.")
		_, err := bufio.NewReader(os.Stdin).ReadString('\n')
		if err != nil {
			errorChan <- err
			return
		}
		if err = completePairing(); err != nil {
			errorChan <- err
			return
		}
		fmt.Println("Pairing completed.")
		errorChan <- nil
		return
	}
	errorChan <- errors.Errorf("Pairing method %s is not supported", options.PairingMethod)
}

// Configuration functions

func configureURL(url string, port int) string {
	// Replace "port" in url with actual port
	replace := "$1:" + strconv.Itoa(port)
	return string(regexp.MustCompile("(https?://[^/]*):port").ReplaceAll([]byte(url), []byte(replace)))
}

func configureSessionServer(url string, port int, privatekeysPath string, irmaconfig *irma.Configuration, verbosity int) error {
	url = configureURL(url, port)
	config := &server.Configuration{
		IrmaConfiguration:    irmaconfig,
		Logger:               logger,
		URL:                  url,
		DisableSchemesUpdate: true,
		Verbose:              verbosity,
	}
	if privatekeysPath != "" {
		config.IssuerPrivateKeysPath = privatekeysPath
	}

	var err error
	irmaServer, err = irmaserver.New(config)
	return err
}

func configureSession(cmd *cobra.Command) (irma.RequestorRequest, *irma.Configuration, error) {
	verbosity, _ := cmd.Flags().GetCount("verbose")
	logger.Level = server.Verbosity(verbosity)
	irma.SetLogger(logger)

	if localIPErr != nil {
		logger.Warn("Could not determine local IP address: ", localIPErr.Error())
	}

	request, irmaconfig, err := configureRequest(cmd)
	if err != nil {
		return nil, nil, err
	}

	// Make sure we always run with latest configuration
	disableUpdate, _ := cmd.Flags().GetBool("disable-schemes-update")
	if !disableUpdate {
		if err = irmaconfig.UpdateSchemes(); err != nil {
			return nil, nil, err
		}
	}

	return request, irmaconfig, nil
}

func init() {
	RootCmd.AddCommand(sessionCmd)

	logger.Formatter = &prefixed.TextFormatter{FullTimestamp: true}

	if localIP != "" {
		defaulturl = "http://" + localIP + ":port"
	}

	flags := sessionCmd.Flags()
	flags.SortFlags = false
	flags.String("server", "", "External IRMA server to post request to (leave blank to use builtin library)")
	flags.StringP("url", "u", defaulturl, "external URL to which IRMA app connects (when not using --server), \":port\" being replaced by --port value")
	flags.IntP("port", "p", 48680, "port to listen at (when not using --server)")
	flags.Bool("noqr", false, "Print JSON instead of draw QR")
	flags.Bool("pairing", false, "Let IRMA app first pair, by entering the pairing code, before it can access the session")
	flags.StringP("request", "r", "", "JSON session request")
	flags.StringP("privkeys", "k", "", "path to private keys")
	flags.Bool("disable-schemes-update", false, "disable scheme updates")

	addRequestFlags(flags)

	flags.String("static", "", "Start a static IRMA session with the given name")
	flags.String("from-package", "", "Start the IRMA session from the given session package")

	flags.CountP("verbose", "v", "verbose (repeatable)")
}
<|endoftext|>


<commit_before>package data

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"path/filepath"
	"strings"
	"sync"
	"time"

	cjson "github.com/tent/canonical-json-go"
)

const (
	KeyIDLength                = sha256.Size * 2
	KeyTypeEd25519             = "ed25519"
	KeyTypeECDSA_SHA2_P256     = "ecdsa-sha2-nistp256"
	KeySchemeEd25519           = "ed25519"
	KeySchemeECDSA_SHA2_P256   = "ecdsa-sha2-nistp256"
	KeyTypeRSASSA_PSS_SHA256   = "rsa"
	KeySchemeRSASSA_PSS_SHA256 = "rsassa-pss-sha256"
)

var (
	HashAlgorithms           = []string{"sha256", "sha512"}
	ErrPathsAndPathHashesSet = errors.New("tuf: failed validation of delegated target: paths and path_hash_prefixes are both set")
)

type Signed struct {
	Signed     json.RawMessage `json:"signed"`
	Signatures []Signature     `json:"signatures"`
}

type Signature struct {
	KeyID     string   `json:"keyid"`
	Signature HexBytes `json:"sig"`
}

type PublicKey struct {
	Type       string          `json:"keytype"`
	Scheme     string          `json:"scheme"`
	Algorithms []string        `json:"keyid_hash_algorithms,omitempty"`
	Value      json.RawMessage `json:"keyval"`

	ids    []string
	idOnce sync.Once
}

type PrivateKey struct {
	Type       string          `json:"keytype"`
	Scheme     string          `json:"scheme,omitempty"`
	Algorithms []string        `json:"keyid_hash_algorithms,omitempty"`
	Value      json.RawMessage `json:"keyval"`
}

func (k *PublicKey) IDs() []string {
	k.idOnce.Do(func() {
		data, err := cjson.Marshal(k)
		if err != nil {
			panic(fmt.Errorf("tuf: error creating key ID: %w", err))
		}
		digest := sha256.Sum256(data)
		k.ids = []string{hex.EncodeToString(digest[:])}
	})
	return k.ids
}

func (k *PublicKey) ContainsID(id string) bool {
	for _, keyid := range k.IDs() {
		if id == keyid {
			return true
		}
	}
	return false
}

func DefaultExpires(role string) time.Time {
	var t time.Time
	switch role {
	case "root":
		t = time.Now().AddDate(1, 0, 0)
	case "targets":
		t = time.Now().AddDate(0, 3, 0)
	case "snapshot":
		t = time.Now().AddDate(0, 0, 7)
	case "timestamp":
		t = time.Now().AddDate(0, 0, 1)
	}
	return t.UTC().Round(time.Second)
}

type Root struct {
	Type        string                `json:"_type"`
	SpecVersion string                `json:"spec_version"`
	Version     int                   `json:"version"`
	Expires     time.Time             `json:"expires"`
	Keys        map[string]*PublicKey `json:"keys"`
	Roles       map[string]*Role      `json:"roles"`
	Custom      *json.RawMessage      `json:"custom,omitempty"`

	ConsistentSnapshot bool `json:"consistent_snapshot"`
}

func NewRoot() *Root {
	return &Root{
		Type:               "root",
		SpecVersion:        "1.0",
		Expires:            DefaultExpires("root"),
		Keys:               make(map[string]*PublicKey),
		Roles:              make(map[string]*Role),
		ConsistentSnapshot: true,
	}
}

func (r *Root) AddKey(key *PublicKey) bool {
	changed := false
	for _, id := range key.IDs() {
		if _, ok := r.Keys[id]; !ok {
			changed = true
			r.Keys[id] = key
		}
	}
	return changed
}

type Role struct {
	KeyIDs    []string `json:"keyids"`
	Threshold int      `json:"threshold"`
}

func (r *Role) AddKeyIDs(ids []string) bool {
	roleIDs := make(map[string]struct{})
	for _, id := range r.KeyIDs {
		roleIDs[id] = struct{}{}
	}
	changed := false
	for _, id := range ids {
		if _, ok := roleIDs[id]; !ok {
			changed = true
			r.KeyIDs = append(r.KeyIDs, id)
		}
	}
	return changed
}

type Files map[string]FileMeta

type FileMeta struct {
	Length int64            `json:"length",omitempty`
	Hashes Hashes           `json:"hashes",omitempty`
	Custom *json.RawMessage `json:"custom,omitempty"`
}

type Hashes map[string]HexBytes

func (f FileMeta) HashAlgorithms() []string {
	funcs := make([]string, 0, len(f.Hashes))
	for name := range f.Hashes {
		funcs = append(funcs, name)
	}
	return funcs
}

type SnapshotFileMeta struct {
	FileMeta
	Version int `json:"version"`
}

type SnapshotFiles map[string]SnapshotFileMeta

type Snapshot struct {
	Type        string           `json:"_type"`
	SpecVersion string           `json:"spec_version"`
	Version     int              `json:"version"`
	Expires     time.Time        `json:"expires"`
	Meta        SnapshotFiles    `json:"meta"`
	Custom      *json.RawMessage `json:"custom,omitempty"`
}

func NewSnapshot() *Snapshot {
	return &Snapshot{
		Type:        "snapshot",
		SpecVersion: "1.0",
		Expires:     DefaultExpires("snapshot"),
		Meta:        make(SnapshotFiles),
	}
}

type TargetFiles map[string]TargetFileMeta

type TargetFileMeta struct {
	FileMeta
}

func (f TargetFileMeta) HashAlgorithms() []string {
	return f.FileMeta.HashAlgorithms()
}

type Targets struct {
	Type        string           `json:"_type"`
	SpecVersion string           `json:"spec_version"`
	Version     int              `json:"version"`
	Expires     time.Time        `json:"expires"`
	Targets     TargetFiles      `json:"targets"`
	Delegations *Delegations     `json:"delegations,omitempty"`
	Custom      *json.RawMessage `json:"custom,omitempty"`
}

// Delegations represents the edges from a parent Targets role to one or more
// delegated target roles. See spec v1.0.19 section 4.5.
type Delegations struct {
	Keys  map[string]*PublicKey `json:"keys"`
	Roles []DelegatedRole       `json:"roles"`
}

// DelegatedRole describes a delegated role, including what paths it is
// reponsible for. See spec v1.0.19 section 4.5.
type DelegatedRole struct {
	Name             string   `json:"name"`
	KeyIDs           []string `json:"keyids"`
	Threshold        int      `json:"threshold"`
	Terminating      bool     `json:"terminating"`
	PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"`
	Paths            []string `json:"paths"`
}

// MatchesPath evaluates whether the path patterns or path hash prefixes match
// a given file. This determines whether a delegated role is responsible for
// signing and verifying the file.
func (d *DelegatedRole) MatchesPath(file string) (bool, error) {
	if err := d.validatePaths(); err != nil {
		return false, err
	}

	for _, pattern := range d.Paths {
		if matched, _ := filepath.Match(pattern, file); matched {
			return true, nil
		}
	}

	pathHash := PathHexDigest(file)
	for _, hashPrefix := range d.PathHashPrefixes {
		if strings.HasPrefix(pathHash, hashPrefix) {
			return true, nil
		}
	}

	return false, nil
}

// validatePaths enforces the spec
// https://theupdateframework.github.io/specification/v1.0.19/index.html#file-formats-targets
// 'role MUST specify only one of the "path_hash_prefixes" or "paths"'
// Marshalling and unmarshalling JSON will fail and return
// ErrPathsAndPathHashesSet if both fields are set and not empty.
func (d *DelegatedRole) validatePaths() error {
	if len(d.PathHashPrefixes) > 0 && len(d.Paths) > 0 {
		return ErrPathsAndPathHashesSet
	}

	return nil
}

// MarshalJSON is called when writing the struct to JSON. We validate prior to
// marshalling to ensure that an invalid delegated role can not be serialized
// to JSON.
func (d *DelegatedRole) MarshalJSON() ([]byte, error) {
	type delegatedRoleAlias DelegatedRole

	if err := d.validatePaths(); err != nil {
		return nil, err
	}

	return json.Marshal((*delegatedRoleAlias)(d))
}

// UnmarshalJSON is called when reading the struct from JSON. We validate once
// unmarshalled to ensure that an error is thrown if an invalid delegated role
// is read.
func (d *DelegatedRole) UnmarshalJSON(b []byte) error {
	type delegatedRoleAlias DelegatedRole

	if err := json.Unmarshal(b, (*delegatedRoleAlias)(d)); err != nil {
		return err
	}

	return d.validatePaths()
}

func NewTargets() *Targets {
	return &Targets{
		Type:        "targets",
		SpecVersion: "1.0",
		Expires:     DefaultExpires("targets"),
		Targets:     make(TargetFiles),
	}
}

type TimestampFileMeta struct {
	FileMeta
	Version int `json:"version"`
}

type TimestampFiles map[string]TimestampFileMeta

type Timestamp struct {
	Type        string           `json:"_type"`
	SpecVersion string           `json:"spec_version"`
	Version     int              `json:"version"`
	Expires     time.Time        `json:"expires"`
	Meta        TimestampFiles   `json:"meta"`
	Custom      *json.RawMessage `json:"custom,omitempty"`
}

func NewTimestamp() *Timestamp {
	return &Timestamp{
		Type:        "timestamp",
		SpecVersion: "1.0",
		Expires:     DefaultExpires("timestamp"),
		Meta:        make(TimestampFiles),
	}
}
<commit_msg>Fix JSON struct tag for FileMeta (#172)<commit_after>package data

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"path/filepath"
	"strings"
	"sync"
	"time"

	cjson "github.com/tent/canonical-json-go"
)

const (
	KeyIDLength                = sha256.Size * 2
	KeyTypeEd25519             = "ed25519"
	KeyTypeECDSA_SHA2_P256     = "ecdsa-sha2-nistp256"
	KeySchemeEd25519           = "ed25519"
	KeySchemeECDSA_SHA2_P256   = "ecdsa-sha2-nistp256"
	KeyTypeRSASSA_PSS_SHA256   = "rsa"
	KeySchemeRSASSA_PSS_SHA256 = "rsassa-pss-sha256"
)

var (
	HashAlgorithms           = []string{"sha256", "sha512"}
	ErrPathsAndPathHashesSet = errors.New("tuf: failed validation of delegated target: paths and path_hash_prefixes are both set")
)

type Signed struct {
	Signed     json.RawMessage `json:"signed"`
	Signatures []Signature     `json:"signatures"`
}

type Signature struct {
	KeyID     string   `json:"keyid"`
	Signature HexBytes `json:"sig"`
}

type PublicKey struct {
	Type       string          `json:"keytype"`
	Scheme     string          `json:"scheme"`
	Algorithms []string        `json:"keyid_hash_algorithms,omitempty"`
	Value      json.RawMessage `json:"keyval"`

	ids    []string
	idOnce sync.Once
}

type PrivateKey struct {
	Type       string          `json:"keytype"`
	Scheme     string          `json:"scheme,omitempty"`
	Algorithms []string        `json:"keyid_hash_algorithms,omitempty"`
	Value      json.RawMessage `json:"keyval"`
}

func (k *PublicKey) IDs() []string {
	k.idOnce.Do(func() {
		data, err := cjson.Marshal(k)
		if err != nil {
			panic(fmt.Errorf("tuf: error creating key ID: %w", err))
		}
		digest := sha256.Sum256(data)
		k.ids = []string{hex.EncodeToString(digest[:])}
	})
	return k.ids
}

func (k *PublicKey) ContainsID(id string) bool {
	for _, keyid := range k.IDs() {
		if id == keyid {
			return true
		}
	}
	return false
}

func DefaultExpires(role string) time.Time {
	var t time.Time
	switch role {
	case "root":
		t = time.Now().AddDate(1, 0, 0)
	case "targets":
		t = time.Now().AddDate(0, 3, 0)
	case "snapshot":
		t = time.Now().AddDate(0, 0, 7)
	case "timestamp":
		t = time.Now().AddDate(0, 0, 1)
	}
	return t.UTC().Round(time.Second)
}

type Root struct {
	Type        string                `json:"_type"`
	SpecVersion string                `json:"spec_version"`
	Version     int                   `json:"version"`
	Expires     time.Time             `json:"expires"`
	Keys        map[string]*PublicKey `json:"keys"`
	Roles       map[string]*Role      `json:"roles"`
	Custom      *json.RawMessage      `json:"custom,omitempty"`

	ConsistentSnapshot bool `json:"consistent_snapshot"`
}

func NewRoot() *Root {
	return &Root{
		Type:               "root",
		SpecVersion:        "1.0",
		Expires:            DefaultExpires("root"),
		Keys:               make(map[string]*PublicKey),
		Roles:              make(map[string]*Role),
		ConsistentSnapshot: true,
	}
}

func (r *Root) AddKey(key *PublicKey) bool {
	changed := false
	for _, id := range key.IDs() {
		if _, ok := r.Keys[id]; !ok {
			changed = true
			r.Keys[id] = key
		}
	}
	return changed
}

type Role struct {
	KeyIDs    []string `json:"keyids"`
	Threshold int      `json:"threshold"`
}

func (r *Role) AddKeyIDs(ids []string) bool {
	roleIDs := make(map[string]struct{})
	for _, id := range r.KeyIDs {
		roleIDs[id] = struct{}{}
	}
	changed := false
	for _, id := range ids {
		if _, ok := roleIDs[id]; !ok {
			changed = true
			r.KeyIDs = append(r.KeyIDs, id)
		}
	}
	return changed
}

type Files map[string]FileMeta

type FileMeta struct {
	Length int64            `json:"length,omitempty"`
	Hashes Hashes           `json:"hashes,omitempty"`
	Custom *json.RawMessage `json:"custom,omitempty"`
}

type Hashes map[string]HexBytes

func (f FileMeta) HashAlgorithms() []string {
	funcs := make([]string, 0, len(f.Hashes))
	for name := range f.Hashes {
		funcs = append(funcs, name)
	}
	return funcs
}

type SnapshotFileMeta struct {
	FileMeta
	Version int `json:"version"`
}

type SnapshotFiles map[string]SnapshotFileMeta

type Snapshot struct {
	Type        string           `json:"_type"`
	SpecVersion string           `json:"spec_version"`
	Version     int              `json:"version"`
	Expires     time.Time        `json:"expires"`
	Meta        SnapshotFiles    `json:"meta"`
	Custom      *json.RawMessage `json:"custom,omitempty"`
}

func NewSnapshot() *Snapshot {
	return &Snapshot{
		Type:        "snapshot",
		SpecVersion: "1.0",
		Expires:     DefaultExpires("snapshot"),
		Meta:        make(SnapshotFiles),
	}
}

type TargetFiles map[string]TargetFileMeta

type TargetFileMeta struct {
	FileMeta
}

func (f TargetFileMeta) HashAlgorithms() []string {
	return f.FileMeta.HashAlgorithms()
}

type Targets struct {
	Type        string           `json:"_type"`
	SpecVersion string           `json:"spec_version"`
	Version     int              `json:"version"`
	Expires     time.Time        `json:"expires"`
	Targets     TargetFiles      `json:"targets"`
	Delegations *Delegations     `json:"delegations,omitempty"`
	Custom      *json.RawMessage `json:"custom,omitempty"`
}

// Delegations represents the edges from a parent Targets role to one or more
// delegated target roles. See spec v1.0.19 section 4.5.
type Delegations struct {
	Keys  map[string]*PublicKey `json:"keys"`
	Roles []DelegatedRole       `json:"roles"`
}

// DelegatedRole describes a delegated role, including what paths it is
// reponsible for. See spec v1.0.19 section 4.5.
type DelegatedRole struct {
	Name             string   `json:"name"`
	KeyIDs           []string `json:"keyids"`
	Threshold        int      `json:"threshold"`
	Terminating      bool     `json:"terminating"`
	PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"`
	Paths            []string `json:"paths"`
}

// MatchesPath evaluates whether the path patterns or path hash prefixes match
// a given file. This determines whether a delegated role is responsible for
// signing and verifying the file.
func (d *DelegatedRole) MatchesPath(file string) (bool, error) {
	if err := d.validatePaths(); err != nil {
		return false, err
	}

	for _, pattern := range d.Paths {
		if matched, _ := filepath.Match(pattern, file); matched {
			return true, nil
		}
	}

	pathHash := PathHexDigest(file)
	for _, hashPrefix := range d.PathHashPrefixes {
		if strings.HasPrefix(pathHash, hashPrefix) {
			return true, nil
		}
	}

	return false, nil
}

// validatePaths enforces the spec
// https://theupdateframework.github.io/specification/v1.0.19/index.html#file-formats-targets
// 'role MUST specify only one of the "path_hash_prefixes" or "paths"'
// Marshalling and unmarshalling JSON will fail and return
// ErrPathsAndPathHashesSet if both fields are set and not empty.
func (d *DelegatedRole) validatePaths() error {
	if len(d.PathHashPrefixes) > 0 && len(d.Paths) > 0 {
		return ErrPathsAndPathHashesSet
	}

	return nil
}

// MarshalJSON is called when writing the struct to JSON. We validate prior to
// marshalling to ensure that an invalid delegated role can not be serialized
// to JSON.
func (d *DelegatedRole) MarshalJSON() ([]byte, error) {
	type delegatedRoleAlias DelegatedRole

	if err := d.validatePaths(); err != nil {
		return nil, err
	}

	return json.Marshal((*delegatedRoleAlias)(d))
}

// UnmarshalJSON is called when reading the struct from JSON. We validate once
// unmarshalled to ensure that an error is thrown if an invalid delegated role
// is read.
func (d *DelegatedRole) UnmarshalJSON(b []byte) error {
	type delegatedRoleAlias DelegatedRole

	if err := json.Unmarshal(b, (*delegatedRoleAlias)(d)); err != nil {
		return err
	}

	return d.validatePaths()
}

func NewTargets() *Targets {
	return &Targets{
		Type:        "targets",
		SpecVersion: "1.0",
		Expires:     DefaultExpires("targets"),
		Targets:     make(TargetFiles),
	}
}

type TimestampFileMeta struct {
	FileMeta
	Version int `json:"version"`
}

type TimestampFiles map[string]TimestampFileMeta

type Timestamp struct {
	Type        string           `json:"_type"`
	SpecVersion string           `json:"spec_version"`
	Version     int              `json:"version"`
	Expires     time.Time        `json:"expires"`
	Meta        TimestampFiles   `json:"meta"`
	Custom      *json.RawMessage `json:"custom,omitempty"`
}

func NewTimestamp() *Timestamp {
	return &Timestamp{
		Type:        "timestamp",
		SpecVersion: "1.0",
		Expires:     DefaultExpires("timestamp"),
		Meta:        make(TimestampFiles),
	}
}
<|endoftext|>


<commit_before>/*
   Copyright 2014 CoreOS, Inc.

   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 etcdserver

import (
	"bytes"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/coreos/etcd/etcdserver/stats"
	"github.com/coreos/etcd/pkg/strutil"
	"github.com/coreos/etcd/raft/raftpb"
)

const raftPrefix = "/raft"

// Sender creates the default production sender used to transport raft messages
// in the cluster. The returned sender will update the given ServerStats and
// LeaderStats appropriately.
func Sender(t *http.Transport, cl *Cluster, ss *stats.ServerStats, ls *stats.LeaderStats) func(msgs []raftpb.Message) {
	c := &http.Client{Transport: t}

	return func(msgs []raftpb.Message) {
		for _, m := range msgs {
			// TODO: reuse go routines
			// limit the number of outgoing connections for the same receiver
			go send(c, cl, m, ss, ls)
		}
	}
}

// send uses the given client to send a message to a member in the given
// ClusterStore, retrying up to 3 times for each message. The given
// ServerStats and LeaderStats are updated appropriately
func send(c *http.Client, cl *Cluster, m raftpb.Message, ss *stats.ServerStats, ls *stats.LeaderStats) {
	cid := cl.ID()
	// TODO (xiangli): reasonable retry logic
	for i := 0; i < 3; i++ {
		memb := cl.Member(m.To)
		if memb == nil {
			// TODO: unknown peer id.. what do we do? I
			// don't think his should ever happen, need to
			// look into this further.
			log.Printf("etcdhttp: no member for %d", m.To)
			return
		}
		u := fmt.Sprintf("%s%s", memb.PickPeerURL(), raftPrefix)

		// TODO: don't block. we should be able to have 1000s
		// of messages out at a time.
		data, err := m.Marshal()
		if err != nil {
			log.Println("etcdhttp: dropping message:", err)
			return // drop bad message
		}
		if m.Type == raftpb.MsgApp {
			ss.SendAppendReq(len(data))
		}
		to := strutil.IDAsHex(m.To)
		fs := ls.Follower(to)

		start := time.Now()
		sent := httpPost(c, u, cid, data)
		end := time.Now()
		if sent {
			fs.Succ(end.Sub(start))
			return
		}
		fs.Fail()
		// TODO: backoff
	}
}

// httpPost POSTs a data payload to a url using the given client. Returns true
// if the POST succeeds, false on any failure.
func httpPost(c *http.Client, url string, cid uint64, data []byte) bool {
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
	if err != nil {
		// TODO: log the error?
		return false
	}
	req.Header.Set("Content-Type", "application/protobuf")
	req.Header.Set("X-Etcd-Cluster-ID", strconv.FormatUint(cid, 16))
	resp, err := c.Do(req)
	if err != nil {
		// TODO: log the error?
		return false
	}
	resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusPreconditionFailed:
		// TODO: shutdown the etcdserver gracefully?
		log.Panicf("clusterID mismatch")
		return false
	case http.StatusForbidden:
		// TODO: stop the server
		log.Panicf("the member has been removed")
		return false
	case http.StatusNoContent:
		return true
	default:
		return false
	}
}
<commit_msg>etcdserver: log member ID as hex string<commit_after>/*
   Copyright 2014 CoreOS, Inc.

   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 etcdserver

import (
	"bytes"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/coreos/etcd/etcdserver/stats"
	"github.com/coreos/etcd/pkg/strutil"
	"github.com/coreos/etcd/raft/raftpb"
)

const raftPrefix = "/raft"

// Sender creates the default production sender used to transport raft messages
// in the cluster. The returned sender will update the given ServerStats and
// LeaderStats appropriately.
func Sender(t *http.Transport, cl *Cluster, ss *stats.ServerStats, ls *stats.LeaderStats) func(msgs []raftpb.Message) {
	c := &http.Client{Transport: t}

	return func(msgs []raftpb.Message) {
		for _, m := range msgs {
			// TODO: reuse go routines
			// limit the number of outgoing connections for the same receiver
			go send(c, cl, m, ss, ls)
		}
	}
}

// send uses the given client to send a message to a member in the given
// ClusterStore, retrying up to 3 times for each message. The given
// ServerStats and LeaderStats are updated appropriately
func send(c *http.Client, cl *Cluster, m raftpb.Message, ss *stats.ServerStats, ls *stats.LeaderStats) {
	cid := cl.ID()
	// TODO (xiangli): reasonable retry logic
	for i := 0; i < 3; i++ {
		memb := cl.Member(m.To)
		if memb == nil {
			// TODO: unknown peer id.. what do we do? I
			// don't think his should ever happen, need to
			// look into this further.
			log.Printf("etcdhttp: no member for %s", strutil.IDAsHex(m.To))
			return
		}
		u := fmt.Sprintf("%s%s", memb.PickPeerURL(), raftPrefix)

		// TODO: don't block. we should be able to have 1000s
		// of messages out at a time.
		data, err := m.Marshal()
		if err != nil {
			log.Println("etcdhttp: dropping message:", err)
			return // drop bad message
		}
		if m.Type == raftpb.MsgApp {
			ss.SendAppendReq(len(data))
		}
		to := strutil.IDAsHex(m.To)
		fs := ls.Follower(to)

		start := time.Now()
		sent := httpPost(c, u, cid, data)
		end := time.Now()
		if sent {
			fs.Succ(end.Sub(start))
			return
		}
		fs.Fail()
		// TODO: backoff
	}
}

// httpPost POSTs a data payload to a url using the given client. Returns true
// if the POST succeeds, false on any failure.
func httpPost(c *http.Client, url string, cid uint64, data []byte) bool {
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
	if err != nil {
		// TODO: log the error?
		return false
	}
	req.Header.Set("Content-Type", "application/protobuf")
	req.Header.Set("X-Etcd-Cluster-ID", strconv.FormatUint(cid, 16))
	resp, err := c.Do(req)
	if err != nil {
		// TODO: log the error?
		return false
	}
	resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusPreconditionFailed:
		// TODO: shutdown the etcdserver gracefully?
		log.Panicf("clusterID mismatch")
		return false
	case http.StatusForbidden:
		// TODO: stop the server
		log.Panicf("the member has been removed")
		return false
	case http.StatusNoContent:
		return true
	default:
		return false
	}
}
<|endoftext|>


<commit_before>// Copyright 2012 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 main

import (
	"image"
	"image/color"
	"tour/pic"
)

type Image [][]uint8

func (m Image) ColorModel() color.Model {
	return color.RGBAModel
}

func (m Image) Bounds() image.Rectangle {
	dx, dy := len(m), 0
	if dx > 0 {
		dy = len(m[0])
	}

	return image.Rect(0, 0, dx, dy)
}

func (m Image) At(x, y int) color.Color {
	v := m[x][y]
	return color.RGBA{v, v, 255, 255}
}

func NewImage(x, y int, f func(int, int) uint8) Image {
	m := make([][]uint8, x)
	for i := range m {
		m[i] = make([]uint8, y)
		for j := range m[i] {
			m[i][j] = f(i, j)
		}
	}
	return m
}

func main() {
	m := NewImage(255, 255, func(a, b int) uint8 { return uint8(a * b) })
	pic.ShowImage(m)
}
<commit_msg>[x/tour] go-tour/gotour/solution: Adding alternative solution to the image exercise.<commit_after>// Copyright 2012 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 main

import (
	"image"
	"image/color"
	"tour/pic"
)

type Image struct {
	Height, Width int
}

func (m Image) ColorModel() color.Model {
	return color.RGBAModel
}

func (m Image) Bounds() image.Rectangle {
	return image.Rect(0, 0, m.Height, m.Width)
}

func (m Image) At(x, y int) color.Color {
	c := uint8(x ^ y)
	return color.RGBA{c, c, 255, 255}
}

func main() {
	m := Image{256, 256}
	pic.ShowImage(m)
}
<|endoftext|>


<commit_before>package e2etest

import (
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"testing"

	"github.com/davecgh/go-spew/spew"
	"github.com/hashicorp/terraform/e2e"
)

// The tests in this file are for the "primary workflow", which includes
// variants of the following sequence, with different details:
// terraform init
// terraform plan
// terraform apply
// terraform destroy

func TestPrimarySeparatePlan(t *testing.T) {
	t.Parallel()

	// This test reaches out to releases.hashicorp.com to download the
	// template and null providers, so it can only run if network access is
	// allowed.
	skipIfCannotAccessNetwork(t)

	fixturePath := filepath.Join("test-fixtures", "full-workflow-null")
	tf := e2e.NewBinary(terraformBin, fixturePath)
	defer tf.Close()

	//// INIT
	stdout, stderr, err := tf.Run("init")
	if err != nil {
		t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
	}

	// Make sure we actually downloaded the plugins, rather than picking up
	// copies that might be already installed globally on the system.
	if !strings.Contains(stdout, "- Downloading plugin for provider \"template\"") {
		t.Errorf("template provider download message is missing from init output:\n%s", stdout)
		t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)")
	}
	if !strings.Contains(stdout, "- Downloading plugin for provider \"null\"") {
		t.Errorf("null provider download message is missing from init output:\n%s", stdout)
		t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)")
	}

	//// PLAN
	stdout, stderr, err = tf.Run("plan", "-out=tfplan")
	if err != nil {
		t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
	}

	if !strings.Contains(stdout, "1 to add, 0 to change, 0 to destroy") {
		t.Errorf("incorrect plan tally; want 1 to add:\n%s", stdout)
	}

	plan, err := tf.Plan("tfplan")
	if err != nil {
		t.Fatalf("failed to read plan file: %s", err)
	}

	stateResources := plan.State.RootModule().Resources
	diffResources := plan.Diff.RootModule().Resources

	if len(stateResources) != 1 || stateResources["data.template_file.test"] == nil {
		t.Errorf("incorrect state in plan; want just data.template_file.test to have been rendered, but have:\n%s", spew.Sdump(stateResources))
	}
	if len(diffResources) != 1 || diffResources["null_resource.test"] == nil {
		t.Errorf("incorrect diff in plan; want just null_resource.test to have been rendered, but have:\n%s", spew.Sdump(diffResources))
	}

	//// APPLY
	stdout, stderr, err = tf.Run("apply", "tfplan")
	if err != nil {
		t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr)
	}

	if !strings.Contains(stdout, "Resources: 1 added, 0 changed, 0 destroyed") {
		t.Errorf("incorrect apply tally; want 1 added:\n%s", stdout)
	}

	state, err := tf.LocalState()
	if err != nil {
		t.Fatalf("failed to read state file: %s", err)
	}

	stateResources = state.RootModule().Resources
	var gotResources []string
	for n := range stateResources {
		gotResources = append(gotResources, n)
	}
	sort.Strings(gotResources)

	wantResources := []string{
		"data.template_file.test",
		"null_resource.test",
	}

	if !reflect.DeepEqual(gotResources, wantResources) {
		t.Errorf("wrong resources in state\ngot: %#v\nwant: %#v", gotResources, wantResources)
	}

	//// DESTROY
	stdout, stderr, err = tf.Run("destroy", "-force")
	if err != nil {
		t.Fatalf("unexpected destroy error: %s\nstderr:\n%s", err, stderr)
	}

	if !strings.Contains(stdout, "Resources: 2 destroyed") {
		t.Errorf("incorrect destroy tally; want 2 destroyed:\n%s", stdout)
	}

	state, err = tf.LocalState()
	if err != nil {
		t.Fatalf("failed to read state file after destroy: %s", err)
	}

	stateResources = state.RootModule().Resources
	if len(stateResources) != 0 {
		t.Errorf("wrong resources in state after destroy; want none, but still have:%s", spew.Sdump(stateResources))
	}

}
<commit_msg>command/e2etest: fix TestPrimarySeparatePlan test<commit_after>package e2etest

import (
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"testing"

	"github.com/davecgh/go-spew/spew"
	"github.com/hashicorp/terraform/e2e"
)

// The tests in this file are for the "primary workflow", which includes
// variants of the following sequence, with different details:
// terraform init
// terraform plan
// terraform apply
// terraform destroy

func TestPrimarySeparatePlan(t *testing.T) {
	t.Parallel()

	// This test reaches out to releases.hashicorp.com to download the
	// template and null providers, so it can only run if network access is
	// allowed.
	skipIfCannotAccessNetwork(t)

	fixturePath := filepath.Join("test-fixtures", "full-workflow-null")
	tf := e2e.NewBinary(terraformBin, fixturePath)
	defer tf.Close()

	//// INIT
	stdout, stderr, err := tf.Run("init")
	if err != nil {
		t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
	}

	// Make sure we actually downloaded the plugins, rather than picking up
	// copies that might be already installed globally on the system.
	if !strings.Contains(stdout, "- Downloading plugin for provider \"template\"") {
		t.Errorf("template provider download message is missing from init output:\n%s", stdout)
		t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)")
	}
	if !strings.Contains(stdout, "- Downloading plugin for provider \"null\"") {
		t.Errorf("null provider download message is missing from init output:\n%s", stdout)
		t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)")
	}

	//// PLAN
	stdout, stderr, err = tf.Run("plan", "-out=tfplan")
	if err != nil {
		t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
	}

	if !strings.Contains(stdout, "1 to add, 0 to change, 0 to destroy") {
		t.Errorf("incorrect plan tally; want 1 to add:\n%s", stdout)
	}

	plan, err := tf.Plan("tfplan")
	if err != nil {
		t.Fatalf("failed to read plan file: %s", err)
	}

	stateResources := plan.State.RootModule().Resources
	diffResources := plan.Diff.RootModule().Resources

	if len(stateResources) != 1 || stateResources["data.template_file.test"] == nil {
		t.Errorf("incorrect state in plan; want just data.template_file.test to have been rendered, but have:\n%s", spew.Sdump(stateResources))
	}
	if len(diffResources) != 1 || diffResources["null_resource.test"] == nil {
		t.Errorf("incorrect diff in plan; want just null_resource.test to have been rendered, but have:\n%s", spew.Sdump(diffResources))
	}

	//// APPLY
	stdout, stderr, err = tf.Run("apply", "tfplan")
	if err != nil {
		t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr)
	}

	if !strings.Contains(stdout, "Resources: 1 added, 0 changed, 0 destroyed") {
		t.Errorf("incorrect apply tally; want 1 added:\n%s", stdout)
	}

	state, err := tf.LocalState()
	if err != nil {
		t.Fatalf("failed to read state file: %s", err)
	}

	stateResources = state.RootModule().Resources
	var gotResources []string
	for n := range stateResources {
		gotResources = append(gotResources, n)
	}
	sort.Strings(gotResources)

	wantResources := []string{
		"data.template_file.test",
		"null_resource.test",
	}

	if !reflect.DeepEqual(gotResources, wantResources) {
		t.Errorf("wrong resources in state\ngot: %#v\nwant: %#v", gotResources, wantResources)
	}

	//// DESTROY
	stdout, stderr, err = tf.Run("destroy", "-force")
	if err != nil {
		t.Fatalf("unexpected destroy error: %s\nstderr:\n%s", err, stderr)
	}

	if !strings.Contains(stdout, "Resources: 1 destroyed") {
		t.Errorf("incorrect destroy tally; want 1 destroyed:\n%s", stdout)
	}

	state, err = tf.LocalState()
	if err != nil {
		t.Fatalf("failed to read state file after destroy: %s", err)
	}

	stateResources = state.RootModule().Resources
	if len(stateResources) != 0 {
		t.Errorf("wrong resources in state after destroy; want none, but still have:%s", spew.Sdump(stateResources))
	}

}
<|endoftext|>