<commit_before>package telebot

import (
	"encoding/json"
	"fmt"
)

// Option is a shorcut flag type for certain message features
// (so-called options). It means that instead of passing
// fully-fledged SendOptions* to Send(), you can use these
// flags instead.
//
// Supported options are defined as iota-constants.
type Option int

const (
	// NoPreview = SendOptions.DisableWebPagePreview
	NoPreview Option = iota

	// Silent = SendOptions.DisableNotification
	Silent

	// ForceReply = ReplyMarkup.ForceReply
	ForceReply

	// OneTimeKeyboard = ReplyMarkup.OneTimeKeyboard
	OneTimeKeyboard
)

// SendOptions has most complete control over in what way the message
// must be sent, providing an API-complete set of custom properties
// and options.
//
// Despite its power, SendOptions is rather inconvenient to use all
// the way through bot logic, so you might want to consider storing
// and re-using it somewhere or be using Option flags instead.
type SendOptions struct {
	// If the message is a reply, original message.
	ReplyTo *Message

	// See ReplyMarkup struct definition.
	ReplyMarkup *ReplyMarkup

	// For text messages, disables previews for links in this message.
	DisableWebPagePreview bool

	// Sends the message silently. iOS users will not receive a notification, Android users will receive a notification with no sound.
	DisableNotification bool

	// ParseMode controls how client apps render your message.
	ParseMode ParseMode
}

func (og *SendOptions) copy() *SendOptions {
	cp := *og
	if cp.ReplyMarkup != nil {
		cp.ReplyMarkup = cp.ReplyMarkup.copy()
	}

	return &cp
}

// ReplyMarkup controls two convenient options for bot-user communications
// such as reply keyboard and inline "keyboard" (a grid of buttons as a part
// of the message).
type ReplyMarkup struct {
	// InlineKeyboard is a grid of InlineButtons displayed in the message.
	//
	// Note: DO NOT confuse with ReplyKeyboard and other keyboard properties!
	InlineKeyboard [][]InlineButton `json:"inline_keyboard,omitempty"`

	// ReplyKeyboard is a grid, consisting of keyboard buttons.
	//
	// Note: you don't need to set HideCustomKeyboard field to show custom keyboard.
	ReplyKeyboard [][]ReplyButton `json:"keyboard,omitempty"`

	// ForceReply forces Telegram clients to display
	// a reply interface to the user (act as if the user
	// has selected the bot‘s message and tapped "Reply").
	ForceReply bool `json:"force_reply,omitempty"`

	// Requests clients to resize the keyboard vertically for optimal fit
	// (e.g. make the keyboard smaller if there are just two rows of buttons).
	//
	// Defaults to false, in which case the custom keyboard is always of the
	// same height as the app's standard keyboard.
	ResizeReplyKeyboard bool `json:"resize_keyboard,omitempty"`

	// Requests clients to hide the reply keyboard as soon as it's been used.
	//
	// Defaults to false.
	OneTimeKeyboard bool `json:"one_time_keyboard,omitempty"`

	// Requests clients to remove the reply keyboard.
	//
	// Dafaults to false.
	ReplyKeyboardRemove bool `json:"remove_keyboard,omitempty"`

	// Use this param if you want to force reply from
	// specific users only.
	//
	// Targets:
	// 1) Users that are @mentioned in the text of the Message object;
	// 2) If the bot's message is a reply (has SendOptions.ReplyTo),
	//       sender of the original message.
	Selective bool `json:"selective,omitempty"`
}

func (og *ReplyMarkup) copy() *ReplyMarkup {
	cp := *og

	cp.ReplyKeyboard = make([][]ReplyButton, len(og.ReplyKeyboard))
	for i, row := range og.ReplyKeyboard {
		cp.ReplyKeyboard[i] = make([]ReplyButton, len(row))
		copy(cp.ReplyKeyboard[i], row)
	}

	cp.InlineKeyboard = make([][]InlineButton, len(og.InlineKeyboard))
	for i, row := range og.InlineKeyboard {
		cp.InlineKeyboard[i] = make([]InlineButton, len(row))
		copy(cp.InlineKeyboard[i], row)
	}

	return &cp
}

// ReplyButton represents a button displayed in reply-keyboard.
//
// Set either Contact or Location to true in order to request
// sensitive info, such as user's phone number or current location.
// (Available in private chats only.)
type ReplyButton struct {
	Text string `json:"text"`

	Contact  bool     `json:"request_contact,omitempty"`
	Location bool     `json:"request_location,omitempty"`
	Poll     PollType `json:"request_poll,omitempty"`

	// Not used anywhere.
	// Will be removed in future releases.
	Action func(*Message) `json:"-"`
}

// InlineKeyboardMarkup represents an inline keyboard that appears
// right next to the message it belongs to.
type InlineKeyboardMarkup struct {
	// Array of button rows, each represented by
	// an Array of KeyboardButton objects.
	InlineKeyboard [][]InlineButton `json:"inline_keyboard,omitempty"`
}

// MarshalJSON implements json.Marshaler. It allows to pass
// PollType as keyboard's poll type instead of KeyboardButtonPollType object.
func (pt PollType) MarshalJSON() ([]byte, error) {
	var aux = struct {
		Type string `json:"type"`
	}{
		Type: string(pt),
	}
	return json.Marshal(&aux)
}

type row []Btn
func (r *ReplyMarkup) Row(many ...Btn) row {
	return many
}

func (r *ReplyMarkup) Inline(rows ...row) {
	inlineKeys := make([][]InlineButton, 0, len(rows))
	for i, row := range rows {
		keys := make([]InlineButton, 0, len(row))
		for j, btn := range row {
			btn := btn.Inline()
			if btn == nil {
				panic(fmt.Sprintf(
					"telebot: button row %d column %d is not an inline button",
					i, j))
			}
			keys = append(keys, *btn)
		}
		inlineKeys = append(inlineKeys, keys)
	}

	r.InlineKeyboard = inlineKeys
}

func (r *ReplyMarkup) Reply(rows ...row) {
	replyKeys := make([][]ReplyButton, 0, len(rows))
	for i, row := range rows {
		keys := make([]ReplyButton, 0, len(row))
		for j, btn := range row {
			btn := btn.Reply()
			if btn == nil {
				panic(fmt.Sprintf(
					"telebot: button row %d column %d is not a reply button",
					i, j))
			}
			keys = append(keys, *btn)
		}
		replyKeys = append(replyKeys, keys)
	}

	r.ReplyKeyboard = replyKeys
}
func(r *ReplyMarkup) Text(unique,text  string) Btn {
	return Btn{Unique: unique, Text: text}
}

func(r *ReplyMarkup) URL(unique,url string) Btn {
	return Btn{Unique: unique, URL: url}
}

func(r *ReplyMarkup) Query(unique string, query string) Btn {
	return Btn{Unique: unique, InlineQuery: query}
}

func(r *ReplyMarkup) QueryChat(unique string, query string) Btn {
	return Btn{Unique: unique, InlineQueryChat: query}
}

func(r *ReplyMarkup) Login(unique,text string,login *Login) Btn {
	return Btn{Unique: unique, Login: login, Text: text}
}

func(r *ReplyMarkup) Contact(text string) Btn {
	return Btn{Contact:true, Text: text}
}

func(r *ReplyMarkup) Location(text string) Btn {
	return Btn{Location:true, Text: text}
}

func(r *ReplyMarkup) Poll(poll PollType) Btn {
	return Btn{Poll: poll}
}

// Btn is a constructor button, which will later become either a reply, or an inline button.
type Btn struct {
	Unique          string
	Text            string
	URL             string
	Data            string
	InlineQuery     string
	InlineQueryChat string
	Contact         bool
	Location        bool
	Poll            PollType
	Login           *Login
}

func (b Btn) Inline() *InlineButton {
	if b.Unique == "" {
		return nil
	}

	return &InlineButton{
		Unique:          b.Unique,
		Text:            b.Text,
		URL:             b.URL,
		Data:            b.Data,
		InlineQuery:     b.InlineQuery,
		InlineQueryChat: b.InlineQueryChat,
		Login:           nil,
	}
}

func (b Btn) Reply() *ReplyButton {
	if b.Unique != "" {
		return nil
	}

	return &ReplyButton{
		Text:     b.Text,
		Contact:  b.Contact,
		Location: b.Location,
		Poll:     b.Poll,
	}
}
<commit_msg>keyboards: fixed url and query constructors<commit_after>package telebot

import (
	"encoding/json"
	"fmt"
)

// Option is a shorcut flag type for certain message features
// (so-called options). It means that instead of passing
// fully-fledged SendOptions* to Send(), you can use these
// flags instead.
//
// Supported options are defined as iota-constants.
type Option int

const (
	// NoPreview = SendOptions.DisableWebPagePreview
	NoPreview Option = iota

	// Silent = SendOptions.DisableNotification
	Silent

	// ForceReply = ReplyMarkup.ForceReply
	ForceReply

	// OneTimeKeyboard = ReplyMarkup.OneTimeKeyboard
	OneTimeKeyboard
)

// SendOptions has most complete control over in what way the message
// must be sent, providing an API-complete set of custom properties
// and options.
//
// Despite its power, SendOptions is rather inconvenient to use all
// the way through bot logic, so you might want to consider storing
// and re-using it somewhere or be using Option flags instead.
type SendOptions struct {
	// If the message is a reply, original message.
	ReplyTo *Message

	// See ReplyMarkup struct definition.
	ReplyMarkup *ReplyMarkup

	// For text messages, disables previews for links in this message.
	DisableWebPagePreview bool

	// Sends the message silently. iOS users will not receive a notification, Android users will receive a notification with no sound.
	DisableNotification bool

	// ParseMode controls how client apps render your message.
	ParseMode ParseMode
}

func (og *SendOptions) copy() *SendOptions {
	cp := *og
	if cp.ReplyMarkup != nil {
		cp.ReplyMarkup = cp.ReplyMarkup.copy()
	}

	return &cp
}

// ReplyMarkup controls two convenient options for bot-user communications
// such as reply keyboard and inline "keyboard" (a grid of buttons as a part
// of the message).
type ReplyMarkup struct {
	// InlineKeyboard is a grid of InlineButtons displayed in the message.
	//
	// Note: DO NOT confuse with ReplyKeyboard and other keyboard properties!
	InlineKeyboard [][]InlineButton `json:"inline_keyboard,omitempty"`

	// ReplyKeyboard is a grid, consisting of keyboard buttons.
	//
	// Note: you don't need to set HideCustomKeyboard field to show custom keyboard.
	ReplyKeyboard [][]ReplyButton `json:"keyboard,omitempty"`

	// ForceReply forces Telegram clients to display
	// a reply interface to the user (act as if the user
	// has selected the bot‘s message and tapped "Reply").
	ForceReply bool `json:"force_reply,omitempty"`

	// Requests clients to resize the keyboard vertically for optimal fit
	// (e.g. make the keyboard smaller if there are just two rows of buttons).
	//
	// Defaults to false, in which case the custom keyboard is always of the
	// same height as the app's standard keyboard.
	ResizeReplyKeyboard bool `json:"resize_keyboard,omitempty"`

	// Requests clients to hide the reply keyboard as soon as it's been used.
	//
	// Defaults to false.
	OneTimeKeyboard bool `json:"one_time_keyboard,omitempty"`

	// Requests clients to remove the reply keyboard.
	//
	// Dafaults to false.
	ReplyKeyboardRemove bool `json:"remove_keyboard,omitempty"`

	// Use this param if you want to force reply from
	// specific users only.
	//
	// Targets:
	// 1) Users that are @mentioned in the text of the Message object;
	// 2) If the bot's message is a reply (has SendOptions.ReplyTo),
	//       sender of the original message.
	Selective bool `json:"selective,omitempty"`
}

func (og *ReplyMarkup) copy() *ReplyMarkup {
	cp := *og

	cp.ReplyKeyboard = make([][]ReplyButton, len(og.ReplyKeyboard))
	for i, row := range og.ReplyKeyboard {
		cp.ReplyKeyboard[i] = make([]ReplyButton, len(row))
		copy(cp.ReplyKeyboard[i], row)
	}

	cp.InlineKeyboard = make([][]InlineButton, len(og.InlineKeyboard))
	for i, row := range og.InlineKeyboard {
		cp.InlineKeyboard[i] = make([]InlineButton, len(row))
		copy(cp.InlineKeyboard[i], row)
	}

	return &cp
}

// ReplyButton represents a button displayed in reply-keyboard.
//
// Set either Contact or Location to true in order to request
// sensitive info, such as user's phone number or current location.
// (Available in private chats only.)
type ReplyButton struct {
	Text string `json:"text"`

	Contact  bool     `json:"request_contact,omitempty"`
	Location bool     `json:"request_location,omitempty"`
	Poll     PollType `json:"request_poll,omitempty"`

	// Not used anywhere.
	// Will be removed in future releases.
	Action func(*Message) `json:"-"`
}

