<commit_before>// Taken from golang.org/x/mobile/exp/audio
package engi

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"sync"
	"time"

	"encoding/binary"

	"golang.org/x/mobile/exp/audio/al"
)

// ReadSeekCloser is an io.ReadSeeker and io.Closer.
type ReadSeekCloser interface {
	io.ReadSeeker
	io.Closer
}

// Format represents a PCM data format.
type Format int

const (
	Mono8 Format = iota + 1
	Mono16
	Stereo8
	Stereo16
)

func (f Format) String() string { return formatStrings[f] }

// formatBytes is the product of bytes per sample and number of channels.
var formatBytes = [...]int64{
	Mono8:    1,
	Mono16:   2,
	Stereo8:  2,
	Stereo16: 4,
}

var formatCodes = [...]uint32{
	Mono8:    al.FormatMono8,
	Mono16:   al.FormatMono16,
	Stereo8:  al.FormatStereo8,
	Stereo16: al.FormatStereo16,
}

var formatStrings = [...]string{
	0:        "unknown",
	Mono8:    "mono8",
	Mono16:   "mono16",
	Stereo8:  "stereo8",
	Stereo16: "stereo16",
}

// State indicates the current playing state of the player.
type State int

const (
	Unknown State = iota
	Initial
	Playing
	Paused
	Stopped
)

func (s State) String() string { return stateStrings[s] }

var stateStrings = [...]string{
	Unknown: "unknown",
	Initial: "initial",
	Playing: "playing",
	Paused:  "paused",
	Stopped: "stopped",
}

var codeToState = map[int32]State{
	0:          Unknown,
	al.Initial: Initial,
	al.Playing: Playing,
	al.Paused:  Paused,
	al.Stopped: Stopped,
}

type track struct {
	format           Format
	samplesPerSecond int64
	src              ReadSeekCloser

	// hasHeader represents whether the audio source contains
	// a PCM header. If true, the audio data starts 44 bytes
	// later in the source.
	hasHeader bool
}

// Player is a basic audio player that plays PCM data.
// Operations on a nil *Player are no-op, a nil *Player can
// be used for testing purposes.
type Player struct {
	t      *track
	source al.Source

	mu        sync.Mutex
	prep      bool
	bufs      []al.Buffer // buffers are created and queued to source during prepare.
	sizeBytes int64       // size of the audio source
}

// NewPlayer returns a new Player.
// It initializes the underlying audio devices and the related resources.
// If zero values are provided for format and sample rate values, the player
// determines them from the source's WAV header.
// An error is returned if the format and sample rate can't be determined.
//
// The audio package is only designed for small audio sources.
func NewPlayer(src ReadSeekCloser, format Format, samplesPerSecond int64) (*Player, error) {
	s := al.GenSources(1)
	if code := al.Error(); code != 0 {
		return nil, fmt.Errorf("audio: cannot generate an audio source [err=%x]", code)
	}
	p := &Player{
		t:      &track{format: format, src: src, samplesPerSecond: samplesPerSecond},
		source: s[0],
	}
	if err := p.discoverHeader(); err != nil {
		return nil, err
	}
	if p.t.format == 0 {
		return nil, errors.New("audio: cannot determine the format")
	}
	if p.t.samplesPerSecond == 0 {
		return nil, errors.New("audio: cannot determine the sample rate")
	}
	return p, nil
}

// headerSize is the size of WAV headers.
// See http://www.topherlee.com/software/pcm-tut-wavformat.html.
const headerSize = 44

var (
	riffHeader = []byte("RIFF")
	waveHeader = []byte("WAVE")
)

func (p *Player) discoverHeader() error {
	buf := make([]byte, headerSize)
	if n, _ := io.ReadFull(p.t.src, buf); n != headerSize {
		// No header present or read error.
		return nil
	}
	if !(bytes.Equal(buf[0:4], riffHeader) && bytes.Equal(buf[8:12], waveHeader)) {
		return nil
	}
	p.t.hasHeader = true
	var format Format
	switch channels, depth := buf[22], buf[34]; {
	case channels == 1 && depth == 8:
		format = Mono8
	case channels == 1 && depth == 16:
		format = Mono16
	case channels == 2 && depth == 8:
		format = Stereo8
	case channels == 2 && depth == 16:
		format = Stereo16
	default:
		return fmt.Errorf("audio: unsupported format; num of channels=%d, bit rate=%d", channels, depth)
	}
	if p.t.format == 0 {
		p.t.format = format
	}
	if p.t.format != format {
		return fmt.Errorf("audio: given format %v does not match header %v", p.t.format, format)
	}
	sampleRate := int64(buf[24]) | int64(buf[25])<<8 | int64(buf[26])<<16 | int64(buf[27])<<24
	if p.t.samplesPerSecond == 0 {
		p.t.samplesPerSecond = sampleRate
	}
	if p.t.samplesPerSecond != sampleRate {
		return fmt.Errorf("audio: given sample rate %v does not match header %v", p.t.samplesPerSecond, sampleRate)
	}
	return nil
}

func (p *Player) prepare(background bool, offset int64, force bool) error {
	p.mu.Lock()
	if !force && p.prep {
		p.mu.Unlock()
		return nil
	}
	p.mu.Unlock()

	if p.t.hasHeader {
		offset += headerSize
	}
	if _, err := p.t.src.Seek(offset, 0); err != nil {
		return err
	}
	var bufs []al.Buffer
	// TODO(jbd): Limit the number of buffers in use, unqueue and reuse
	// the existing buffers as buffers are processed.
	buf := make([]byte, 128*1024)
	size := offset
	for {
		n, err := p.t.src.Read(buf)
		if n > 0 {
			size += int64(n)
			b := al.GenBuffers(1)
			if !background && p.t.format == Stereo16 {
				inputBuffer := bytes.NewBuffer(buf)
				outputBuffer := new(bytes.Buffer)
				var left, right int16

				// TODO: this might not be the best way to convert Stereo16 to Mono16, but it's something
				for converted := 0; converted < n; converted += 4 {
					binary.Read(inputBuffer, binary.LittleEndian, &left)
					binary.Read(inputBuffer, binary.LittleEndian, &right)

					binary.Write(outputBuffer, binary.LittleEndian, int16((int32(left)+int32(right))/2))
				}
				b[0].BufferData(formatCodes[Mono16], outputBuffer.Bytes(), int32(p.t.samplesPerSecond/2))

			} else {
				b[0].BufferData(formatCodes[p.t.format], buf[:n], int32(p.t.samplesPerSecond))
			}
			bufs = append(bufs, b[0])
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
	}

	p.mu.Lock()
	if len(p.bufs) > 0 {
		p.source.UnqueueBuffers(p.bufs...)
		al.DeleteBuffers(p.bufs...)
	}
	p.sizeBytes = size
	p.bufs = bufs
	p.prep = true
	if len(bufs) > 0 {
		p.source.QueueBuffers(bufs...)
	}
	p.mu.Unlock()
	return nil
}

// Play buffers the source audio to the audio device and starts
// to play the source.
// If the player paused or stopped, it reuses the previously buffered
// resources to keep playing from the time it has paused or stopped.
func (p *Player) Play(background bool) error {
	if p == nil {
		return nil
	}
	// Prepares if the track hasn't been buffered before.
	if err := p.prepare(background, 0, false); err != nil {
		return err
	}
	al.PlaySources(p.source)
	return lastErr()
}

// Pause pauses the player.
func (p *Player) Pause() error {
	if p == nil {
		return nil
	}
	al.PauseSources(p.source)
	return lastErr()
}

// Stop stops the player.
func (p *Player) Stop() error {
	if p == nil {
		return nil
	}
	al.StopSources(p.source)
	return lastErr()
}

// Seek moves the play head to the given offset relative to the start of the source.
func (p *Player) Seek(background bool, offset time.Duration) error {
	if p == nil {
		return nil
	}
	if err := p.Stop(); err != nil {
		return err
	}
	size := durToByteOffset(p.t, offset)
	if err := p.prepare(background, size, true); err != nil {
		return err
	}
	al.PlaySources(p.source)
	return lastErr()
}

// Current returns the current playback position of the audio that is being played.
func (p *Player) Current() time.Duration {
	if p == nil {
		return 0
	}
	// TODO(jbd): Current never returns the Total when the playing is finished.
	// OpenAL may be returning the last buffer's start point as an OffsetByte.
	return byteOffsetToDur(p.t, int64(p.source.OffsetByte()))
}

// Total returns the total duration of the audio source.
func (p *Player) Total(background bool) time.Duration {
	if p == nil {
		return 0
	}
	// Prepare is required to determine the length of the source.
	// We need to read the entire source to calculate the length.
	p.prepare(background, 0, false)
	return byteOffsetToDur(p.t, p.sizeBytes)
}

// Volume returns the current player volume. The range of the volume is [0, 1].
func (p *Player) Volume() float64 {
	if p == nil {
		return 0
	}
	return float64(p.source.Gain())
}

// SetVolume sets the volume of the player. The range of the volume is [0, 1].
func (p *Player) SetVolume(vol float64) {
	if p == nil {
		return
	}
	p.source.SetGain(float32(vol))
}

// State returns the player's current state.
func (p *Player) State() State {
	if p == nil {
		return Unknown
	}
	return codeToState[p.source.State()]
}

// Close closes the device and frees the underlying resources
// used by the player.
// It should be called as soon as the player is not in-use anymore.
func (p *Player) Close() error {
	if p == nil {
		return nil
	}
	if p.source != 0 {
		al.DeleteSources(p.source)
	}
	p.mu.Lock()
	if len(p.bufs) > 0 {
		al.DeleteBuffers(p.bufs...)
	}
	p.mu.Unlock()
	p.t.src.Close()
	return nil
}

func byteOffsetToDur(t *track, offset int64) time.Duration {
	return time.Duration(offset * formatBytes[t.format] * int64(time.Second) / t.samplesPerSecond)
}

func durToByteOffset(t *track, dur time.Duration) int64 {
	return int64(dur) * t.samplesPerSecond / (formatBytes[t.format] * int64(time.Second))
}

// lastErr returns the last error or nil if the last operation
// has been succesful.
func lastErr() error {
	if code := al.Error(); code != 0 {
		return fmt.Errorf("audio: openal failed with %x", code)
	}
	return nil
}

// TODO(jbd): Close the device.
<commit_msg>fix golang/x/mobile/exp/audio call failed<commit_after>// Taken from golang.org/x/mobile/exp/audio
package engi

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"sync"
	"time"

	"encoding/binary"

	"golang.org/x/mobile/exp/audio/al"
)

// ReadSeekCloser is an io.ReadSeeker and io.Closer.
type ReadSeekCloser interface {
	io.ReadSeeker
	io.Closer
}

// Format represents a PCM data format.
type Format int

const (
	Mono8 Format = iota + 1
	Mono16
	Stereo8
	Stereo16
)

func (f Format) String() string { return formatStrings[f] }

// formatBytes is the product of bytes per sample and number of channels.
var formatBytes = [...]int64{
	Mono8:    1,
	Mono16:   2,
	Stereo8:  2,
	Stereo16: 4,
}

var formatCodes = [...]uint32{
	Mono8:    al.FormatMono8,
	Mono16:   al.FormatMono16,
	Stereo8:  al.FormatStereo8,
	Stereo16: al.FormatStereo16,
}

var formatStrings = [...]string{
	0:        "unknown",
	Mono8:    "mono8",
	Mono16:   "mono16",
	Stereo8:  "stereo8",
	Stereo16: "stereo16",
}

// State indicates the current playing state of the player.
type State int

const (
	Unknown State = iota
	Initial
	Playing
	Paused
	Stopped
)

func (s State) String() string { return stateStrings[s] }

var stateStrings = [...]string{
	Unknown: "unknown",
	Initial: "initial",
	Playing: "playing",
	Paused:  "paused",
	Stopped: "stopped",
}

var codeToState = map[int32]State{
	0:          Unknown,
	al.Initial: Initial,
	al.Playing: Playing,
	al.Paused:  Paused,
	al.Stopped: Stopped,
}

