<commit_before>// Copyright (C) 2017 Google 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 gvr

import (
	"context"
	"reflect"

	"github.com/google/gapid/gapis/api"
	"github.com/google/gapid/gapis/api/gles"
	"github.com/google/gapid/gapis/extensions"
	"github.com/google/gapid/gapis/resolve"
	"github.com/google/gapid/gapis/resolve/cmdgrouper"
	"github.com/google/gapid/gapis/service"
	"github.com/google/gapid/gapis/service/path"
)

func init() {
	extensions.Register(extensions.Extension{
		Name:           "GVR",
		AdjustContexts: adjustContexts,
		CmdGroupers:    newReprojectionGroupers,
		Events:         newReprojectionEvents,
	})
}

type contextUsage int

const (
	rendererCtx = contextUsage(iota)
	reprojectionCtx
)

func adjustContexts(ctx context.Context, ctxs []*api.ContextInfo) {
	// Look for the renderer context.
	tyGvrFrameSubmit := reflect.TypeOf(&Gvr_frame_submit{})
	if c := findContextByCommand(ctxs, tyGvrFrameSubmit); c != nil {
		c.UserData[rendererCtx] = true
		c.Name = "Main context (" + c.Name + ")"
	}

	// Look for the reprojection context.
	tyGlFlush := reflect.TypeOf(&gles.GlFlush{})
	if c := findContextByCommand(ctxs, tyGlFlush); c != nil {
		c.UserData[reprojectionCtx] = true
		c.Name = "Reprojection context"
	}
}

func findContextByCommand(ctxs []*api.ContextInfo, ty reflect.Type) *api.ContextInfo {
	highest, best := 0, (*api.ContextInfo)(nil)
	for _, c := range ctxs {
		if count := c.NumCommandsByType[ty]; count > highest {
			highest, best = count, c
		}
	}
	return best
}

func isReprojectionContext(ctx context.Context, p *path.Context) bool {
	// Only group if we're looking at the reprojection thread.
	if p == nil {
		return false
	}
	c, err := resolve.Context(ctx, p)
	if c == nil || err != nil {
		return false
	}
	_, ok := c.UserData[reprojectionCtx]
	return ok
}

func newReprojectionGroupers(ctx context.Context, p *path.CommandTree) []cmdgrouper.Grouper {
	if !isReprojectionContext(ctx, p.Capture.Context(p.GetFilter().GetContext().ID())) {
		return nil
	}
	glFenceSync := func(cond gles.GLenum) func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			c, ok := cmd.(*gles.GlFenceSync)
			return ok && c.Condition == cond
		}
	}
	glEndTilingQCOM := func(cond gles.GLbitfield) func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			c, ok := cmd.(*gles.GlEndTilingQCOM)
			return ok && c.PreserveMask == cond
		}
	}
	eglDestroySyncKHR := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.EglDestroySyncKHR)
			return ok
		}
	}
	glClientWaitSync := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.GlClientWaitSync)
			return ok
		}
	}
	glDeleteSync := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.GlDeleteSync)
			return ok
		}
	}
	glDrawElements := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.GlDrawElements)
			return ok
		}
	}
	notGlDrawElements := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.GlDrawElements)
			return !ok
		}
	}
	notGlFlush := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.GlFlush)
			return !ok
		}
	}
	return []cmdgrouper.Grouper{
		noSubFrameEventGrouper{cmdgrouper.Sequence("Left eye",
			cmdgrouper.Rule{Pred: eglDestroySyncKHR()},
			cmdgrouper.Rule{Pred: glClientWaitSync()},
			cmdgrouper.Rule{Pred: glDeleteSync()},
			cmdgrouper.Rule{Pred: notGlDrawElements(), Repeats: true},
			cmdgrouper.Rule{Pred: glDrawElements()},
		)},
		noSubFrameEventGrouper{cmdgrouper.Sequence("Right eye",
			cmdgrouper.Rule{Pred: glFenceSync(gles.GLenum_GL_SYNC_GPU_COMMANDS_COMPLETE)},
			cmdgrouper.Rule{Pred: glEndTilingQCOM(1), Optional: true},
			cmdgrouper.Rule{Pred: glClientWaitSync()},
			cmdgrouper.Rule{Pred: glDeleteSync()},
			cmdgrouper.Rule{Pred: notGlDrawElements(), Repeats: true},
			cmdgrouper.Rule{Pred: glDrawElements()},
			cmdgrouper.Rule{Pred: notGlFlush(), Repeats: true},
		)},
	}
}

type noSubFrameEventGrouper struct {
	cmdgrouper.Grouper
}

func (n noSubFrameEventGrouper) Build(end api.CmdID) []cmdgrouper.Group {
	out := n.Grouper.Build(end)
	for i := range out {
		out[i].UserData = &resolve.CmdGroupData{
			Thumbnail:          api.CmdNoID,
			NoFrameEventGroups: true,
		}
	}
	return out
}

func newReprojectionEvents(ctx context.Context, p *path.Events) extensions.EventProvider {
	if !isReprojectionContext(ctx, p.Capture.Context(p.GetFilter().GetContext().ID())) {
		return nil
	}

	var pending []service.EventKind
	return func(ctx context.Context, id api.CmdID, cmd api.Cmd, s *api.GlobalState) []*service.Event {
		events := []*service.Event{}
		for _, kind := range pending {
			events = append(events, &service.Event{
				Kind:    kind,
				Command: p.Capture.Command(uint64(id)),
			})
		}
		pending = nil
		if _, ok := cmd.(*gles.GlFlush); ok {
			if p.LastInFrame {
				events = append(events, &service.Event{
					Kind:    service.EventKind_LastInFrame,
					Command: p.Capture.Command(uint64(id)),
				})
			}
			if p.FirstInFrame {
				pending = append(pending, service.EventKind_FirstInFrame)
			}
		}
		return events
	}
}
<commit_msg>gvr: Minor tweak to GVR extension.<commit_after>// Copyright (C) 2017 Google 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 gvr

import (
	"context"
	"reflect"

	"github.com/google/gapid/gapis/api"
	"github.com/google/gapid/gapis/api/gles"
	"github.com/google/gapid/gapis/extensions"
	"github.com/google/gapid/gapis/resolve"
	"github.com/google/gapid/gapis/resolve/cmdgrouper"
	"github.com/google/gapid/gapis/service"
	"github.com/google/gapid/gapis/service/path"
)

func init() {
	extensions.Register(extensions.Extension{
		Name:           "GVR",
		AdjustContexts: adjustContexts,
		CmdGroupers:    newReprojectionGroupers,
		Events:         newReprojectionEvents,
	})
}

type contextUsage int

const (
	rendererCtx = contextUsage(iota)
	reprojectionCtx
)

func adjustContexts(ctx context.Context, ctxs []*api.ContextInfo) {
	// Look for the renderer context.
	tyGvrFrameSubmit := reflect.TypeOf(&Gvr_frame_submit{})
	tyGlFlush := reflect.TypeOf(&gles.GlFlush{})

	renderer := findContextByCommand(ctxs, tyGvrFrameSubmit)
	reprojection := findContextByCommand(ctxs, tyGlFlush)

	if renderer != nil && reprojection != nil {
		renderer.UserData[rendererCtx] = true
		renderer.Name = "Main context (" + renderer.Name + ")"
		reprojection.UserData[reprojectionCtx] = true
		reprojection.Name = "Reprojection context (" + reprojection.Name + ")"
	}
}

func findContextByCommand(ctxs []*api.ContextInfo, ty reflect.Type) *api.ContextInfo {
	highest, best := 0, (*api.ContextInfo)(nil)
	for _, c := range ctxs {
		if count := c.NumCommandsByType[ty]; count > highest {
			highest, best = count, c
		}
	}
	return best
}

func isReprojectionContext(ctx context.Context, p *path.Context) bool {
	// Only group if we're looking at the reprojection thread.
	if p == nil {
		return false
	}
	c, err := resolve.Context(ctx, p)
	if c == nil || err != nil {
		return false
	}
	_, ok := c.UserData[reprojectionCtx]
	return ok
}

func newReprojectionGroupers(ctx context.Context, p *path.CommandTree) []cmdgrouper.Grouper {
	if !isReprojectionContext(ctx, p.Capture.Context(p.GetFilter().GetContext().ID())) {
		return nil
	}
	glFenceSync := func(cond gles.GLenum) func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			c, ok := cmd.(*gles.GlFenceSync)
			return ok && c.Condition == cond
		}
	}
	glEndTilingQCOM := func(cond gles.GLbitfield) func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			c, ok := cmd.(*gles.GlEndTilingQCOM)
			return ok && c.PreserveMask == cond
		}
	}
	eglDestroySyncKHR := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.EglDestroySyncKHR)
			return ok
		}
	}
	glClientWaitSync := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.GlClientWaitSync)
			return ok
		}
	}
	glDeleteSync := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.GlDeleteSync)
			return ok
		}
	}
	glDrawElements := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.GlDrawElements)
			return ok
		}
	}
	notGlDrawElements := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.GlDrawElements)
			return !ok
		}
	}
	notGlFlush := func() func(cmd, prev api.Cmd) bool {
		return func(cmd, prev api.Cmd) bool {
			_, ok := cmd.(*gles.GlFlush)
			return !ok
		}
	}
	return []cmdgrouper.Grouper{
		noSubFrameEventGrouper{cmdgrouper.Sequence("Left eye",
			cmdgrouper.Rule{Pred: eglDestroySyncKHR()},
			cmdgrouper.Rule{Pred: glClientWaitSync()},
			cmdgrouper.Rule{Pred: glDeleteSync()},
			cmdgrouper.Rule{Pred: notGlDrawElements(), Repeats: true},
			cmdgrouper.Rule{Pred: glDrawElements()},
		)},
		noSubFrameEventGrouper{cmdgrouper.Sequence("Right eye",
			cmdgrouper.Rule{Pred: glFenceSync(gles.GLenum_GL_SYNC_GPU_COMMANDS_COMPLETE)},
			cmdgrouper.Rule{Pred: glEndTilingQCOM(1), Optional: true},
			cmdgrouper.Rule{Pred: glClientWaitSync()},
			cmdgrouper.Rule{Pred: glDeleteSync()},
			cmdgrouper.Rule{Pred: notGlDrawElements(), Repeats: true},
			cmdgrouper.Rule{Pred: glDrawElements()},
			cmdgrouper.Rule{Pred: notGlFlush(), Repeats: true},
		)},
	}
}

type noSubFrameEventGrouper struct {
	cmdgrouper.Grouper
}

func (n noSubFrameEventGrouper) Build(end api.CmdID) []cmdgrouper.Group {
	out := n.Grouper.Build(end)
	for i := range out {
		out[i].UserData = &resolve.CmdGroupData{
			Thumbnail:          api.CmdNoID,
			NoFrameEventGroups: true,
		}
	}
	return out
}