// InlineKeyboardMarkup represents an inline keyboard that appears
// right next to the message it belongs to.
type InlineKeyboardMarkup struct {
	// Array of button rows, each represented by
	// an Array of KeyboardButton objects.
	InlineKeyboard [][]InlineButton `json:"inline_keyboard,omitempty"`
}

// MarshalJSON implements json.Marshaler. It allows to pass
// PollType as keyboard's poll type instead of KeyboardButtonPollType object.
func (pt PollType) MarshalJSON() ([]byte, error) {
	var aux = struct {
		Type string `json:"type"`
	}{
		Type: string(pt),
	}
	return json.Marshal(&aux)
}

type row []Btn
func (r *ReplyMarkup) Row(many ...Btn) row {
	return many
}

func (r *ReplyMarkup) Inline(rows ...row) {
	inlineKeys := make([][]InlineButton, 0, len(rows))
	for i, row := range rows {
		keys := make([]InlineButton, 0, len(row))
		for j, btn := range row {
			btn := btn.Inline()
			if btn == nil {
				panic(fmt.Sprintf(
					"telebot: button row %d column %d is not an inline button",
					i, j))
			}
			keys = append(keys, *btn)
		}
		inlineKeys = append(inlineKeys, keys)
	}

	r.InlineKeyboard = inlineKeys
}

func (r *ReplyMarkup) Reply(rows ...row) {
	replyKeys := make([][]ReplyButton, 0, len(rows))
	for i, row := range rows {
		keys := make([]ReplyButton, 0, len(row))
		for j, btn := range row {
			btn := btn.Reply()
			if btn == nil {
				panic(fmt.Sprintf(
					"telebot: button row %d column %d is not a reply button",
					i, j))
			}
			keys = append(keys, *btn)
		}
		replyKeys = append(replyKeys, keys)
	}

	r.ReplyKeyboard = replyKeys
}
func(r *ReplyMarkup) Text(unique,text  string) Btn {
	return Btn{Unique: unique, Text: text}
}

func(r *ReplyMarkup) URL(unique,text,url string) Btn {
	return Btn{Unique: unique, Text: text, URL: url}
}

func(r *ReplyMarkup) Query(unique string, text,query string) Btn {
	return Btn{Unique: unique,Text: text, InlineQuery: query}
}

func(r *ReplyMarkup) QueryChat(unique string, text,query string) Btn {
	return Btn{Unique: unique,Text: text, InlineQueryChat: query}
}

func(r *ReplyMarkup) Login(unique,text string,login *Login) Btn {
	return Btn{Unique: unique, Login: login, Text: text}
}

func(r *ReplyMarkup) Contact(text string) Btn {
	return Btn{Contact:true, Text: text}
}

func(r *ReplyMarkup) Location(text string) Btn {
	return Btn{Location:true, Text: text}
}

func(r *ReplyMarkup) Poll(poll PollType) Btn {
	return Btn{Poll: poll}
}

// Btn is a constructor button, which will later become either a reply, or an inline button.
type Btn struct {
	Unique          string
	Text            string
	URL             string
	Data            string
	InlineQuery     string
	InlineQueryChat string
	Contact         bool
	Location        bool
	Poll            PollType
	Login           *Login
}

func (b Btn) Inline() *InlineButton {
	if b.Unique == "" {
		return nil
	}

	return &InlineButton{
		Unique:          b.Unique,
		Text:            b.Text,
		URL:             b.URL,
		Data:            b.Data,
		InlineQuery:     b.InlineQuery,
		InlineQueryChat: b.InlineQueryChat,
		Login:           nil,
	}
}

func (b Btn) Reply() *ReplyButton {
	if b.Unique != "" {
		return nil
	}

	return &ReplyButton{
		Text:     b.Text,
		Contact:  b.Contact,
		Location: b.Location,
		Poll:     b.Poll,
	}
}
<|endoftext|>


<commit_before>package telebot

// SendOptions represents a set of custom options that could
// be appled to messages sent.
type SendOptions struct {
	// If the message is a reply, original message.
	ReplyTo Message

	// See ReplyMarkup struct definition.
	ReplyMarkup ReplyMarkup

	// For text messages, disables previews for links in this message.
	DisableWebPagePreview bool
}

type ReplyMarkup struct {
	// ForceReply forces Telegram clients to display
	// a reply interface to the user (act as if the user
	// has selected the bot‘s message and tapped "Reply").
	ForceReply bool `json:"force_reply,omitempty"`

	// CustomKeyboard is Array of button rows, each represented by an Array of Strings.
	//
	// Note: you don't need to set HideCustomKeyboard field to show custom keyboard.
	CustomKeyboard [][]string `json:"keyboard,omitempty"`
	// Requests clients to resize the keyboard vertically for optimal fit
	// (e.g., make the keyboard smaller if there are just two rows of buttons).
	// Defaults to false, in which case the custom keyboard is always of the
	// same height as the app's standard keyboard.
	ResizeKeyboard bool `json:"resize_keyboar,omitemptyd"`
	// Requests clients to hide the keyboard as soon as it's been used. Defaults to false.
	OneTimeKeyboard bool `json:"one_time_keyboard,omitempty"`

	// Requests clients to hide the custom keyboard.
	//
	// Note: You dont need to set CustomKeyboard field to hide custom keyboard.
	HideCustomKeyboard bool `json:"hide_keyboard,omitempty"`

	// Use this param if you want to force reply from
	// specific users only.
	//
	// Targets:
	// 1) Users that are @mentioned in the text of the Message object;
	// 2) If the bot's message is a reply (has SendOptions.ReplyTo),
	//       sender of the original message.
	Selective bool `json:"selective,omitempty"`
}
<commit_msg>fix typo in json key<commit_after>package telebot

// SendOptions represents a set of custom options that could
// be appled to messages sent.
type SendOptions struct {
	// If the message is a reply, original message.
	ReplyTo Message

	// See ReplyMarkup struct definition.
	ReplyMarkup ReplyMarkup

	// For text messages, disables previews for links in this message.
	DisableWebPagePreview bool
}

type ReplyMarkup struct {
	// ForceReply forces Telegram clients to display
	// a reply interface to the user (act as if the user
	// has selected the bot‘s message and tapped "Reply").
	ForceReply bool `json:"force_reply,omitempty"`

	// CustomKeyboard is Array of button rows, each represented by an Array of Strings.
	//
	// Note: you don't need to set HideCustomKeyboard field to show custom keyboard.
	CustomKeyboard [][]string `json:"keyboard,omitempty"`
	// Requests clients to resize the keyboard vertically for optimal fit
	// (e.g., make the keyboard smaller if there are just two rows of buttons).
	// Defaults to false, in which case the custom keyboard is always of the
	// same height as the app's standard keyboard.
	ResizeKeyboard bool `json:"resize_keyboard,omitemptyd"`
	// Requests clients to hide the keyboard as soon as it's been used. Defaults to false.
	OneTimeKeyboard bool `json:"one_time_keyboard,omitempty"`

	// Requests clients to hide the custom keyboard.
	//
	// Note: You dont need to set CustomKeyboard field to hide custom keyboard.
	HideCustomKeyboard bool `json:"hide_keyboard,omitempty"`

	// Use this param if you want to force reply from
	// specific users only.
	//
	// Targets:
	// 1) Users that are @mentioned in the text of the Message object;
	// 2) If the bot's message is a reply (has SendOptions.ReplyTo),
	//       sender of the original message.
	Selective bool `json:"selective,omitempty"`
}
<|endoftext|>


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

// +build linux,cgo

package lxc

import (
	"os"
)

// AttachOptions type is used for defining various attach options.
type AttachOptions struct {

	// Specify the namespaces to attach to, as OR'ed list of clone flags (syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS ...).
	Namespaces int

	// Specify the architecture which the kernel should appear to be running as to the command executed.
	Arch Personality

	// Cwd specifies the working directory of the command.
	Cwd string

	// UID specifies the user id to run as.
	UID int

	// GID specifies the group id to run as.
	GID int

	// If ClearEnv is true the environment is cleared before running the command.
	ClearEnv bool

	// Env specifies the environment of the process.
	Env []string

	// EnvToKeep specifies the environment of the process when ClearEnv is true.
	EnvToKeep []string

	// StdinFd specifies the fd to read input from.
	StdinFd uintptr

	// StdoutFd specifies the fd to write output to.
	StdoutFd uintptr

	// StderrFd specifies the fd to write error output to.
	StderrFd uintptr
}

// DefaultAttachOptions is a convenient set of options to be used.
var DefaultAttachOptions = AttachOptions{
	Namespaces: -1,
	Arch:       -1,
	Cwd:        "/",
	UID:        -1,
	GID:        -1,
	ClearEnv:   false,
	Env:        nil,
	EnvToKeep:  nil,
	StdinFd:    os.Stdin.Fd(),
	StdoutFd:   os.Stdout.Fd(),
	StderrFd:   os.Stderr.Fd(),
}

// TemplateOptions type is used for defining various template options.
type TemplateOptions struct {

	// Template specifies the name of the template.
	Template string

	// Backend specifies the type of the backend.
	Backend BackendStore

	// Distro specifies the name of the distribution.
	Distro string

	// Release specifies the name/version of the distribution.
	Release string

	// Arch specified the architecture of the container.
	Arch string

	// Variant specifies the variant of the image (default: "default").
	Variant string

	// Image server (default: "images.linuxcontainers.org").
	Server string

	// GPG keyid (default: 0x...).
	KeyID string

	// GPG keyserver to use.
	KeyServer string

	// Disable GPG validation (not recommended).
	DisableGPGValidation bool

	// Flush the local copy (if present).
	FlushCache bool

	// Force the use of the local copy even if expired.
	ForceCache bool

	// ExtraArgs provides a way to specify template specific args.
	ExtraArgs []string
}

// DownloadTemplateOptions is a convenient set of options for "download" template.
var DownloadTemplateOptions = TemplateOptions{
	Template: "download",
	Distro:   "ubuntu",
	Release:  "trusty",
	Arch:     "amd64",
}

// BusyboxTemplateOptions is a convenient set of options for "busybox" template.
var BusyboxTemplateOptions = TemplateOptions{
	Template: "busybox",
}

// UbuntuTemplateOptions is a convenient set of options for "ubuntu" template.
var UbuntuTemplateOptions = TemplateOptions{
	Template: "ubuntu",
}

// ConsoleOptions type is used for defining various console options.
type ConsoleOptions struct {

	// Tty number to attempt to allocate, -1 to allocate the first available tty, or 0 to allocate the console.
	Tty int

	// StdinFd specifies the fd to read input from.
	StdinFd uintptr

	// StdoutFd specifies the fd to write output to.
	StdoutFd uintptr

	// StderrFd specifies the fd to write error output to.
	StderrFd uintptr

	// EscapeCharacter (a means <Ctrl a>, b maens <Ctrl b>).
	EscapeCharacter rune
}

// DefailtConsoleOptions is a convenient set of options to be used.
var DefaultConsoleOptions = ConsoleOptions{
	Tty:             -1,
	StdinFd:         os.Stdin.Fd(),
	StdoutFd:        os.Stdout.Fd(),
	StderrFd:        os.Stderr.Fd(),
	EscapeCharacter: 'a',
}

// CloneOptions type is used for defining various clone options.
type CloneOptions struct {

	// Backend specifies the type of the backend.
	Backend BackendStore

	// lxcpath in which to create the new container. If not set the original container's lxcpath will be used.
	ConfigPath string

	// Do not change the hostname of the container (in the root filesystem).
	KeepName bool

	// Use the same MAC address as the original container, rather than generating a new random one.
	KeepMAC bool

	// Create a snapshot rather than copy.
	Snapshot bool
}

// DefaultCloneOptions is a convenient set of options to be used.
var DefaultCloneOptions = CloneOptions{
	Backend: Directory,
}

type CheckpointOptions struct {
	Directory string
	Stop      bool
	Verbose   bool
}

type RestoreOptions struct {
	Directory string
	Verbose   bool
}
<commit_msg>Add comments to CheckpointOptions/RestoreOptions<commit_after>// Copyright © 2013, 2014, The Go-LXC Authors. All rights reserved.
// Use of this source code is governed by a LGPLv2.1
// license that can be found in the LICENSE file.

// +build linux,cgo

package lxc

import (
	"os"
)

// AttachOptions type is used for defining various attach options.
type AttachOptions struct {

	// Specify the namespaces to attach to, as OR'ed list of clone flags (syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS ...).
	Namespaces int

	// Specify the architecture which the kernel should appear to be running as to the command executed.
	Arch Personality

	// Cwd specifies the working directory of the command.
	Cwd string

	// UID specifies the user id to run as.
	UID int

	// GID specifies the group id to run as.
	GID int

	// If ClearEnv is true the environment is cleared before running the command.
	ClearEnv bool

	// Env specifies the environment of the process.
	Env []string

	// EnvToKeep specifies the environment of the process when ClearEnv is true.
	EnvToKeep []string

	// StdinFd specifies the fd to read input from.
	StdinFd uintptr

	// StdoutFd specifies the fd to write output to.
	StdoutFd uintptr

	// StderrFd specifies the fd to write error output to.
	StderrFd uintptr
}