type track struct {
	format           Format
	samplesPerSecond int64
	src              ReadSeekCloser

	// hasHeader represents whether the audio source contains
	// a PCM header. If true, the audio data starts 44 bytes
	// later in the source.
	hasHeader bool
}

// Player is a basic audio player that plays PCM data.
// Operations on a nil *Player are no-op, a nil *Player can
// be used for testing purposes.
type Player struct {
	t      *track
	source al.Source

	mu        sync.Mutex
	prep      bool
	bufs      []al.Buffer // buffers are created and queued to source during prepare.
	sizeBytes int64       // size of the audio source
}

// NewPlayer returns a new Player.
// It initializes the underlying audio devices and the related resources.
// If zero values are provided for format and sample rate values, the player
// determines them from the source's WAV header.
// An error is returned if the format and sample rate can't be determined.
//
// The audio package is only designed for small audio sources.
func NewPlayer(src ReadSeekCloser, format Format, samplesPerSecond int64) (*Player, error) {
	s := al.GenSources(1)
	if code := al.Error(); code != 0 {
		return nil, fmt.Errorf("audio: cannot generate an audio source [err=%x]", code)
	}
	p := &Player{
		t:      &track{format: format, src: src, samplesPerSecond: samplesPerSecond},
		source: s[0],
	}
	if err := p.discoverHeader(); err != nil {
		return nil, err
	}
	if p.t.format == 0 {
		return nil, errors.New("audio: cannot determine the format")
	}
	if p.t.samplesPerSecond == 0 {
		return nil, errors.New("audio: cannot determine the sample rate")
	}
	return p, nil
}

// headerSize is the size of WAV headers.
// See http://www.topherlee.com/software/pcm-tut-wavformat.html.
const headerSize = 44

var (
	riffHeader = []byte("RIFF")
	waveHeader = []byte("WAVE")
)

func (p *Player) discoverHeader() error {
	buf := make([]byte, headerSize)
	if n, _ := io.ReadFull(p.t.src, buf); n != headerSize {
		// No header present or read error.
		return nil
	}
	if !(bytes.Equal(buf[0:4], riffHeader) && bytes.Equal(buf[8:12], waveHeader)) {
		return nil
	}
	p.t.hasHeader = true
	var format Format
	switch channels, depth := buf[22], buf[34]; {
	case channels == 1 && depth == 8:
		format = Mono8
	case channels == 1 && depth == 16:
		format = Mono16
	case channels == 2 && depth == 8:
		format = Stereo8
	case channels == 2 && depth == 16:
		format = Stereo16
	default:
		return fmt.Errorf("audio: unsupported format; num of channels=%d, bit rate=%d", channels, depth)
	}
	if p.t.format == 0 {
		p.t.format = format
	}
	if p.t.format != format {
		return fmt.Errorf("audio: given format %v does not match header %v", p.t.format, format)
	}
	sampleRate := int64(buf[24]) | int64(buf[25])<<8 | int64(buf[26])<<16 | int64(buf[27])<<24
	if p.t.samplesPerSecond == 0 {
		p.t.samplesPerSecond = sampleRate
	}
	if p.t.samplesPerSecond != sampleRate {
		return fmt.Errorf("audio: given sample rate %v does not match header %v", p.t.samplesPerSecond, sampleRate)
	}
	return nil
}

func (p *Player) prepare(background bool, offset int64, force bool) error {
	p.mu.Lock()
	if !force && p.prep {
		p.mu.Unlock()
		return nil
	}
	p.mu.Unlock()

	if p.t.hasHeader {
		offset += headerSize
	}
	if _, err := p.t.src.Seek(offset, 0); err != nil {
		return err
	}
	var bufs []al.Buffer
	// TODO(jbd): Limit the number of buffers in use, unqueue and reuse
	// the existing buffers as buffers are processed.
	buf := make([]byte, 128*1024)
	size := offset
	for {
		n, err := p.t.src.Read(buf)
		if n > 0 {
			size += int64(n)
			b := al.GenBuffers(1)
			if !background && p.t.format == Stereo16 {
				inputBuffer := bytes.NewBuffer(buf)
				outputBuffer := new(bytes.Buffer)
				var left, right int16

				// TODO: this might not be the best way to convert Stereo16 to Mono16, but it's something
				for converted := 0; converted < n; converted += 4 {
					binary.Read(inputBuffer, binary.LittleEndian, &left)
					binary.Read(inputBuffer, binary.LittleEndian, &right)

					binary.Write(outputBuffer, binary.LittleEndian, int16((int32(left)+int32(right))/2))
				}
				b[0].BufferData(formatCodes[Mono16], outputBuffer.Bytes(), int32(p.t.samplesPerSecond/2))

			} else {
				b[0].BufferData(formatCodes[p.t.format], buf[:n], int32(p.t.samplesPerSecond))
			}
			bufs = append(bufs, b[0])
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
	}

	p.mu.Lock()
	if len(p.bufs) > 0 {
		p.source.UnqueueBuffers(p.bufs)
		al.DeleteBuffers(p.bufs)
	}
	p.sizeBytes = size
	p.bufs = bufs
	p.prep = true
	if len(bufs) > 0 {
		p.source.QueueBuffers(bufs)
	}
	p.mu.Unlock()
	return nil
}

// Play buffers the source audio to the audio device and starts
// to play the source.
// If the player paused or stopped, it reuses the previously buffered
// resources to keep playing from the time it has paused or stopped.
func (p *Player) Play(background bool) error {
	if p == nil {
		return nil
	}
	// Prepares if the track hasn't been buffered before.
	if err := p.prepare(background, 0, false); err != nil {
		return err
	}
	al.PlaySources(p.source)
	return lastErr()
}

// Pause pauses the player.
func (p *Player) Pause() error {
	if p == nil {
		return nil
	}
	al.PauseSources(p.source)
	return lastErr()
}

// Stop stops the player.
func (p *Player) Stop() error {
	if p == nil {
		return nil
	}
	al.StopSources(p.source)
	return lastErr()
}

// Seek moves the play head to the given offset relative to the start of the source.
func (p *Player) Seek(background bool, offset time.Duration) error {
	if p == nil {
		return nil
	}
	if err := p.Stop(); err != nil {
		return err
	}
	size := durToByteOffset(p.t, offset)
	if err := p.prepare(background, size, true); err != nil {
		return err
	}
	al.PlaySources(p.source)
	return lastErr()
}

// Current returns the current playback position of the audio that is being played.
func (p *Player) Current() time.Duration {
	if p == nil {
		return 0
	}
	// TODO(jbd): Current never returns the Total when the playing is finished.
	// OpenAL may be returning the last buffer's start point as an OffsetByte.
	return byteOffsetToDur(p.t, int64(p.source.OffsetByte()))
}

// Total returns the total duration of the audio source.
func (p *Player) Total(background bool) time.Duration {
	if p == nil {
		return 0
	}
	// Prepare is required to determine the length of the source.
	// We need to read the entire source to calculate the length.
	p.prepare(background, 0, false)
	return byteOffsetToDur(p.t, p.sizeBytes)
}

// Volume returns the current player volume. The range of the volume is [0, 1].
func (p *Player) Volume() float64 {
	if p == nil {
		return 0
	}
	return float64(p.source.Gain())
}

// SetVolume sets the volume of the player. The range of the volume is [0, 1].
func (p *Player) SetVolume(vol float64) {
	if p == nil {
		return
	}
	p.source.SetGain(float32(vol))
}

// State returns the player's current state.
func (p *Player) State() State {
	if p == nil {
		return Unknown
	}
	return codeToState[p.source.State()]
}

// Close closes the device and frees the underlying resources
// used by the player.
// It should be called as soon as the player is not in-use anymore.
func (p *Player) Close() error {
	if p == nil {
		return nil
	}
	if p.source != 0 {
		al.DeleteSources(p.source)
	}
	p.mu.Lock()
	if len(p.bufs) > 0 {
		al.DeleteBuffers(p.bufs)
	}
	p.mu.Unlock()
	p.t.src.Close()
	return nil
}

func byteOffsetToDur(t *track, offset int64) time.Duration {
	return time.Duration(offset * formatBytes[t.format] * int64(time.Second) / t.samplesPerSecond)
}

func durToByteOffset(t *track, dur time.Duration) int64 {
	return int64(dur) * t.samplesPerSecond / (formatBytes[t.format] * int64(time.Second))
}

// lastErr returns the last error or nil if the last operation
// has been succesful.
func lastErr() error {
	if code := al.Error(); code != 0 {
		return fmt.Errorf("audio: openal failed with %x", code)
	}
	return nil
}

// TODO(jbd): Close the device.
<|endoftext|>


<commit_before>// Copyright 2019 Anapaya Systems
//
// 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 tmpl

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"

	"github.com/scionproto/scion/go/lib/addr"
	"github.com/scionproto/scion/go/lib/scrypto"
	"github.com/scionproto/scion/go/lib/scrypto/cert"
	"github.com/scionproto/scion/go/lib/scrypto/trc"
	"github.com/scionproto/scion/go/lib/serrors"
	"github.com/scionproto/scion/go/tools/scion-pki/internal/conf"
	"github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn"
)

var (
	notBefore   uint32
	rawValidity string
)

type topoGen struct {
	Dirs     pkicmn.Dirs
	Validity conf.Validity
}

func (g topoGen) Run(topo topoFile) error {
	if err := g.setupDirs(topo); err != nil {
		return serrors.WrapStr("unable to setup dirs", err)
	}
	trcs, err := g.genTRCs(topo)
	if err != nil {
		return serrors.WrapStr("unable to generate TRC configs", err)
	}
	if err := g.genKeys(topo, trcs); err != nil {
		return serrors.WrapStr("unable to generate key configs", err)
	}
	if err := g.genCerts(topo); err != nil {
		return serrors.WrapStr("unable to generate certificate configs", err)
	}
	pkicmn.QuietPrint("Generated all configuration files\n")
	return nil
}

func (g topoGen) setupDirs(topo topoFile) error {
	for isd := range topo.ISDs() {
		dir := filepath.Dir(conf.TRCFile(g.Dirs.Root, isd, 1))
		if err := os.MkdirAll(dir, 0755); err != nil {
			return serrors.WrapStr("unable to make ISD directory", err, "isd", isd, "dir", dir)
		}
	}
	for ia := range topo.ASes {
		if err := os.MkdirAll(filepath.Dir(conf.KeysFile(g.Dirs.Root, ia)), 0755); err != nil {
			return serrors.WrapStr("unable to make AS directory", err, "ia", ia)
		}
	}
	return nil
}

func (g topoGen) genTRCs(topo topoFile) (map[addr.ISD]conf.TRC, error) {
	trcs := make(map[addr.ISD]conf.TRC)
	for isd := range topo.ISDs() {
		trcs[isd] = g.genTRC(isd, topo)
		var buf bytes.Buffer
		if err := trcs[isd].Encode(&buf); err != nil {
			return nil, serrors.WithCtx(err, "isd", isd)
		}
		file := conf.TRCFile(g.Dirs.Root, isd, trcs[isd].Version)
		if err := pkicmn.WriteToFile(buf.Bytes(), file, 0644); err != nil {
			return nil, serrors.WrapStr("unable to write TRC config", err, "isd", isd, "file", file)
		}
	}
	return trcs, nil
}

func (g topoGen) genTRC(isd addr.ISD, topo topoFile) conf.TRC {
	primaries := topo.Primaries(isd)
	reset := true
	cfg := conf.TRC{
		Description: fmt.Sprintf("ISD %d", isd),
		Version:     1,
		BaseVersion: 1,
		// XXX(roosd): Choose quorum according to your security needs.
		// This simply serves an example.
		VotingQuorum:      uint16(primaries.Voters()/2 + 1),
		TrustResetAllowed: &reset,
		Votes:             []addr.AS{},
		Validity:          g.Validity,
		PrimaryASes:       make(map[addr.AS]conf.Primary),
	}
	for _, primaryAS := range primaries {
		cfg.PrimaryASes[primaryAS.AS] = primaryAS.ToConf()
	}
	return cfg
}