func newReprojectionEvents(ctx context.Context, p *path.Events) extensions.EventProvider {
	if !isReprojectionContext(ctx, p.Capture.Context(p.GetFilter().GetContext().ID())) {
		return nil
	}

	var pending []service.EventKind
	return func(ctx context.Context, id api.CmdID, cmd api.Cmd, s *api.GlobalState) []*service.Event {
		events := []*service.Event{}
		for _, kind := range pending {
			events = append(events, &service.Event{
				Kind:    kind,
				Command: p.Capture.Command(uint64(id)),
			})
		}
		pending = nil
		if _, ok := cmd.(*gles.GlFlush); ok {
			if p.LastInFrame {
				events = append(events, &service.Event{
					Kind:    service.EventKind_LastInFrame,
					Command: p.Capture.Command(uint64(id)),
				})
			}
			if p.FirstInFrame {
				pending = append(pending, service.EventKind_FirstInFrame)
			}
		}
		return events
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/NoahShen/gotunnelme/src/gotunnelme"
	"github.com/appleboy/com/random"
	"github.com/appleboy/drone-facebook/template"
	"github.com/fatih/color"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"golang.org/x/crypto/acme/autocert"
)

const defaultPreviewImageURL = "https://cdn4.iconfinder.com/data/icons/miu/24/device-camera-recorder-video-glyph-256.png"

type (
	// Repo information.
	Repo struct {
		Owner string
		Name  string
	}

	// Build information.
	Build struct {
		Tag      string
		Event    string
		Number   int
		Commit   string
		Branch   string
		Author   string
		Email    string
		Message  string
		Status   string
		Link     string
		Started  float64
		Finished float64
		DeployTo string
	}

	// Config for the plugin.
	Config struct {
		ChannelToken  string
		ChannelSecret string
		To            []string
		ToRoom        string
		ToGroup       string
		Delimiter     string
		Message       []string
		Image         []string
		Video         []string
		Audio         []string
		Sticker       []string
		Location      []string
		MatchEmail    bool
		Port          int
		Tunnel        bool
		Debug         bool
		Domain        string
		AutoTLS       bool
		Host          []string
	}

	// Plugin values.
	Plugin struct {
		Repo   Repo
		Build  Build
		Config Config
	}

	// Audio format
	Audio struct {
		URL      string
		Duration int
	}

	// Location format
	Location struct {
		Title     string
		Address   string
		Latitude  float64
		Longitude float64
	}
)

var (
	// ReceiveCount is receive notification count
	ReceiveCount int64
	// SendCount is send notification count
	SendCount int64
)

func init() {
	// Support metrics
	m := NewMetrics()
	prometheus.MustRegister(m)
}

func trimElement(keys []string) []string {
	var newKeys []string

	for _, value := range keys {
		value = strings.Trim(value, " ")
		if len(value) == 0 {
			continue
		}
		newKeys = append(newKeys, value)
	}

	return newKeys
}

func convertImage(value, delimiter string) []string {
	values := trimElement(strings.Split(value, delimiter))

	if len(values) < 2 {
		values = append(values, values[0])
	}

	return values
}

func convertVideo(value, delimiter string) []string {
	values := trimElement(strings.Split(value, delimiter))

	if len(values) < 2 {
		values = append(values, defaultPreviewImageURL)
	}

	return values
}

func convertAudio(value, delimiter string) (Audio, bool) {
	values := trimElement(strings.Split(value, delimiter))

	if len(values) < 2 {
		return Audio{}, true
	}

	duration, err := strconv.Atoi(values[1])

	if err != nil {
		log.Println(err.Error())
		return Audio{}, true
	}

	return Audio{
		URL:      values[0],
		Duration: duration,
	}, false
}

func convertSticker(value, delimiter string) ([]string, bool) {
	values := trimElement(strings.Split(value, delimiter))

	if len(values) < 2 {
		return []string{}, true
	}

	return values, false
}

func convertLocation(value, delimiter string) (Location, bool) {
	var latitude, longitude float64
	var err error
	values := trimElement(strings.Split(value, delimiter))

	if len(values) < 4 {
		return Location{}, true
	}

	latitude, err = strconv.ParseFloat(values[2], 64)

	if err != nil {
		log.Println(err.Error())
		return Location{}, true
	}

	longitude, err = strconv.ParseFloat(values[3], 64)

	if err != nil {
		log.Println(err.Error())
		return Location{}, true
	}

	return Location{
		Title:     values[0],
		Address:   values[1],
		Latitude:  latitude,
		Longitude: longitude,
	}, false
}

func parseTo(to []string, authorEmail string, matchEmail bool, delimiter string) []string {
	var emails []string
	var ids []string
	attachEmail := true

	for _, value := range trimElement(to) {
		idArray := trimElement(strings.Split(value, delimiter))

		// check match author email
		if len(idArray) > 1 {
			if email := idArray[1]; email != authorEmail {
				continue
			}

			emails = append(emails, idArray[0])
			attachEmail = false
			continue
		}

		ids = append(ids, idArray[0])
	}

	if matchEmail == true && attachEmail == false {
		return emails
	}

	for _, value := range emails {
		ids = append(ids, value)
	}

	return ids
}

// Bot is new Line Bot clien.
func (p Plugin) Bot() (*linebot.Client, error) {
	if len(p.Config.ChannelToken) == 0 || len(p.Config.ChannelSecret) == 0 {
		log.Println("missing line bot config")

		return nil, errors.New("missing line bot config")
	}

	return linebot.New(p.Config.ChannelSecret, p.Config.ChannelToken)
}

func (p Plugin) getTunnelDomain() (string, error) {
	var domain string
	if p.Config.Domain != "" {
		if len(p.Config.Domain) < 4 || len(p.Config.Domain) > 63 {
			return "", errors.New("tunnel host name must be lowercase and between 4 and 63 alphanumeric characters")
		}
		domain = p.Config.Domain
	} else {
		domain = strings.ToLower(random.String(10))
	}

	return domain, nil
}

// Handler is http handler.
func (p Plugin) Handler(bot *linebot.Client) *http.ServeMux {
	mux := http.NewServeMux()

	// Setup HTTP Server for receiving requests from LINE platform
	mux.HandleFunc("/callback", func(w http.ResponseWriter, req *http.Request) {
		events, err := bot.ParseRequest(req)
		if err != nil {
			if err == linebot.ErrInvalidSignature {
				w.WriteHeader(400)
			} else {
				w.WriteHeader(500)
			}
			return
		}
		for _, event := range events {
			if event.Type == linebot.EventTypeMessage {
				switch message := event.Message.(type) {
				case *linebot.TextMessage:
					log.Printf("User ID is %v\n", event.Source.UserID)
					log.Printf("Room ID is %v\n", event.Source.RoomID)
					log.Printf("Group ID is %v\n", event.Source.GroupID)

					ReceiveCount++
					if message.Text == "test" {
						SendCount++
						if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage("count + 1")).Do(); err != nil {
							log.Print(err)
						}
					}
					if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(message.Text)).Do(); err != nil {
						log.Print(err)
					}
				}
			}
		}
	})

	mux.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
		promhttp.Handler().ServeHTTP(w, req)
	})

	// Setup HTTP Server for receiving requests from LINE platform
	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(w, "Welcome to Line webhook page.")
	})

	return mux
}

// Webhook support line callback service.
func (p Plugin) Webhook() error {
	readyToListen := false
	bot, err := p.Bot()

	if err != nil {
		return err
	}
	mux := p.Handler(bot)

	if p.Config.Tunnel {
		if p.Config.Debug {
			gotunnelme.Debug = true
		}

		domain, err := p.getTunnelDomain()
		if err != nil {
			panic(err)
		}

		tunnel := gotunnelme.NewTunnel()
		url, err := tunnel.GetUrl(domain)
		if err != nil {
			panic("Could not get localtunnel.me URL. " + err.Error())
		}
		go func() {
			for !readyToListen {
				time.Sleep(1 * time.Second)
			}
			c := color.New(color.FgYellow)
			c.Println("Tunnel URL:", url)
			err := tunnel.CreateTunnel(p.Config.Port)
			if err != nil {
				panic("Could not create tunnel. " + err.Error())
			}
		}()
	}

	readyToListen = true
	if p.Config.Port != 443 && !p.Config.AutoTLS {
		log.Println("Line Webhook Server Listin on " + strconv.Itoa(p.Config.Port) + " port")
		if err := http.ListenAndServe(":"+strconv.Itoa(p.Config.Port), mux); err != nil {
			log.Fatal(err)
		}
	}

	if p.Config.AutoTLS && len(p.Config.Host) != 0 {
		log.Println("Line Webhook Server Listin on 443 port, hostname: " + strings.Join(p.Config.Host, ", "))
		return http.Serve(autocert.NewListener(p.Config.Host...), mux)
	}

	return nil
}

// Notify for Line notify service
// https://notify-bot.line.me
func (p Plugin) Notify() error {
	if p.Config.ChannelToken == "" || len(p.Config.Message) == 0 {
		return errors.New("missing token or message")
	}

	for _, m := range p.Config.Message {
		if err := p.notify(m, p.Config.ChannelToken); err != nil {
			return err
		}
	}

	return nil
}

func (p Plugin) notify(message, token string) error {
	data := url.Values{}
	data.Add("message", message)

	u, _ := url.ParseRequestURI("https://notify-api.line.me/api/notify")
	urlStr := u.String()

	req, err := http.NewRequest(
		"POST",
		urlStr,
		strings.NewReader(data.Encode()),
	)

	if err != nil {
		return errors.New("failed to create request:" + err.Error())
	}

	req.Header.Add("Authorization", "Bearer "+token)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))

	client := &http.Client{}
	res, err := client.Do(req)

	if err != nil {
		return errors.New("failed to process request:" + err.Error())
	}

	defer res.Body.Close()

	if p.Config.Debug {
		log.Println("=================================")
		log.Printf("%#v\n", res)
		log.Println("=================================")
	}

	if res.Status == "200 OK" {
		log.Println("successfully send notfiy")
	}

	if err != nil {
		return errors.New("failed to create request:" + err.Error())
	}

	return nil
}

// Exec executes the plugin.
func (p Plugin) Exec() error {

	bot, err := p.Bot()

	if err != nil {
		return err
	}

	if len(p.Config.To) == 0 && len(p.Config.ToRoom) == 0 && len(p.Config.ToGroup) == 0 {
		log.Println("missing line user config")

		return errors.New("missing line user config")
	}

	var message []string
	if len(p.Config.Message) > 0 {
		message = p.Config.Message
	} else {
		message = p.Message(p.Repo, p.Build)
	}

	// Initial messages array.
	var messages []linebot.Message

	for _, value := range trimElement(message) {
		txt, err := template.RenderTrim(value, p)
		if err != nil {
			return err
		}

		messages = append(messages, linebot.NewTextMessage(txt))
	}

	// Add image message
	for _, value := range trimElement(p.Config.Image) {
		values := convertImage(value, p.Config.Delimiter)

		messages = append(messages, linebot.NewImageMessage(values[0], values[1]))
	}

	// Add image message.
	for _, value := range trimElement(p.Config.Video) {
		values := convertVideo(value, p.Config.Delimiter)

		messages = append(messages, linebot.NewVideoMessage(values[0], values[1]))
	}

	// Add Audio message.
	for _, value := range trimElement(p.Config.Audio) {
		audio, empty := convertAudio(value, p.Config.Delimiter)

		if empty == true {
			continue
		}

		messages = append(messages, linebot.NewAudioMessage(audio.URL, audio.Duration))
	}

	// Add Sticker message.
	for _, value := range trimElement(p.Config.Sticker) {
		sticker, empty := convertSticker(value, p.Config.Delimiter)

		if empty == true {
			continue
		}

		messages = append(messages, linebot.NewStickerMessage(sticker[0], sticker[1]))
	}

	// check Location array.
	for _, value := range trimElement(p.Config.Location) {
		location, empty := convertLocation(value, p.Config.Delimiter)

		if empty == true {
			continue
		}

		messages = append(messages, linebot.NewLocationMessage(location.Title, location.Address, location.Latitude, location.Longitude))
	}

	uids := parseTo(p.Config.To, p.Build.Email, p.Config.MatchEmail, p.Config.Delimiter)

	// Send messages to multiple users at any time.
	if len(uids) > 0 {
		if _, err := bot.Multicast(uids, messages...).Do(); err != nil {
			log.Println(err.Error())
		}
	}

	// Send messages to single room at any time.
	rid := p.Config.ToRoom
	if rid != "" {
		if _, err := bot.PushMessage(rid, messages...).Do(); err != nil {
			log.Println(err.Error())
		}
	}

	// Send messages to single group at any time.
	gid := p.Config.ToGroup
	if gid != "" {
		if _, err := bot.PushMessage(gid, messages...).Do(); err != nil {
			log.Println(err.Error())
		}
	}

	return nil
}

// Message is line default message.
func (p Plugin) Message(repo Repo, build Build) []string {
	return []string{fmt.Sprintf("[%s] <%s> (%s)『%s』by %s",
		build.Status,
		build.Link,
		build.Branch,
		build.Message,
		build.Author,
	)}
}
<commit_msg>fix: update linebot structure<commit_after>package main

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/NoahShen/gotunnelme/src/gotunnelme"
	"github.com/appleboy/com/random"
	"github.com/appleboy/drone-facebook/template"
	"github.com/fatih/color"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"golang.org/x/crypto/acme/autocert"
)

const defaultPreviewImageURL = "https://cdn4.iconfinder.com/data/icons/miu/24/device-camera-recorder-video-glyph-256.png"