// DefaultAttachOptions is a convenient set of options to be used.
var DefaultAttachOptions = AttachOptions{
	Namespaces: -1,
	Arch:       -1,
	Cwd:        "/",
	UID:        -1,
	GID:        -1,
	ClearEnv:   false,
	Env:        nil,
	EnvToKeep:  nil,
	StdinFd:    os.Stdin.Fd(),
	StdoutFd:   os.Stdout.Fd(),
	StderrFd:   os.Stderr.Fd(),
}

// TemplateOptions type is used for defining various template options.
type TemplateOptions struct {

	// Template specifies the name of the template.
	Template string

	// Backend specifies the type of the backend.
	Backend BackendStore

	// Distro specifies the name of the distribution.
	Distro string

	// Release specifies the name/version of the distribution.
	Release string

	// Arch specified the architecture of the container.
	Arch string

	// Variant specifies the variant of the image (default: "default").
	Variant string

	// Image server (default: "images.linuxcontainers.org").
	Server string

	// GPG keyid (default: 0x...).
	KeyID string

	// GPG keyserver to use.
	KeyServer string

	// Disable GPG validation (not recommended).
	DisableGPGValidation bool

	// Flush the local copy (if present).
	FlushCache bool

	// Force the use of the local copy even if expired.
	ForceCache bool

	// ExtraArgs provides a way to specify template specific args.
	ExtraArgs []string
}

// DownloadTemplateOptions is a convenient set of options for "download" template.
var DownloadTemplateOptions = TemplateOptions{
	Template: "download",
	Distro:   "ubuntu",
	Release:  "trusty",
	Arch:     "amd64",
}

// BusyboxTemplateOptions is a convenient set of options for "busybox" template.
var BusyboxTemplateOptions = TemplateOptions{
	Template: "busybox",
}

// UbuntuTemplateOptions is a convenient set of options for "ubuntu" template.
var UbuntuTemplateOptions = TemplateOptions{
	Template: "ubuntu",
}

// ConsoleOptions type is used for defining various console options.
type ConsoleOptions struct {

	// Tty number to attempt to allocate, -1 to allocate the first available tty, or 0 to allocate the console.
	Tty int

	// StdinFd specifies the fd to read input from.
	StdinFd uintptr

	// StdoutFd specifies the fd to write output to.
	StdoutFd uintptr

	// StderrFd specifies the fd to write error output to.
	StderrFd uintptr

	// EscapeCharacter (a means <Ctrl a>, b maens <Ctrl b>).
	EscapeCharacter rune
}

// DefailtConsoleOptions is a convenient set of options to be used.
var DefaultConsoleOptions = ConsoleOptions{
	Tty:             -1,
	StdinFd:         os.Stdin.Fd(),
	StdoutFd:        os.Stdout.Fd(),
	StderrFd:        os.Stderr.Fd(),
	EscapeCharacter: 'a',
}

// CloneOptions type is used for defining various clone options.
type CloneOptions struct {

	// Backend specifies the type of the backend.
	Backend BackendStore

	// lxcpath in which to create the new container. If not set the original container's lxcpath will be used.
	ConfigPath string

	// Do not change the hostname of the container (in the root filesystem).
	KeepName bool

	// Use the same MAC address as the original container, rather than generating a new random one.
	KeepMAC bool

	// Create a snapshot rather than copy.
	Snapshot bool
}

// DefaultCloneOptions is a convenient set of options to be used.
var DefaultCloneOptions = CloneOptions{
	Backend: Directory,
}

// CheckpointOptions type is used for defining checkpoint options for CRIU
type CheckpointOptions struct {
	Directory string
	Stop      bool
	Verbose   bool
}

// RestoreOptions type is used for defining restore options for CRIU
type RestoreOptions struct {
	Directory string
	Verbose   bool
}
<|endoftext|>


<commit_before>package cfclient

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/pkg/errors"
)

type ServicePlanVisibilitiesResponse struct {
	Count     int                             `json:"total_results"`
	Pages     int                             `json:"total_pages"`
	NextUrl   string                          `json:"next_url"`
	Resources []ServicePlanVisibilityResource `json:"resources"`
}

type ServicePlanVisibilityResource struct {
	Meta   Meta                  `json:"metadata"`
	Entity ServicePlanVisibility `json:"entity"`
}

type ServicePlanVisibility struct {
	Guid             string `json:"guid"`
	ServicePlanGuid  string `json:"service_plan_guid"`
	OrganizationGuid string `json:"organization_guid"`
	ServicePlanUrl   string `json:"service_plan_url"`
	OrganizationUrl  string `json:"organization_url"`
	c                *Client
}

func (c *Client) ListServicePlanVisibilitiesByQuery(query url.Values) ([]ServicePlanVisibility, error) {
	var servicePlanVisibilities []ServicePlanVisibility
	requestUrl := "/v2/service_plan_visibilities?" + query.Encode()
	for {
		var servicePlanVisibilitiesResp ServicePlanVisibilitiesResponse
		r := c.NewRequest("GET", requestUrl)
		resp, err := c.DoRequest(r)
		if err != nil {
			return nil, errors.Wrap(err, "Error requesting service plan visibilities")
		}
		resBody, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, errors.Wrap(err, "Error reading service plan visibilities request:")
		}

		err = json.Unmarshal(resBody, &servicePlanVisibilitiesResp)
		if err != nil {
			return nil, errors.Wrap(err, "Error unmarshaling service plan visibilities")
		}
		for _, servicePlanVisibility := range servicePlanVisibilitiesResp.Resources {
			servicePlanVisibility.Entity.Guid = servicePlanVisibility.Meta.Guid
			servicePlanVisibility.Entity.c = c
			servicePlanVisibilities = append(servicePlanVisibilities, servicePlanVisibility.Entity)
		}
		requestUrl = servicePlanVisibilitiesResp.NextUrl
		if requestUrl == "" {
			break
		}
	}
	return servicePlanVisibilities, nil
}

func (c *Client) ListServicePlanVisibilities() ([]ServicePlanVisibility, error) {
	return c.ListServicePlanVisibilitiesByQuery(nil)
}

func (c *Client) CreateServicePlanVisibility(servicePlanGuid string, organizationGuid string) (*ServicePlanVisibility, error) {
	req := c.NewRequest("POST", "/v2/service_plan_visibilities")
	req.obj = map[string]interface{}{
		"service_plan_guid": servicePlanGuid,
		"organization_guid": organizationGuid,
	}
	resp, err := c.DoRequest(req)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusCreated {
		return nil, errors.Wrapf(err, "Error creating service plan visibility, response code: %d", resp.StatusCode)
	}
	return respBodyToServicePlanVisibility(resp.Body, c)
}

func respBodyToServicePlanVisibility(body io.ReadCloser, c *Client) (*ServicePlanVisibility, error) {
	bodyRaw, err := ioutil.ReadAll(body)
	if err != nil {
		return nil, err
	}
	servicePlanVisibilityRes := ServicePlanVisibilityResource{}
	err = json.Unmarshal([]byte(bodyRaw), &servicePlanVisibilityRes)
	if err != nil {
		return nil, err
	}
	servicePlanVisibility := servicePlanVisibilityRes.Entity
	servicePlanVisibility.Guid = servicePlanVisibilityRes.Meta.Guid
	servicePlanVisibility.c = c
	return &servicePlanVisibility, nil
}
<commit_msg>Return value instead of pointer (#113)<commit_after>package cfclient

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/pkg/errors"
)

type ServicePlanVisibilitiesResponse struct {
	Count     int                             `json:"total_results"`
	Pages     int                             `json:"total_pages"`
	NextUrl   string                          `json:"next_url"`
	Resources []ServicePlanVisibilityResource `json:"resources"`
}

type ServicePlanVisibilityResource struct {
	Meta   Meta                  `json:"metadata"`
	Entity ServicePlanVisibility `json:"entity"`
}

type ServicePlanVisibility struct {
	Guid             string `json:"guid"`
	ServicePlanGuid  string `json:"service_plan_guid"`
	OrganizationGuid string `json:"organization_guid"`
	ServicePlanUrl   string `json:"service_plan_url"`
	OrganizationUrl  string `json:"organization_url"`
	c                *Client
}

func (c *Client) ListServicePlanVisibilitiesByQuery(query url.Values) ([]ServicePlanVisibility, error) {
	var servicePlanVisibilities []ServicePlanVisibility
	requestUrl := "/v2/service_plan_visibilities?" + query.Encode()
	for {
		var servicePlanVisibilitiesResp ServicePlanVisibilitiesResponse
		r := c.NewRequest("GET", requestUrl)
		resp, err := c.DoRequest(r)
		if err != nil {
			return nil, errors.Wrap(err, "Error requesting service plan visibilities")
		}
		resBody, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, errors.Wrap(err, "Error reading service plan visibilities request:")
		}

		err = json.Unmarshal(resBody, &servicePlanVisibilitiesResp)
		if err != nil {
			return nil, errors.Wrap(err, "Error unmarshaling service plan visibilities")
		}
		for _, servicePlanVisibility := range servicePlanVisibilitiesResp.Resources {
			servicePlanVisibility.Entity.Guid = servicePlanVisibility.Meta.Guid
			servicePlanVisibility.Entity.c = c
			servicePlanVisibilities = append(servicePlanVisibilities, servicePlanVisibility.Entity)
		}
		requestUrl = servicePlanVisibilitiesResp.NextUrl
		if requestUrl == "" {
			break
		}
	}
	return servicePlanVisibilities, nil
}

func (c *Client) ListServicePlanVisibilities() ([]ServicePlanVisibility, error) {
	return c.ListServicePlanVisibilitiesByQuery(nil)
}

func (c *Client) CreateServicePlanVisibility(servicePlanGuid string, organizationGuid string) (ServicePlanVisibility, error) {
	req := c.NewRequest("POST", "/v2/service_plan_visibilities")
	req.obj = map[string]interface{}{
		"service_plan_guid": servicePlanGuid,
		"organization_guid": organizationGuid,
	}
	resp, err := c.DoRequest(req)
	if err != nil {
		return ServicePlanVisibility{}, err
	}
	if resp.StatusCode != http.StatusCreated {
		return ServicePlanVisibility{}, errors.Wrapf(err, "Error creating service plan visibility, response code: %d", resp.StatusCode)
	}
	return respBodyToServicePlanVisibility(resp.Body, c)
}

func respBodyToServicePlanVisibility(body io.ReadCloser, c *Client) (ServicePlanVisibility, error) {
	bodyRaw, err := ioutil.ReadAll(body)
	if err != nil {
		return ServicePlanVisibility{}, err
	}
	servicePlanVisibilityRes := ServicePlanVisibilityResource{}
	err = json.Unmarshal([]byte(bodyRaw), &servicePlanVisibilityRes)
	if err != nil {
		return ServicePlanVisibility{}, err
	}
	servicePlanVisibility := servicePlanVisibilityRes.Entity
	servicePlanVisibility.Guid = servicePlanVisibilityRes.Meta.Guid
	servicePlanVisibility.c = c
	return servicePlanVisibility, nil
}
<|endoftext|>


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

package envoye2e

import (
	env "istio.io/proxy/test/envoye2e/env"
)

var ProxyE2ETests *env.TestInventory