func (g topoGen) genKeys(topo topoFile, cfg map[addr.ISD]conf.TRC) error {
	for ia := range topo.ASes {
		keys := g.genASKeys(ia.A, cfg[ia.I])
		var buf bytes.Buffer
		if err := keys.Encode(&buf); err != nil {
			return serrors.WithCtx(err, "ia", ia)
		}
		file := conf.KeysFile(g.Dirs.Root, ia)
		if err := pkicmn.WriteToFile(buf.Bytes(), file, 0644); err != nil {
			return serrors.WrapStr("unable to write key config", err, "ia", ia, "file", file)
		}
	}
	return nil
}

func (g topoGen) genASKeys(as addr.AS, cfg conf.TRC) conf.Keys {
	keys := conf.Keys{
		Primary: make(map[trc.KeyType]map[scrypto.KeyVersion]conf.KeyMeta),
		Issuer:  make(map[cert.KeyType]map[scrypto.KeyVersion]conf.KeyMeta),
		AS: map[cert.KeyType]map[scrypto.KeyVersion]conf.KeyMeta{
			cert.SigningKey:    {1: {Algorithm: scrypto.Ed25519, Validity: g.Validity}},
			cert.RevocationKey: {1: {Algorithm: scrypto.Ed25519, Validity: g.Validity}},
			cert.EncryptionKey: {1: {Algorithm: scrypto.Curve25519xSalsa20Poly1305,
				Validity: g.Validity}},
		},
	}
	primary, ok := cfg.PrimaryASes[as]
	_ = ok
	if primary.Attributes.Contains(trc.Voting) {
		keys.Primary[trc.OnlineKey] = map[scrypto.KeyVersion]conf.KeyMeta{
			1: {Algorithm: scrypto.Ed25519, Validity: g.Validity},
		}
		keys.Primary[trc.OfflineKey] = map[scrypto.KeyVersion]conf.KeyMeta{
			1: {Algorithm: scrypto.Ed25519, Validity: g.Validity},
		}
	}
	if primary.Attributes.Contains(trc.Issuing) {
		keys.Primary[trc.IssuingKey] = map[scrypto.KeyVersion]conf.KeyMeta{
			1: {Algorithm: scrypto.Ed25519, Validity: g.Validity},
		}
		keys.Issuer[cert.IssuingKey] = map[scrypto.KeyVersion]conf.KeyMeta{
			1: {Algorithm: scrypto.Ed25519, Validity: g.Validity},
		}
	}
	return keys
}

func (g topoGen) genCerts(topo topoFile) error {
	if err := g.genIssuerCerts(topo); err != nil {
		return serrors.WrapStr("unable to generate issuer certificates", err)
	}
	if err := g.genASCerts(topo); err != nil {
		return serrors.WrapStr("unable to generate AS certificates", err)
	}
	return nil
}

func (g topoGen) genIssuerCerts(topo topoFile) error {
	for ia, entry := range topo.ASes {
		if !entry.Issuing {
			continue
		}
		cfg := g.genIssuerCert(ia)
		var buf bytes.Buffer
		if err := cfg.Encode(&buf); err != nil {
			return serrors.WithCtx(err, "ia", ia)
		}
		file := conf.IssuerFile(g.Dirs.Root, ia, cfg.Version)
		if err := pkicmn.WriteToFile(buf.Bytes(), file, 0644); err != nil {
			return serrors.WrapStr("unable to write issuer config", err, "ia", ia, "file", file)
		}
	}
	return nil
}

func (g topoGen) genIssuerCert(ia addr.IA) conf.Issuer {
	issKey := scrypto.KeyVersion(1)
	cfg := conf.Issuer{
		Description:          fmt.Sprintf("Issuer certificate %s", ia),
		Version:              1,
		IssuingKeyVersion:    &issKey,
		RevocationKeyVersion: nil,
		TRCVersion:           1,
		OptDistPoints:        []addr.IA{},
		Validity:             g.Validity,
	}
	return cfg
}

func (g topoGen) genASCerts(topo topoFile) error {
	for ia, entry := range topo.ASes {
		issuer := entry.Issuer
		if entry.Issuing {
			issuer = ia
		}
		cfg := g.genASCert(ia, issuer)
		var buf bytes.Buffer
		if err := cfg.Encode(&buf); err != nil {
			return serrors.WithCtx(err, "ia", ia)
		}
		file := conf.ASFile(g.Dirs.Root, ia, cfg.Version)
		if err := pkicmn.WriteToFile(buf.Bytes(), file, 0644); err != nil {
			return serrors.WrapStr("unable to write AS config", err, "ia", ia, "file", file)
		}
	}
	return nil
}

func (g topoGen) genASCert(ia, issuer addr.IA) conf.AS {
	sigKey, encKey, revKey := scrypto.KeyVersion(1), scrypto.KeyVersion(1), scrypto.KeyVersion(1)
	cfg := conf.AS{
		Description:          fmt.Sprintf("AS certificate %s", ia),
		Version:              1,
		SigningKeyVersion:    &sigKey,
		EncryptionKeyVersion: &encKey,
		RevocationKeyVersion: &revKey,
		IssuerIA:             issuer,
		IssuerCertVersion:    1,
		OptDistPoints:        []addr.IA{},
		Validity:             g.Validity,
	}
	return cfg
}

// topoFile is used to parse the topology description.
type topoFile struct {
	ASes map[addr.IA]asEntry `yaml:"ASes"`
}

func (t topoFile) ISDs() map[addr.ISD]struct{} {
	m := make(map[addr.ISD]struct{})
	for ia := range t.ASes {
		m[ia.I] = struct{}{}
	}
	return m
}

func (t topoFile) Primaries(isd addr.ISD) primaryASes {
	var primaries primaryASes
	for ia, entry := range t.ASes {
		if ia.I == isd && entry.Primary() {
			primaries = append(primaries, primaryAS{AS: ia.A, asEntry: entry})
		}
	}
	return primaries
}

type asEntry struct {
	Authoritative bool    `yaml:"authoritative"`
	Core          bool    `yaml:"core"`
	Issuing       bool    `yaml:"issuing"`
	Voting        bool    `yaml:"voting"`
	Issuer        addr.IA `yaml:"cert_issuer"`
}

func (e asEntry) Primary() bool {
	return e.Authoritative || e.Core || e.Issuing || e.Voting
}

type primaryAS struct {
	AS addr.AS
	asEntry
}

func (p primaryAS) ToConf() conf.Primary {
	cp := conf.Primary{}
	if p.Authoritative {
		cp.Attributes = append(cp.Attributes, trc.Authoritative)
	}
	if p.Core {
		cp.Attributes = append(cp.Attributes, trc.Core)
	}
	if p.Issuing {
		iss := scrypto.KeyVersion(1)
		cp.Attributes = append(cp.Attributes, trc.Issuing)
		cp.IssuingKeyVersion = &iss
	}
	if p.Voting {
		on, off := scrypto.KeyVersion(1), scrypto.KeyVersion(1)
		cp.Attributes = append(cp.Attributes, trc.Voting)
		cp.VotingOnlineKeyVersion = &on
		cp.VotingOfflineKeyVersion = &off
	}
	return cp
}

type primaryASes []primaryAS

func (p primaryASes) Voters() int {
	voters := 0
	for _, pAS := range p {
		if pAS.Voting {
			voters++
		}
	}
	return voters
}
<commit_msg>SPKI: tmpl error if no voting/issuing AS is specified (#3596)<commit_after>// Copyright 2019 Anapaya Systems
//
// 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 tmpl

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"

	"github.com/scionproto/scion/go/lib/addr"
	"github.com/scionproto/scion/go/lib/scrypto"
	"github.com/scionproto/scion/go/lib/scrypto/cert"
	"github.com/scionproto/scion/go/lib/scrypto/trc"
	"github.com/scionproto/scion/go/lib/serrors"
	"github.com/scionproto/scion/go/tools/scion-pki/internal/conf"
	"github.com/scionproto/scion/go/tools/scion-pki/internal/pkicmn"
)

var (
	notBefore   uint32
	rawValidity string
)

type topoGen struct {
	Dirs     pkicmn.Dirs
	Validity conf.Validity
}

func (g topoGen) Run(topo topoFile) error {
	if err := g.setupDirs(topo); err != nil {
		return serrors.WrapStr("unable to setup dirs", err)
	}
	trcs, err := g.genTRCs(topo)
	if err != nil {
		return serrors.WrapStr("unable to generate TRC configs", err)
	}
	if err := g.genKeys(topo, trcs); err != nil {
		return serrors.WrapStr("unable to generate key configs", err)
	}
	if err := g.genCerts(topo); err != nil {
		return serrors.WrapStr("unable to generate certificate configs", err)
	}
	pkicmn.QuietPrint("Generated all configuration files\n")
	return nil
}

func (g topoGen) setupDirs(topo topoFile) error {
	for isd := range topo.ISDs() {
		dir := filepath.Dir(conf.TRCFile(g.Dirs.Root, isd, 1))
		if err := os.MkdirAll(dir, 0755); err != nil {
			return serrors.WrapStr("unable to make ISD directory", err, "isd", isd, "dir", dir)
		}
	}
	for ia := range topo.ASes {
		if err := os.MkdirAll(filepath.Dir(conf.KeysFile(g.Dirs.Root, ia)), 0755); err != nil {
			return serrors.WrapStr("unable to make AS directory", err, "ia", ia)
		}
	}
	return nil
}

func (g topoGen) genTRCs(topo topoFile) (map[addr.ISD]conf.TRC, error) {
	trcs := make(map[addr.ISD]conf.TRC)
	for isd := range topo.ISDs() {
		cfg, err := g.genTRC(isd, topo)
		if err != nil {
			return nil, serrors.WithCtx(err, "isd", isd)
		}
		trcs[isd] = cfg
		var buf bytes.Buffer
		if err := trcs[isd].Encode(&buf); err != nil {
			return nil, serrors.WithCtx(err, "isd", isd)
		}
		file := conf.TRCFile(g.Dirs.Root, isd, trcs[isd].Version)
		if err := pkicmn.WriteToFile(buf.Bytes(), file, 0644); err != nil {
			return nil, serrors.WrapStr("unable to write TRC config", err, "isd", isd, "file", file)
		}
	}
	return trcs, nil
}

func (g topoGen) genTRC(isd addr.ISD, topo topoFile) (conf.TRC, error) {
	primaries := topo.Primaries(isd)
	if primaries.Count(trc.Voting) == 0 {
		return conf.TRC{}, serrors.New("no voting AS specified")
	}
	if primaries.Count(trc.Issuing) == 0 {
		return conf.TRC{}, serrors.New("no issuing AS specified")
	}
	reset := true
	cfg := conf.TRC{
		Description: fmt.Sprintf("ISD %d", isd),
		Version:     1,
		BaseVersion: 1,
		// XXX(roosd): Choose quorum according to your security needs.
		// This simply serves an example.
		VotingQuorum:      uint16(primaries.Count(trc.Voting)/2 + 1),
		TrustResetAllowed: &reset,
		Votes:             []addr.AS{},
		Validity:          g.Validity,
		PrimaryASes:       make(map[addr.AS]conf.Primary),
	}
	for _, primaryAS := range primaries {
		cfg.PrimaryASes[primaryAS.AS] = primaryAS.ToConf()
	}
	return cfg, nil
}

func (g topoGen) genKeys(topo topoFile, cfg map[addr.ISD]conf.TRC) error {
	for ia := range topo.ASes {
		keys := g.genASKeys(ia.A, cfg[ia.I])
		var buf bytes.Buffer
		if err := keys.Encode(&buf); err != nil {
			return serrors.WithCtx(err, "ia", ia)
		}
		file := conf.KeysFile(g.Dirs.Root, ia)
		if err := pkicmn.WriteToFile(buf.Bytes(), file, 0644); err != nil {
			return serrors.WrapStr("unable to write key config", err, "ia", ia, "file", file)
		}
	}
	return nil
}