type (
	// Repo information.
	Repo struct {
		Owner string
		Name  string
	}

	// Build information.
	Build struct {
		Tag      string
		Event    string
		Number   int
		Commit   string
		Branch   string
		Author   string
		Email    string
		Message  string
		Status   string
		Link     string
		Started  float64
		Finished float64
		DeployTo string
	}

	// Config for the plugin.
	Config struct {
		ChannelToken  string
		ChannelSecret string
		To            []string
		ToRoom        string
		ToGroup       string
		Delimiter     string
		Message       []string
		Image         []string
		Video         []string
		Audio         []string
		Sticker       []string
		Location      []string
		MatchEmail    bool
		Port          int
		Tunnel        bool
		Debug         bool
		Domain        string
		AutoTLS       bool
		Host          []string
	}

	// Plugin values.
	Plugin struct {
		Repo   Repo
		Build  Build
		Config Config
	}

	// Audio format
	Audio struct {
		URL      string
		Duration int
	}

	// Location format
	Location struct {
		Title     string
		Address   string
		Latitude  float64
		Longitude float64
	}
)

var (
	// ReceiveCount is receive notification count
	ReceiveCount int64
	// SendCount is send notification count
	SendCount int64
)

func init() {
	// Support metrics
	m := NewMetrics()
	prometheus.MustRegister(m)
}

func trimElement(keys []string) []string {
	var newKeys []string

	for _, value := range keys {
		value = strings.Trim(value, " ")
		if len(value) == 0 {
			continue
		}
		newKeys = append(newKeys, value)
	}

	return newKeys
}

func convertImage(value, delimiter string) []string {
	values := trimElement(strings.Split(value, delimiter))

	if len(values) < 2 {
		values = append(values, values[0])
	}

	return values
}

func convertVideo(value, delimiter string) []string {
	values := trimElement(strings.Split(value, delimiter))

	if len(values) < 2 {
		values = append(values, defaultPreviewImageURL)
	}

	return values
}

func convertAudio(value, delimiter string) (Audio, bool) {
	values := trimElement(strings.Split(value, delimiter))

	if len(values) < 2 {
		return Audio{}, true
	}

	duration, err := strconv.Atoi(values[1])

	if err != nil {
		log.Println(err.Error())
		return Audio{}, true
	}

	return Audio{
		URL:      values[0],
		Duration: duration,
	}, false
}

func convertSticker(value, delimiter string) ([]string, bool) {
	values := trimElement(strings.Split(value, delimiter))

	if len(values) < 2 {
		return []string{}, true
	}

	return values, false
}

func convertLocation(value, delimiter string) (Location, bool) {
	var latitude, longitude float64
	var err error
	values := trimElement(strings.Split(value, delimiter))

	if len(values) < 4 {
		return Location{}, true
	}

	latitude, err = strconv.ParseFloat(values[2], 64)

	if err != nil {
		log.Println(err.Error())
		return Location{}, true
	}

	longitude, err = strconv.ParseFloat(values[3], 64)

	if err != nil {
		log.Println(err.Error())
		return Location{}, true
	}

	return Location{
		Title:     values[0],
		Address:   values[1],
		Latitude:  latitude,
		Longitude: longitude,
	}, false
}

func parseTo(to []string, authorEmail string, matchEmail bool, delimiter string) []string {
	var emails []string
	var ids []string
	attachEmail := true

	for _, value := range trimElement(to) {
		idArray := trimElement(strings.Split(value, delimiter))

		// check match author email
		if len(idArray) > 1 {
			if email := idArray[1]; email != authorEmail {
				continue
			}

			emails = append(emails, idArray[0])
			attachEmail = false
			continue
		}

		ids = append(ids, idArray[0])
	}

	if matchEmail == true && attachEmail == false {
		return emails
	}

	for _, value := range emails {
		ids = append(ids, value)
	}

	return ids
}

// Bot is new Line Bot clien.
func (p Plugin) Bot() (*linebot.Client, error) {
	if len(p.Config.ChannelToken) == 0 || len(p.Config.ChannelSecret) == 0 {
		log.Println("missing line bot config")

		return nil, errors.New("missing line bot config")
	}

	return linebot.New(p.Config.ChannelSecret, p.Config.ChannelToken)
}

func (p Plugin) getTunnelDomain() (string, error) {
	var domain string
	if p.Config.Domain != "" {
		if len(p.Config.Domain) < 4 || len(p.Config.Domain) > 63 {
			return "", errors.New("tunnel host name must be lowercase and between 4 and 63 alphanumeric characters")
		}
		domain = p.Config.Domain
	} else {
		domain = strings.ToLower(random.String(10))
	}

	return domain, nil
}

// Handler is http handler.
func (p Plugin) Handler(bot *linebot.Client) *http.ServeMux {
	mux := http.NewServeMux()

	// Setup HTTP Server for receiving requests from LINE platform
	mux.HandleFunc("/callback", func(w http.ResponseWriter, req *http.Request) {
		events, err := bot.ParseRequest(req)
		if err != nil {
			if err == linebot.ErrInvalidSignature {
				w.WriteHeader(400)
			} else {
				w.WriteHeader(500)
			}
			return
		}
		for _, event := range events {
			if event.Type == linebot.EventTypeMessage {
				switch message := event.Message.(type) {
				case *linebot.TextMessage:
					log.Printf("User ID is %v\n", event.Source.UserID)
					log.Printf("Room ID is %v\n", event.Source.RoomID)
					log.Printf("Group ID is %v\n", event.Source.GroupID)

					ReceiveCount++
					if message.Text == "test" {
						SendCount++
						if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage("count + 1")).Do(); err != nil {
							log.Print(err)
						}
					}
					if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(message.Text)).Do(); err != nil {
						log.Print(err)
					}
				}
			}
		}
	})

	mux.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
		promhttp.Handler().ServeHTTP(w, req)
	})

	// Setup HTTP Server for receiving requests from LINE platform
	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintln(w, "Welcome to Line webhook page.")
	})

	return mux
}

// Webhook support line callback service.
func (p Plugin) Webhook() error {
	readyToListen := false
	bot, err := p.Bot()

	if err != nil {
		return err
	}
	mux := p.Handler(bot)

	if p.Config.Tunnel {
		if p.Config.Debug {
			gotunnelme.Debug = true
		}

		domain, err := p.getTunnelDomain()
		if err != nil {
			panic(err)
		}

		tunnel := gotunnelme.NewTunnel()
		url, err := tunnel.GetUrl(domain)
		if err != nil {
			panic("Could not get localtunnel.me URL. " + err.Error())
		}
		go func() {
			for !readyToListen {
				time.Sleep(1 * time.Second)
			}
			c := color.New(color.FgYellow)
			c.Println("Tunnel URL:", url)
			err := tunnel.CreateTunnel(p.Config.Port)
			if err != nil {
				panic("Could not create tunnel. " + err.Error())
			}
		}()
	}

	readyToListen = true
	if p.Config.Port != 443 && !p.Config.AutoTLS {
		log.Println("Line Webhook Server Listin on " + strconv.Itoa(p.Config.Port) + " port")
		if err := http.ListenAndServe(":"+strconv.Itoa(p.Config.Port), mux); err != nil {
			log.Fatal(err)
		}
	}

	if p.Config.AutoTLS && len(p.Config.Host) != 0 {
		log.Println("Line Webhook Server Listin on 443 port, hostname: " + strings.Join(p.Config.Host, ", "))
		return http.Serve(autocert.NewListener(p.Config.Host...), mux)
	}

	return nil
}

// Notify for Line notify service
// https://notify-bot.line.me
func (p Plugin) Notify() error {
	if p.Config.ChannelToken == "" || len(p.Config.Message) == 0 {
		return errors.New("missing token or message")
	}

	for _, m := range p.Config.Message {
		if err := p.notify(m, p.Config.ChannelToken); err != nil {
			return err
		}
	}

	return nil
}

func (p Plugin) notify(message, token string) error {
	data := url.Values{}
	data.Add("message", message)

	u, _ := url.ParseRequestURI("https://notify-api.line.me/api/notify")
	urlStr := u.String()

	req, err := http.NewRequest(
		"POST",
		urlStr,
		strings.NewReader(data.Encode()),
	)

	if err != nil {
		return errors.New("failed to create request:" + err.Error())
	}

	req.Header.Add("Authorization", "Bearer "+token)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))

	client := &http.Client{}
	res, err := client.Do(req)

	if err != nil {
		return errors.New("failed to process request:" + err.Error())
	}

	defer res.Body.Close()

	if p.Config.Debug {
		log.Println("=================================")
		log.Printf("%#v\n", res)
		log.Println("=================================")
	}

	if res.Status == "200 OK" {
		log.Println("successfully send notfiy")
	}

	if err != nil {
		return errors.New("failed to create request:" + err.Error())
	}

	return nil
}

// Exec executes the plugin.
func (p Plugin) Exec() error {

	bot, err := p.Bot()

	if err != nil {
		return err
	}

	if len(p.Config.To) == 0 && len(p.Config.ToRoom) == 0 && len(p.Config.ToGroup) == 0 {
		log.Println("missing line user config")

		return errors.New("missing line user config")
	}

	var message []string
	if len(p.Config.Message) > 0 {
		message = p.Config.Message
	} else {
		message = p.Message(p.Repo, p.Build)
	}

	// Initial messages array.
	var messages []linebot.SendingMessage

	for _, value := range trimElement(message) {
		txt, err := template.RenderTrim(value, p)
		if err != nil {
			return err
		}

		messages = append(messages, linebot.NewTextMessage(txt))
	}

	// Add image message
	for _, value := range trimElement(p.Config.Image) {
		values := convertImage(value, p.Config.Delimiter)

		messages = append(messages, linebot.NewImageMessage(values[0], values[1]))
	}

	// Add image message.
	for _, value := range trimElement(p.Config.Video) {
		values := convertVideo(value, p.Config.Delimiter)

		messages = append(messages, linebot.NewVideoMessage(values[0], values[1]))
	}

	// Add Audio message.
	for _, value := range trimElement(p.Config.Audio) {
		audio, empty := convertAudio(value, p.Config.Delimiter)

		if empty == true {
			continue
		}

		messages = append(messages, linebot.NewAudioMessage(audio.URL, audio.Duration))
	}

	// Add Sticker message.
	for _, value := range trimElement(p.Config.Sticker) {
		sticker, empty := convertSticker(value, p.Config.Delimiter)

		if empty == true {
			continue
		}

		messages = append(messages, linebot.NewStickerMessage(sticker[0], sticker[1]))
	}

	// check Location array.
	for _, value := range trimElement(p.Config.Location) {
		location, empty := convertLocation(value, p.Config.Delimiter)

		if empty == true {
			continue
		}

		messages = append(messages, linebot.NewLocationMessage(location.Title, location.Address, location.Latitude, location.Longitude))
	}

	uids := parseTo(p.Config.To, p.Build.Email, p.Config.MatchEmail, p.Config.Delimiter)

	// Send messages to multiple users at any time.
	if len(uids) > 0 {
		if _, err := bot.Multicast(uids, messages...).Do(); err != nil {
			log.Println(err.Error())
		}
	}

	// Send messages to single room at any time.
	rid := p.Config.ToRoom
	if rid != "" {
		if _, err := bot.PushMessage(rid, messages...).Do(); err != nil {
			log.Println(err.Error())
		}
	}

	// Send messages to single group at any time.
	gid := p.Config.ToGroup
	if gid != "" {
		if _, err := bot.PushMessage(gid, messages...).Do(); err != nil {
			log.Println(err.Error())
		}
	}

	return nil
}

// Message is line default message.
func (p Plugin) Message(repo Repo, build Build) []string {
	return []string{fmt.Sprintf("[%s] <%s> (%s)『%s』by %s",
		build.Status,
		build.Link,
		build.Branch,
		build.Message,
		build.Author,
	)}
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"

	"github.com/go-chi/chi"
	"github.com/pressly/saml"
	"github.com/pkg/errors"
)

var (
	flagInitiatedBy = flag.String("initiated-by", "", "Either idp or sp")
	flagListenAddr  = flag.String("listen-addr", "127.0.0.1:1113", "Bind to address")
	flagPublicURL   = flag.String("public-url", "http://127.0.0.1:1113", "Service's public URL")

	flagMetadataURL = flag.String("idp-metadata-url", "http://127.0.0.1:1117/metadata.xml", "IdP's metadata URL")
	flagRelayState  = flag.String("relay-state", "", "Relay state")

	flagSSOServiceBinding = flag.String("sso-service-binding", "redirect", "SSO service binding")

	flagPubCert = flag.String("pubkey-cert-pem", "", "Load public key from PEM file")
	flagPrivKey = flag.String("privkey-pem", "", "Load private key from PEM file")

	flagHelp = flag.Bool("help", false, "Display help")
)