func init() {
	// TODO(bianpengyuan): automatically generate this.
	ProxyE2ETests = &env.TestInventory{
		Tests: []string{
			"TestBasicFlow",
			"TestBasicHTTP",
			"TestBasicHTTPwithTLS",
			"TestBasicHTTPGateway",
			"TestBasicTCPFlow",
			"TestHTTPExchange",
			"TestStackdriverAccessLog/StackdriverAndAccessLogPlugin",
			"TestStackdriverAccessLog/RequestGetsLoggedAgain",
			"TestStackdriverAccessLog/AllErrorRequestsGetsLogged",
			"TestStackdriverAccessLog/AllClientErrorRequestsGetsLoggedOnNoMxAndError",
			"TestStackdriverAccessLog/NoClientRequestsGetsLoggedOnErrorConfigAndAllSuccessRequests",
			"TestStackdriverPayload",
			"TestStackdriverPayloadGateway",
			"TestStackdriverPayloadWithTLS",
			"TestStackdriverReload",
			"TestStackdriverVMReload",
			"TestStackdriverParallel",
			"TestStackdriverGCEInstances",
			"TestStackdriverTCPMetadataExchange/BaseCase",
			"TestStackdriverTCPMetadataExchange/NoAlpn",
			"TestStackdriverAttributeGen",
			"TestStackdriverCustomAccessLog",
			"TestStackdriverRbacAccessDenied",
			"TestStackdriverRbacTCPDryRun",
			"TestStackdriverMetricExpiry",
			"TestStatsPayload/Default/envoy.wasm.runtime.null",
			"TestStatsPayload/Customized/envoy.wasm.runtime.null",
			"TestStatsPayload/UseHostHeader/envoy.wasm.runtime.null",
			"TestStatsPayload/DisableHostHeader/envoy.wasm.runtime.null",
			"TestStatsPayload/Default/envoy.wasm.runtime.v8",
			"TestStatsPayload/Customized/envoy.wasm.runtime.v8",
			"TestStatsPayload/UseHostHeader/envoy.wasm.runtime.v8",
			"TestStatsPayload/DisableHostHeader/envoy.wasm.runtime.v8",
			"TestStatsEndpointLabels",
			"TestStatsParallel",
			"TestStatsGrpc",
			"TestTCPMetadataExchange",
			"TestStackdriverAuditLog",
			"TestTCPMetadataExchangeNoAlpn",
			"TestAttributeGen",
			"TestStats403Failure/envoy.wasm.runtime.null",
			"TestStats403Failure/envoy.wasm.runtime.v8",
			"TestStats403Failure/envoy.wasm.runtime.v8#01",
			"TestStatsECDS/envoy.wasm.runtime.null",
			"TestStatsECDS/envoy.wasm.runtime.v8",
			"TestStatsECDS/envoy.wasm.runtime.v8#01",
			"TestHTTPLocalRatelimit",
		},
	}
}
<commit_msg>Fix rbac dry run integration test flake. (#3258)<commit_after>// Copyright 2020 Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package envoye2e

import (
	env "istio.io/proxy/test/envoye2e/env"
)

var ProxyE2ETests *env.TestInventory

func init() {
	// TODO(bianpengyuan): automatically generate this.
	ProxyE2ETests = &env.TestInventory{
		Tests: []string{
			"TestBasicFlow",
			"TestBasicHTTP",
			"TestBasicHTTPwithTLS",
			"TestBasicHTTPGateway",
			"TestBasicTCPFlow",
			"TestHTTPExchange",
			"TestStackdriverAccessLog/StackdriverAndAccessLogPlugin",
			"TestStackdriverAccessLog/RequestGetsLoggedAgain",
			"TestStackdriverAccessLog/AllErrorRequestsGetsLogged",
			"TestStackdriverAccessLog/AllClientErrorRequestsGetsLoggedOnNoMxAndError",
			"TestStackdriverAccessLog/NoClientRequestsGetsLoggedOnErrorConfigAndAllSuccessRequests",
			"TestStackdriverPayload",
			"TestStackdriverPayloadGateway",
			"TestStackdriverPayloadWithTLS",
			"TestStackdriverReload",
			"TestStackdriverVMReload",
			"TestStackdriverParallel",
			"TestStackdriverGCEInstances",
			"TestStackdriverTCPMetadataExchange/BaseCase",
			"TestStackdriverTCPMetadataExchange/NoAlpn",
			"TestStackdriverAttributeGen",
			"TestStackdriverCustomAccessLog",
			"TestStackdriverRbacAccessDenied",
			"TestStackdriverRbacTCPDryRun",
			"TestStackdriverMetricExpiry",
			"TestStatsPayload/Default/envoy.wasm.runtime.null",
			"TestStatsPayload/Customized/envoy.wasm.runtime.null",
			"TestStatsPayload/UseHostHeader/envoy.wasm.runtime.null",
			"TestStatsPayload/DisableHostHeader/envoy.wasm.runtime.null",
			"TestStatsPayload/Default/envoy.wasm.runtime.v8",
			"TestStatsPayload/Customized/envoy.wasm.runtime.v8",
			"TestStatsPayload/UseHostHeader/envoy.wasm.runtime.v8",
			"TestStatsPayload/DisableHostHeader/envoy.wasm.runtime.v8",
			"TestStatsEndpointLabels",
			"TestStatsParallel",
			"TestStatsGrpc",
			"TestTCPMetadataExchange",
			"TestStackdriverAuditLog",
			"TestTCPMetadataExchangeNoAlpn",
			"TestAttributeGen",
			"TestStats403Failure/envoy.wasm.runtime.null",
			"TestStats403Failure/envoy.wasm.runtime.v8",
			"TestStats403Failure/envoy.wasm.runtime.v8#01",
			"TestStatsECDS/envoy.wasm.runtime.null",
			"TestStatsECDS/envoy.wasm.runtime.v8",
			"TestStatsECDS/envoy.wasm.runtime.v8#01",
			"TestHTTPLocalRatelimit",
			"TestStackdriverRbacTCPDryRun/BaseCase",
			"TestStackdriverRbacTCPDryRun/NoAlpn",
		},
	}
}
<|endoftext|>


<commit_before>package src

import (
	"log"
	"strings"

	"github.com/coreos/go-etcd/etcd"
)

// Env represents all the necessary data the core needs to run
type Env struct {
	// Etcd address
	Etcd *string
	// Directory inside etcd that contains the configuration
	EtcdDir *string
	// Structure that holds the configuration data in memory
	Data map[string]interface{}
	// An instance of a renderer
	Renderer Renderer
	// An instance of a reloader
	Reloader Reloader
}

// Cycles the rails environemnt, by rendering a new configuration
// file and reloading the Rails processes. Uses the existing renderer
// and reloader instances.
func (env *Env) Cycle() {
	log.Printf("[ENV] Rendering and reloading...")

	env.Renderer.Render(*env)
	env.Reloader.Reload()
}

// Taking a etcd node and a prefix, updates the in memory data.
// If the etcd node represents a nested directory, this function calls recursively
// with the new prefix, trying to create a tree structure in memory.
func (env *Env) BuildData(node etcd.Node, prefix string, data map[string]interface{}) {
	for i := range node.Nodes {
		node := node.Nodes[i]
		key := env.NakedKey(node.Key, prefix)

		if node.Dir {
			data[key] = make(map[string]interface{})
			env.BuildData(node, prefix+"/"+key, data[key].(map[string]interface{}))
		} else {
			data[key] = node.Value
		}
	}
}

// Updates the data from an etcd watch update. Takes into consideration the type of action
// (set or delete) and navigates through the parts until if finds the correct node to update.
func (env *Env) UpdateData(parts []string, value string, action string, data map[string]interface{}) {
	head := parts[0]
	tail := parts[1:]

	if len(tail) == 0 {
		if action == "set" {
			data[head] = value
		}
		if action == "delete" {
			delete(data, head)
		}
	} else {
		if _, ok := data[head]; !ok {
			newData := make(map[string]interface{})
			data[head] = newData
		}
		env.UpdateData(tail, value, action, data[head].(map[string]interface{}))
	}
}

// Removes the prefix from a key, including trailing slashes
func (env *Env) NakedKey(key string, prefix string) string {
	key = strings.Replace(key, prefix, "", -1)
	return strings.Replace(key, "/", "", 1)
}
<commit_msg>Fix node reference in env.go<commit_after>package src

import (
	"log"
	"strings"

	"github.com/coreos/go-etcd/etcd"
)

// Env represents all the necessary data the core needs to run
type Env struct {
	// Etcd address
	Etcd *string
	// Directory inside etcd that contains the configuration
	EtcdDir *string
	// Structure that holds the configuration data in memory
	Data map[string]interface{}
	// An instance of a renderer
	Renderer Renderer
	// An instance of a reloader
	Reloader Reloader
}

// Cycles the rails environemnt, by rendering a new configuration
// file and reloading the Rails processes. Uses the existing renderer
// and reloader instances.
func (env *Env) Cycle() {
	log.Printf("[ENV] Rendering and reloading...")

	env.Renderer.Render(*env)
	env.Reloader.Reload()
}

// Taking a etcd node and a prefix, updates the in memory data.
// If the etcd node represents a nested directory, this function calls recursively
// with the new prefix, trying to create a tree structure in memory.
func (env *Env) BuildData(node etcd.Node, prefix string, data map[string]interface{}) {
	for i := range node.Nodes {
		node := node.Nodes[i]
		key := env.NakedKey(node.Key, prefix)

		if node.Dir {
			data[key] = make(map[string]interface{})
			env.BuildData(*node, prefix+"/"+key, data[key].(map[string]interface{}))
		} else {
			data[key] = node.Value
		}
	}
}

// Updates the data from an etcd watch update. Takes into consideration the type of action
// (set or delete) and navigates through the parts until if finds the correct node to update.
func (env *Env) UpdateData(parts []string, value string, action string, data map[string]interface{}) {
	head := parts[0]
	tail := parts[1:]

	if len(tail) == 0 {
		if action == "set" {
			data[head] = value
		}
		if action == "delete" {
			delete(data, head)
		}
	} else {
		if _, ok := data[head]; !ok {
			newData := make(map[string]interface{})
			data[head] = newData
		}
		env.UpdateData(tail, value, action, data[head].(map[string]interface{}))
	}
}

// Removes the prefix from a key, including trailing slashes
func (env *Env) NakedKey(key string, prefix string) string {
	key = strings.Replace(key, prefix, "", -1)
	return strings.Replace(key, "/", "", 1)
}
<|endoftext|>


<commit_before>// Copyright 2011, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
/*
 *  Filename:    repository.go
 *  Package:     main
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     Sun Jul  3 19:10:10 PDT 2011
 *  Description:
 */
import (
    "os"
    "exec"
)

type RepoType int
const(
    NilRepoType RepoType = iota
    GitType
    MercurialType
    // ...
)

type Repository interface {
    Initialize(commit bool) os.Error // Initialize the working directory as a new repository.
    Add(paths...string)     os.Error // Add a set of paths to the repository.
    Commit(message string)  os.Error // Commit changes with a given message.
    //IsClean()               bool     // Returns true if there is nothing to commit on the working branch.
}

type GitRepository struct {
}
func (git GitRepository) Initialize(add, commit bool) os.Error {
    var (
        initcmd = exec.Command("git", "init")
    )
    errInit := initcmd.Run()
    if errInit != nil {
        return errInit
    }
    if add {
        errAdd := git.Add(".")
        if errAdd != nil {
            return errAdd
        }
    }
    if commit {
        errCommit := git.Commit("Empty project created by gonew")
        if errCommit != nil {
            return errCommit
        }
    }
    return nil
}

func (git GitRepository) Add(paths...string) os.Error {
    var cmdslice = make([]string,len(paths)+1)
    cmdslice[0] = "add"
    copy(cmdslice[1:], paths)
    var (
        addcmd = exec.Command("git", cmdslice...)
        errAdd = addcmd.Run()
    )
    return errAdd
}

func (git GitRepository) Commit(message string) os.Error {
    var (
        commitcmd = exec.Command("git", "commit",
            "-a", "-m", "Empty project generated by gonew.")
        errCommit = commitcmd.Run()
    )
    return errCommit
}

type MercurialRepository struct {
}
func (hg MercurialRepository) Initialize(add, commit bool) os.Error {
    var (
        initcmd = exec.Command("hg", "init")
    )
    errInit := initcmd.Run()
    if errInit != nil {
        return errInit
    }
    if add {
        errAdd := hg.Add()
        if errAdd != nil {
            return errAdd
        }
    }
    if commit {
        errCommit := hg.Commit("Empty project created by gonew")
        if errCommit != nil {
            return errCommit
        }
    }
    return nil
}
func (hg MercurialRepository) Add(paths...string) os.Error {
    var cmdslice = make([]string,len(paths)+1)
    cmdslice[0] = "add"
    copy(cmdslice[1:], paths)
    var (
        addcmd = exec.Command("hg", cmdslice...)
        errAdd = addcmd.Run()
    )
    return errAdd
}

func (hg MercurialRepository) Commit(message string) os.Error {
    var (
        commitcmd = exec.Command("hg", "commit",
            "-m", "Empty project generated by gonew.")
        errCommit = commitcmd.Run()
    )
    return errCommit
}
<commit_msg>Add RepoType.String() method. Add interface method Repository.Type().<commit_after>// Copyright 2011, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
/*
 *  Filename:    repository.go
 *  Package:     main
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     Sun Jul  3 19:10:10 PDT 2011
 *  Description:
 */
import (
    "os"
    "exec"
)

type RepoType int

const (
    NilRepoType RepoType = iota
    GitType
    MercurialType
    // ...
)

var repotypestrings = []string{
    NilRepoType:   "Nil",
    GitType:       "Git",
    MercurialType: "Mercurial",
}

func (rt RepoType) String() string {
    return repotypestrings[rt]
}

type Repository interface {
    Type() RepoType
    Initialize(commit bool) os.Error // Initialize the working directory as a new repository.
    Add(paths ...string) os.Error    // Add a set of paths to the repository.
    Commit(message string) os.Error  // Commit changes with a given message.
    //IsClean()               bool   // Returns true if there is nothing to commit on the working branch.
}

type GitRepository struct{}

func (git GitRepository) Type() RepoType { return GitType }

func (git GitRepository) Initialize(add, commit bool) os.Error {
    var (
        initcmd = exec.Command("git", "init")
    )
    errInit := initcmd.Run()
    if errInit != nil {
        return errInit
    }
    if add {
        errAdd := git.Add(".")
        if errAdd != nil {
            return errAdd
        }
    }
    if commit {
        errCommit := git.Commit("Empty project created by gonew")
        if errCommit != nil {
            return errCommit
        }
    }
    return nil
}

func (git GitRepository) Add(paths ...string) os.Error {
    var cmdslice = make([]string, len(paths)+1)
    cmdslice[0] = "add"
    copy(cmdslice[1:], paths)
    var (
        addcmd = exec.Command("git", cmdslice...)
        errAdd = addcmd.Run()
    )
    return errAdd
}

func (git GitRepository) Commit(message string) os.Error {
    var (
        commitcmd = exec.Command("git", "commit",
            "-a", "-m", "Empty project generated by gonew.")
        errCommit = commitcmd.Run()
    )
    return errCommit
}

type MercurialRepository struct{}

func (hg MercurialRepository) Type() RepoType { return MercurialType }

func (hg MercurialRepository) Initialize(add, commit bool) os.Error {
    var (
        initcmd = exec.Command("hg", "init")
    )
    errInit := initcmd.Run()
    if errInit != nil {
        return errInit
    }
    if add {
        errAdd := hg.Add()
        if errAdd != nil {
            return errAdd
        }
    }
    if commit {
        errCommit := hg.Commit("Empty project created by gonew")
        if errCommit != nil {
            return errCommit
        }
    }
    return nil
}
func (hg MercurialRepository) Add(paths ...string) os.Error {
    var cmdslice = make([]string, len(paths)+1)
    cmdslice[0] = "add"
    copy(cmdslice[1:], paths)
    var (
        addcmd = exec.Command("hg", cmdslice...)
        errAdd = addcmd.Run()
    )
    return errAdd
}

func (hg MercurialRepository) Commit(message string) os.Error {
    var (
        commitcmd = exec.Command("hg", "commit",
            "-m", "Empty project generated by gonew.")
        errCommit = commitcmd.Run()
    )
    return errCommit
}
<|endoftext|>


<commit_before>package torrent

import (
	"container/heap"
	"context"
	"encoding/gob"
	"math/rand"
	"reflect"
	"runtime/pprof"
	"time"
	"unsafe"

	"github.com/RoaringBitmap/roaring"
	"github.com/anacrolix/log"
	"github.com/anacrolix/multiless"

	request_strategy "github.com/anacrolix/torrent/request-strategy"
)

func (cl *Client) tickleRequester() {
	cl.updateRequests.Broadcast()
}

func (cl *Client) getRequestStrategyInput() request_strategy.Input {
	ts := make([]request_strategy.Torrent, 0, len(cl.torrents))
	for _, t := range cl.torrents {
		if !t.haveInfo() {
			// This would be removed if metadata is handled here. We have to guard against not
			// knowing the piece size. If we have no info, we have no pieces too, so the end result
			// is the same.
			continue
		}
		rst := request_strategy.Torrent{
			InfoHash:       t.infoHash,
			ChunksPerPiece: t.chunksPerRegularPiece(),
		}
		if t.storage != nil {
			rst.Capacity = t.storage.Capacity
		}
		rst.Pieces = make([]request_strategy.Piece, 0, len(t.pieces))
		for i := range t.pieces {
			p := &t.pieces[i]
			rst.Pieces = append(rst.Pieces, request_strategy.Piece{
				Request:           !t.ignorePieceForRequests(i),
				Priority:          p.purePriority(),
				Partial:           t.piecePartiallyDownloaded(i),
				Availability:      p.availability,
				Length:            int64(p.length()),
				NumPendingChunks:  int(t.pieceNumPendingChunks(i)),
				IterPendingChunks: &p.undirtiedChunksIter,
			})
		}
		t.iterPeers(func(p *Peer) {
			if p.closed.IsSet() {
				return
			}
			if p.piecesReceivedSinceLastRequestUpdate > p.maxPiecesReceivedBetweenRequestUpdates {
				p.maxPiecesReceivedBetweenRequestUpdates = p.piecesReceivedSinceLastRequestUpdate
			}
			p.piecesReceivedSinceLastRequestUpdate = 0
			rst.Peers = append(rst.Peers, request_strategy.Peer{
				Pieces:           *p.newPeerPieces(),
				MaxRequests:      p.nominalMaxRequests(),
				ExistingRequests: p.actualRequestState.Requests,
				Choking:          p.peerChoking,
				PieceAllowedFast: p.peerAllowedFast,
				DownloadRate:     p.downloadRate(),
				Age:              time.Since(p.completedHandshake),
				Id: peerId{
					Peer: p,
					ptr:  uintptr(unsafe.Pointer(p)),
				},
			})
		})
		ts = append(ts, rst)
	}
	return request_strategy.Input{
		Torrents:           ts,
		MaxUnverifiedBytes: cl.config.MaxUnverifiedBytes,
	}
}

func init() {
	gob.Register(peerId{})
}

type peerId struct {
	*Peer
	ptr uintptr
}

func (p peerId) Uintptr() uintptr {
	return p.ptr
}

func (p peerId) GobEncode() (b []byte, _ error) {
	*(*reflect.SliceHeader)(unsafe.Pointer(&b)) = reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(&p.ptr)),
		Len:  int(unsafe.Sizeof(p.ptr)),
		Cap:  int(unsafe.Sizeof(p.ptr)),
	}
	return
}