func (g topoGen) genASKeys(as addr.AS, cfg conf.TRC) conf.Keys {
	keys := conf.Keys{
		Primary: make(map[trc.KeyType]map[scrypto.KeyVersion]conf.KeyMeta),
		Issuer:  make(map[cert.KeyType]map[scrypto.KeyVersion]conf.KeyMeta),
		AS: map[cert.KeyType]map[scrypto.KeyVersion]conf.KeyMeta{
			cert.SigningKey:    {1: {Algorithm: scrypto.Ed25519, Validity: g.Validity}},
			cert.RevocationKey: {1: {Algorithm: scrypto.Ed25519, Validity: g.Validity}},
			cert.EncryptionKey: {1: {Algorithm: scrypto.Curve25519xSalsa20Poly1305,
				Validity: g.Validity}},
		},
	}
	primary, ok := cfg.PrimaryASes[as]
	_ = ok
	if primary.Attributes.Contains(trc.Voting) {
		keys.Primary[trc.OnlineKey] = map[scrypto.KeyVersion]conf.KeyMeta{
			1: {Algorithm: scrypto.Ed25519, Validity: g.Validity},
		}
		keys.Primary[trc.OfflineKey] = map[scrypto.KeyVersion]conf.KeyMeta{
			1: {Algorithm: scrypto.Ed25519, Validity: g.Validity},
		}
	}
	if primary.Attributes.Contains(trc.Issuing) {
		keys.Primary[trc.IssuingKey] = map[scrypto.KeyVersion]conf.KeyMeta{
			1: {Algorithm: scrypto.Ed25519, Validity: g.Validity},
		}
		keys.Issuer[cert.IssuingKey] = map[scrypto.KeyVersion]conf.KeyMeta{
			1: {Algorithm: scrypto.Ed25519, Validity: g.Validity},
		}
	}
	return keys
}

func (g topoGen) genCerts(topo topoFile) error {
	if err := g.genIssuerCerts(topo); err != nil {
		return serrors.WrapStr("unable to generate issuer certificates", err)
	}
	if err := g.genASCerts(topo); err != nil {
		return serrors.WrapStr("unable to generate AS certificates", err)
	}
	return nil
}

func (g topoGen) genIssuerCerts(topo topoFile) error {
	for ia, entry := range topo.ASes {
		if !entry.Issuing {
			continue
		}
		cfg := g.genIssuerCert(ia)
		var buf bytes.Buffer
		if err := cfg.Encode(&buf); err != nil {
			return serrors.WithCtx(err, "ia", ia)
		}
		file := conf.IssuerFile(g.Dirs.Root, ia, cfg.Version)
		if err := pkicmn.WriteToFile(buf.Bytes(), file, 0644); err != nil {
			return serrors.WrapStr("unable to write issuer config", err, "ia", ia, "file", file)
		}
	}
	return nil
}

func (g topoGen) genIssuerCert(ia addr.IA) conf.Issuer {
	issKey := scrypto.KeyVersion(1)
	cfg := conf.Issuer{
		Description:          fmt.Sprintf("Issuer certificate %s", ia),
		Version:              1,
		IssuingKeyVersion:    &issKey,
		RevocationKeyVersion: nil,
		TRCVersion:           1,
		OptDistPoints:        []addr.IA{},
		Validity:             g.Validity,
	}
	return cfg
}

func (g topoGen) genASCerts(topo topoFile) error {
	for ia, entry := range topo.ASes {
		issuer := entry.Issuer
		if entry.Issuing {
			issuer = ia
		}
		cfg := g.genASCert(ia, issuer)
		var buf bytes.Buffer
		if err := cfg.Encode(&buf); err != nil {
			return serrors.WithCtx(err, "ia", ia)
		}
		file := conf.ASFile(g.Dirs.Root, ia, cfg.Version)
		if err := pkicmn.WriteToFile(buf.Bytes(), file, 0644); err != nil {
			return serrors.WrapStr("unable to write AS config", err, "ia", ia, "file", file)
		}
	}
	return nil
}

func (g topoGen) genASCert(ia, issuer addr.IA) conf.AS {
	sigKey, encKey, revKey := scrypto.KeyVersion(1), scrypto.KeyVersion(1), scrypto.KeyVersion(1)
	cfg := conf.AS{
		Description:          fmt.Sprintf("AS certificate %s", ia),
		Version:              1,
		SigningKeyVersion:    &sigKey,
		EncryptionKeyVersion: &encKey,
		RevocationKeyVersion: &revKey,
		IssuerIA:             issuer,
		IssuerCertVersion:    1,
		OptDistPoints:        []addr.IA{},
		Validity:             g.Validity,
	}
	return cfg
}

// topoFile is used to parse the topology description.
type topoFile struct {
	ASes map[addr.IA]asEntry `yaml:"ASes"`
}

func (t topoFile) ISDs() map[addr.ISD]struct{} {
	m := make(map[addr.ISD]struct{})
	for ia := range t.ASes {
		m[ia.I] = struct{}{}
	}
	return m
}

func (t topoFile) Primaries(isd addr.ISD) primaryASes {
	var primaries primaryASes
	for ia, entry := range t.ASes {
		if ia.I == isd && entry.Primary() {
			primaries = append(primaries, primaryAS{AS: ia.A, asEntry: entry})
		}
	}
	return primaries
}

type asEntry struct {
	Authoritative bool    `yaml:"authoritative"`
	Core          bool    `yaml:"core"`
	Issuing       bool    `yaml:"issuing"`
	Voting        bool    `yaml:"voting"`
	Issuer        addr.IA `yaml:"cert_issuer"`
}

func (e asEntry) Primary() bool {
	return e.Authoritative || e.Core || e.Issuing || e.Voting
}

type primaryAS struct {
	AS addr.AS
	asEntry
}

func (p primaryAS) ToConf() conf.Primary {
	cp := conf.Primary{}
	if p.Authoritative {
		cp.Attributes = append(cp.Attributes, trc.Authoritative)
	}
	if p.Core {
		cp.Attributes = append(cp.Attributes, trc.Core)
	}
	if p.Issuing {
		iss := scrypto.KeyVersion(1)
		cp.Attributes = append(cp.Attributes, trc.Issuing)
		cp.IssuingKeyVersion = &iss
	}
	if p.Voting {
		on, off := scrypto.KeyVersion(1), scrypto.KeyVersion(1)
		cp.Attributes = append(cp.Attributes, trc.Voting)
		cp.VotingOnlineKeyVersion = &on
		cp.VotingOfflineKeyVersion = &off
	}
	return cp
}

type primaryASes []primaryAS

func (p primaryASes) Count(attr trc.Attribute) int {
	c := 0
	for _, as := range p {
		switch {
		case attr == trc.Authoritative && as.Authoritative:
			c++
		case attr == trc.Core && as.Core:
			c++
		case attr == trc.Issuing && as.Issuing:
			c++
		case attr == trc.Voting && as.Voting:
			c++
		}
	}
	return c
}
<|endoftext|>


<commit_before>package aranGoConnection

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

// AranGoConnection represent to connection
type AranGoConnection struct {
	urlRoot   string
	jwtString string
}

// NewAranGoConnection creates a new instance of a AranGoDriver-Connection.
// Need a host (e.g. "http://localhost:8529/")
func NewAranGoConnection(host string) *AranGoConnection {
	return &AranGoConnection{host, ""}
}

// SetJwtKey sets the JWT-Token
func (connection *AranGoConnection) SetJwtKey(jwtString string) {
	connection.jwtString = jwtString
}

// Get creates a GET-Request
func (connection *AranGoConnection) Get(url string) (string, map[string]interface{}, error) {
	url = connection.urlRoot + url
	log.Println("GET:>", url)

	// build request
	req, err := http.NewRequest("GET", url, nil)
	failOnError(err, "Failed while build GET-request")

	return fireRequestAndUnmarshal(connection, req)
}

// Post creates a POST-Request
func (connection *AranGoConnection) Post(url string, object interface{}) (string, map[string]interface{}, error) {
	// marshal body
	jsonBody, err := json.Marshal(object)
	failOnError(err, "Cant marshal object")
	return connection.PostJSON(url, jsonBody)
}

// PostJSON creates a POST-Request with JSONbody
func (connection *AranGoConnection) PostJSON(url string, jsonBody []byte) (string, map[string]interface{}, error) {
	// build url
	url = connection.urlRoot + url
	log.Println("POST:>", url)

	// build request
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
	if err != nil {
		return "", nil, err
	}
	return fireRequestAndUnmarshal(connection, req)
}

// Put creates a PUT-Request
func (connection *AranGoConnection) Put(url string, object interface{}) (string, map[string]interface{}, error) {
	// marshal body
	jsonBody, err := json.Marshal(object)
	failOnError(err, "Cant marshal object")
	return connection.PutJSON(url, jsonBody)
}

// PutJSON creates a PUT-Request with a jsonString
func (connection *AranGoConnection) PutJSON(url string, jsonBody []byte) (string, map[string]interface{}, error) {
	// build url
	url = connection.urlRoot + url
	log.Println("PUT:>", url)

	// build request
	req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonBody))
	if err != nil {
		return "", nil, err
	}
	return fireRequestAndUnmarshal(connection, req)
}

// Delete creates a DELETE-request
func (connection *AranGoConnection) Delete(url string) (string, map[string]interface{}, error) {
	// build url
	url = connection.urlRoot + url
	log.Println("DELETE:>", url)

	// build request
	req, err := http.NewRequest("DELETE", url, nil)
	failOnError(err, "Failed while build DELETE-request")

	return fireRequestAndUnmarshal(connection, req)
}

// Patch creates a PATCH-request
func (connection *AranGoConnection) Patch(url string, object interface{}) (string, map[string]interface{}, error) {
	// marshal body
	jsonBody, err := json.Marshal(object)
	failOnError(err, "Cant marshal object")
	return connection.PatchJSON(url, jsonBody)
}

// PatchJSON creates a PATCH-request with JSONbody
func (connection *AranGoConnection) PatchJSON(url string, jsonBody []byte) (string, map[string]interface{}, error) {
	// build url
	url = connection.urlRoot + url
	log.Println("PATCH:>", url)

	// build request
	req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonBody))
	if err != nil {
		return "", nil, err
	}
	return fireRequestAndUnmarshal(connection, req)
}

func fireRequestAndUnmarshal(connection *AranGoConnection, request *http.Request) (string, map[string]interface{}, error) {
	// set headers
	request.Header.Set("Content-Type", "application/json")
	if &connection.jwtString != nil {
		request.Header.Set("Authorization", "Bearer "+connection.jwtString)
	}

	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return "", nil, err
	}

	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)

	// unmarshal to map
	var responseMap map[string]interface{}
	decoder := json.NewDecoder(strings.NewReader(string(body)))
	decoder.UseNumber()
	err = decoder.Decode(&responseMap)

	return string(body), responseMap, err
}

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}
<commit_msg>remove logging<commit_after>package aranGoConnection

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

// AranGoConnection represent to connection
type AranGoConnection struct {
	urlRoot   string
	jwtString string
}

// NewAranGoConnection creates a new instance of a AranGoDriver-Connection.
// Need a host (e.g. "http://localhost:8529/")
func NewAranGoConnection(host string) *AranGoConnection {
	return &AranGoConnection{host, ""}
}

// SetJwtKey sets the JWT-Token
func (connection *AranGoConnection) SetJwtKey(jwtString string) {
	connection.jwtString = jwtString
}

// Get creates a GET-Request
func (connection *AranGoConnection) Get(url string) (string, map[string]interface{}, error) {
	url = connection.urlRoot + url

	// build request
	req, err := http.NewRequest("GET", url, nil)
	failOnError(err, "Failed while build GET-request")

	return fireRequestAndUnmarshal(connection, req)
}

// Post creates a POST-Request
func (connection *AranGoConnection) Post(url string, object interface{}) (string, map[string]interface{}, error) {
	// marshal body
	jsonBody, err := json.Marshal(object)
	failOnError(err, "Cant marshal object")
	return connection.PostJSON(url, jsonBody)
}