var (
	serviceProvider saml.ServiceProvider
)

const (
	metadataPath = "/metadata.xml"
	acsPath      = "/saml/acs"
)

func accessGrantedHandler(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, err.Error(), 400)
		return
	}
	assertion, err := serviceProvider.AssertResponse(r.Form.Get("SAMLResponse"))
	if err != nil {
		http.Error(w, err.Error(), 400)
		return
	}

	r.ParseForm()

	relayState := r.Form.Get("RelayState")

	log.Printf("Login OK, RelayState: %v", relayState)

	claims := map[string]interface{}{}
	for _, attr := range assertion.AttributeStatement.Attributes {
		values := []string{}
		for _, value := range attr.Values {
			values = append(values, value.Value)
		}
		key := attr.FriendlyName
		if key == "" {
			key = attr.Name
		}
		claims[key] = values
	}
	buf, err := json.Marshal(claims)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Write(buf)
}

func logHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("URL: %s", r.URL.String())
		next.ServeHTTP(w, r)
	})
}

func metadataHandler(w http.ResponseWriter, r *http.Request) {
	xml, err := serviceProvider.MetadataXML()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	w.Header().Set("Content-Type", "application/xml; charset=utf8")
	w.Write([]byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"))
	w.Write(xml)
}

func authRequestHandler(w http.ResponseWriter, r *http.Request) {
	successRedirectURL := r.FormValue("redirect_url")

	switch serviceProvider.IdPSSOServiceBinding {

	case saml.HTTPRedirectBinding:
		idpRedirectURL, err := serviceProvider.AuthnRequestURL(successRedirectURL)
		if err != nil {
			http.Error(w, err.Error(), 500)
		}
		log.Printf("Redirecting to IdP with SAMLRequest: %+v", idpRedirectURL)
		w.Header().Add("Location", idpRedirectURL)
		w.WriteHeader(http.StatusFound)

	case saml.HTTPPostBinding:
		samlRequest, err := serviceProvider.NewPostSAMLRequest()
		if err != nil {
			http.Error(w, err.Error(), 500)
			return
		}

		payload := fmt.Sprintf(`
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
	<body onload="document.forms[0].submit()">
		<form action="%s" method="post">
			<div>
				<input type="hidden" name="RelayState" value="%s" />
				<input type="hidden" name="SAMLRequest" value="%s" />
			</div>
	  </form>
	</body>
</html>
	`, serviceProvider.IdPSSOServiceURL, successRedirectURL, samlRequest)

		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		w.Write([]byte(payload))

	}
	http.Error(w, errors.Errorf("invalid sso service binding: %v", serviceProvider.IdPSSOServiceBinding).Error(), 500)
	return
}

func setRelayState(nextFn func(w http.ResponseWriter, r *http.Request)) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := context.WithValue(r.Context(), "saml.RelayState", *flagRelayState)
		log.Printf("Setting RelayState: %s", *flagRelayState)
		nextFn(w, r.WithContext(ctx))
	}
}

func main() {
	flag.Parse()

	if (flagHelp != nil && *flagHelp) || flagInitiatedBy == nil {
		flag.PrintDefaults()
		return
	}

	if flagListenAddr == nil || flagPublicURL == nil || flagRelayState == nil {
		flag.PrintDefaults()
		return
	}

	if flagPubCert == nil || *flagPubCert == "" {
		log.Fatal("Missing -pubkey-cert-pem")
	}

	if flagPrivKey == nil || *flagPrivKey == "" {
		log.Fatal("Missing -privkey-pem")
	}

	serviceProvider = saml.ServiceProvider{
		CertFile: *flagPubCert,
		KeyFile:  *flagPrivKey,

		IdPMetadataURL: *flagMetadataURL,

		MetadataURL: *flagPublicURL + metadataPath,
		ACSURL:      *flagPublicURL + acsPath,

		SecurityOpts: saml.SecurityOpts{
			AllowSelfSignedCert: true,
		},
	}

	serviceProvider.IdPMetadataURL = *flagMetadataURL
	idpMetadata, err := serviceProvider.ParseIdPMetadata()
	if err != nil {
		log.Fatal(errors.Wrap(err, "failed to parse idp metadata"))
	}
	serviceProvider.IdPMetadata = idpMetadata

	var binding string
	switch *flagSSOServiceBinding {
	case "redirect":
		binding = saml.HTTPRedirectBinding
	case "post":
		binding = saml.HTTPPostBinding
	}
	ssoService := idpMetadata.SSOService(binding)
	if ssoService != nil {
		serviceProvider.IdPSSOServiceBinding = ssoService.Binding
		serviceProvider.IdPSSOServiceURL = ssoService.Location
	}

	idpCert := idpMetadata.Cert()
	serviceProvider.IdPPubkeyPEM = idpCert
	serviceProvider.IdPEntityID = idpMetadata.EntityID

	r := chi.NewRouter()
	r.Use(logHandler)

	r.Get(metadataPath, metadataHandler)

	r.Post(acsPath, accessGrantedHandler)

	log.Printf("Test SP server listening at %s (%s)", *flagListenAddr, *flagPublicURL)
	switch *flagInitiatedBy {
	case "sp":
		r.Get("/", setRelayState(authRequestHandler))
		log.Printf("Go to %s to begin the SP initiated login.", *flagPublicURL)
	}

	log.Fatal(http.ListenAndServe(*flagListenAddr, r))
}
<commit_msg>Updating sp server example.<commit_after>package main

import (
	"context"
	"encoding/json"
	"flag"
	"log"
	"net/http"

	"github.com/go-chi/chi"
	"github.com/pkg/errors"
	"github.com/pressly/saml"
)

var (
	flagInitiatedBy = flag.String("initiated-by", "", "Either idp or sp")
	flagListenAddr  = flag.String("listen-addr", "127.0.0.1:1113", "Bind to address")
	flagPublicURL   = flag.String("public-url", "http://127.0.0.1:1113", "Service's public URL")

	flagMetadataURL = flag.String("idp-metadata-url", "http://127.0.0.1:1117/metadata.xml", "IdP's metadata URL")
	flagRelayState  = flag.String("relay-state", "", "Relay state")

	flagSSOServiceBinding = flag.String("sso-service-binding", "redirect", "SSO service binding")

	flagPubCert = flag.String("pubkey-cert-pem", "", "Load public key from PEM file")
	flagPrivKey = flag.String("privkey-pem", "", "Load private key from PEM file")

	flagHelp = flag.Bool("help", false, "Display help")
)

var (
	serviceProvider saml.ServiceProvider
)

const (
	metadataPath = "/metadata.xml"
	acsPath      = "/saml/acs"
)

func accessGrantedHandler(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, err.Error(), 400)
		return
	}
	assertion, err := serviceProvider.AssertResponse(r.Form.Get("SAMLResponse"))
	if err != nil {
		http.Error(w, err.Error(), 400)
		return
	}

	r.ParseForm()

	relayState := r.Form.Get("RelayState")

	log.Printf("Login OK, RelayState: %v", relayState)

	claims := map[string]interface{}{}
	for _, attr := range assertion.AttributeStatement.Attributes {
		values := []string{}
		for _, value := range attr.Values {
			values = append(values, value.Value)
		}
		key := attr.FriendlyName
		if key == "" {
			key = attr.Name
		}
		claims[key] = values
	}
	buf, err := json.Marshal(claims)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	w.Write(buf)
}

func logHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("URL: %s", r.URL.String())
		next.ServeHTTP(w, r)
	})
}

func metadataHandler(w http.ResponseWriter, r *http.Request) {
	xml, err := serviceProvider.MetadataXML()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	w.Header().Set("Content-Type", "application/xml; charset=utf8")
	w.Write([]byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"))
	w.Write(xml)
}

func authRequestHandler(w http.ResponseWriter, r *http.Request) {
	successRedirectURL := r.FormValue("redirect_url")

	samlRequest, err := serviceProvider.SAMLRequest(successRedirectURL)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}

	switch serviceProvider.IdPSSOServiceBinding {
	case saml.HTTPRedirectBinding:
		w.Header().Add("Location", samlRequest)
		w.WriteHeader(http.StatusFound)

	case saml.HTTPPostBinding:
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		w.Write([]byte(samlRequest))
	}
	http.Error(w, errors.Errorf("invalid sso service binding: %v", serviceProvider.IdPSSOServiceBinding).Error(), 500)
	return
}

func setRelayState(nextFn func(w http.ResponseWriter, r *http.Request)) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := context.WithValue(r.Context(), "saml.RelayState", *flagRelayState)
		log.Printf("Setting RelayState: %s", *flagRelayState)
		nextFn(w, r.WithContext(ctx))
	}
}

func main() {
	flag.Parse()

	if (flagHelp != nil && *flagHelp) || flagInitiatedBy == nil {
		flag.PrintDefaults()
		return
	}

	if flagListenAddr == nil || flagPublicURL == nil || flagRelayState == nil {
		flag.PrintDefaults()
		return
	}

	if flagPubCert == nil || *flagPubCert == "" {
		log.Fatal("Missing -pubkey-cert-pem")
	}

	if flagPrivKey == nil || *flagPrivKey == "" {
		log.Fatal("Missing -privkey-pem")
	}

	serviceProvider = saml.ServiceProvider{
		CertFile: *flagPubCert,
		KeyFile:  *flagPrivKey,

		IdPMetadataURL: *flagMetadataURL,

		MetadataURL: *flagPublicURL + metadataPath,
		ACSURL:      *flagPublicURL + acsPath,

		SecurityOpts: saml.SecurityOpts{
			AllowSelfSignedCert: true,
		},
	}

	serviceProvider.IdPMetadataURL = *flagMetadataURL
	idpMetadata, err := serviceProvider.ParseIdPMetadata()
	if err != nil {
		log.Fatal(errors.Wrap(err, "failed to parse idp metadata"))
	}
	serviceProvider.IdPMetadata = idpMetadata

	var binding string
	switch *flagSSOServiceBinding {
	case "redirect":
		binding = saml.HTTPRedirectBinding
	case "post":
		binding = saml.HTTPPostBinding
	}
	ssoService := idpMetadata.SSOService(binding)
	if ssoService != nil {
		serviceProvider.IdPSSOServiceBinding = ssoService.Binding
		serviceProvider.IdPSSOServiceURL = ssoService.Location
	}

	idpCert := idpMetadata.Cert()
	serviceProvider.IdPPubkeyPEM = idpCert
	serviceProvider.IdPEntityID = idpMetadata.EntityID

	r := chi.NewRouter()
	r.Use(logHandler)

	r.Get(metadataPath, metadataHandler)

	r.Post(acsPath, accessGrantedHandler)

	log.Printf("Test SP server listening at %s (%s)", *flagListenAddr, *flagPublicURL)
	switch *flagInitiatedBy {
	case "sp":
		r.Get("/", setRelayState(authRequestHandler))
		log.Printf("Go to %s to begin the SP initiated login.", *flagPublicURL)
	}

	log.Fatal(http.ListenAndServe(*flagListenAddr, r))
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"

	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsIamGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsIamGroupCreate,
		Read:   resourceAwsIamGroupRead,
		Update: resourceAwsIamGroupUpdate,
		Delete: resourceAwsIamGroupDelete,

		Schema: map[string]*schema.Schema{
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"unique_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"path": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "/",
			},
		},
	}
}

func resourceAwsIamGroupCreate(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn
	name := d.Get("name").(string)
	path := d.Get("path").(string)

	request := &iam.CreateGroupInput{
		Path:      aws.String(path),
		GroupName: aws.String(name),
	}

	createResp, err := iamconn.CreateGroup(request)
	if err != nil {
		return fmt.Errorf("Error creating IAM Group %s: %s", name, err)
	}
	return resourceAwsIamGroupReadResult(d, createResp.Group)
}

func resourceAwsIamGroupRead(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn
	name := d.Get("name").(string)

	request := &iam.GetGroupInput{
		GroupName: aws.String(name),
	}

	getResp, err := iamconn.GetGroup(request)
	if err != nil {
		if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error reading IAM Group %s: %s", d.Id(), err)
	}
	return resourceAwsIamGroupReadResult(d, getResp.Group)
}