func (p *peerId) GobDecode(b []byte) error {
	if uintptr(len(b)) != unsafe.Sizeof(p.ptr) {
		panic(len(b))
	}
	ptr := unsafe.Pointer(&b[0])
	p.ptr = *(*uintptr)(ptr)
	log.Printf("%p", ptr)
	dst := reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(&p.Peer)),
		Len:  int(unsafe.Sizeof(p.Peer)),
		Cap:  int(unsafe.Sizeof(p.Peer)),
	}
	copy(*(*[]byte)(unsafe.Pointer(&dst)), b)
	return nil
}

type RequestIndex = request_strategy.RequestIndex
type chunkIndexType = request_strategy.ChunkIndex

type peerRequests struct {
	requestIndexes       []RequestIndex
	peer                 *Peer
	torrentStrategyInput request_strategy.Torrent
}

func (p *peerRequests) Len() int {
	return len(p.requestIndexes)
}

func (p *peerRequests) Less(i, j int) bool {
	leftRequest := p.requestIndexes[i]
	rightRequest := p.requestIndexes[j]
	t := p.peer.t
	leftPieceIndex := leftRequest / p.torrentStrategyInput.ChunksPerPiece
	rightPieceIndex := rightRequest / p.torrentStrategyInput.ChunksPerPiece
	leftCurrent := p.peer.actualRequestState.Requests.Contains(leftRequest)
	rightCurrent := p.peer.actualRequestState.Requests.Contains(rightRequest)
	pending := func(index RequestIndex, current bool) int {
		ret := t.pendingRequests.Get(index)
		if current {
			ret--
		}
		// See https://github.com/anacrolix/torrent/issues/679 for possible issues. This should be
		// resolved.
		if ret < 0 {
			panic(ret)
		}
		return ret
	}
	ml := multiless.New()
	// Push requests that can't be served right now to the end. But we don't throw them away unless
	// there's a better alternative. This is for when we're using the fast extension and get choked
	// but our requests could still be good when we get unchoked.
	if p.peer.peerChoking {
		ml = ml.Bool(
			!p.peer.peerAllowedFast.Contains(leftPieceIndex),
			!p.peer.peerAllowedFast.Contains(rightPieceIndex),
		)
	}
	ml = ml.Int(
		pending(leftRequest, leftCurrent),
		pending(rightRequest, rightCurrent))
	ml = ml.Bool(!leftCurrent, !rightCurrent)
	ml = ml.Int(
		-int(p.torrentStrategyInput.Pieces[leftPieceIndex].Priority),
		-int(p.torrentStrategyInput.Pieces[rightPieceIndex].Priority),
	)
	ml = ml.Int(
		int(p.torrentStrategyInput.Pieces[leftPieceIndex].Availability),
		int(p.torrentStrategyInput.Pieces[rightPieceIndex].Availability))
	ml = ml.Uint32(leftPieceIndex, rightPieceIndex)
	ml = ml.Uint32(leftRequest, rightRequest)
	return ml.MustLess()
}

func (p *peerRequests) Swap(i, j int) {
	p.requestIndexes[i], p.requestIndexes[j] = p.requestIndexes[j], p.requestIndexes[i]
}

func (p *peerRequests) Push(x interface{}) {
	p.requestIndexes = append(p.requestIndexes, x.(RequestIndex))
}

func (p *peerRequests) Pop() interface{} {
	last := len(p.requestIndexes) - 1
	x := p.requestIndexes[last]
	p.requestIndexes = p.requestIndexes[:last]
	return x
}

func (p *Peer) getDesiredRequestState() (desired requestState) {
	input := p.t.cl.getRequestStrategyInput()
	requestHeap := peerRequests{
		peer: p,
	}
	for _, t := range input.Torrents {
		if t.InfoHash == p.t.infoHash {
			requestHeap.torrentStrategyInput = t
			break
		}
	}
	request_strategy.GetRequestablePieces(
		input,
		func(t *request_strategy.Torrent, rsp *request_strategy.Piece, pieceIndex int) {
			if t.InfoHash != p.t.infoHash {
				return
			}
			if !p.peerHasPiece(pieceIndex) {
				return
			}
			allowedFast := p.peerAllowedFast.ContainsInt(pieceIndex)
			rsp.IterPendingChunks.Iter(func(ci request_strategy.ChunkIndex) {
				if !allowedFast {
					// We must signal interest to request this
					desired.Interested = true
					// We can make or will allow sustaining a request here if we're not choked, or
					// have made the request previously (presumably while unchoked), and haven't had
					// the peer respond yet (and the request was retained because we are using the
					// fast extension).
					if p.peerChoking && !p.actualRequestState.Requests.Contains(ci) {
						// We can't request this right now.
						return
					}
				}
				requestHeap.requestIndexes = append(
					requestHeap.requestIndexes,
					p.t.pieceRequestIndexOffset(pieceIndex)+ci)
			})
		},
	)
	p.t.assertPendingRequests()
	heap.Init(&requestHeap)
	for requestHeap.Len() != 0 && desired.Requests.GetCardinality() < uint64(p.nominalMaxRequests()) {
		requestIndex := heap.Pop(&requestHeap).(RequestIndex)
		desired.Requests.Add(requestIndex)
	}
	return
}

func (p *Peer) maybeUpdateActualRequestState() bool {
	if p.needRequestUpdate == "" {
		return true
	}
	var more bool
	pprof.Do(
		context.Background(),
		pprof.Labels("update request", p.needRequestUpdate),
		func(_ context.Context) {
			next := p.getDesiredRequestState()
			more = p.applyRequestState(next)
		},
	)
	return more
}

// Transmit/action the request state to the peer.
func (p *Peer) applyRequestState(next requestState) bool {
	current := &p.actualRequestState
	if !p.setInterested(next.Interested) {
		return false
	}
	more := true
	cancel := roaring.AndNot(&current.Requests, &next.Requests)
	cancel.Iterate(func(req uint32) bool {
		more = p.cancel(req)
		return more
	})
	if !more {
		return false
	}
	// We randomize the order in which requests are issued, to reduce the overlap with requests to
	// other peers. Note that although it really depends on what order the peer services the
	// requests, if we are only able to issue some requests before buffering, or the peer starts
	// handling our requests before they've all arrived, then this randomization should reduce
	// overlap. Note however that if we received the desired requests in priority order, then
	// randomizing would throw away that benefit.
	for _, x := range rand.Perm(int(next.Requests.GetCardinality())) {
		req, err := next.Requests.Select(uint32(x))
		if err != nil {
			panic(err)
		}
		if p.cancelledRequests.Contains(req) {
			// Waiting for a reject or piece message, which will suitably trigger us to update our
			// requests, so we can skip this one with no additional consideration.
			continue
		}
		// The cardinality of our desired requests shouldn't exceed the max requests since it's used
		// in the calculation of the requests. However if we cancelled requests and they haven't
		// been rejected or serviced yet with the fast extension enabled, we can end up with more
		// extra outstanding requests. We could subtract the number of outstanding cancels from the
		// next request cardinality, but peers might not like that.
		if maxRequests(current.Requests.GetCardinality()) >= p.nominalMaxRequests() {
			//log.Printf("not assigning all requests [desired=%v, cancelled=%v, current=%v, max=%v]",
			//	next.Requests.GetCardinality(),
			//	p.cancelledRequests.GetCardinality(),
			//	current.Requests.GetCardinality(),
			//	p.nominalMaxRequests(),
			//)
			break
		}
		more, err = p.request(req)
		if err != nil {
			panic(err)
		}
		if !more {
			break
		}
	}
	p.updateRequestsTimer.Stop()
	if more {
		p.needRequestUpdate = ""
		if !current.Requests.IsEmpty() {
			p.updateRequestsTimer.Reset(3 * time.Second)
		}
	}
	return more
}
<commit_msg>Fix iter pending chunk request offsets<commit_after>package torrent