// PostJSON creates a POST-Request with JSONbody
func (connection *AranGoConnection) PostJSON(url string, jsonBody []byte) (string, map[string]interface{}, error) {
	// build url
	url = connection.urlRoot + url

	// build request
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
	if err != nil {
		return "", nil, err
	}
	return fireRequestAndUnmarshal(connection, req)
}

// Put creates a PUT-Request
func (connection *AranGoConnection) Put(url string, object interface{}) (string, map[string]interface{}, error) {
	// marshal body
	jsonBody, err := json.Marshal(object)
	failOnError(err, "Cant marshal object")
	return connection.PutJSON(url, jsonBody)
}

// PutJSON creates a PUT-Request with a jsonString
func (connection *AranGoConnection) PutJSON(url string, jsonBody []byte) (string, map[string]interface{}, error) {
	// build url
	url = connection.urlRoot + url

	// build request
	req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonBody))
	if err != nil {
		return "", nil, err
	}
	return fireRequestAndUnmarshal(connection, req)
}

// Delete creates a DELETE-request
func (connection *AranGoConnection) Delete(url string) (string, map[string]interface{}, error) {
	// build url
	url = connection.urlRoot + url

	// build request
	req, err := http.NewRequest("DELETE", url, nil)
	failOnError(err, "Failed while build DELETE-request")

	return fireRequestAndUnmarshal(connection, req)
}

// Patch creates a PATCH-request
func (connection *AranGoConnection) Patch(url string, object interface{}) (string, map[string]interface{}, error) {
	// marshal body
	jsonBody, err := json.Marshal(object)
	failOnError(err, "Cant marshal object")
	return connection.PatchJSON(url, jsonBody)
}

// PatchJSON creates a PATCH-request with JSONbody
func (connection *AranGoConnection) PatchJSON(url string, jsonBody []byte) (string, map[string]interface{}, error) {
	// build url
	url = connection.urlRoot + url

	// build request
	req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonBody))
	if err != nil {
		return "", nil, err
	}
	return fireRequestAndUnmarshal(connection, req)
}

func fireRequestAndUnmarshal(connection *AranGoConnection, request *http.Request) (string, map[string]interface{}, error) {
	// set headers
	request.Header.Set("Content-Type", "application/json")
	if &connection.jwtString != nil {
		request.Header.Set("Authorization", "Bearer "+connection.jwtString)
	}

	client := &http.Client{}
	resp, err := client.Do(request)
	if err != nil {
		return "", nil, err
	}

	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)

	// unmarshal to map
	var responseMap map[string]interface{}
	decoder := json.NewDecoder(strings.NewReader(string(body)))
	decoder.UseNumber()
	err = decoder.Decode(&responseMap)

	return string(body), responseMap, err
}

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}
<|endoftext|>


<commit_before>package auth

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/rackspace/rack/commandoptions"
	"github.com/rackspace/rack/internal/github.com/Sirupsen/logrus"
	"github.com/rackspace/rack/internal/github.com/codegangsta/cli"
	"github.com/rackspace/rack/internal/github.com/rackspace/gophercloud"
	tokens2 "github.com/rackspace/rack/internal/github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
	"github.com/rackspace/rack/internal/github.com/rackspace/gophercloud/rackspace"
	"github.com/rackspace/rack/util"
)

var usernameAuthErrSlice = []string{"There are some required Rackspace Cloud credentials that we couldn't find.",
	"Here's what we have:",
	"%s",
	"and here's what we're missing:",
	"%s",
	"",
	"You can set any of these credentials in the following ways:",
	"- Run `rack configure` to interactively create a configuration file,",
	"- Specify it in the command as a flag (--username, --api-key), or",
	"- Export it as an environment variable (RS_USERNAME, RS_API_KEY).",
	"",
}

var tenantIDAuthErrSlice = []string{"There are some required Rackspace Cloud credentials that we couldn't find.",
	"Here's what we have:",
	"%s",
	"and here's what we're missing:",
	"%s",
	"",
	"You can set the missing credentials with command-line flags (--auth-token, --auth-tenant-id)",
	"",
}

// Err returns the custom error to print when authentication fails.
func Err(have map[string]commandoptions.Cred, want map[string]string, errMsg []string) error {
	haveString := ""
	for k, v := range have {
		haveString += fmt.Sprintf("%s: %s (from %s)\n", k, v.Value, v.From)
	}

	if len(want) > 0 {
		wantString := ""
		for k := range want {
			wantString += fmt.Sprintf("%s\n", k)
		}

		return fmt.Errorf(fmt.Sprintf(strings.Join(errMsg, "\n"), haveString, wantString))
	}

	return nil
}

// CredentialsResult holds the information acquired from looking for authentication
// credentials.
type CredentialsResult struct {
	AuthOpts *gophercloud.AuthOptions
	Region   string
	Have     map[string]commandoptions.Cred
	Want     map[string]string
}

func findAuthOpts(c *cli.Context, have map[string]commandoptions.Cred, want map[string]string) error {
	// use command-line options if available
	commandoptions.CLIopts(c, have, want)
	// are there any unset auth variables?
	if len(want) != 0 {
		// if so, look in config file
		err := commandoptions.ConfigFile(c, have, want)
		if err != nil {
			return err
		}
		// still unset auth variables?
		if len(want) != 0 {
			// if so, look in environment variables
			envvars(have, want)
		}
	}

	return nil
}

// reauthFunc is what the ServiceClient uses to re-authenticate.
func reauthFunc(pc *gophercloud.ProviderClient, ao gophercloud.AuthOptions) func() error {
	return func() error {
		return rackspace.AuthenticateV2(pc, ao)
	}
}

// NewClient creates and returns a Rackspace client for the given service.
func NewClient(c *cli.Context, serviceType string, logger *logrus.Logger, noCache bool, useServiceNet bool) (*gophercloud.ServiceClient, error) {
	// get the user's authentication credentials
	credsResult, err := Credentials(c, logger)
	if err != nil {
		return nil, err
	}

	logMsg := "Using public endpoint"
	urlType := gophercloud.AvailabilityPublic
	if useServiceNet {
		logMsg = "Using service net endpoint"
		urlType = gophercloud.AvailabilityInternal
	}
	logger.Infoln(logMsg)

	if noCache {
		return authFromScratch(credsResult, serviceType, urlType, logger)
	}

	ao := credsResult.AuthOpts
	region := credsResult.Region

	// form the cache key
	cacheKey := CacheKey(*ao, region, serviceType, urlType)
	// initialize cache
	cache := &Cache{}
	logger.Infof("Looking in the cache for cache key: %s\n", cacheKey)
	// get the value from the cache
	creds, err := cache.Value(cacheKey)
	// if there was an error accessing the cache or there was nothing in the cache,
	// authenticate from scratch
	if err == nil && creds != nil {
		// we successfully retrieved a value from the cache
		logger.Infof("Using token from cache: %s\n", creds.TokenID)
		pc, err := rackspace.NewClient(ao.IdentityEndpoint)
		if err == nil {
			pc.TokenID = creds.TokenID
			pc.ReauthFunc = reauthFunc(pc, *ao)
			pc.UserAgent.Prepend(util.UserAgent)
			pc.HTTPClient = newHTTPClient()
			return &gophercloud.ServiceClient{
				ProviderClient: pc,
				Endpoint:       creds.ServiceEndpoint,
			}, nil
		}
	} else {
		return authFromScratch(credsResult, serviceType, urlType, logger)
	}

	return nil, nil
}

func authFromScratch(credsResult *CredentialsResult, serviceType string, urlType gophercloud.Availability, logger *logrus.Logger) (*gophercloud.ServiceClient, error) {
	logger.Info("Not using cache; Authenticating from scratch.\n")

	ao := credsResult.AuthOpts
	region := credsResult.Region

	pc, err := rackspace.AuthenticatedClient(*ao)
	if err != nil {
		switch err.(type) {
		case *tokens2.ErrNoPassword:
			return nil, errors.New("Please supply an API key.")
		}
		return nil, err
	}
	pc.HTTPClient = newHTTPClient()
	var sc *gophercloud.ServiceClient
	switch serviceType {
	case "compute":
		sc, err = rackspace.NewComputeV2(pc, gophercloud.EndpointOpts{
			Region:       region,
			Availability: urlType,
		})
		break
	case "object-store":
		sc, err = rackspace.NewObjectStorageV1(pc, gophercloud.EndpointOpts{
			Region:       region,
			Availability: urlType,
		})
		break
	case "blockstorage":
		sc, err = rackspace.NewBlockStorageV1(pc, gophercloud.EndpointOpts{
			Region:       region,
			Availability: urlType,
		})
		break
	case "network":
		sc, err = rackspace.NewNetworkV2(pc, gophercloud.EndpointOpts{
			Region:       region,
			Availability: urlType,
		})
		break
	case "orchestration":
		sc, err = rackspace.NewOrchestrationV1(pc, gophercloud.EndpointOpts{
			Region:       region,
			Availability: urlType,
		})
		break
	}
	if err != nil {
		return nil, err
	}
	if sc == nil {
		return nil, fmt.Errorf("Unable to create service client: Unknown service type: %s\n", serviceType)
	}
	if sc.Endpoint == "/" {
		return nil, fmt.Errorf(strings.Join([]string{"You wanted to use service net for the %s request",
			"but the %s service doesn't have an internal URL.\n"}, " "), serviceType, serviceType)
	}
	logger.Debugf("Created %s service client: %+v", serviceType, sc)
	sc.UserAgent.Prepend(util.UserAgent)
	return sc, nil
}

// Credentials determines the appropriate authentication method for the user.
// It returns a gophercloud.AuthOptions object and a region.
//
// It will use command-line authentication parameters if available, then it will
// look for any unset parameters in the config file, and then finally in
// environment variables.
func Credentials(c *cli.Context, logger *logrus.Logger) (*CredentialsResult, error) {
	ao := &gophercloud.AuthOptions{
		AllowReauth: true,
	}

	have := make(map[string]commandoptions.Cred)

	// let's looks for a region and identity endpoint
	want := map[string]string{
		"auth-url": "",
		"region":   "",
	}

	err := findAuthOpts(c, have, want)
	if err != nil {
		return nil, err
	}

	// if the user didn't provide an auth URL, default to the Rackspace US endpoint
	if _, ok := have["auth-url"]; !ok || have["auth-url"].Value == "" {
		have["auth-url"] = commandoptions.Cred{Value: rackspace.RackspaceUSIdentity, From: "default value"}
		delete(want, "auth-url")
	}
	ao.IdentityEndpoint = have["auth-url"].Value

	// upper-case the region
	region := strings.ToUpper(have["region"].Value)
	delete(want, "region")

	// now we check for token authentication (only allowed via the command-line)
	want["auth-tenant-id"] = ""
	want["auth-token"] = ""
	commandoptions.CLIopts(c, have, want)

	// if a tenant ID was provided on the command-line, we don't bother checking for a
	// username or api key
	if have["auth-tenant-id"].Value != "" || have["auth-token"].Value != "" {
		if tenantID, ok := have["auth-tenant-id"]; ok {
			ao.TenantID = tenantID.Value
			ao.TokenID = have["auth-token"].Value
			delete(want, "auth-token")
		} else {
			return nil, Err(have, want, tenantIDAuthErrSlice)
		}
	} else {
		// otherwise, let's look for a username and API key
		want = map[string]string{
			"username": "",
			"api-key":  "",
		}
		err = findAuthOpts(c, have, want)
		if err != nil {
			return nil, err
		}
		if have["username"].Value != "" || have["api-key"].Value != "" {
			if username, ok := have["username"]; ok {
				ao.Username = username.Value
				ao.APIKey = have["api-key"].Value
				delete(want, "api-key")
			} else {
				return nil, Err(have, want, usernameAuthErrSlice)
			}
		} else {
			return nil, Err(have, want, usernameAuthErrSlice)
		}
	}

	if logger != nil {
		haveString := ""
		for k, v := range have {
			haveString += fmt.Sprintf("%s: %s (from %s)\n", k, v.Value, v.From)
		}
		logger.Infof("Authentication Credentials:\n%s\n", haveString)
	}

	credsResult := &CredentialsResult{
		AuthOpts: ao,
		Region:   region,
		Have:     have,
		Want:     want,
	}

	return credsResult, nil
}