func resourceAwsIamGroupReadResult(d *schema.ResourceData, group *iam.Group) error {
	d.SetId(*group.GroupName)
	if err := d.Set("name", group.GroupName); err != nil {
		return err
	}
	if err := d.Set("arn", group.Arn); err != nil {
		return err
	}
	if err := d.Set("path", group.Path); err != nil {
		return err
	}
	if err := d.Set("unique_id", group.GroupId); err != nil {
		return err
	}
	return nil
}

func resourceAwsIamGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	if d.HasChange("name") || d.HasChange("path") {
		iamconn := meta.(*AWSClient).iamconn
		on, nn := d.GetChange("name")
		op, np := d.GetChange("path")

		request := &iam.UpdateGroupInput{
			GroupName:    aws.String(on.(string)),
			NewGroupName: aws.String(nn.(string)),
			NewPath:      aws.String(np.(string)),
		}
		_, err := iamconn.UpdateGroup(request)
		if err != nil {
			return fmt.Errorf("Error updating IAM Group %s: %s", d.Id(), err)
		}
		return resourceAwsIamGroupRead(d, meta)
	}
	return nil
}

func resourceAwsIamGroupDelete(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	request := &iam.DeleteGroupInput{
		GroupName: aws.String(d.Id()),
	}

	if _, err := iamconn.DeleteGroup(request); err != nil {
		return fmt.Errorf("Error deleting IAM Group %s: %s", d.Id(), err)
	}
	return nil
}
<commit_msg>provider/aws: removed build-blocking unused variable.<commit_after>package aws

import (
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/iam"

	"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsIamGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsIamGroupCreate,
		Read:   resourceAwsIamGroupRead,
		Update: resourceAwsIamGroupUpdate,
		Delete: resourceAwsIamGroupDelete,

		Schema: map[string]*schema.Schema{
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"unique_id": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"path": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "/",
			},
		},
	}
}

func resourceAwsIamGroupCreate(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn
	name := d.Get("name").(string)
	path := d.Get("path").(string)

	request := &iam.CreateGroupInput{
		Path:      aws.String(path),
		GroupName: aws.String(name),
	}

	createResp, err := iamconn.CreateGroup(request)
	if err != nil {
		return fmt.Errorf("Error creating IAM Group %s: %s", name, err)
	}
	return resourceAwsIamGroupReadResult(d, createResp.Group)
}

func resourceAwsIamGroupRead(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn
	name := d.Get("name").(string)

	request := &iam.GetGroupInput{
		GroupName: aws.String(name),
	}

	getResp, err := iamconn.GetGroup(request)
	if err != nil {
		if iamerr, ok := err.(awserr.Error); ok && iamerr.Code() == "NoSuchEntity" {
			d.SetId("")
			return nil
		}
		return fmt.Errorf("Error reading IAM Group %s: %s", d.Id(), err)
	}
	return resourceAwsIamGroupReadResult(d, getResp.Group)
}

func resourceAwsIamGroupReadResult(d *schema.ResourceData, group *iam.Group) error {
	d.SetId(*group.GroupName)
	if err := d.Set("name", group.GroupName); err != nil {
		return err
	}
	if err := d.Set("arn", group.Arn); err != nil {
		return err
	}
	if err := d.Set("path", group.Path); err != nil {
		return err
	}
	if err := d.Set("unique_id", group.GroupId); err != nil {
		return err
	}
	return nil
}

func resourceAwsIamGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	if d.HasChange("name") || d.HasChange("path") {
		iamconn := meta.(*AWSClient).iamconn
		on, nn := d.GetChange("name")
		_, np := d.GetChange("path")

		request := &iam.UpdateGroupInput{
			GroupName:    aws.String(on.(string)),
			NewGroupName: aws.String(nn.(string)),
			NewPath:      aws.String(np.(string)),
		}
		_, err := iamconn.UpdateGroup(request)
		if err != nil {
			return fmt.Errorf("Error updating IAM Group %s: %s", d.Id(), err)
		}
		return resourceAwsIamGroupRead(d, meta)
	}
	return nil
}

func resourceAwsIamGroupDelete(d *schema.ResourceData, meta interface{}) error {
	iamconn := meta.(*AWSClient).iamconn

	request := &iam.DeleteGroupInput{
		GroupName: aws.String(d.Id()),
	}

	if _, err := iamconn.DeleteGroup(request); err != nil {
		return fmt.Errorf("Error deleting IAM Group %s: %s", d.Id(), err)
	}
	return nil
}
<|endoftext|>


<commit_before>package release

import (
	"os"
	"testing"

	"github.com/goreleaser/goreleaser/context"
	"github.com/stretchr/testify/assert"
)

func TestDescribeBody(t *testing.T) {
	var assert = assert.New(t)
	var changelog = "\nfeature1: description\nfeature2: other description"
	var ctx = &context.Context{
		ReleaseNotes: changelog,
	}
	out, err := describeBody(ctx)
	assert.NoError(err)
	assert.Contains(out.String(), changelog)
	assert.Contains(out.String(), "Automated with [GoReleaser]")
	assert.Contains(out.String(), "Built with go version go1.8")
}

func TestDontEscapeHTML(t *testing.T) {
	var assert = assert.New(t)
	var changelog = "
test
"
	var ctx = &context.Context{
		ReleaseNotes: changelog,
	}
	out, err := describeBody(ctx)
	assert.NoError(err)
	assert.Contains(changelog, out.String())
}

func TestGoVersionFails(t *testing.T) {
	var assert = assert.New(t)
	var path = os.Getenv("PATH")
	defer func() {
		assert.NoError(os.Setenv("PATH", path))
	}()
	assert.NoError(os.Setenv("PATH", ""))
	var ctx = &context.Context{
		ReleaseNotes: "changelog",
	}
	_, err := describeBody(ctx)
	assert.Error(err)
}
<commit_msg>fixed tests<commit_after>package release

import (
	"os"
	"testing"

	"github.com/goreleaser/goreleaser/context"
	"github.com/stretchr/testify/assert"
)

func TestDescribeBody(t *testing.T) {
	var assert = assert.New(t)
	var changelog = "\nfeature1: description\nfeature2: other description"
	var ctx = &context.Context{
		ReleaseNotes: changelog,
	}
	out, err := describeBody(ctx)
	assert.NoError(err)
	assert.Contains(out.String(), changelog)
	assert.Contains(out.String(), "Automated with [GoReleaser]")
	assert.Contains(out.String(), "Built with go version go1.")
}

func TestDontEscapeHTML(t *testing.T) {
	var assert = assert.New(t)
	var changelog = "
test
"
	var ctx = &context.Context{
		ReleaseNotes: changelog,
	}
	out, err := describeBody(ctx)
	assert.NoError(err)
	assert.Contains(out.String(), changelog)
}

func TestGoVersionFails(t *testing.T) {
	var assert = assert.New(t)
	var path = os.Getenv("PATH")
	defer func() {
		assert.NoError(os.Setenv("PATH", path))
	}()
	assert.NoError(os.Setenv("PATH", ""))
	var ctx = &context.Context{
		ReleaseNotes: "changelog",
	}
	_, err := describeBody(ctx)
	assert.Error(err)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 gce_pd

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"time"

	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/util/exec"
	"k8s.io/kubernetes/pkg/util/mount"
	"k8s.io/kubernetes/pkg/util/sets"
	"k8s.io/kubernetes/pkg/volume"
)

type gcePersistentDiskAttacher struct {
	host volume.VolumeHost
}

var _ volume.Attacher = &gcePersistentDiskAttacher{}

var _ volume.AttachableVolumePlugin = &gcePersistentDiskPlugin{}

func (plugin *gcePersistentDiskPlugin) NewAttacher() (volume.Attacher, error) {
	return &gcePersistentDiskAttacher{host: plugin.host}, nil
}

func (plugin *gcePersistentDiskPlugin) GetDeviceName(spec *volume.Spec) (string, error) {
	volumeSource, _ := getVolumeSource(spec)
	if volumeSource == nil {
		return "", fmt.Errorf("Spec does not reference a GCE volume type")
	}

	return volumeSource.PDName, nil
}

// Attach checks with the GCE cloud provider if the specified volume is already
// attached to the specified node. If the volume is attached, it succeeds
// (returns nil). If it is not, Attach issues a call to the GCE cloud provider
// to attach it.
// Callers are responsible for retryinging on failure.
// Callers are responsible for thread safety between concurrent attach and
// detach operations.
func (attacher *gcePersistentDiskAttacher) Attach(spec *volume.Spec, hostName string) error {
	volumeSource, readOnly := getVolumeSource(spec)
	pdName := volumeSource.PDName

	gceCloud, err := getCloudProvider(attacher.host.GetCloudProvider())
	if err != nil {
		return err
	}

	attached, err := gceCloud.DiskIsAttached(pdName, hostName)
	if err != nil {
		// Log error and continue with attach
		glog.Errorf(
			"Error checking if PD (%q) is already attached to current node (%q). Will continue and try attach anyway. err=%v",
			pdName, hostName, err)
	}

	if attached {
		// Volume is already attached to node.
		glog.Infof("Attach operation is successful. PD %q is already attached to node %q.", pdName, hostName)
		return nil
	}

	if err = gceCloud.AttachDisk(pdName, hostName, readOnly); err != nil {
		glog.Errorf("Error attaching PD %q to node %q: %+v", pdName, hostName, err)
		return err
	}

	return nil
}

func (attacher *gcePersistentDiskAttacher) WaitForAttach(spec *volume.Spec, timeout time.Duration) (string, error) {
	ticker := time.NewTicker(checkSleepDuration)
	defer ticker.Stop()
	timer := time.NewTimer(timeout)
	defer timer.Stop()

	volumeSource, _ := getVolumeSource(spec)
	pdName := volumeSource.PDName
	partition := ""
	if volumeSource.Partition != 0 {
		partition = strconv.Itoa(int(volumeSource.Partition))
	}

	sdBefore, err := filepath.Glob(diskSDPattern)
	if err != nil {
		glog.Errorf("Error filepath.Glob(\"%s\"): %v\r\n", diskSDPattern, err)
	}
	sdBeforeSet := sets.NewString(sdBefore...)

	devicePaths := getDiskByIdPaths(pdName, partition)
	for {
		select {
		case <-ticker.C:
			glog.V(5).Infof("Checking GCE PD %q is attached.", pdName)
			path, err := verifyDevicePath(devicePaths, sdBeforeSet)
			if err != nil {
				// Log error, if any, and continue checking periodically. See issue #11321
				glog.Errorf("Error verifying GCE PD (%q) is attached: %v", pdName, err)
			} else if path != "" {
				// A device path has successfully been created for the PD
				glog.Infof("Successfully found attached GCE PD %q.", pdName)
				return path, nil
			}
		case <-timer.C:
			return "", fmt.Errorf("Could not find attached GCE PD %q. Timeout waiting for mount paths to be created.", pdName)
		}
	}
}

func (attacher *gcePersistentDiskAttacher) GetDeviceMountPath(spec *volume.Spec) string {
	volumeSource, _ := getVolumeSource(spec)
	return makeGlobalPDName(attacher.host, volumeSource.PDName)
}

func (attacher *gcePersistentDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, mounter mount.Interface) error {
	// Only mount the PD globally once.
	notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
	if err != nil {
		if os.IsNotExist(err) {
			if err := os.MkdirAll(deviceMountPath, 0750); err != nil {
				return err
			}
			notMnt = true
		} else {
			return err
		}
	}

	volumeSource, readOnly := getVolumeSource(spec)

	options := []string{}
	if readOnly {
		options = append(options, "ro")
	}
	if notMnt {
		diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}
		err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, options)
		if err != nil {
			os.Remove(deviceMountPath)
			return err
		}
	}
	return nil
}

type gcePersistentDiskDetacher struct {
	host volume.VolumeHost
}

var _ volume.Detacher = &gcePersistentDiskDetacher{}

func (plugin *gcePersistentDiskPlugin) NewDetacher() (volume.Detacher, error) {
	return &gcePersistentDiskDetacher{host: plugin.host}, nil
}