import (
	"container/heap"
	"context"
	"encoding/gob"
	"math/rand"
	"reflect"
	"runtime/pprof"
	"time"
	"unsafe"

	"github.com/RoaringBitmap/roaring"
	"github.com/anacrolix/log"
	"github.com/anacrolix/multiless"

	request_strategy "github.com/anacrolix/torrent/request-strategy"
)

func (cl *Client) tickleRequester() {
	cl.updateRequests.Broadcast()
}

func (cl *Client) getRequestStrategyInput() request_strategy.Input {
	ts := make([]request_strategy.Torrent, 0, len(cl.torrents))
	for _, t := range cl.torrents {
		if !t.haveInfo() {
			// This would be removed if metadata is handled here. We have to guard against not
			// knowing the piece size. If we have no info, we have no pieces too, so the end result
			// is the same.
			continue
		}
		rst := request_strategy.Torrent{
			InfoHash:       t.infoHash,
			ChunksPerPiece: t.chunksPerRegularPiece(),
		}
		if t.storage != nil {
			rst.Capacity = t.storage.Capacity
		}
		rst.Pieces = make([]request_strategy.Piece, 0, len(t.pieces))
		for i := range t.pieces {
			p := &t.pieces[i]
			rst.Pieces = append(rst.Pieces, request_strategy.Piece{
				Request:           !t.ignorePieceForRequests(i),
				Priority:          p.purePriority(),
				Partial:           t.piecePartiallyDownloaded(i),
				Availability:      p.availability,
				Length:            int64(p.length()),
				NumPendingChunks:  int(t.pieceNumPendingChunks(i)),
				IterPendingChunks: &p.undirtiedChunksIter,
			})
		}
		t.iterPeers(func(p *Peer) {
			if p.closed.IsSet() {
				return
			}
			if p.piecesReceivedSinceLastRequestUpdate > p.maxPiecesReceivedBetweenRequestUpdates {
				p.maxPiecesReceivedBetweenRequestUpdates = p.piecesReceivedSinceLastRequestUpdate
			}
			p.piecesReceivedSinceLastRequestUpdate = 0
			rst.Peers = append(rst.Peers, request_strategy.Peer{
				Pieces:           *p.newPeerPieces(),
				MaxRequests:      p.nominalMaxRequests(),
				ExistingRequests: p.actualRequestState.Requests,
				Choking:          p.peerChoking,
				PieceAllowedFast: p.peerAllowedFast,
				DownloadRate:     p.downloadRate(),
				Age:              time.Since(p.completedHandshake),
				Id: peerId{
					Peer: p,
					ptr:  uintptr(unsafe.Pointer(p)),
				},
			})
		})
		ts = append(ts, rst)
	}
	return request_strategy.Input{
		Torrents:           ts,
		MaxUnverifiedBytes: cl.config.MaxUnverifiedBytes,
	}
}

func init() {
	gob.Register(peerId{})
}

type peerId struct {
	*Peer
	ptr uintptr
}

func (p peerId) Uintptr() uintptr {
	return p.ptr
}

func (p peerId) GobEncode() (b []byte, _ error) {
	*(*reflect.SliceHeader)(unsafe.Pointer(&b)) = reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(&p.ptr)),
		Len:  int(unsafe.Sizeof(p.ptr)),
		Cap:  int(unsafe.Sizeof(p.ptr)),
	}
	return
}

func (p *peerId) GobDecode(b []byte) error {
	if uintptr(len(b)) != unsafe.Sizeof(p.ptr) {
		panic(len(b))
	}
	ptr := unsafe.Pointer(&b[0])
	p.ptr = *(*uintptr)(ptr)
	log.Printf("%p", ptr)
	dst := reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(&p.Peer)),
		Len:  int(unsafe.Sizeof(p.Peer)),
		Cap:  int(unsafe.Sizeof(p.Peer)),
	}
	copy(*(*[]byte)(unsafe.Pointer(&dst)), b)
	return nil
}

type RequestIndex = request_strategy.RequestIndex
type chunkIndexType = request_strategy.ChunkIndex

type peerRequests struct {
	requestIndexes       []RequestIndex
	peer                 *Peer
	torrentStrategyInput request_strategy.Torrent
}

func (p *peerRequests) Len() int {
	return len(p.requestIndexes)
}

func (p *peerRequests) Less(i, j int) bool {
	leftRequest := p.requestIndexes[i]
	rightRequest := p.requestIndexes[j]
	t := p.peer.t
	leftPieceIndex := leftRequest / p.torrentStrategyInput.ChunksPerPiece
	rightPieceIndex := rightRequest / p.torrentStrategyInput.ChunksPerPiece
	leftCurrent := p.peer.actualRequestState.Requests.Contains(leftRequest)
	rightCurrent := p.peer.actualRequestState.Requests.Contains(rightRequest)
	pending := func(index RequestIndex, current bool) int {
		ret := t.pendingRequests.Get(index)
		if current {
			ret--
		}
		// See https://github.com/anacrolix/torrent/issues/679 for possible issues. This should be
		// resolved.
		if ret < 0 {
			panic(ret)
		}
		return ret
	}
	ml := multiless.New()
	// Push requests that can't be served right now to the end. But we don't throw them away unless
	// there's a better alternative. This is for when we're using the fast extension and get choked
	// but our requests could still be good when we get unchoked.
	if p.peer.peerChoking {
		ml = ml.Bool(
			!p.peer.peerAllowedFast.Contains(leftPieceIndex),
			!p.peer.peerAllowedFast.Contains(rightPieceIndex),
		)
	}
	ml = ml.Int(
		pending(leftRequest, leftCurrent),
		pending(rightRequest, rightCurrent))
	ml = ml.Bool(!leftCurrent, !rightCurrent)
	ml = ml.Int(
		-int(p.torrentStrategyInput.Pieces[leftPieceIndex].Priority),
		-int(p.torrentStrategyInput.Pieces[rightPieceIndex].Priority),
	)
	ml = ml.Int(
		int(p.torrentStrategyInput.Pieces[leftPieceIndex].Availability),
		int(p.torrentStrategyInput.Pieces[rightPieceIndex].Availability))
	ml = ml.Uint32(leftPieceIndex, rightPieceIndex)
	ml = ml.Uint32(leftRequest, rightRequest)
	return ml.MustLess()
}

func (p *peerRequests) Swap(i, j int) {
	p.requestIndexes[i], p.requestIndexes[j] = p.requestIndexes[j], p.requestIndexes[i]
}

func (p *peerRequests) Push(x interface{}) {
	p.requestIndexes = append(p.requestIndexes, x.(RequestIndex))
}

func (p *peerRequests) Pop() interface{} {
	last := len(p.requestIndexes) - 1
	x := p.requestIndexes[last]
	p.requestIndexes = p.requestIndexes[:last]
	return x
}

func (p *Peer) getDesiredRequestState() (desired requestState) {
	input := p.t.cl.getRequestStrategyInput()
	requestHeap := peerRequests{
		peer: p,
	}
	for _, t := range input.Torrents {
		if t.InfoHash == p.t.infoHash {
			requestHeap.torrentStrategyInput = t
			break
		}
	}
	request_strategy.GetRequestablePieces(
		input,
		func(t *request_strategy.Torrent, rsp *request_strategy.Piece, pieceIndex int) {
			if t.InfoHash != p.t.infoHash {
				return
			}
			if !p.peerHasPiece(pieceIndex) {
				return
			}
			allowedFast := p.peerAllowedFast.ContainsInt(pieceIndex)
			rsp.IterPendingChunks.Iter(func(ci request_strategy.ChunkIndex) {
				r := p.t.pieceRequestIndexOffset(pieceIndex) + ci
				if !allowedFast {
					// We must signal interest to request this
					desired.Interested = true
					// We can make or will allow sustaining a request here if we're not choked, or
					// have made the request previously (presumably while unchoked), and haven't had
					// the peer respond yet (and the request was retained because we are using the
					// fast extension).
					if p.peerChoking && !p.actualRequestState.Requests.Contains(r) {
						// We can't request this right now.
						return
					}
				}
				requestHeap.requestIndexes = append(requestHeap.requestIndexes, r)
			})
		},
	)
	p.t.assertPendingRequests()
	heap.Init(&requestHeap)
	for requestHeap.Len() != 0 && desired.Requests.GetCardinality() < uint64(p.nominalMaxRequests()) {
		requestIndex := heap.Pop(&requestHeap).(RequestIndex)
		desired.Requests.Add(requestIndex)
	}
	return
}

func (p *Peer) maybeUpdateActualRequestState() bool {
	if p.needRequestUpdate == "" {
		return true
	}
	var more bool
	pprof.Do(
		context.Background(),
		pprof.Labels("update request", p.needRequestUpdate),
		func(_ context.Context) {
			next := p.getDesiredRequestState()
			more = p.applyRequestState(next)
		},
	)
	return more
}

// Transmit/action the request state to the peer.
func (p *Peer) applyRequestState(next requestState) bool {
	current := &p.actualRequestState
	if !p.setInterested(next.Interested) {
		return false
	}
	more := true
	cancel := roaring.AndNot(&current.Requests, &next.Requests)
	cancel.Iterate(func(req uint32) bool {
		more = p.cancel(req)
		return more
	})
	if !more {
		return false
	}
	// We randomize the order in which requests are issued, to reduce the overlap with requests to
	// other peers. Note that although it really depends on what order the peer services the
	// requests, if we are only able to issue some requests before buffering, or the peer starts
	// handling our requests before they've all arrived, then this randomization should reduce
	// overlap. Note however that if we received the desired requests in priority order, then
	// randomizing would throw away that benefit.
	for _, x := range rand.Perm(int(next.Requests.GetCardinality())) {
		req, err := next.Requests.Select(uint32(x))
		if err != nil {
			panic(err)
		}
		if p.cancelledRequests.Contains(req) {
			// Waiting for a reject or piece message, which will suitably trigger us to update our
			// requests, so we can skip this one with no additional consideration.
			continue
		}
		// The cardinality of our desired requests shouldn't exceed the max requests since it's used
		// in the calculation of the requests. However if we cancelled requests and they haven't
		// been rejected or serviced yet with the fast extension enabled, we can end up with more
		// extra outstanding requests. We could subtract the number of outstanding cancels from the
		// next request cardinality, but peers might not like that.
		if maxRequests(current.Requests.GetCardinality()) >= p.nominalMaxRequests() {
			//log.Printf("not assigning all requests [desired=%v, cancelled=%v, current=%v, max=%v]",
			//	next.Requests.GetCardinality(),
			//	p.cancelledRequests.GetCardinality(),
			//	current.Requests.GetCardinality(),
			//	p.nominalMaxRequests(),
			//)
			break
		}
		more, err = p.request(req)
		if err != nil {
			panic(err)
		}
		if !more {
			break
		}
	}
	p.updateRequestsTimer.Stop()
	if more {
		p.needRequestUpdate = ""
		if !current.Requests.IsEmpty() {
			p.updateRequestsTimer.Reset(3 * time.Second)
		}
	}
	return more
}
<|endoftext|>


<commit_before>package irma

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

	"github.com/go-errors/errors"
	"github.com/hashicorp/go-multierror"
	"github.com/privacybydesign/gabi/big"
	"github.com/privacybydesign/gabi/revocation"
	"github.com/privacybydesign/gabi/signed"
	"github.com/timshannon/bolthold"
	bolt "go.etcd.io/bbolt"
)

type (
	// DB is a bolthold database storing revocation state for a particular accumulator
	// (Record instances, and IssuanceRecord instances if used by an issuer).
	DB struct {
		Current  revocation.Accumulator
		Updated  time.Time
		onChange []func(*revocation.Record)
		bolt     *bolthold.Store
		keystore revocation.Keystore
	}

	RevocationStorage struct {
		dbs  map[CredentialTypeIdentifier]*DB
		conf *Configuration
	}

	TimeRecord struct {
		Index      uint64
		Start, End int64
	}

	// IssuanceRecord contains information generated during issuance, needed for later revocation.
	IssuanceRecord struct {
		Key        string
		Attr       *big.Int
		Issued     int64
		ValidUntil int64
		RevokedAt  int64 // 0 if not currently revoked
	}

	currentRecord struct {
		Index uint64
	}
)