// LogRoundTripper satisfies the http.RoundTripper interface and is used to
// customize the default Gophercloud RoundTripper to allow for logging.
type LogRoundTripper struct {
	Logger            *logrus.Logger
	rt                http.RoundTripper
	numReauthAttempts int
}

// newHTTPClient return a custom HTTP client that allows for logging relevant
// information before and after the HTTP request.
func newHTTPClient() http.Client {
	return http.Client{
		Transport: &LogRoundTripper{
			rt: http.DefaultTransport,
		},
	}
}

// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
	var err error

	if lrt.Logger.Level == logrus.DebugLevel && request.Body != nil {
		fmt.Println("Logging request body...")
		request.Body, err = lrt.logRequestBody(request.Body, request.Header)
		if err != nil {
			return nil, err
		}
	}

	info, err := json.MarshalIndent(request.Header, "", "  ")
	if err != nil {
		lrt.Logger.Debugf(fmt.Sprintf("Error logging request headers: %s\n", err))
	}
	lrt.Logger.Debugf("Request Headers: %+v\n", string(info))

	lrt.Logger.Infof("Request URL: %s\n", request.URL)

	response, err := lrt.rt.RoundTrip(request)
	if response == nil {
		return nil, err
	}

	if response.StatusCode == http.StatusUnauthorized {
		if lrt.numReauthAttempts == 3 {
			return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
		}
		lrt.numReauthAttempts++
	}

	lrt.Logger.Debugf("Response Status: %s\n", response.Status)

	info, err = json.MarshalIndent(response.Header, "", "  ")
	if err != nil {
		lrt.Logger.Debugf(fmt.Sprintf("Error logging response headers: %s\n", err))
	}
	lrt.Logger.Debugf("Response Headers: %+v\n", string(info))

	return response, err
}

func (lrt *LogRoundTripper) logRequestBody(original io.ReadCloser, headers http.Header) (io.ReadCloser, error) {
	defer original.Close()

	var bs bytes.Buffer
	_, err := io.Copy(&bs, original)
	if err != nil {
		return nil, err
	}

	contentType := headers.Get("Content-Type")
	if strings.HasPrefix(contentType, "application/json") {
		debugInfo := lrt.formatJSON(bs.Bytes())
		lrt.Logger.Debugf("Request Options: %s\n", debugInfo)
	} else {
		lrt.Logger.Debugf("Request Options: %s\n", bs.String())
	}

	return ioutil.NopCloser(strings.NewReader(bs.String())), nil
}

func (lrt *LogRoundTripper) formatJSON(raw []byte) string {
	var data map[string]interface{}

	err := json.Unmarshal(raw, &data)
	if err != nil {
		lrt.Logger.Debugf("Unable to parse JSON: %s\n\n", err)
		return string(raw)
	}

	pretty, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		lrt.Logger.Debugf("Unable to re-marshal JSON: %s\n", err)
		return string(raw)
	}

	return string(pretty)
}
<commit_msg>close body when done<commit_after>package auth

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/rackspace/rack/commandoptions"
	"github.com/rackspace/rack/internal/github.com/Sirupsen/logrus"
	"github.com/rackspace/rack/internal/github.com/codegangsta/cli"
	"github.com/rackspace/rack/internal/github.com/rackspace/gophercloud"
	tokens2 "github.com/rackspace/rack/internal/github.com/rackspace/gophercloud/openstack/identity/v2/tokens"
	"github.com/rackspace/rack/internal/github.com/rackspace/gophercloud/rackspace"
	"github.com/rackspace/rack/util"
)

var usernameAuthErrSlice = []string{"There are some required Rackspace Cloud credentials that we couldn't find.",
	"Here's what we have:",
	"%s",
	"and here's what we're missing:",
	"%s",
	"",
	"You can set any of these credentials in the following ways:",
	"- Run `rack configure` to interactively create a configuration file,",
	"- Specify it in the command as a flag (--username, --api-key), or",
	"- Export it as an environment variable (RS_USERNAME, RS_API_KEY).",
	"",
}

var tenantIDAuthErrSlice = []string{"There are some required Rackspace Cloud credentials that we couldn't find.",
	"Here's what we have:",
	"%s",
	"and here's what we're missing:",
	"%s",
	"",
	"You can set the missing credentials with command-line flags (--auth-token, --auth-tenant-id)",
	"",
}

// Err returns the custom error to print when authentication fails.
func Err(have map[string]commandoptions.Cred, want map[string]string, errMsg []string) error {
	haveString := ""
	for k, v := range have {
		haveString += fmt.Sprintf("%s: %s (from %s)\n", k, v.Value, v.From)
	}

	if len(want) > 0 {
		wantString := ""
		for k := range want {
			wantString += fmt.Sprintf("%s\n", k)
		}

		return fmt.Errorf(fmt.Sprintf(strings.Join(errMsg, "\n"), haveString, wantString))
	}

	return nil
}

// CredentialsResult holds the information acquired from looking for authentication
// credentials.
type CredentialsResult struct {
	AuthOpts *gophercloud.AuthOptions
	Region   string
	Have     map[string]commandoptions.Cred
	Want     map[string]string
}

func findAuthOpts(c *cli.Context, have map[string]commandoptions.Cred, want map[string]string) error {
	// use command-line options if available
	commandoptions.CLIopts(c, have, want)
	// are there any unset auth variables?
	if len(want) != 0 {
		// if so, look in config file
		err := commandoptions.ConfigFile(c, have, want)
		if err != nil {
			return err
		}
		// still unset auth variables?
		if len(want) != 0 {
			// if so, look in environment variables
			envvars(have, want)
		}
	}

	return nil
}

// reauthFunc is what the ServiceClient uses to re-authenticate.
func reauthFunc(pc *gophercloud.ProviderClient, ao gophercloud.AuthOptions) func() error {
	return func() error {
		return rackspace.AuthenticateV2(pc, ao)
	}
}

// NewClient creates and returns a Rackspace client for the given service.
func NewClient(c *cli.Context, serviceType string, logger *logrus.Logger, noCache bool, useServiceNet bool) (*gophercloud.ServiceClient, error) {
	// get the user's authentication credentials
	credsResult, err := Credentials(c, logger)
	if err != nil {
		return nil, err
	}

	logMsg := "Using public endpoint"
	urlType := gophercloud.AvailabilityPublic
	if useServiceNet {
		logMsg = "Using service net endpoint"
		urlType = gophercloud.AvailabilityInternal
	}
	logger.Infoln(logMsg)

	if noCache {
		return authFromScratch(credsResult, serviceType, urlType, logger)
	}

	ao := credsResult.AuthOpts
	region := credsResult.Region

	// form the cache key
	cacheKey := CacheKey(*ao, region, serviceType, urlType)
	// initialize cache
	cache := &Cache{}
	logger.Infof("Looking in the cache for cache key: %s\n", cacheKey)
	// get the value from the cache
	creds, err := cache.Value(cacheKey)
	// if there was an error accessing the cache or there was nothing in the cache,
	// authenticate from scratch
	if err == nil && creds != nil {
		// we successfully retrieved a value from the cache
		logger.Infof("Using token from cache: %s\n", creds.TokenID)
		pc, err := rackspace.NewClient(ao.IdentityEndpoint)
		if err == nil {
			pc.TokenID = creds.TokenID
			pc.ReauthFunc = reauthFunc(pc, *ao)
			pc.UserAgent.Prepend(util.UserAgent)
			pc.HTTPClient = newHTTPClient()
			return &gophercloud.ServiceClient{
				ProviderClient: pc,
				Endpoint:       creds.ServiceEndpoint,
			}, nil
		}
	} else {
		return authFromScratch(credsResult, serviceType, urlType, logger)
	}

	return nil, nil
}

func authFromScratch(credsResult *CredentialsResult, serviceType string, urlType gophercloud.Availability, logger *logrus.Logger) (*gophercloud.ServiceClient, error) {
	logger.Info("Not using cache; Authenticating from scratch.\n")

	ao := credsResult.AuthOpts
	region := credsResult.Region

	pc, err := rackspace.AuthenticatedClient(*ao)
	if err != nil {
		switch err.(type) {
		case *tokens2.ErrNoPassword:
			return nil, errors.New("Please supply an API key.")
		}
		return nil, err
	}
	pc.HTTPClient = newHTTPClient()
	var sc *gophercloud.ServiceClient
	switch serviceType {
	case "compute":
		sc, err = rackspace.NewComputeV2(pc, gophercloud.EndpointOpts{
			Region:       region,
			Availability: urlType,
		})
		break
	case "object-store":
		sc, err = rackspace.NewObjectStorageV1(pc, gophercloud.EndpointOpts{
			Region:       region,
			Availability: urlType,
		})
		break
	case "blockstorage":
		sc, err = rackspace.NewBlockStorageV1(pc, gophercloud.EndpointOpts{
			Region:       region,
			Availability: urlType,
		})
		break
	case "network":
		sc, err = rackspace.NewNetworkV2(pc, gophercloud.EndpointOpts{
			Region:       region,
			Availability: urlType,
		})
		break
	case "orchestration":
		sc, err = rackspace.NewOrchestrationV1(pc, gophercloud.EndpointOpts{
			Region:       region,
			Availability: urlType,
		})
		break
	}
	if err != nil {
		return nil, err
	}
	if sc == nil {
		return nil, fmt.Errorf("Unable to create service client: Unknown service type: %s\n", serviceType)
	}
	if sc.Endpoint == "/" {
		return nil, fmt.Errorf(strings.Join([]string{"You wanted to use service net for the %s request",
			"but the %s service doesn't have an internal URL.\n"}, " "), serviceType, serviceType)
	}
	logger.Debugf("Created %s service client: %+v", serviceType, sc)
	sc.UserAgent.Prepend(util.UserAgent)
	return sc, nil
}

// Credentials determines the appropriate authentication method for the user.
// It returns a gophercloud.AuthOptions object and a region.
//
// It will use command-line authentication parameters if available, then it will
// look for any unset parameters in the config file, and then finally in
// environment variables.
func Credentials(c *cli.Context, logger *logrus.Logger) (*CredentialsResult, error) {
	ao := &gophercloud.AuthOptions{
		AllowReauth: true,
	}

	have := make(map[string]commandoptions.Cred)

	// let's looks for a region and identity endpoint
	want := map[string]string{
		"auth-url": "",
		"region":   "",
	}

	err := findAuthOpts(c, have, want)
	if err != nil {
		return nil, err
	}

	// if the user didn't provide an auth URL, default to the Rackspace US endpoint
	if _, ok := have["auth-url"]; !ok || have["auth-url"].Value == "" {
		have["auth-url"] = commandoptions.Cred{Value: rackspace.RackspaceUSIdentity, From: "default value"}
		delete(want, "auth-url")
	}
	ao.IdentityEndpoint = have["auth-url"].Value

	// upper-case the region
	region := strings.ToUpper(have["region"].Value)
	delete(want, "region")

	// now we check for token authentication (only allowed via the command-line)
	want["auth-tenant-id"] = ""
	want["auth-token"] = ""
	commandoptions.CLIopts(c, have, want)

	// if a tenant ID was provided on the command-line, we don't bother checking for a
	// username or api key
	if have["auth-tenant-id"].Value != "" || have["auth-token"].Value != "" {
		if tenantID, ok := have["auth-tenant-id"]; ok {
			ao.TenantID = tenantID.Value
			ao.TokenID = have["auth-token"].Value
			delete(want, "auth-token")
		} else {
			return nil, Err(have, want, tenantIDAuthErrSlice)
		}
	} else {
		// otherwise, let's look for a username and API key
		want = map[string]string{
			"username": "",
			"api-key":  "",
		}
		err = findAuthOpts(c, have, want)
		if err != nil {
			return nil, err
		}
		if have["username"].Value != "" || have["api-key"].Value != "" {
			if username, ok := have["username"]; ok {
				ao.Username = username.Value
				ao.APIKey = have["api-key"].Value
				delete(want, "api-key")
			} else {
				return nil, Err(have, want, usernameAuthErrSlice)
			}
		} else {
			return nil, Err(have, want, usernameAuthErrSlice)
		}
	}

	if logger != nil {
		haveString := ""
		for k, v := range have {
			haveString += fmt.Sprintf("%s: %s (from %s)\n", k, v.Value, v.From)
		}
		logger.Infof("Authentication Credentials:\n%s\n", haveString)
	}

	credsResult := &CredentialsResult{
		AuthOpts: ao,
		Region:   region,
		Have:     have,
		Want:     want,
	}

	return credsResult, nil
}