// Detach checks with the GCE cloud provider if the specified volume is already
// attached to the specified node. If the volume is not attached, it succeeds
// (returns nil). If it is attached, Detach issues a call to the GCE cloud
// provider to attach it.
// Callers are responsible for retryinging on failure.
// Callers are responsible for thread safety between concurrent attach and detach
// operations.
func (detacher *gcePersistentDiskDetacher) Detach(deviceMountPath string, hostName string) error {
	pdName := path.Base(deviceMountPath)

	gceCloud, err := getCloudProvider(detacher.host.GetCloudProvider())
	if err != nil {
		return err
	}

	attached, err := gceCloud.DiskIsAttached(pdName, hostName)
	if err != nil {
		// Log error and continue with detach
		glog.Errorf(
			"Error checking if PD (%q) is already attached to current node (%q). Will continue and try detach anyway. err=%v",
			pdName, hostName, err)
	}

	if !attached {
		// Volume is not attached to node. Success!
		glog.Infof("Detach operation is successful. PD %q was not attached to node %q.", pdName, hostName)
		return nil
	}

	if err = gceCloud.DetachDisk(pdName, hostName); err != nil {
		glog.Errorf("Error detaching PD %q from node %q: %v", pdName, hostName, err)
		return err
	}

	return nil
}

func (detacher *gcePersistentDiskDetacher) WaitForDetach(devicePath string, timeout time.Duration) error {
	ticker := time.NewTicker(checkSleepDuration)
	defer ticker.Stop()
	timer := time.NewTimer(timeout)
	defer timer.Stop()

	for {
		select {
		case <-ticker.C:
			glog.V(5).Infof("Checking device %q is detached.", devicePath)
			if pathExists, err := pathExists(devicePath); err != nil {
				return fmt.Errorf("Error checking if device path exists: %v", err)
			} else if !pathExists {
				return nil
			}
		case <-timer.C:
			return fmt.Errorf("Timeout reached; PD Device %v is still attached", devicePath)
		}
	}
}