const boltCurrentIndexKey = "currentIndex"

func (rdb *DB) EnableRevocation(sk *revocation.PrivateKey) error {
	msg, acc, err := revocation.NewAccumulator(sk)
	if err != nil {
		return err
	}
	if err = rdb.Add(msg, sk.Counter); err != nil {
		return err
	}
	rdb.Current = *acc
	rdb.Updated = time.Now()
	return nil
}

// Revoke revokes the credential specified specified by key if found within the current database,
// by updating its revocation time to now, adding its revocation attribute to the current accumulator,
// and updating the revocation database on disk.
func (rdb *DB) Revoke(sk *revocation.PrivateKey, key []byte) error {
	return rdb.bolt.Bolt().Update(func(tx *bolt.Tx) error {
		var err error
		cr := IssuanceRecord{}
		if err = rdb.bolt.TxGet(tx, key, &cr); err != nil {
			return err
		}
		cr.RevokedAt = time.Now().UnixNano()
		if err = rdb.bolt.TxUpdate(tx, key, &cr); err != nil {
			return err
		}
		return rdb.revokeAttr(sk, cr.Attr, tx)
	})
}

// Get returns all records that a client requires to update its revocation state if it is currently
// at the specified index, that is, all records whose end index is greater than or equal to
// the specified index.
func (rdb *DB) RevocationRecords(index int) ([]*revocation.Record, error) {
	var records []*revocation.Record
	if err := rdb.bolt.Find(&records, bolthold.Where(bolthold.Key).Ge(uint64(index))); err != nil {
		return nil, err
	}
	return records, nil
}

func (rdb *DB) LatestRecords(count int) ([]*revocation.Record, error) {
	c := int(rdb.Current.Index) - count + 1
	if c < 0 {
		c = 0
	}
	return rdb.RevocationRecords(c)
}