// LogRoundTripper satisfies the http.RoundTripper interface and is used to
// customize the default Gophercloud RoundTripper to allow for logging.
type LogRoundTripper struct {
	Logger            *logrus.Logger
	rt                http.RoundTripper
	numReauthAttempts int
}

// newHTTPClient return a custom HTTP client that allows for logging relevant
// information before and after the HTTP request.
func newHTTPClient() http.Client {
	return http.Client{
		Transport: &LogRoundTripper{
			rt: http.DefaultTransport,
		},
	}
}

// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
	defer func() {
		if request.Body != nil {
			request.Body.Close()
		}
	}()

	var err error

	if lrt.Logger.Level == logrus.DebugLevel && request.Body != nil {
		fmt.Println("Logging request body...")
		request.Body, err = lrt.logRequestBody(request.Body, request.Header)
		if err != nil {
			return nil, err
		}
	}

	info, err := json.MarshalIndent(request.Header, "", "  ")
	if err != nil {
		lrt.Logger.Debugf(fmt.Sprintf("Error logging request headers: %s\n", err))
	}
	lrt.Logger.Debugf("Request Headers: %+v\n", string(info))

	lrt.Logger.Infof("Request URL: %s\n", request.URL)

	response, err := lrt.rt.RoundTrip(request)
	if response == nil {
		return nil, err
	}

	if response.StatusCode == http.StatusUnauthorized {
		if lrt.numReauthAttempts == 3 {
			return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
		}
		lrt.numReauthAttempts++
	}

	lrt.Logger.Debugf("Response Status: %s\n", response.Status)

	info, err = json.MarshalIndent(response.Header, "", "  ")
	if err != nil {
		lrt.Logger.Debugf(fmt.Sprintf("Error logging response headers: %s\n", err))
	}
	lrt.Logger.Debugf("Response Headers: %+v\n", string(info))

	return response, err
}

func (lrt *LogRoundTripper) logRequestBody(original io.ReadCloser, headers http.Header) (io.ReadCloser, error) {
	defer original.Close()

	var bs bytes.Buffer
	_, err := io.Copy(&bs, original)
	if err != nil {
		return nil, err
	}

	contentType := headers.Get("Content-Type")
	if strings.HasPrefix(contentType, "application/json") {
		debugInfo := lrt.formatJSON(bs.Bytes())
		lrt.Logger.Debugf("Request Options: %s\n", debugInfo)
	} else {
		lrt.Logger.Debugf("Request Options: %s\n", bs.String())
	}

	return ioutil.NopCloser(strings.NewReader(bs.String())), nil
}

func (lrt *LogRoundTripper) formatJSON(raw []byte) string {
	var data map[string]interface{}

	err := json.Unmarshal(raw, &data)
	if err != nil {
		lrt.Logger.Debugf("Unable to parse JSON: %s\n\n", err)
		return string(raw)
	}

	pretty, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		lrt.Logger.Debugf("Unable to re-marshal JSON: %s\n", err)
		return string(raw)
	}

	return string(pretty)
}
<|endoftext|>


<commit_before>// ----------------------------------------------------------------------------
//
//     ***     AUTO GENERATED CODE    ***    AUTO GENERATED CODE     ***
//
// ----------------------------------------------------------------------------
//
//     This file is automatically generated by Magic Modules and manual
//     changes will be clobbered when the file is regenerated.
//
//     Please read more about how to change this file in
//     .github/CONTRIBUTING.md.
//
// ----------------------------------------------------------------------------

package google