func (detacher *gcePersistentDiskDetacher) UnmountDevice(deviceMountPath string, mounter mount.Interface) error {
	return unmountPDAndRemoveGlobalPath(deviceMountPath, mounter)
}
<commit_msg>Fix GCE attacher/detacher to ignore return value of failed calls.<commit_after>/*
Copyright 2016 The Kubernetes Authors All rights reserved.

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 gce_pd

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"time"

	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/util/exec"
	"k8s.io/kubernetes/pkg/util/mount"
	"k8s.io/kubernetes/pkg/util/sets"
	"k8s.io/kubernetes/pkg/volume"
)

type gcePersistentDiskAttacher struct {
	host volume.VolumeHost
}

var _ volume.Attacher = &gcePersistentDiskAttacher{}

var _ volume.AttachableVolumePlugin = &gcePersistentDiskPlugin{}

func (plugin *gcePersistentDiskPlugin) NewAttacher() (volume.Attacher, error) {
	return &gcePersistentDiskAttacher{host: plugin.host}, nil
}

func (plugin *gcePersistentDiskPlugin) GetDeviceName(spec *volume.Spec) (string, error) {
	volumeSource, _ := getVolumeSource(spec)
	if volumeSource == nil {
		return "", fmt.Errorf("Spec does not reference a GCE volume type")
	}

	return volumeSource.PDName, nil
}

// Attach checks with the GCE cloud provider if the specified volume is already
// attached to the specified node. If the volume is attached, it succeeds
// (returns nil). If it is not, Attach issues a call to the GCE cloud provider
// to attach it.
// Callers are responsible for retryinging on failure.
// Callers are responsible for thread safety between concurrent attach and
// detach operations.
func (attacher *gcePersistentDiskAttacher) Attach(spec *volume.Spec, hostName string) error {
	volumeSource, readOnly := getVolumeSource(spec)
	pdName := volumeSource.PDName

	gceCloud, err := getCloudProvider(attacher.host.GetCloudProvider())
	if err != nil {
		return err
	}

	attached, err := gceCloud.DiskIsAttached(pdName, hostName)
	if err != nil {
		// Log error and continue with attach
		glog.Errorf(
			"Error checking if PD (%q) is already attached to current node (%q). Will continue and try attach anyway. err=%v",
			pdName, hostName, err)
	}

	if err == nil && attached {
		// Volume is already attached to node.
		glog.Infof("Attach operation is successful. PD %q is already attached to node %q.", pdName, hostName)
		return nil
	}

	if err = gceCloud.AttachDisk(pdName, hostName, readOnly); err != nil {
		glog.Errorf("Error attaching PD %q to node %q: %+v", pdName, hostName, err)
		return err
	}

	return nil
}

func (attacher *gcePersistentDiskAttacher) WaitForAttach(spec *volume.Spec, timeout time.Duration) (string, error) {
	ticker := time.NewTicker(checkSleepDuration)
	defer ticker.Stop()
	timer := time.NewTimer(timeout)
	defer timer.Stop()

	volumeSource, _ := getVolumeSource(spec)
	pdName := volumeSource.PDName
	partition := ""
	if volumeSource.Partition != 0 {
		partition = strconv.Itoa(int(volumeSource.Partition))
	}

	sdBefore, err := filepath.Glob(diskSDPattern)
	if err != nil {
		glog.Errorf("Error filepath.Glob(\"%s\"): %v\r\n", diskSDPattern, err)
	}
	sdBeforeSet := sets.NewString(sdBefore...)

	devicePaths := getDiskByIdPaths(pdName, partition)
	for {
		select {
		case <-ticker.C:
			glog.V(5).Infof("Checking GCE PD %q is attached.", pdName)
			path, err := verifyDevicePath(devicePaths, sdBeforeSet)
			if err != nil {
				// Log error, if any, and continue checking periodically. See issue #11321
				glog.Errorf("Error verifying GCE PD (%q) is attached: %v", pdName, err)
			} else if path != "" {
				// A device path has successfully been created for the PD
				glog.Infof("Successfully found attached GCE PD %q.", pdName)
				return path, nil
			}
		case <-timer.C:
			return "", fmt.Errorf("Could not find attached GCE PD %q. Timeout waiting for mount paths to be created.", pdName)
		}
	}
}

func (attacher *gcePersistentDiskAttacher) GetDeviceMountPath(spec *volume.Spec) string {
	volumeSource, _ := getVolumeSource(spec)
	return makeGlobalPDName(attacher.host, volumeSource.PDName)
}

func (attacher *gcePersistentDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, mounter mount.Interface) error {
	// Only mount the PD globally once.
	notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
	if err != nil {
		if os.IsNotExist(err) {
			if err := os.MkdirAll(deviceMountPath, 0750); err != nil {
				return err
			}
			notMnt = true
		} else {
			return err
		}
	}

	volumeSource, readOnly := getVolumeSource(spec)

	options := []string{}
	if readOnly {
		options = append(options, "ro")
	}
	if notMnt {
		diskMounter := &mount.SafeFormatAndMount{Interface: mounter, Runner: exec.New()}
		err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, options)
		if err != nil {
			os.Remove(deviceMountPath)
			return err
		}
	}
	return nil
}

type gcePersistentDiskDetacher struct {
	host volume.VolumeHost
}

var _ volume.Detacher = &gcePersistentDiskDetacher{}

func (plugin *gcePersistentDiskPlugin) NewDetacher() (volume.Detacher, error) {
	return &gcePersistentDiskDetacher{host: plugin.host}, nil
}

// Detach checks with the GCE cloud provider if the specified volume is already
// attached to the specified node. If the volume is not attached, it succeeds
// (returns nil). If it is attached, Detach issues a call to the GCE cloud
// provider to attach it.
// Callers are responsible for retryinging on failure.
// Callers are responsible for thread safety between concurrent attach and detach
// operations.
func (detacher *gcePersistentDiskDetacher) Detach(deviceMountPath string, hostName string) error {
	pdName := path.Base(deviceMountPath)

	gceCloud, err := getCloudProvider(detacher.host.GetCloudProvider())
	if err != nil {
		return err
	}

	attached, err := gceCloud.DiskIsAttached(pdName, hostName)
	if err != nil {
		// Log error and continue with detach
		glog.Errorf(
			"Error checking if PD (%q) is already attached to current node (%q). Will continue and try detach anyway. err=%v",
			pdName, hostName, err)
	}

	if err == nil && !attached {
		// Volume is not attached to node. Success!
		glog.Infof("Detach operation is successful. PD %q was not attached to node %q.", pdName, hostName)
		return nil
	}

	if err = gceCloud.DetachDisk(pdName, hostName); err != nil {
		glog.Errorf("Error detaching PD %q from node %q: %v", pdName, hostName, err)
		return err
	}

	return nil
}

func (detacher *gcePersistentDiskDetacher) WaitForDetach(devicePath string, timeout time.Duration) error {
	ticker := time.NewTicker(checkSleepDuration)
	defer ticker.Stop()
	timer := time.NewTimer(timeout)
	defer timer.Stop()

	for {
		select {
		case <-ticker.C:
			glog.V(5).Infof("Checking device %q is detached.", devicePath)
			if pathExists, err := pathExists(devicePath); err != nil {
				return fmt.Errorf("Error checking if device path exists: %v", err)
			} else if !pathExists {
				return nil
			}
		case <-timer.C:
			return fmt.Errorf("Timeout reached; PD Device %v is still attached", devicePath)
		}
	}
}

func (detacher *gcePersistentDiskDetacher) UnmountDevice(deviceMountPath string, mounter mount.Interface) error {
	return unmountPDAndRemoveGlobalPath(deviceMountPath, mounter)
}
<|endoftext|>


<commit_before>// Copyright (c) 2020 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package main

import (
	"context"
	"flag"
	"fmt"
	"strings"

	gnmilib "github.com/aristanetworks/goarista/gnmi"
	"github.com/aristanetworks/goarista/gnmireverse"

	"github.com/aristanetworks/glog"
	"github.com/openconfig/gnmi/proto/gnmi"
	"golang.org/x/sync/errgroup"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

type multiPath struct {
	p []*gnmi.Path
}

func (m *multiPath) String() string {
	if m == nil {
		return ""
	}
	s := make([]string, len(m.p))
	for i, p := range m.p {
		s[i] = gnmilib.StrPath(p)
	}
	return strings.Join(s, ", ")
}

// Set implements flag.Value interface
func (m *multiPath) Set(s string) error {
	gnmiPath, err := gnmilib.ParseGNMIElements(gnmilib.SplitPath(s))
	if err != nil {
		return err
	}
	m.p = append(m.p, gnmiPath)
	return nil
}

func main() {
	targetAddr := flag.String("target_addr", "127.0.0.1:6030", "address of the gNMI target")
	destAddr := flag.String("collector_addr", "",
		"address of collector in the form of [<vrf-name>/]address:port")
	target := flag.String("target_value", "",
		"value to use in the target field of the Subscribe")
	paths := multiPath{}
	flag.Var(&paths, "subscribe",
		"Path to subscribe to. This option can be repeated multiple times.")

	username := flag.String("username", "", "username to authenticate with target")
	password := flag.String("password", "", "password to authenticate with target")
	sourceAddr := flag.String("source_addr", "", "addr to use as source in connection to collector")

	clientCert := flag.String("cert", "",
		"path to certificate to use to authenticate with collector")
	ca := flag.String("ca", "", "path to CA to verify collector")

	flag.Parse()

	var (
		_ = sourceAddr
		_ = clientCert
		_ = ca
	)

	// TODO: handle vrf, sourceAddr, clientCert
	destConn, err := grpc.Dial(*destAddr)
	if err != nil {
		glog.Fatalf("error dialing destination %q: %s", *destAddr, err)
	}
	targetConn, err := grpc.Dial(*targetAddr)
	if err != nil {
		glog.Fatalf("error dialing target %q: %s", *targetAddr, err)
	}

	for {
		// Start publisher and subscriber in a loop, each running in
		// their own goroutine. If either of them encounters an error,
		// retry.
		eg, ctx := errgroup.WithContext(context.Background())
		// c is used to send subscribe responses from subscriber to
		// publisher.
		c := make(chan *gnmi.SubscribeResponse)
		eg.Go(func() error {
			return publish(ctx, destConn, c)
		})
		eg.Go(func() error {
			return subscribe(ctx, targetConn, c, *username, *password, *target, paths.p)
		})
		err := eg.Wait()
		if err != nil {
			glog.Errorf("encountered error, retrying: %s", err)
		}
	}
}

func publish(ctx context.Context, destConn *grpc.ClientConn,
	c <-chan *gnmi.SubscribeResponse) error {
	client := gnmireverse.NewGNMIReverseClient(destConn)
	stream, err := client.Publish(ctx)
	if err != nil {
		return fmt.Errorf("error from Publish: %s", err)
	}
	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case response := <-c:
			if err := stream.Send(response); err != nil {
				return fmt.Errorf("error from Publish.Send: %s", err)
			}
		}
	}
}

func subscribe(ctx context.Context, targetConn *grpc.ClientConn,
	c chan<- *gnmi.SubscribeResponse, username, password, target string, paths []*gnmi.Path) error {
	client := gnmi.NewGNMIClient(targetConn)
	subList := &gnmi.SubscriptionList{
		Prefix: &gnmi.Path{Target: target},
	}

	for _, p := range paths {
		subList.Subscription = append(subList.Subscription,
			&gnmi.Subscription{
				Path: p,
				Mode: gnmi.SubscriptionMode_TARGET_DEFINED,
			},
		)
	}
	request := &gnmi.SubscribeRequest{
		Request: &gnmi.SubscribeRequest_Subscribe{
			Subscribe: subList,
		},
	}

	if username != "" {
		ctx = metadata.NewOutgoingContext(ctx,
			metadata.Pairs(
				"username", username,
				"password", password),
		)
	}
	stream, err := client.Subscribe(ctx)
	if err != nil {
		return fmt.Errorf("error from Subscribe: %s", err)
	}
	if err := stream.Send(request); err != nil {
		return fmt.Errorf("error sending SubscribeRequest: %s", err)
	}

	for {
		resp, err := stream.Recv()
		if err != nil {
			return fmt.Errorf("error from Subscribe.Recv: %s", err)
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		case c <- resp:
		}
	}
}
<commit_msg>gnmireverse/client: Add support for TLS config<commit_after>// Copyright (c) 2020 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"flag"
	"fmt"
	"io/ioutil"
	"strings"

	gnmilib "github.com/aristanetworks/goarista/gnmi"
	"github.com/aristanetworks/goarista/gnmireverse"

	"github.com/aristanetworks/glog"
	"github.com/openconfig/gnmi/proto/gnmi"
	"golang.org/x/sync/errgroup"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
)

type multiPath struct {
	p []*gnmi.Path
}

func (m *multiPath) String() string {
	if m == nil {
		return ""
	}
	s := make([]string, len(m.p))
	for i, p := range m.p {
		s[i] = gnmilib.StrPath(p)
	}
	return strings.Join(s, ", ")
}

// Set implements flag.Value interface
func (m *multiPath) Set(s string) error {
	gnmiPath, err := gnmilib.ParseGNMIElements(gnmilib.SplitPath(s))
	if err != nil {
		return err
	}
	m.p = append(m.p, gnmiPath)
	return nil
}

func newTLSConfig(useTLS bool, skipVerify bool, certFile, keyFile, caFile string) (grpc.DialOption,
	error) {
	if !useTLS {
		return grpc.WithInsecure(), nil
	}
	tlsConfig := tls.Config{}
	if skipVerify {
		tlsConfig.InsecureSkipVerify = true
	} else if caFile != "" {
		b, err := ioutil.ReadFile(caFile)
		if err != nil {
			return nil, err
		}
		cp := x509.NewCertPool()
		if !cp.AppendCertsFromPEM(b) {
			return nil, fmt.Errorf("credentials: failed to append certificates")
		}
		tlsConfig.RootCAs = cp
	}
	if certFile != "" {
		if keyFile == "" {
			return nil, fmt.Errorf("please provide both -collector_certfile and -collector_keyfile")
		}
		cert, err := tls.LoadX509KeyPair(certFile, keyFile)
		if err != nil {
			return nil, err
		}
		tlsConfig.Certificates = []tls.Certificate{cert}
	}
	return grpc.WithTransportCredentials(credentials.NewTLS(&tlsConfig)), nil
}

func main() {
	targetAddr := flag.String("target_addr", "127.0.0.1:6030", "address of the gNMI target")
	destAddr := flag.String("collector_addr", "",
		"address of collector in the form of [<vrf-name>/]address:port")
	target := flag.String("target_value", "",
		"value to use in the target field of the Subscribe")
	paths := multiPath{}
	flag.Var(&paths, "subscribe",
		"Path to subscribe to. This option can be repeated multiple times.")

	username := flag.String("username", "", "username to authenticate with target")
	password := flag.String("password", "", "password to authenticate with target")
	sourceAddr := flag.String("source_addr", "", "addr to use as source in connection to collector")

	_ = sourceAddr

	clientCert := flag.String("collector_certfile", "",
		"path to TLS certificate file to authenticate with collector")
	clientKey := flag.String("collector_keyfile", "",
		"path to TLS key file to authenticate with collector")
	caFile := flag.String("collector_cafile", "",
		"path to TLS CA file to verify collector (leave empty to use host's root CA set)")
	useTlS := flag.Bool("collector_tls", true, "use TLS in connection with collector")
	skipVerify := flag.Bool("collector_tls_skipverify", false,
		"don't verify collector's certificate (insecure)")

	flag.Parse()

	tlsDialOption, err := newTLSConfig(*useTlS, *skipVerify, *clientCert, *clientKey, *caFile)
	if err != nil {
		glog.Fatal(err)
	}

	// TODO: handle vrf, sourceAddr
	destConn, err := grpc.Dial(*destAddr, tlsDialOption)
	if err != nil {
		glog.Fatalf("error dialing destination %q: %s", *destAddr, err)
	}
	targetConn, err := grpc.Dial(*targetAddr, grpc.WithInsecure())
	if err != nil {
		glog.Fatalf("error dialing target %q: %s", *targetAddr, err)
	}

	for {
		// Start publisher and subscriber in a loop, each running in
		// their own goroutine. If either of them encounters an error,
		// retry.
		eg, ctx := errgroup.WithContext(context.Background())
		// c is used to send subscribe responses from subscriber to
		// publisher.
		c := make(chan *gnmi.SubscribeResponse)
		eg.Go(func() error {
			return publish(ctx, destConn, c)
		})
		eg.Go(func() error {
			return subscribe(ctx, targetConn, c, *username, *password, *target, paths.p)
		})
		err := eg.Wait()
		if err != nil {
			glog.Errorf("encountered error, retrying: %s", err)
		}
	}
}

func publish(ctx context.Context, destConn *grpc.ClientConn,
	c <-chan *gnmi.SubscribeResponse) error {
	client := gnmireverse.NewGNMIReverseClient(destConn)
	stream, err := client.Publish(ctx)
	if err != nil {
		return fmt.Errorf("error from Publish: %s", err)
	}
	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case response := <-c:
			if err := stream.Send(response); err != nil {
				return fmt.Errorf("error from Publish.Send: %s", err)
			}
		}
	}
}

func subscribe(ctx context.Context, targetConn *grpc.ClientConn,
	c chan<- *gnmi.SubscribeResponse, username, password, target string, paths []*gnmi.Path) error {
	client := gnmi.NewGNMIClient(targetConn)
	subList := &gnmi.SubscriptionList{
		Prefix: &gnmi.Path{Target: target},
	}

	for _, p := range paths {
		subList.Subscription = append(subList.Subscription,
			&gnmi.Subscription{
				Path: p,
				Mode: gnmi.SubscriptionMode_TARGET_DEFINED,
			},
		)
	}
	request := &gnmi.SubscribeRequest{
		Request: &gnmi.SubscribeRequest_Subscribe{
			Subscribe: subList,
		},
	}

	if username != "" {
		ctx = metadata.NewOutgoingContext(ctx,
			metadata.Pairs(
				"username", username,
				"password", password),
		)
	}
	stream, err := client.Subscribe(ctx)
	if err != nil {
		return fmt.Errorf("error from Subscribe: %s", err)
	}
	if err := stream.Send(request); err != nil {
		return fmt.Errorf("error sending SubscribeRequest: %s", err)
	}

	for {
		resp, err := stream.Recv()
		if err != nil {
			return fmt.Errorf("error from Subscribe.Recv: %s", err)
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		case c <- resp:
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2020 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 subtle provides subtle implementations of the DeterministicAEAD
// primitive.
package subtle

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/subtle"
	"errors"
	"fmt"
	"math"

	// Placeholder for internal crypto/cipher allowlist, please ignore.
	// Placeholder for internal crypto/subtle allowlist, please ignore.
)

// AESSIV is an implementation of AES-SIV-CMAC as defined in
// https://tools.ietf.org/html/rfc5297.
//
// AESSIV implements a deterministic encryption with additional data (i.e. the
// DeterministicAEAD interface). Hence the implementation below is restricted
// to one AD component.
//
// Security Note:
//
// Chatterjee, Menezes and Sarkar analyze AES-SIV in Section 5.1 of
// https://www.math.uwaterloo.ca/~ajmeneze/publications/tightness.pdf
//
// Their analysis shows that AES-SIV is susceptible to an attack in
// a multi-user setting. Concretely, if an attacker knows the encryption
// of a message m encrypted and authenticated with k different keys,
// then it is possible  to find one of the MAC keys in time 2^b / k
// where b is the size of the MAC key. A consequence of this attack
// is that 128-bit MAC keys give unsufficient security.
// Since 192-bit AES keys are not supported by tink for voodoo reasons
// and RFC 5297 only supports same size encryption and MAC keys this
// implies that keys must be 64 bytes (2*256 bits) long.
type AESSIV struct {
	K1     []byte
	K2     []byte
	CmacK1 []byte
	CmacK2 []byte
	Cipher cipher.Block
}

const (
	// AESSIVKeySize is the key size in bytes.
	AESSIVKeySize = 64

	intSize = 32 << (^uint(0) >> 63) // 32 or 64
	maxInt  = 1<<(intSize-1) - 1
)

// NewAESSIV returns an AESSIV instance.
func NewAESSIV(key []byte) (*AESSIV, error) {
	if len(key) != AESSIVKeySize {
		return nil, fmt.Errorf("aes_siv: invalid key size %d", len(key))
	}

	k1 := key[:32]
	k2 := key[32:]
	c, err := aes.NewCipher(k1)
	if err != nil {
		return nil, fmt.Errorf("aes_siv: aes.NewCipher(%s) failed, %v", k1, err)
	}

	block := make([]byte, aes.BlockSize)
	c.Encrypt(block, block)
	multiplyByX(block)
	cmacK1 := make([]byte, aes.BlockSize)
	copy(cmacK1, block)
	multiplyByX(block)
	cmacK2 := make([]byte, aes.BlockSize)
	copy(cmacK2, block)

	return &AESSIV{
		K1:     k1,
		K2:     k2,
		CmacK1: cmacK1,
		CmacK2: cmacK2,
		Cipher: c,
	}, nil
}

// multiplyByX multiplies an element in GF(2^128) by its generator.
//
// This function is incorrectly named "doubling" in section 2.3 of RFC 5297.
func multiplyByX(block []byte) {
	carry := int(block[0] >> 7)
	for i := 0; i < aes.BlockSize-1; i++ {
		block[i] = (block[i] << 1) | (block[i+1] >> 7)
	}

	block[aes.BlockSize-1] = (block[aes.BlockSize-1] << 1) ^ byte(subtle.ConstantTimeSelect(carry, 0x87, 0x00))
}

// EncryptDeterministically deterministically encrypts plaintext with
// additionalData as additional authenticated data.
func (asc *AESSIV) EncryptDeterministically(pt, aad []byte) ([]byte, error) {
	if len(pt) > maxInt-aes.BlockSize {
		return nil, fmt.Errorf("aes_siv: plaintext too long")
	}
	siv := make([]byte, aes.BlockSize)
	asc.s2v(pt, aad, siv)

	ct := make([]byte, len(pt)+aes.BlockSize)
	copy(ct[:aes.BlockSize], siv)
	if err := asc.ctrCrypt(siv, pt, ct[aes.BlockSize:]); err != nil {
		return nil, err
	}

	return ct, nil
}

// DecryptDeterministically deterministically decrypts ciphertext with
// additionalData as additional authenticated data.
func (asc *AESSIV) DecryptDeterministically(ct, aad []byte) ([]byte, error) {
	if len(ct) < aes.BlockSize {
		return nil, errors.New("aes_siv: ciphertext is too short")
	}

	pt := make([]byte, len(ct)-aes.BlockSize)
	siv := ct[:aes.BlockSize]
	asc.ctrCrypt(siv, ct[aes.BlockSize:], pt)
	s2v := make([]byte, aes.BlockSize)
	asc.s2v(pt, aad, s2v)

	diff := byte(0)
	for i := 0; i < aes.BlockSize; i++ {
		diff |= siv[i] ^ s2v[i]
	}
	if diff != 0 {
		return nil, errors.New("aes_siv: invalid ciphertext")
	}

	return pt, nil
}

// ctrCrypt encrypts (or decrypts) the bytes in in using an SIV and writes the
// result to out.
func (asc *AESSIV) ctrCrypt(siv, in, out []byte) error {
	// siv might be used outside of ctrCrypt(), so making a copy of it.
	iv := make([]byte, aes.BlockSize)
	copy(iv, siv)
	iv[8] &= 0x7f
	iv[12] &= 0x7f

	c, err := aes.NewCipher(asc.K2)
	if err != nil {
		return fmt.Errorf("aes_siv: aes.NewCipher(%s) failed, %v", asc.K2, err)
	}

	steam := cipher.NewCTR(c, iv)
	steam.XORKeyStream(out, in)
	return nil
}

// s2v is a Pseudo-Random Function (PRF) construction:
// https://tools.ietf.org/html/rfc5297.
func (asc *AESSIV) s2v(msg, aad, siv []byte) {
	block := make([]byte, aes.BlockSize)
	asc.cmac(block, block)
	multiplyByX(block)

	aadMac := make([]byte, aes.BlockSize)
	asc.cmac(aad, aadMac)
	xorBlock(aadMac, block)

	if len(msg) >= aes.BlockSize {
		asc.cmacLong(msg, block, siv)
	} else {
		multiplyByX(block)
		for i := 0; i < len(msg); i++ {
			block[i] ^= msg[i]
		}
		block[len(msg)] ^= 0x80
		asc.cmac(block, siv)
	}
}

// cmacLong computes CMAC(XorEnd(data, last)), where XorEnd xors the bytes in
// last to the last bytes in data.
//
// The size of the data must be at least 16 bytes.
func (asc *AESSIV) cmacLong(data, last, mac []byte) {
	block := make([]byte, aes.BlockSize)
	copy(block, data[:aes.BlockSize])

	idx := aes.BlockSize
	for aes.BlockSize <= len(data)-idx {
		asc.Cipher.Encrypt(block, block)
		xorBlock(data[idx:idx+aes.BlockSize], block)
		idx += aes.BlockSize
	}

	remaining := len(data) - idx
	for i := 0; i < aes.BlockSize-remaining; i++ {
		block[remaining+i] ^= last[i]
	}
	if remaining == 0 {
		xorBlock(asc.CmacK1, block)
	} else {
		asc.Cipher.Encrypt(block, block)
		for i := 0; i < remaining; i++ {
			block[i] ^= last[aes.BlockSize-remaining+i]
			block[i] ^= data[idx+i]
		}
		block[remaining] ^= 0x80
		xorBlock(asc.CmacK2, block)
	}

	asc.Cipher.Encrypt(mac, block)
}

// cmac computes a CMAC of some data.
func (asc *AESSIV) cmac(data, mac []byte) {
	numBs := int(math.Ceil(float64(len(data)) / aes.BlockSize))
	if numBs == 0 {
		numBs = 1
	}
	lastBSize := len(data) - (numBs-1)*aes.BlockSize

	block := make([]byte, aes.BlockSize)
	idx := 0
	for i := 0; i < numBs-1; i++ {
		xorBlock(data[idx:idx+aes.BlockSize], block)
		asc.Cipher.Encrypt(block, block)
		idx += aes.BlockSize
	}
	for j := 0; j < lastBSize; j++ {
		block[j] ^= data[idx+j]
	}

	if lastBSize == aes.BlockSize {
		xorBlock(asc.CmacK1, block)
	} else {
		block[lastBSize] ^= 0x80
		xorBlock(asc.CmacK2, block)
	}

	asc.Cipher.Encrypt(mac, block)
}

// xorBlock sets block[i] = x[i] ^ block[i].
func xorBlock(x, block []byte) {
	for i := 0; i < aes.BlockSize; i++ {
		block[i] ^= x[i]
	}
}
<commit_msg>Rename additional data to associated data in aes_siv.go.<commit_after>// Copyright 2020 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 subtle provides subtle implementations of the DeterministicAEAD
// primitive.
package subtle

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/subtle"
	"errors"
	"fmt"
	"math"

	// Placeholder for internal crypto/cipher allowlist, please ignore.
	// Placeholder for internal crypto/subtle allowlist, please ignore.
)

// AESSIV is an implementation of AES-SIV-CMAC as defined in
// https://tools.ietf.org/html/rfc5297.
//
// AESSIV implements a deterministic encryption with associated data (i.e. the
// DeterministicAEAD interface). Hence the implementation below is restricted
// to one AD component.
//
// Security Note:
//
// Chatterjee, Menezes and Sarkar analyze AES-SIV in Section 5.1 of
// https://www.math.uwaterloo.ca/~ajmeneze/publications/tightness.pdf
//
// Their analysis shows that AES-SIV is susceptible to an attack in
// a multi-user setting. Concretely, if an attacker knows the encryption
// of a message m encrypted and authenticated with k different keys,
// then it is possible  to find one of the MAC keys in time 2^b / k
// where b is the size of the MAC key. A consequence of this attack
// is that 128-bit MAC keys give unsufficient security.
// Since 192-bit AES keys are not supported by tink for voodoo reasons
// and RFC 5297 only supports same size encryption and MAC keys this
// implies that keys must be 64 bytes (2*256 bits) long.
type AESSIV struct {
	K1     []byte
	K2     []byte
	CmacK1 []byte
	CmacK2 []byte
	Cipher cipher.Block
}

const (
	// AESSIVKeySize is the key size in bytes.
	AESSIVKeySize = 64

	intSize = 32 << (^uint(0) >> 63) // 32 or 64
	maxInt  = 1<<(intSize-1) - 1
)

// NewAESSIV returns an AESSIV instance.
func NewAESSIV(key []byte) (*AESSIV, error) {
	if len(key) != AESSIVKeySize {
		return nil, fmt.Errorf("aes_siv: invalid key size %d", len(key))
	}

	k1 := key[:32]
	k2 := key[32:]
	c, err := aes.NewCipher(k1)
	if err != nil {
		return nil, fmt.Errorf("aes_siv: aes.NewCipher(%s) failed, %v", k1, err)
	}

	block := make([]byte, aes.BlockSize)
	c.Encrypt(block, block)
	multiplyByX(block)
	cmacK1 := make([]byte, aes.BlockSize)
	copy(cmacK1, block)
	multiplyByX(block)
	cmacK2 := make([]byte, aes.BlockSize)
	copy(cmacK2, block)

	return &AESSIV{
		K1:     k1,
		K2:     k2,
		CmacK1: cmacK1,
		CmacK2: cmacK2,
		Cipher: c,
	}, nil
}

// multiplyByX multiplies an element in GF(2^128) by its generator.
//
// This function is incorrectly named "doubling" in section 2.3 of RFC 5297.
func multiplyByX(block []byte) {
	carry := int(block[0] >> 7)
	for i := 0; i < aes.BlockSize-1; i++ {
		block[i] = (block[i] << 1) | (block[i+1] >> 7)
	}

	block[aes.BlockSize-1] = (block[aes.BlockSize-1] << 1) ^ byte(subtle.ConstantTimeSelect(carry, 0x87, 0x00))
}

// EncryptDeterministically deterministically encrypts plaintext with associatedData.
func (asc *AESSIV) EncryptDeterministically(plaintext, associatedData []byte) ([]byte, error) {
	if len(plaintext) > maxInt-aes.BlockSize {
		return nil, fmt.Errorf("aes_siv: plaintext too long")
	}
	siv := make([]byte, aes.BlockSize)
	asc.s2v(plaintext, associatedData, siv)

	ct := make([]byte, len(plaintext)+aes.BlockSize)
	copy(ct[:aes.BlockSize], siv)
	if err := asc.ctrCrypt(siv, plaintext, ct[aes.BlockSize:]); err != nil {
		return nil, err
	}

	return ct, nil
}

// DecryptDeterministically deterministically decrypts ciphertext with associatedData.
func (asc *AESSIV) DecryptDeterministically(ciphertext, associatedData []byte) ([]byte, error) {
	if len(ciphertext) < aes.BlockSize {
		return nil, errors.New("aes_siv: ciphertext is too short")
	}

	pt := make([]byte, len(ciphertext)-aes.BlockSize)
	siv := ciphertext[:aes.BlockSize]
	asc.ctrCrypt(siv, ciphertext[aes.BlockSize:], pt)
	s2v := make([]byte, aes.BlockSize)
	asc.s2v(pt, associatedData, s2v)

	diff := byte(0)
	for i := 0; i < aes.BlockSize; i++ {
		diff |= siv[i] ^ s2v[i]
	}
	if diff != 0 {
		return nil, errors.New("aes_siv: invalid ciphertext")
	}

	return pt, nil
}

// ctrCrypt encrypts (or decrypts) the bytes in in using an SIV and writes the
// result to out.
func (asc *AESSIV) ctrCrypt(siv, in, out []byte) error {
	// siv might be used outside of ctrCrypt(), so making a copy of it.
	iv := make([]byte, aes.BlockSize)
	copy(iv, siv)
	iv[8] &= 0x7f
	iv[12] &= 0x7f

	c, err := aes.NewCipher(asc.K2)
	if err != nil {
		return fmt.Errorf("aes_siv: aes.NewCipher(%s) failed, %v", asc.K2, err)
	}

	steam := cipher.NewCTR(c, iv)
	steam.XORKeyStream(out, in)
	return nil
}

// s2v is a Pseudo-Random Function (PRF) construction:
// https://tools.ietf.org/html/rfc5297.
func (asc *AESSIV) s2v(msg, ad, siv []byte) {
	block := make([]byte, aes.BlockSize)
	asc.cmac(block, block)
	multiplyByX(block)

	adMac := make([]byte, aes.BlockSize)
	asc.cmac(ad, adMac)
	xorBlock(adMac, block)

	if len(msg) >= aes.BlockSize {
		asc.cmacLong(msg, block, siv)
	} else {
		multiplyByX(block)
		for i := 0; i < len(msg); i++ {
			block[i] ^= msg[i]
		}
		block[len(msg)] ^= 0x80
		asc.cmac(block, siv)
	}
}

// cmacLong computes CMAC(XorEnd(data, last)), where XorEnd xors the bytes in
// last to the last bytes in data.
//
// The size of the data must be at least 16 bytes.
func (asc *AESSIV) cmacLong(data, last, mac []byte) {
	block := make([]byte, aes.BlockSize)
	copy(block, data[:aes.BlockSize])

	idx := aes.BlockSize
	for aes.BlockSize <= len(data)-idx {
		asc.Cipher.Encrypt(block, block)
		xorBlock(data[idx:idx+aes.BlockSize], block)
		idx += aes.BlockSize
	}

	remaining := len(data) - idx
	for i := 0; i < aes.BlockSize-remaining; i++ {
		block[remaining+i] ^= last[i]
	}
	if remaining == 0 {
		xorBlock(asc.CmacK1, block)
	} else {
		asc.Cipher.Encrypt(block, block)
		for i := 0; i < remaining; i++ {
			block[i] ^= last[aes.BlockSize-remaining+i]
			block[i] ^= data[idx+i]
		}
		block[remaining] ^= 0x80
		xorBlock(asc.CmacK2, block)
	}

	asc.Cipher.Encrypt(mac, block)
}

// cmac computes a CMAC of some data.
func (asc *AESSIV) cmac(data, mac []byte) {
	numBs := int(math.Ceil(float64(len(data)) / aes.BlockSize))
	if numBs == 0 {
		numBs = 1
	}
	lastBSize := len(data) - (numBs-1)*aes.BlockSize

	block := make([]byte, aes.BlockSize)
	idx := 0
	for i := 0; i < numBs-1; i++ {
		xorBlock(data[idx:idx+aes.BlockSize], block)
		asc.Cipher.Encrypt(block, block)
		idx += aes.BlockSize
	}
	for j := 0; j < lastBSize; j++ {
		block[j] ^= data[idx+j]
	}

	if lastBSize == aes.BlockSize {
		xorBlock(asc.CmacK1, block)
	} else {
		block[lastBSize] ^= 0x80
		xorBlock(asc.CmacK2, block)
	}

	asc.Cipher.Encrypt(mac, block)
}

// xorBlock sets block[i] = x[i] ^ block[i].
func xorBlock(x, block []byte) {
	for i := 0; i < aes.BlockSize; i++ {
		block[i] ^= x[i]
	}
}
<|endoftext|>