func (rdb *DB) IssuanceRecordExists(key []byte) (bool, error) {
	_, err := rdb.IssuanceRecord(key)
	switch err {
	case nil:
		return true, nil
	case bolthold.ErrNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (rdb *DB) AddIssuanceRecord(r *IssuanceRecord) error {
	return rdb.bolt.Insert([]byte(r.Key), r)
}

func (rdb *DB) IssuanceRecord(key []byte) (*IssuanceRecord, error) {
	r := &IssuanceRecord{}
	if err := rdb.bolt.Get(key, r); err != nil {
		return nil, err
	}
	return r, nil
}

func (rdb *DB) AddRecords(records []*revocation.Record) error {
	var err error
	for _, r := range records {
		if err = rdb.Add(r.Message, r.PublicKeyIndex); err != nil {
			return err
		}
	}
	rdb.Updated = time.Now() // TODO update this in add()?
	return nil
}

// TODO this should use revocation.Record.UnmarshalVerify
func (rdb *DB) Add(updateMsg signed.Message, counter uint) error {
	var err error
	var update revocation.AccumulatorUpdate

	pk, err := rdb.keystore(counter)
	if err != nil {
		return err
	}

	if err = signed.UnmarshalVerify(pk.ECDSA, updateMsg, &update); err != nil {
		return err
	}

	return rdb.bolt.Bolt().Update(func(tx *bolt.Tx) error {
		return rdb.add(update, updateMsg, counter, tx)
	})
}

func (rdb *DB) add(update revocation.AccumulatorUpdate, updateMsg signed.Message, pkCounter uint, tx *bolt.Tx) error {
	var err error
	record := &revocation.Record{
		StartIndex:     update.StartIndex,
		EndIndex:       update.Accumulator.Index,
		PublicKeyIndex: pkCounter,
		Message:        updateMsg,
	}
	if err = rdb.bolt.TxInsert(tx, update.Accumulator.Index, record); err != nil {
		return err
	}

	if update.Accumulator.Index != 0 {
		var tr TimeRecord
		if err = rdb.bolt.TxGet(tx, update.Accumulator.Index-1, &tr); err == nil {
			tr.End = time.Now().UnixNano()
			if err = rdb.bolt.TxUpdate(tx, update.Accumulator.Index-1, &tr); err != nil {
				return err
			}
		}
	}
	if err = rdb.bolt.TxInsert(tx, update.Accumulator.Index, &TimeRecord{
		Index: update.Accumulator.Index,
		Start: time.Now().UnixNano(),
	}); err != nil {
		return err
	}

	if err = rdb.bolt.TxUpsert(tx, boltCurrentIndexKey, &currentRecord{update.Accumulator.Index}); err != nil {
		return err
	}

	for _, f := range rdb.onChange {
		f(record)
	}

	rdb.Current = update.Accumulator
	return nil
}

func (rdb *DB) Enabled() bool {
	var currentIndex currentRecord
	err := rdb.bolt.Get(boltCurrentIndexKey, &currentIndex)
	return err == nil
}

func (rdb *DB) loadCurrent() error {
	var currentIndex currentRecord
	if err := rdb.bolt.Get(boltCurrentIndexKey, &currentIndex); err == bolthold.ErrNotFound {
		return errors.New("revocation database not initialized")
	} else if err != nil {
		return err
	}

	var record revocation.Record
	if err := rdb.bolt.Get(currentIndex.Index, &record); err != nil {
		return err
	}
	pk, err := rdb.keystore(record.PublicKeyIndex)
	if err != nil {
		return err
	}
	var u revocation.AccumulatorUpdate
	if err = signed.UnmarshalVerify(pk.ECDSA, record.Message, &u); err != nil {
		return err
	}
	rdb.Current = u.Accumulator
	return nil
}

func (rdb *DB) RevokeAttr(sk *revocation.PrivateKey, e *big.Int) error {
	return rdb.bolt.Bolt().Update(func(tx *bolt.Tx) error {
		return rdb.revokeAttr(sk, e, tx)
	})
}

func (rdb *DB) revokeAttr(sk *revocation.PrivateKey, e *big.Int, tx *bolt.Tx) error {
	// don't update rdb.Current until after all possible errors are handled
	newAcc, err := rdb.Current.Remove(sk, e)
	if err != nil {
		return err
	}
	update := revocation.AccumulatorUpdate{
		Accumulator: *newAcc,
		StartIndex:  newAcc.Index,
		Revoked:     []*big.Int{e},
		Time:        time.Now().UnixNano(),
	}
	updateMsg, err := signed.MarshalSign(sk.ECDSA, update)
	if err != nil {
		return err
	}
	if err = rdb.add(update, updateMsg, sk.Counter, tx); err != nil {
		return err
	}
	rdb.Current = *newAcc
	return nil
}

func (rdb *DB) Close() error {
	rdb.onChange = nil
	if rdb.bolt != nil {
		return rdb.bolt.Close()
	}
	return nil
}

func (rdb *DB) OnChange(handler func(*revocation.Record)) {
	rdb.onChange = append(rdb.onChange, handler)
}

func (rs *RevocationStorage) loadDB(credid CredentialTypeIdentifier) (*DB, error) {
	path := filepath.Join(rs.conf.RevocationPath, credid.String())
	keystore := rs.Keystore(credid.IssuerIdentifier())

	b, err := bolthold.Open(path, 0600, &bolthold.Options{Options: &bolt.Options{Timeout: 1 * time.Second}})
	if err != nil {
		return nil, err
	}
	db := &DB{
		bolt:     b,
		keystore: keystore,
		Updated:  time.Unix(0, 0),
	}
	if db.Enabled() {
		if err = db.loadCurrent(); err != nil {
			_ = db.Close()
			return nil, err
		}
	}
	return db, nil
}

func (rs *RevocationStorage) PublicKey(issid IssuerIdentifier, counter uint) (*revocation.PublicKey, error) {
	pk, err := rs.conf.PublicKey(issid, int(counter))
	if err != nil {
		return nil, err
	}
	if pk == nil {
		return nil, errors.Errorf("unknown public key: %s-%d", issid, counter)
	}
	revpk, err := pk.RevocationKey()
	if err != nil {
		return nil, err
	}
	return revpk, nil
}

func (rs *RevocationStorage) GetUpdates(credid CredentialTypeIdentifier, index uint64) ([]*revocation.Record, error) {
	var records []*revocation.Record
	err := NewHTTPTransport(rs.conf.CredentialTypes[credid].RevocationServer).
		Get(fmt.Sprintf("-/revocation/records/%s/%d", credid, index), &records)
	if err != nil {
		return nil, err
	}
	return records, nil
}

func (rs *RevocationStorage) UpdateAll() error {
	var err error
	for credid := range rs.dbs {
		if err = rs.UpdateDB(credid); err != nil {
			return err
		}
	}
	return nil
}

func (rs *RevocationStorage) SetRecords(b *BaseRequest) error {
	if len(b.Revocation) == 0 {
		return nil
	}
	b.RevocationUpdates = make(map[CredentialTypeIdentifier][]*revocation.Record, len(b.Revocation))
	for _, credid := range b.Revocation {
		db, err := rs.DB(credid)
		if err != nil {
			return err
		}
		if err = rs.updateDelayed(credid, db); err != nil {
			return err
		}
		b.RevocationUpdates[credid], err = db.LatestRecords(revocationUpdateCount)
		if err != nil {
			return err
		}
	}
	return nil
}

func (rs *RevocationStorage) UpdateDB(credid CredentialTypeIdentifier) error {
	db, err := rs.DB(credid)
	if err != nil {
		return err
	}
	var index uint64
	if db.Enabled() {
		index = db.Current.Index + 1
	}
	records, err := rs.GetUpdates(credid, index)
	if err != nil {
		return err
	}
	return db.AddRecords(records)
}

func (rs *RevocationStorage) DB(credid CredentialTypeIdentifier) (*DB, error) {
	if _, known := rs.conf.CredentialTypes[credid]; !known {
		return nil, errors.New("unknown credential type")
	}
	if rs.dbs == nil {
		rs.dbs = make(map[CredentialTypeIdentifier]*DB)
	}
	if rs.dbs[credid] == nil {
		var err error
		db, err := rs.loadDB(credid)
		if err != nil {
			return nil, err
		}
		rs.dbs[credid] = db
	}
	return rs.dbs[credid], nil
}

func (rs *RevocationStorage) updateDelayed(credid CredentialTypeIdentifier, db *DB) error {
	if db.Updated.Before(time.Now().Add(-5 * time.Minute)) {
		if err := rs.UpdateDB(credid); err != nil {
			return err
		}
	}
	return nil
}

func (rs *RevocationStorage) SendIssuanceRecord(cred CredentialTypeIdentifier, rec *IssuanceRecord) error {
	credtype := rs.conf.CredentialTypes[cred]
	if credtype == nil {
		return errors.New("unknown credential type")
	}
	if credtype.RevocationServer == "" {
		return errors.New("credential type has no revocation server")
	}
	sk, err := rs.conf.PrivateKey(cred.IssuerIdentifier())
	if err != nil {
		return err
	}
	if sk == nil {
		return errors.New("private key not found")
	}
	revsk, err := sk.RevocationKey()
	if err != nil {
		return err
	}

	message, err := signed.MarshalSign(revsk.ECDSA, rec)
	if err != nil {
		return err
	}
	return NewHTTPTransport(credtype.RevocationServer).Post(
		fmt.Sprintf("-/revocation/issuancerecord/%s/%d", cred, sk.Counter), nil, []byte(message),
	)
}

func (rs *RevocationStorage) Revoke(credid CredentialTypeIdentifier, key string) error {
	sk, err := rs.conf.PrivateKey(credid.IssuerIdentifier())
	if err != nil {
		return err
	}
	if sk == nil {
		return errors.New("private key not found")
	}
	rsk, err := sk.RevocationKey()
	if err != nil {
		return err
	}

	db, err := rs.DB(credid)
	if err != nil {
		return err
	}
	return db.Revoke(rsk, []byte(key))
}

func (rs *RevocationStorage) Close() error {
	merr := &multierror.Error{}
	var err error
	for _, db := range rs.dbs {
		if err = db.Close(); err != nil {
			merr = multierror.Append(merr, err)
		}
	}
	rs.dbs = nil
	return merr.ErrorOrNil()
}

func (rs *RevocationStorage) Keystore(issuerid IssuerIdentifier) revocation.Keystore {
	return func(counter uint) (*revocation.PublicKey, error) {
		return rs.PublicKey(issuerid, counter)
	}
}
<commit_msg>refactor: simplify revocation record adding<commit_after>package irma

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

	"github.com/go-errors/errors"
	"github.com/hashicorp/go-multierror"
	"github.com/privacybydesign/gabi/big"
	"github.com/privacybydesign/gabi/revocation"
	"github.com/privacybydesign/gabi/signed"
	"github.com/timshannon/bolthold"
	bolt "go.etcd.io/bbolt"
)

type (
	// DB is a bolthold database storing revocation state for a particular accumulator
	// (Record instances, and IssuanceRecord instances if used by an issuer).
	DB struct {
		Current  revocation.Accumulator
		Updated  time.Time
		onChange []func(*revocation.Record)
		bolt     *bolthold.Store
		keystore revocation.Keystore
	}

	RevocationStorage struct {
		dbs  map[CredentialTypeIdentifier]*DB
		conf *Configuration
	}

	TimeRecord struct {
		Index      uint64
		Start, End int64
	}

	// IssuanceRecord contains information generated during issuance, needed for later revocation.
	IssuanceRecord struct {
		Key        string
		Attr       *big.Int
		Issued     int64
		ValidUntil int64
		RevokedAt  int64 // 0 if not currently revoked
	}

	currentRecord struct {
		Index uint64
	}
)

const boltCurrentIndexKey = "currentIndex"

func (rdb *DB) EnableRevocation(sk *revocation.PrivateKey) error {
	msg, acc, err := revocation.NewAccumulator(sk)
	if err != nil {
		return err
	}
	if err = rdb.Add(&revocation.Record{
		PublicKeyIndex: sk.Counter,
		Message:        msg,
	}); err != nil {
		return err
	}
	rdb.Current = *acc
	rdb.Updated = time.Now()
	return nil
}

// Revoke revokes the credential specified specified by key if found within the current database,
// by updating its revocation time to now, adding its revocation attribute to the current accumulator,
// and updating the revocation database on disk.
func (rdb *DB) Revoke(sk *revocation.PrivateKey, key []byte) error {
	return rdb.bolt.Bolt().Update(func(tx *bolt.Tx) error {
		var err error
		cr := IssuanceRecord{}
		if err = rdb.bolt.TxGet(tx, key, &cr); err != nil {
			return err
		}
		cr.RevokedAt = time.Now().UnixNano()
		if err = rdb.bolt.TxUpdate(tx, key, &cr); err != nil {
			return err
		}
		return rdb.revokeAttr(sk, cr.Attr, tx)
	})
}

// Get returns all records that a client requires to update its revocation state if it is currently
// at the specified index, that is, all records whose end index is greater than or equal to
// the specified index.
func (rdb *DB) RevocationRecords(index int) ([]*revocation.Record, error) {
	var records []*revocation.Record
	if err := rdb.bolt.Find(&records, bolthold.Where(bolthold.Key).Ge(uint64(index))); err != nil {
		return nil, err
	}
	return records, nil
}

func (rdb *DB) LatestRecords(count int) ([]*revocation.Record, error) {
	c := int(rdb.Current.Index) - count + 1
	if c < 0 {
		c = 0
	}
	return rdb.RevocationRecords(c)
}

func (rdb *DB) IssuanceRecordExists(key []byte) (bool, error) {
	_, err := rdb.IssuanceRecord(key)
	switch err {
	case nil:
		return true, nil
	case bolthold.ErrNotFound:
		return false, nil
	default:
		return false, err
	}
}

func (rdb *DB) AddIssuanceRecord(r *IssuanceRecord) error {
	return rdb.bolt.Insert([]byte(r.Key), r)
}

func (rdb *DB) IssuanceRecord(key []byte) (*IssuanceRecord, error) {
	r := &IssuanceRecord{}
	if err := rdb.bolt.Get(key, r); err != nil {
		return nil, err
	}
	return r, nil
}

func (rdb *DB) AddRecords(records []*revocation.Record) error {
	var err error
	for _, r := range records {
		if err = rdb.Add(r); err != nil {
			return err
		}
	}
	return nil
}

func (rdb *DB) Add(record *revocation.Record) error {
	return rdb.bolt.Bolt().Update(func(tx *bolt.Tx) error {
		return rdb.add(record, tx)
	})
}

func (rdb *DB) add(record *revocation.Record, tx *bolt.Tx) error {
	pk, err := rdb.keystore(record.PublicKeyIndex)
	if err != nil {
		return err
	}
	update, err := record.UnmarshalVerify(pk)
	if err != nil {
		return err
	}

	if err = rdb.bolt.TxInsert(tx, update.Accumulator.Index, record); err != nil {
		return err
	}

	if update.Accumulator.Index != 0 {
		var tr TimeRecord
		if err = rdb.bolt.TxGet(tx, update.Accumulator.Index-1, &tr); err == nil {
			tr.End = time.Now().UnixNano()
			if err = rdb.bolt.TxUpdate(tx, update.Accumulator.Index-1, &tr); err != nil {
				return err
			}
		}
	}
	if err = rdb.bolt.TxInsert(tx, update.Accumulator.Index, &TimeRecord{
		Index: update.Accumulator.Index,
		Start: time.Now().UnixNano(),
	}); err != nil {
		return err
	}

	if err = rdb.bolt.TxUpsert(tx, boltCurrentIndexKey, &currentRecord{update.Accumulator.Index}); err != nil {
		return err
	}

	for _, f := range rdb.onChange {
		f(record)
	}

	rdb.Updated = time.Now()
	rdb.Current = update.Accumulator
	return nil
}

func (rdb *DB) Enabled() bool {
	var currentIndex currentRecord
	err := rdb.bolt.Get(boltCurrentIndexKey, &currentIndex)
	return err == nil
}

func (rdb *DB) loadCurrent() error {
	var currentIndex currentRecord
	if err := rdb.bolt.Get(boltCurrentIndexKey, &currentIndex); err == bolthold.ErrNotFound {
		return errors.New("revocation database not initialized")
	} else if err != nil {
		return err
	}

	var record revocation.Record
	if err := rdb.bolt.Get(currentIndex.Index, &record); err != nil {
		return err
	}
	pk, err := rdb.keystore(record.PublicKeyIndex)
	if err != nil {
		return err
	}
	var u revocation.AccumulatorUpdate
	if err = signed.UnmarshalVerify(pk.ECDSA, record.Message, &u); err != nil {
		return err
	}
	rdb.Current = u.Accumulator
	return nil
}

func (rdb *DB) RevokeAttr(sk *revocation.PrivateKey, e *big.Int) error {
	return rdb.bolt.Bolt().Update(func(tx *bolt.Tx) error {
		return rdb.revokeAttr(sk, e, tx)
	})
}

func (rdb *DB) revokeAttr(sk *revocation.PrivateKey, e *big.Int, tx *bolt.Tx) error {
	// don't update rdb.Current until after all possible errors are handled
	newAcc, err := rdb.Current.Remove(sk, e)
	if err != nil {
		return err
	}
	update := &revocation.AccumulatorUpdate{
		Accumulator: *newAcc,
		StartIndex:  newAcc.Index,
		Revoked:     []*big.Int{e},
		Time:        time.Now().UnixNano(),
	}
	updateMsg, err := signed.MarshalSign(sk.ECDSA, update)
	if err != nil {
		return err
	}
	if err = rdb.add(&revocation.Record{
		StartIndex:     newAcc.Index,
		EndIndex:       newAcc.Index,
		PublicKeyIndex: sk.Counter,
		Message:        updateMsg,
	}, tx); err != nil {
		return err
	}
	rdb.Current = *newAcc
	return nil
}

func (rdb *DB) Close() error {
	rdb.onChange = nil
	if rdb.bolt != nil {
		return rdb.bolt.Close()
	}
	return nil
}

func (rdb *DB) OnChange(handler func(*revocation.Record)) {
	rdb.onChange = append(rdb.onChange, handler)
}

func (rs *RevocationStorage) loadDB(credid CredentialTypeIdentifier) (*DB, error) {
	path := filepath.Join(rs.conf.RevocationPath, credid.String())
	keystore := rs.Keystore(credid.IssuerIdentifier())

	b, err := bolthold.Open(path, 0600, &bolthold.Options{Options: &bolt.Options{Timeout: 1 * time.Second}})
	if err != nil {
		return nil, err
	}
	db := &DB{
		bolt:     b,
		keystore: keystore,
		Updated:  time.Unix(0, 0),
	}
	if db.Enabled() {
		if err = db.loadCurrent(); err != nil {
			_ = db.Close()
			return nil, err
		}
	}
	return db, nil
}

func (rs *RevocationStorage) PublicKey(issid IssuerIdentifier, counter uint) (*revocation.PublicKey, error) {
	pk, err := rs.conf.PublicKey(issid, int(counter))
	if err != nil {
		return nil, err
	}
	if pk == nil {
		return nil, errors.Errorf("unknown public key: %s-%d", issid, counter)
	}
	revpk, err := pk.RevocationKey()
	if err != nil {
		return nil, err
	}
	return revpk, nil
}

func (rs *RevocationStorage) GetUpdates(credid CredentialTypeIdentifier, index uint64) ([]*revocation.Record, error) {
	var records []*revocation.Record
	err := NewHTTPTransport(rs.conf.CredentialTypes[credid].RevocationServer).
		Get(fmt.Sprintf("-/revocation/records/%s/%d", credid, index), &records)
	if err != nil {
		return nil, err
	}
	return records, nil
}

func (rs *RevocationStorage) UpdateAll() error {
	var err error
	for credid := range rs.dbs {
		if err = rs.UpdateDB(credid); err != nil {
			return err
		}
	}
	return nil
}

func (rs *RevocationStorage) SetRecords(b *BaseRequest) error {
	if len(b.Revocation) == 0 {
		return nil
	}
	b.RevocationUpdates = make(map[CredentialTypeIdentifier][]*revocation.Record, len(b.Revocation))
	for _, credid := range b.Revocation {
		db, err := rs.DB(credid)
		if err != nil {
			return err
		}
		if err = rs.updateDelayed(credid, db); err != nil {
			return err
		}
		b.RevocationUpdates[credid], err = db.LatestRecords(revocationUpdateCount)
		if err != nil {
			return err
		}
	}
	return nil
}

func (rs *RevocationStorage) UpdateDB(credid CredentialTypeIdentifier) error {
	db, err := rs.DB(credid)
	if err != nil {
		return err
	}
	var index uint64
	if db.Enabled() {
		index = db.Current.Index + 1
	}
	records, err := rs.GetUpdates(credid, index)
	if err != nil {
		return err
	}
	return db.AddRecords(records)
}

func (rs *RevocationStorage) DB(credid CredentialTypeIdentifier) (*DB, error) {
	if _, known := rs.conf.CredentialTypes[credid]; !known {
		return nil, errors.New("unknown credential type")
	}
	if rs.dbs == nil {
		rs.dbs = make(map[CredentialTypeIdentifier]*DB)
	}
	if rs.dbs[credid] == nil {
		var err error
		db, err := rs.loadDB(credid)
		if err != nil {
			return nil, err
		}
		rs.dbs[credid] = db
	}
	return rs.dbs[credid], nil
}

func (rs *RevocationStorage) updateDelayed(credid CredentialTypeIdentifier, db *DB) error {
	if db.Updated.Before(time.Now().Add(-5 * time.Minute)) {
		if err := rs.UpdateDB(credid); err != nil {
			return err
		}
	}
	return nil
}

func (rs *RevocationStorage) SendIssuanceRecord(cred CredentialTypeIdentifier, rec *IssuanceRecord) error {
	credtype := rs.conf.CredentialTypes[cred]
	if credtype == nil {
		return errors.New("unknown credential type")
	}
	if credtype.RevocationServer == "" {
		return errors.New("credential type has no revocation server")
	}
	sk, err := rs.conf.PrivateKey(cred.IssuerIdentifier())
	if err != nil {
		return err
	}
	if sk == nil {
		return errors.New("private key not found")
	}
	revsk, err := sk.RevocationKey()
	if err != nil {
		return err
	}

	message, err := signed.MarshalSign(revsk.ECDSA, rec)
	if err != nil {
		return err
	}
	return NewHTTPTransport(credtype.RevocationServer).Post(
		fmt.Sprintf("-/revocation/issuancerecord/%s/%d", cred, sk.Counter), nil, []byte(message),
	)
}

func (rs *RevocationStorage) Revoke(credid CredentialTypeIdentifier, key string) error {
	sk, err := rs.conf.PrivateKey(credid.IssuerIdentifier())
	if err != nil {
		return err
	}
	if sk == nil {
		return errors.New("private key not found")
	}
	rsk, err := sk.RevocationKey()
	if err != nil {
		return err
	}

	db, err := rs.DB(credid)
	if err != nil {
		return err
	}
	return db.Revoke(rsk, []byte(key))
}

func (rs *RevocationStorage) Close() error {
	merr := &multierror.Error{}
	var err error
	for _, db := range rs.dbs {
		if err = db.Close(); err != nil {
			merr = multierror.Append(merr, err)
		}
	}
	rs.dbs = nil
	return merr.ErrorOrNil()
}

func (rs *RevocationStorage) Keystore(issuerid IssuerIdentifier) revocation.Keystore {
	return func(counter uint) (*revocation.PublicKey, error) {
		return rs.PublicKey(issuerid, counter)
	}
}
<|endoftext|>