import (
	"reflect"

	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func GetAccessApprovalFolderSettingsCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := assetName(d, config, "//accessapproval.googleapis.com/folders/{{folder_id}}/accessApprovalSettings")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetAccessApprovalFolderSettingsApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "accessapproval.googleapis.com/FolderSettings",
			Resource: &AssetResource{
				Version:              "v1",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/accessapproval/v1/rest",
				DiscoveryName:        "FolderSettings",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetAccessApprovalFolderSettingsApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	obj := make(map[string]interface{})
	notificationEmailsProp, err := expandAccessApprovalFolderSettingsNotificationEmails(d.Get("notification_emails"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("notification_emails"); !isEmptyValue(reflect.ValueOf(notificationEmailsProp)) && (ok || !reflect.DeepEqual(v, notificationEmailsProp)) {
		obj["notificationEmails"] = notificationEmailsProp
	}
	enrolledServicesProp, err := expandAccessApprovalFolderSettingsEnrolledServices(d.Get("enrolled_services"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("enrolled_services"); !isEmptyValue(reflect.ValueOf(enrolledServicesProp)) && (ok || !reflect.DeepEqual(v, enrolledServicesProp)) {
		obj["enrolledServices"] = enrolledServicesProp
	}

	return obj, nil
}

func expandAccessApprovalFolderSettingsNotificationEmails(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandAccessApprovalFolderSettingsEnrolledServices(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	l := v.([]interface{})
	req := make([]interface{}, 0, len(l))
	for _, raw := range l {
		if raw == nil {
			continue
		}
		original := raw.(map[string]interface{})
		transformed := make(map[string]interface{})

		transformedCloudProduct, err := expandAccessApprovalFolderSettingsEnrolledServicesCloudProduct(original["cloud_product"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedCloudProduct); val.IsValid() && !isEmptyValue(val) {
			transformed["cloudProduct"] = transformedCloudProduct
		}

		transformedEnrollmentLevel, err := expandAccessApprovalFolderSettingsEnrolledServicesEnrollmentLevel(original["enrollment_level"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedEnrollmentLevel); val.IsValid() && !isEmptyValue(val) {
			transformed["enrollmentLevel"] = transformedEnrollmentLevel
		}

		req = append(req, transformed)
	}
	return req, nil
}

func expandAccessApprovalFolderSettingsEnrolledServicesCloudProduct(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandAccessApprovalFolderSettingsEnrolledServicesEnrollmentLevel(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}
<commit_msg>fix permadiff with new access approval cloud product mappings (#4065) (#544)<commit_after>// ----------------------------------------------------------------------------
//
//     ***     AUTO GENERATED CODE    ***    AUTO GENERATED CODE     ***
//
// ----------------------------------------------------------------------------
//
//     This file is automatically generated by Magic Modules and manual
//     changes will be clobbered when the file is regenerated.
//
//     Please read more about how to change this file in
//     .github/CONTRIBUTING.md.
//
// ----------------------------------------------------------------------------

package google

import (
	"bytes"
	"fmt"
	"reflect"
	"strings"

	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

var accessApprovalCloudProductMapping = map[string]string{
	"appengine.googleapis.com": "App Engine",
	"bigquery.googleapis.com":  "BigQuery",
	"bigtable.googleapis.com":  "Cloud Bigtable",
	"cloudkms.googleapis.com":  "Cloud Key Management Service",
	"compute.googleapis.com":   "Compute Engine",
	"dataflow.googleapis.com":  "Cloud Dataflow",
	"iam.googleapis.com":       "Cloud Identity and Access Management",
	"pubsub.googleapis.com":    "Cloud Pub/Sub",
	"storage.googleapis.com":   "Cloud Storage",
}

func accessApprovalEnrolledServicesHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	cp := m["cloud_product"].(string)
	if n, ok := accessApprovalCloudProductMapping[cp]; ok {
		cp = n
	}
	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(cp))) // ToLower just in case
	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["enrollment_level"].(string))))
	return hashcode(buf.String())
}

func GetAccessApprovalFolderSettingsCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := assetName(d, config, "//accessapproval.googleapis.com/folders/{{folder_id}}/accessApprovalSettings")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetAccessApprovalFolderSettingsApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "accessapproval.googleapis.com/FolderSettings",
			Resource: &AssetResource{
				Version:              "v1",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/accessapproval/v1/rest",
				DiscoveryName:        "FolderSettings",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetAccessApprovalFolderSettingsApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	obj := make(map[string]interface{})
	notificationEmailsProp, err := expandAccessApprovalFolderSettingsNotificationEmails(d.Get("notification_emails"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("notification_emails"); !isEmptyValue(reflect.ValueOf(notificationEmailsProp)) && (ok || !reflect.DeepEqual(v, notificationEmailsProp)) {
		obj["notificationEmails"] = notificationEmailsProp
	}
	enrolledServicesProp, err := expandAccessApprovalFolderSettingsEnrolledServices(d.Get("enrolled_services"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("enrolled_services"); !isEmptyValue(reflect.ValueOf(enrolledServicesProp)) && (ok || !reflect.DeepEqual(v, enrolledServicesProp)) {
		obj["enrolledServices"] = enrolledServicesProp
	}

	return obj, nil
}

func expandAccessApprovalFolderSettingsNotificationEmails(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	return v, nil
}

func expandAccessApprovalFolderSettingsEnrolledServices(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	v = v.(*schema.Set).List()
	l := v.([]interface{})
	req := make([]interface{}, 0, len(l))
	for _, raw := range l {
		if raw == nil {
			continue
		}
		original := raw.(map[string]interface{})
		transformed := make(map[string]interface{})

		transformedCloudProduct, err := expandAccessApprovalFolderSettingsEnrolledServicesCloudProduct(original["cloud_product"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedCloudProduct); val.IsValid() && !isEmptyValue(val) {
			transformed["cloudProduct"] = transformedCloudProduct
		}

		transformedEnrollmentLevel, err := expandAccessApprovalFolderSettingsEnrolledServicesEnrollmentLevel(original["enrollment_level"], d, config)
		if err != nil {
			return nil, err
		} else if val := reflect.ValueOf(transformedEnrollmentLevel); val.IsValid() && !isEmptyValue(val) {
			transformed["enrollmentLevel"] = transformedEnrollmentLevel
		}

		req = append(req, transformed)
	}
	return req, nil
}

func expandAccessApprovalFolderSettingsEnrolledServicesCloudProduct(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandAccessApprovalFolderSettingsEnrolledServicesEnrollmentLevel(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}
<|endoftext|>


<commit_before>package audio

import (
	"errors"
	"math"
)

var (
	// ErrInvalidBuffer is a generic error returned when trying to read/write to an invalid buffer.
	ErrInvalidBuffer = errors.New("invalid buffer")
)

// Format is a high level representation of the underlying data.
type Format struct {
	// NumChannels is the number of channels contained in the data
	NumChannels int
	// SampleRate is the sampling rate in Hz
	SampleRate int
}

// Buffer is the representation of an audio buffer.
type Buffer interface {
	// PCMFormat is the format of buffer (describing the buffer content/format).
	PCMFormat() *Format
	// NumFrames returns the number of frames contained in the buffer.
	NumFrames() int
	// AsFloatBuffer returns a float 64 buffer from this buffer.
	AsFloatBuffer() *FloatBuffer
	// AsFloat32Buffer returns a float 32 buffer from this buffer.
	AsFloat32Buffer() *Float32Buffer
	// AsIntBuffer returns an int buffer from this buffer.
	AsIntBuffer() *IntBuffer
	// Clone creates a clean clone that can be modified without
	// changing the source buffer.
	Clone() Buffer
}

// FloatBuffer is an audio buffer with its PCM data formatted as float64.
type FloatBuffer struct {
	// Format is the representation of the underlying data format
	Format *Format
	// Data is the buffer PCM data as floats
	Data []float64
}

// PCMFormat returns the buffer format information.
func (buf *FloatBuffer) PCMFormat() *Format { return buf.Format }

// AsFloatBuffer implements the Buffer interface and returns itself.
func (buf *FloatBuffer) AsFloatBuffer() *FloatBuffer { return buf }

// AsFloat32Buffer implements the Buffer interface and returns a float 32 version of itself.
func (buf *FloatBuffer) AsFloat32Buffer() *Float32Buffer {
	newB := &Float32Buffer{}
	newB.Data = make([]float32, len(buf.Data))
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = float32(buf.Data[i])
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// AsIntBuffer returns a copy of this buffer but with data truncated to Ints.
func (buf *FloatBuffer) AsIntBuffer() *IntBuffer {
	newB := &IntBuffer{}
	newB.Data = make([]int, len(buf.Data))
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = int(buf.Data[i])
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// Clone creates a clean clone that can be modified without
// changing the source buffer.
func (buf *FloatBuffer) Clone() Buffer {
	if buf == nil {
		return nil
	}
	newB := &FloatBuffer{}
	newB.Data = make([]float64, len(buf.Data))
	copy(newB.Data, buf.Data)
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// NumFrames returns the number of frames contained in the buffer.
func (buf *FloatBuffer) NumFrames() int {
	if buf == nil || buf.Format == nil {
		return 0
	}
	numChannels := buf.Format.NumChannels
	if numChannels == 0 {
		numChannels = 1
	}

	return len(buf.Data) / numChannels
}

// Float32Buffer is an audio buffer with its PCM data formatted as float32.
type Float32Buffer struct {
	// Format is the representation of the underlying data format
	Format *Format
	// Data is the buffer PCM data as floats
	Data []float32
}

// PCMFormat returns the buffer format information.
func (buf *Float32Buffer) PCMFormat() *Format { return buf.Format }

// AsFloatBuffer implements the Buffer interface and returns a float64 version of itself.
func (buf *Float32Buffer) AsFloatBuffer() *FloatBuffer {
	newB := &FloatBuffer{}
	newB.Data = make([]float64, len(buf.Data))
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = float64(buf.Data[i])
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// AsFloat32Buffer implements the Buffer interface and returns itself.
func (buf *Float32Buffer) AsFloat32Buffer() *Float32Buffer { return buf }

// AsIntBuffer returns a copy of this buffer but with data truncated to Ints.
func (buf *Float32Buffer) AsIntBuffer() *IntBuffer {
	newB := &IntBuffer{}
	newB.Data = make([]int, len(buf.Data))
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = int(buf.Data[i])
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// Clone creates a clean clone that can be modified without
// changing the source buffer.
func (buf *Float32Buffer) Clone() Buffer {
	if buf == nil {
		return nil
	}
	newB := &Float32Buffer{}
	newB.Data = make([]float32, len(buf.Data))
	copy(newB.Data, buf.Data)
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// NumFrames returns the number of frames contained in the buffer.
func (buf *Float32Buffer) NumFrames() int {
	if buf == nil || buf.Format == nil {
		return 0
	}
	numChannels := buf.Format.NumChannels
	if numChannels == 0 {
		numChannels = 1
	}

	return len(buf.Data) / numChannels
}

// IntBuffer is an audio buffer with its PCM data formatted as int.
type IntBuffer struct {
	// Format is the representation of the underlying data format
	Format *Format
	// Data is the buffer PCM data as ints
	Data []int
}

// PCMFormat returns the buffer format information.
func (buf *IntBuffer) PCMFormat() *Format { return buf.Format }

// AsFloatBuffer returns a copy of this buffer but with data converted to floats.
func (buf *IntBuffer) AsFloatBuffer() *FloatBuffer {
	newB := &FloatBuffer{}
	newB.Data = make([]float64, len(buf.Data))
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = float64(buf.Data[i])
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// AsFloat32Buffer returns a copy of this buffer but with data converted to float 32.
func (buf *IntBuffer) AsFloat32Buffer() *Float32Buffer {
	newB := &Float32Buffer{}
	newB.Data = make([]float32, len(buf.Data))
	max := 0
	// hack to handle different bit depths without having the information
	bitDepth := 2.0
	for _, s := range buf.Data {
		if s > max {
			max = s
		}
	}
	// greater than int16, expecting int32
	if max > 32767 {
		bitDepth = 4.0
	}
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = float32(float64(int16(buf.Data[i])) / math.Pow(bitDepth, 8*2-1))
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// AsIntBuffer implements the Buffer interface and returns itself.
func (buf *IntBuffer) AsIntBuffer() *IntBuffer { return buf }

// NumFrames returns the number of frames contained in the buffer.
func (buf *IntBuffer) NumFrames() int {
	if buf == nil || buf.Format == nil {
		return 0
	}
	numChannels := buf.Format.NumChannels
	if numChannels == 0 {
		numChannels = 1
	}

	return len(buf.Data) / numChannels
}

// Clone creates a clean clone that can be modified without
// changing the source buffer.
func (buf *IntBuffer) Clone() Buffer {
	if buf == nil {
		return nil
	}
	newB := &IntBuffer{}
	newB.Data = make([]int, len(buf.Data))
	copy(newB.Data, buf.Data)
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}
<commit_msg>better support for float conversion of ints with different bit depths<commit_after>package audio

import (
	"errors"
	"math"
)

var (
	// ErrInvalidBuffer is a generic error returned when trying to read/write to an invalid buffer.
	ErrInvalidBuffer = errors.New("invalid buffer")
)

// Format is a high level representation of the underlying data.
type Format struct {
	// NumChannels is the number of channels contained in the data
	NumChannels int
	// SampleRate is the sampling rate in Hz
	SampleRate int
}

// Buffer is the representation of an audio buffer.
type Buffer interface {
	// PCMFormat is the format of buffer (describing the buffer content/format).
	PCMFormat() *Format
	// NumFrames returns the number of frames contained in the buffer.
	NumFrames() int
	// AsFloatBuffer returns a float 64 buffer from this buffer.
	AsFloatBuffer() *FloatBuffer
	// AsFloat32Buffer returns a float 32 buffer from this buffer.
	AsFloat32Buffer() *Float32Buffer
	// AsIntBuffer returns an int buffer from this buffer.
	AsIntBuffer() *IntBuffer
	// Clone creates a clean clone that can be modified without
	// changing the source buffer.
	Clone() Buffer
}

// FloatBuffer is an audio buffer with its PCM data formatted as float64.
type FloatBuffer struct {
	// Format is the representation of the underlying data format
	Format *Format
	// Data is the buffer PCM data as floats
	Data []float64
}

// PCMFormat returns the buffer format information.
func (buf *FloatBuffer) PCMFormat() *Format { return buf.Format }

// AsFloatBuffer implements the Buffer interface and returns itself.
func (buf *FloatBuffer) AsFloatBuffer() *FloatBuffer { return buf }

// AsFloat32Buffer implements the Buffer interface and returns a float 32 version of itself.
func (buf *FloatBuffer) AsFloat32Buffer() *Float32Buffer {
	newB := &Float32Buffer{}
	newB.Data = make([]float32, len(buf.Data))
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = float32(buf.Data[i])
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// AsIntBuffer returns a copy of this buffer but with data truncated to Ints.
func (buf *FloatBuffer) AsIntBuffer() *IntBuffer {
	newB := &IntBuffer{}
	newB.Data = make([]int, len(buf.Data))
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = int(buf.Data[i])
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// Clone creates a clean clone that can be modified without
// changing the source buffer.
func (buf *FloatBuffer) Clone() Buffer {
	if buf == nil {
		return nil
	}
	newB := &FloatBuffer{}
	newB.Data = make([]float64, len(buf.Data))
	copy(newB.Data, buf.Data)
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// NumFrames returns the number of frames contained in the buffer.
func (buf *FloatBuffer) NumFrames() int {
	if buf == nil || buf.Format == nil {
		return 0
	}
	numChannels := buf.Format.NumChannels
	if numChannels == 0 {
		numChannels = 1
	}

	return len(buf.Data) / numChannels
}

// Float32Buffer is an audio buffer with its PCM data formatted as float32.
type Float32Buffer struct {
	// Format is the representation of the underlying data format
	Format *Format
	// Data is the buffer PCM data as floats
	Data []float32
}

// PCMFormat returns the buffer format information.
func (buf *Float32Buffer) PCMFormat() *Format { return buf.Format }

// AsFloatBuffer implements the Buffer interface and returns a float64 version of itself.
func (buf *Float32Buffer) AsFloatBuffer() *FloatBuffer {
	newB := &FloatBuffer{}
	newB.Data = make([]float64, len(buf.Data))
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = float64(buf.Data[i])
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// AsFloat32Buffer implements the Buffer interface and returns itself.
func (buf *Float32Buffer) AsFloat32Buffer() *Float32Buffer { return buf }

// AsIntBuffer returns a copy of this buffer but with data truncated to Ints.
func (buf *Float32Buffer) AsIntBuffer() *IntBuffer {
	newB := &IntBuffer{}
	newB.Data = make([]int, len(buf.Data))
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = int(buf.Data[i])
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// Clone creates a clean clone that can be modified without
// changing the source buffer.
func (buf *Float32Buffer) Clone() Buffer {
	if buf == nil {
		return nil
	}
	newB := &Float32Buffer{}
	newB.Data = make([]float32, len(buf.Data))
	copy(newB.Data, buf.Data)
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// NumFrames returns the number of frames contained in the buffer.
func (buf *Float32Buffer) NumFrames() int {
	if buf == nil || buf.Format == nil {
		return 0
	}
	numChannels := buf.Format.NumChannels
	if numChannels == 0 {
		numChannels = 1
	}

	return len(buf.Data) / numChannels
}

// IntBuffer is an audio buffer with its PCM data formatted as int.
type IntBuffer struct {
	// Format is the representation of the underlying data format
	Format *Format
	// Data is the buffer PCM data as ints
	Data []int
	// SourceBitDepth helps us know if the source was encoded on
	// 1 (int8), 2 (int16), 3(int24), 4(int32), 8(int64) bytes.
	SourceBitDepth int
}

// PCMFormat returns the buffer format information.
func (buf *IntBuffer) PCMFormat() *Format { return buf.Format }

// AsFloatBuffer returns a copy of this buffer but with data converted to floats.
func (buf *IntBuffer) AsFloatBuffer() *FloatBuffer {
	newB := &FloatBuffer{}
	newB.Data = make([]float64, len(buf.Data))
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = float64(buf.Data[i])
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// AsFloat32Buffer returns a copy of this buffer but with data converted to float 32.
func (buf *IntBuffer) AsFloat32Buffer() *Float32Buffer {
	newB := &Float32Buffer{}
	newB.Data = make([]float32, len(buf.Data))
	max := 0
	// hack to handle different bit depths without having the information
	bitDepth := buf.SourceBitDepth
	if bitDepth == 0 {
		for _, s := range buf.Data {
			if s > max {
				max = s
			}
		}
		bitDepth = 2
		// greater than int16, expecting int24
		if max > 32767 {
			bitDepth = 3
		}
		// int 32
		if max > 8388607 {
			bitDepth = 4
		}
		// int 64
		if max > 4294967295 {
			bitDepth = 8
		}
	}
	factor := math.Pow(2, 8*float64(bitDepth)-1)
	for i := 0; i < len(buf.Data); i++ {
		newB.Data[i] = float32(float64(int64(buf.Data[i])) / factor)
	}
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}

// AsIntBuffer implements the Buffer interface and returns itself.
func (buf *IntBuffer) AsIntBuffer() *IntBuffer { return buf }

// NumFrames returns the number of frames contained in the buffer.
func (buf *IntBuffer) NumFrames() int {
	if buf == nil || buf.Format == nil {
		return 0
	}
	numChannels := buf.Format.NumChannels
	if numChannels == 0 {
		numChannels = 1
	}

	return len(buf.Data) / numChannels
}

// Clone creates a clean clone that can be modified without
// changing the source buffer.
func (buf *IntBuffer) Clone() Buffer {
	if buf == nil {
		return nil
	}
	newB := &IntBuffer{}
	newB.Data = make([]int, len(buf.Data))
	copy(newB.Data, buf.Data)
	newB.Format = &Format{
		NumChannels: buf.Format.NumChannels,
		SampleRate:  buf.Format.SampleRate,
	}
	return newB
}
<|endoftext|>