Browse Source

initial commit

Christoph Hack 13 năm trước cách đây
commit
6dc5ab0edc
6 tập tin đã thay đổi với 1059 bổ sung0 xóa
  1. 45 0
      README.md
  2. 228 0
      convert.go
  3. 348 0
      gocql.go
  4. 145 0
      gocql_test.go
  5. 192 0
      uuid/uuid.go
  6. 101 0
      uuid/uuid_test.go

+ 45 - 0
README.md

@@ -0,0 +1,45 @@
+gocql
+=====
+
+The gocql package provides a database/sql driver for CQL, the Cassandra
+query language.
+
+This package requires a recent version of Cassandra (≥ 1.2) that supports
+CQL 3.0 and the new native protocol. The native protocol is still considered
+beta and must be enabled manually in Cassandra 1.2 by setting
+"start_native_transport" to true in conf/cassandra.yaml.
+
+Installation
+------------
+
+    go get github.com/tux21b/gocql
+
+Example
+-------
+
+    db, err := sql.Open("gocql", "localhost:8000 keyspace=system")
+    // ...
+    rows, err := db.Query("SELECT keyspace_name FROM schema_keyspaces")
+    // ...
+    for rows.Next() {
+         var keyspace string
+         err = rows.Scan(&keyspace)
+         // ...
+         fmt.Println(keyspace)
+    }
+    if err := rows.Err(); err != nil {
+        // ...
+    }
+
+Features
+--------
+
+* Modern Cassandra client that is based on Cassandra's new native protocol
+* Compatible with Go's `database/sql` package
+* Built-In support for UUIDs (version 1 and 4)
+
+License
+-------
+
+> Copyright (c) 2012 by Christoph Hack <christoph@tux21b.org>  
+> All rights reserved. Distributed under the Simplified BSD License.

+ 228 - 0
convert.go

@@ -0,0 +1,228 @@
+// Copyright (c) 2012 by Christoph Hack <christoph@tux21b.org>
+// All rights reserved. Distributed under the Simplified BSD License.
+
+package gocql
+
+import (
+	"database/sql/driver"
+	"encoding/binary"
+	"fmt"
+	"github.com/tux21b/gocql/uuid"
+	"math"
+	"strconv"
+	"time"
+)
+
+const (
+	typeCustom    uint16 = 0x0000
+	typeAscii     uint16 = 0x0001
+	typeBigInt    uint16 = 0x0002
+	typeBlob      uint16 = 0x0003
+	typeBool      uint16 = 0x0004
+	typeCounter   uint16 = 0x0005
+	typeDecimal   uint16 = 0x0006
+	typeDouble    uint16 = 0x0007
+	typeFloat     uint16 = 0x0008
+	typeInt       uint16 = 0x0009
+	typeText      uint16 = 0x000A
+	typeTimestamp uint16 = 0x000B
+	typeUUID      uint16 = 0x000C
+	typeVarchar   uint16 = 0x000D
+	typeVarint    uint16 = 0x000E
+	typeTimeUUID  uint16 = 0x000F
+	typeList      uint16 = 0x0020
+	typeMap       uint16 = 0x0021
+	typeSet       uint16 = 0x0022
+)
+
+func decode(b []byte, t uint16) driver.Value {
+	switch t {
+	case typeBool:
+		if len(b) >= 1 && b[0] != 0 {
+			return true
+		}
+		return false
+	case typeBlob:
+		return b
+	case typeVarchar, typeText, typeAscii:
+		return string(b)
+	case typeInt:
+		return int64(int32(binary.BigEndian.Uint32(b)))
+	case typeFloat:
+		return float64(math.Float32frombits(binary.BigEndian.Uint32(b)))
+	case typeDouble:
+		return math.Float64frombits(binary.BigEndian.Uint64(b))
+	case typeTimestamp:
+		t := int64(binary.BigEndian.Uint64(b))
+		sec := t / 1000
+		nsec := (t - sec*1000) * 1000000
+		return time.Unix(sec, nsec)
+	case typeUUID, typeTimeUUID:
+		return uuid.FromBytes(b)
+	default:
+		fmt.Println("unsupprted type")
+		panic("unsupported type")
+	}
+	return b
+}
+
+type columnEncoder struct {
+	columnTypes []uint16
+}
+
+func (e *columnEncoder) ColumnConverter(idx int) ValueConverter {
+	switch e.columnTypes[idx] {
+	case typeInt:
+		return ValueConverter(encInt)
+	case typeFloat:
+		return ValueConverter(encFloat)
+	case typeDouble:
+		return ValueConverter(encDouble)
+	case typeBool:
+		return ValueConverter(encBool)
+	case typeVarchar, typeText, typeAscii:
+		return ValueConverter(encVarchar)
+	case typeBlob:
+		return ValueConverter(encBlob)
+	case typeTimestamp:
+		return ValueConverter(encTimestamp)
+	case typeUUID, typeTimeUUID:
+		return ValueConverter(encUUID)
+	}
+	panic("not implemented")
+}
+
+type ValueConverter func(v interface{}) (driver.Value, error)
+
+func (vc ValueConverter) ConvertValue(v interface{}) (driver.Value, error) {
+	return vc(v)
+}
+
+func encBool(v interface{}) (driver.Value, error) {
+	b, err := driver.Bool.ConvertValue(v)
+	if err != nil {
+		return nil, err
+	}
+	if b.(bool) {
+		return []byte{1}, nil
+	}
+	return []byte{0}, nil
+}
+
+func encInt(v interface{}) (driver.Value, error) {
+	x, err := driver.Int32.ConvertValue(v)
+	if err != nil {
+		return nil, err
+	}
+	b := make([]byte, 4)
+	binary.BigEndian.PutUint32(b, uint32(x.(int64)))
+	return b, nil
+}
+
+func encVarchar(v interface{}) (driver.Value, error) {
+	x, err := driver.String.ConvertValue(v)
+	if err != nil {
+		return nil, err
+	}
+	return []byte(x.(string)), nil
+}
+
+func encFloat(v interface{}) (driver.Value, error) {
+	x, err := driver.DefaultParameterConverter.ConvertValue(v)
+	if err != nil {
+		return nil, err
+	}
+	var f float64
+	switch x := x.(type) {
+	case float64:
+		f = x
+	case int64:
+		f = float64(x)
+	case []byte:
+		if f, err = strconv.ParseFloat(string(x), 64); err != nil {
+			return nil, err
+		}
+	default:
+		return nil, fmt.Errorf("can not convert %T to float64", x)
+	}
+	b := make([]byte, 4)
+	binary.BigEndian.PutUint32(b, math.Float32bits(float32(f)))
+	return b, nil
+}
+
+func encDouble(v interface{}) (driver.Value, error) {
+	x, err := driver.DefaultParameterConverter.ConvertValue(v)
+	if err != nil {
+		return nil, err
+	}
+	var f float64
+	switch x := x.(type) {
+	case float64:
+		f = x
+	case int64:
+		f = float64(x)
+	case []byte:
+		if f, err = strconv.ParseFloat(string(x), 64); err != nil {
+			return nil, err
+		}
+	default:
+		return nil, fmt.Errorf("can not convert %T to float64", x)
+	}
+	b := make([]byte, 8)
+	binary.BigEndian.PutUint64(b, math.Float64bits(f))
+	return b, nil
+}
+
+func encTimestamp(v interface{}) (driver.Value, error) {
+	x, err := driver.DefaultParameterConverter.ConvertValue(v)
+	if err != nil {
+		return nil, err
+	}
+	var millis int64
+	switch x := x.(type) {
+	case time.Time:
+		x = x.In(time.UTC)
+		millis = x.UnixNano() / 1000000
+	default:
+		return nil, fmt.Errorf("can not convert %T to a timestamp", x)
+	}
+	b := make([]byte, 8)
+	binary.BigEndian.PutUint64(b, uint64(millis))
+	return b, nil
+}
+
+func encBlob(v interface{}) (driver.Value, error) {
+	x, err := driver.DefaultParameterConverter.ConvertValue(v)
+	if err != nil {
+		return nil, err
+	}
+	var b []byte
+	switch x := x.(type) {
+	case string:
+		b = []byte(x)
+	case []byte:
+		b = x
+	default:
+		return nil, fmt.Errorf("can not convert %T to a []byte", x)
+	}
+	return b, nil
+}
+
+func encUUID(v interface{}) (driver.Value, error) {
+	var u uuid.UUID
+	switch v := v.(type) {
+	case string:
+		var err error
+		u, err = uuid.ParseUUID(v)
+		if err != nil {
+			return nil, err
+		}
+	case []byte:
+		u = uuid.FromBytes(v)
+	case uuid.UUID:
+		u = v
+	default:
+		return nil, fmt.Errorf("can not convert %T to a UUID", v)
+	}
+	return u.Bytes(), nil
+}

+ 348 - 0
gocql.go

@@ -0,0 +1,348 @@
+// Copyright (c) 2012 by Christoph Hack <christoph@tux21b.org>
+// All rights reserved. Distributed under the Simplified BSD License.
+
+// The gocql package provides a database/sql driver for CQL, the Cassandra
+// query language.
+//
+// This package requires a recent version of Cassandra (≥ 1.2) that supports
+// CQL 3.0 and the new native protocol. The native protocol is still considered
+// beta and must be enabled manually in Cassandra 1.2 by setting
+// "start_native_transport" to true in conf/cassandra.yaml.
+//
+// Example Usage:
+//
+//     db, err := sql.Open("gocql", "localhost:8000 keyspace=system")
+//     // ...
+//     rows, err := db.Query("SELECT keyspace_name FROM schema_keyspaces")
+//     // ...
+//     for rows.Next() {
+//          var keyspace string
+//          err = rows.Scan(&keyspace)
+//          // ...
+//          fmt.Println(keyspace)
+//     }
+//     if err := rows.Err(); err != nil {
+//         // ...
+//     }
+//
+package gocql
+
+import (
+	"database/sql"
+	"database/sql/driver"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"net"
+	"strings"
+)
+
+const (
+	protoRequest  byte = 0x01
+	protoResponse byte = 0x81
+
+	opError        byte = 0x00
+	opStartup      byte = 0x01
+	opReady        byte = 0x02
+	opAuthenticate byte = 0x03
+	opCredentials  byte = 0x04
+	opOptions      byte = 0x05
+	opSupported    byte = 0x06
+	opQuery        byte = 0x07
+	opResult       byte = 0x08
+	opPrepare      byte = 0x09
+	opExecute      byte = 0x0A
+
+	flagCompressed byte = 0x01
+)
+
+type drv struct{}
+
+func (d drv) Open(name string) (driver.Conn, error) {
+	return Open(name)
+}
+
+type connection struct {
+	c net.Conn
+}
+
+func Open(name string) (*connection, error) {
+	parts := strings.Split(name, " ")
+	address := ""
+	if len(parts) >= 1 {
+		address = parts[0]
+	}
+	c, err := net.Dial("tcp", address)
+	if err != nil {
+		return nil, err
+	}
+	cn := &connection{c: c}
+
+	version := []byte("3.0.0")
+	body := make([]byte, 4+len(version))
+	binary.BigEndian.PutUint16(body[0:2], uint16(len(version)))
+	copy(body[2:len(body)-2], version)
+	binary.BigEndian.PutUint16(body[len(body)-2:], 0)
+	if err := cn.send(opStartup, body); err != nil {
+		return nil, err
+	}
+
+	opcode, body, err := cn.recv()
+	if err != nil {
+		return nil, err
+	}
+	if opcode != opReady {
+		return nil, fmt.Errorf("connection not ready")
+	}
+
+	keyspace := ""
+	for i := 1; i < len(parts); i++ {
+		switch {
+		case parts[i] == "":
+			continue
+		case strings.HasPrefix(parts[i], "keyspace="):
+			keyspace = parts[i][9:]
+		default:
+			return nil, fmt.Errorf("unsupported option %q", parts[i])
+		}
+	}
+	if keyspace != "" {
+		st, err := cn.Prepare(fmt.Sprintf("USE %s", keyspace))
+		if err != nil {
+			return nil, err
+		}
+		if _, err = st.Exec([]driver.Value{}); err != nil {
+			return nil, err
+		}
+	}
+
+	return cn, nil
+}
+
+func (cn *connection) send(opcode byte, body []byte) error {
+	frame := make([]byte, len(body)+8)
+	frame[0] = protoRequest
+	frame[1] = 0
+	frame[2] = 0
+	frame[3] = opcode
+	binary.BigEndian.PutUint32(frame[4:8], uint32(len(body)))
+	copy(frame[8:], body)
+	if _, err := cn.c.Write(frame); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (cn *connection) recv() (byte, []byte, error) {
+	header := make([]byte, 8)
+	if _, err := cn.c.Read(header); err != nil {
+		return 0, nil, err
+	}
+	opcode := header[3]
+	length := binary.BigEndian.Uint32(header[4:8])
+	var body []byte
+	if length > 0 {
+		body = make([]byte, length)
+		if _, err := cn.c.Read(body); err != nil {
+			return 0, nil, err
+		}
+	}
+	if opcode == opError {
+		code := binary.BigEndian.Uint32(body[0:4])
+		msglen := binary.BigEndian.Uint16(body[4:6])
+		msg := string(body[6 : 6+msglen])
+		return opcode, body, Error{Code: int(code), Msg: msg}
+	}
+	return opcode, body, nil
+}
+
+func (cn *connection) Begin() (driver.Tx, error) {
+	return cn, nil
+}
+
+func (cn *connection) Commit() error {
+	return nil
+}
+
+func (cn *connection) Close() error {
+	return cn.c.Close()
+}
+
+func (cn *connection) Rollback() error {
+	return nil
+}
+
+func (cn *connection) Prepare(query string) (driver.Stmt, error) {
+	body := make([]byte, len(query)+4)
+	binary.BigEndian.PutUint32(body[0:4], uint32(len(query)))
+	copy(body[4:], []byte(query))
+	if err := cn.send(opPrepare, body); err != nil {
+		return nil, err
+	}
+	opcode, body, err := cn.recv()
+	if err != nil {
+		return nil, err
+	}
+	if opcode != opResult || binary.BigEndian.Uint32(body) != 4 {
+		return nil, fmt.Errorf("expected prepared result")
+	}
+	prepared := int(binary.BigEndian.Uint32(body[4:]))
+	columns, meta, _ := parseMeta(body[8:])
+	return &statement{cn: cn, query: query,
+		prepared: prepared, columns: columns, meta: meta}, nil
+}
+
+type statement struct {
+	cn       *connection
+	query    string
+	prepared int
+	columns  []string
+	meta     []uint16
+}
+
+func (s *statement) Close() error {
+	return nil
+}
+
+func (st *statement) ColumnConverter(idx int) driver.ValueConverter {
+	return (&columnEncoder{st.meta}).ColumnConverter(idx)
+}
+
+func (st *statement) NumInput() int {
+	return len(st.columns)
+}
+
+func parseMeta(body []byte) ([]string, []uint16, int) {
+	flags := binary.BigEndian.Uint32(body)
+	globalTableSpec := flags&1 == 1
+	columnCount := int(binary.BigEndian.Uint32(body[4:]))
+	i := 8
+	if globalTableSpec {
+		l := int(binary.BigEndian.Uint16(body[i:]))
+		keyspace := string(body[i+2 : i+2+l])
+		i += 2 + l
+		l = int(binary.BigEndian.Uint16(body[i:]))
+		tablename := string(body[i+2 : i+2+l])
+		i += 2 + l
+		_, _ = keyspace, tablename
+	}
+	columns := make([]string, columnCount)
+	meta := make([]uint16, columnCount)
+	for c := 0; c < columnCount; c++ {
+		l := int(binary.BigEndian.Uint16(body[i:]))
+		columns[c] = string(body[i+2 : i+2+l])
+		i += 2 + l
+		meta[c] = binary.BigEndian.Uint16(body[i:])
+		i += 2
+	}
+	return columns, meta, i
+}
+
+func (st *statement) exec(v []driver.Value) error {
+	sz := 8
+	for i := range v {
+		if b, ok := v[i].([]byte); ok {
+			sz += len(b) + 4
+		}
+	}
+	body, p := make([]byte, sz), 6
+	binary.BigEndian.PutUint32(body, uint32(st.prepared))
+	binary.BigEndian.PutUint16(body[4:], uint16(len(v)))
+	for i := range v {
+		b, ok := v[i].([]byte)
+		if !ok {
+			return fmt.Errorf("unsupported type %T at column %d", v[i], i)
+		}
+		binary.BigEndian.PutUint32(body[p:], uint32(len(b)))
+		copy(body[p+4:], b)
+		p += 4 + len(b)
+	}
+	if err := st.cn.send(opExecute, body); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (st *statement) Exec(v []driver.Value) (driver.Result, error) {
+	if err := st.exec(v); err != nil {
+		return nil, err
+	}
+	opcode, body, err := st.cn.recv()
+	if err != nil {
+		return nil, err
+	}
+	_, _ = opcode, body
+	return nil, nil
+}
+
+func (st *statement) Query(v []driver.Value) (driver.Rows, error) {
+	if err := st.exec(v); err != nil {
+		return nil, err
+	}
+	opcode, body, err := st.cn.recv()
+	if err != nil {
+		return nil, err
+	}
+	kind := binary.BigEndian.Uint32(body[0:4])
+	if opcode != opResult || kind != 2 {
+		return nil, fmt.Errorf("expected rows as result")
+	}
+	columns, meta, n := parseMeta(body[4:])
+	i := n + 4
+	rows := &rows{
+		columns: columns,
+		meta:    meta,
+		numRows: int(binary.BigEndian.Uint32(body[i:])),
+	}
+	i += 4
+	rows.body = body[i:]
+	return rows, nil
+}
+
+type rows struct {
+	columns []string
+	meta    []uint16
+	body    []byte
+	row     int
+	numRows int
+}
+
+func (r *rows) Close() error {
+	return nil
+}
+
+func (r *rows) Columns() []string {
+	return r.columns
+}
+
+func (r *rows) Next(values []driver.Value) error {
+	if r.row >= r.numRows {
+		return io.EOF
+	}
+	for column := 0; column < len(r.columns); column++ {
+		n := int(binary.BigEndian.Uint32(r.body))
+		r.body = r.body[4:]
+		if n >= 0 {
+			values[column] = decode(r.body[:n], r.meta[column])
+			r.body = r.body[n:]
+		} else {
+			fmt.Println(column, n)
+			values[column] = nil
+		}
+	}
+	r.row++
+	return nil
+}
+
+type Error struct {
+	Code int
+	Msg  string
+}
+
+func (e Error) Error() string {
+	return e.Msg
+}
+
+func init() {
+	sql.Register("gocql", &drv{})
+}

+ 145 - 0
gocql_test.go

@@ -0,0 +1,145 @@
+// Copyright (c) 2012 by Christoph Hack <christoph@tux21b.org>
+// All rights reserved. Distributed under the Simplified BSD License.
+
+package gocql
+
+import (
+	"bytes"
+	"database/sql"
+	"github.com/tux21b/gocql/uuid"
+	"testing"
+	"time"
+)
+
+func TestSimple(t *testing.T) {
+	db, err := sql.Open("gocql", "localhost:8000 keyspace=system")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	rows, err := db.Query("SELECT keyspace_name FROM schema_keyspaces")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for rows.Next() {
+		var keyspace string
+		if err := rows.Scan(&keyspace); err != nil {
+			t.Fatal(err)
+		}
+	}
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+type Page struct {
+	Title      string
+	RevID      uuid.UUID
+	Body       string
+	Hits       int
+	Protected  bool
+	Modified   time.Time
+	Attachment []byte
+}
+
+var pages = []*Page{
+	&Page{"Frontpage", uuid.TimeUUID(), "Hello world!", 0, false,
+		time.Date(2012, 8, 20, 10, 0, 0, 0, time.UTC), nil},
+	&Page{"Frontpage", uuid.TimeUUID(), "Hello modified world!", 0, false,
+		time.Date(2012, 8, 22, 10, 0, 0, 0, time.UTC), []byte("img data\x00")},
+	&Page{"LoremIpsum", uuid.TimeUUID(), "Lorem ipsum dolor sit amet", 12,
+		true, time.Date(2012, 8, 22, 10, 0, 8, 0, time.UTC), nil},
+}
+
+func TestWiki(t *testing.T) {
+	db, err := sql.Open("gocql", "localhost:8000")
+	if err != nil {
+		t.Fatal(err)
+	}
+	db.Exec("DROP KEYSPACE gocql_wiki")
+	if _, err := db.Exec(`CREATE KEYSPACE gocql_wiki
+        WITH strategy_class = 'SimpleStrategy'
+        AND strategy_options:replication_factor = 1`); err != nil {
+		t.Fatal(err)
+	}
+	if _, err := db.Exec("USE gocql_wiki"); err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err := db.Exec(`CREATE TABLE page (
+        title varchar,
+        revid timeuuid,
+        body varchar,
+        hits int,
+        protected boolean,
+        modified timestamp,
+        attachment blob,
+        PRIMARY KEY (title, revid)
+        )`); err != nil {
+		t.Fatal(err)
+	}
+	for _, p := range pages {
+		if _, err := db.Exec(`INSERT INTO page (title, revid, body, hits,
+            protected, modified, attachment) VALUES (?, ?, ?, ?, ?, ?, ?);`,
+			p.Title, p.RevID, p.Body, p.Hits, p.Protected, p.Modified,
+			p.Attachment); err != nil {
+			t.Fatal(err)
+		}
+	}
+
+	rowCount := 0
+	rows, err := db.Query(`SELECT revid FROM page`)
+	if err != nil {
+		t.Fatal(err)
+	}
+	for rows.Next() {
+		var revid uuid.UUID
+		if err := rows.Scan(&revid); err != nil {
+			t.Fatal(err)
+		}
+		rowCount++
+	}
+	if err := rows.Err(); err != nil {
+		t.Fatal(err)
+	}
+	if rowCount != len(pages) {
+		t.Fatalf("expected %d rows, got %d", len(pages), rowCount)
+	}
+
+	for _, page := range pages {
+		row := db.QueryRow(`SELECT title, revid, body, hits, protected,
+            modified, attachment
+            FROM page WHERE title = ? AND revid = ?`, page.Title, page.RevID)
+		var p Page
+		err := row.Scan(&p.Title, &p.RevID, &p.Body, &p.Hits, &p.Protected,
+			&p.Modified, &p.Attachment)
+		if err != nil {
+			t.Fatal(err)
+		}
+		p.Modified = p.Modified.In(time.UTC)
+		if page.Title != p.Title || page.RevID != p.RevID ||
+			page.Body != p.Body || page.Modified != p.Modified ||
+			page.Hits != p.Hits || page.Protected != p.Protected ||
+			!bytes.Equal(page.Attachment, p.Attachment) {
+			t.Errorf("expected %#v got %#v", *page, p)
+		}
+	}
+
+	row := db.QueryRow(`SELECT title, revid, body, hits, protected,
+        modified, attachment
+        FROM page WHERE title = ? ORDER BY revid DESC`, "Frontpage")
+	var p Page
+	if err := row.Scan(&p.Title, &p.RevID, &p.Body, &p.Hits, &p.Protected,
+		&p.Modified, &p.Attachment); err != nil {
+		t.Error(err)
+	}
+	p.Modified = p.Modified.In(time.UTC)
+	page := pages[1]
+	if page.Title != p.Title || page.RevID != p.RevID ||
+		page.Body != p.Body || page.Modified != p.Modified ||
+		page.Hits != p.Hits || page.Protected != p.Protected ||
+		!bytes.Equal(page.Attachment, p.Attachment) {
+		t.Errorf("expected %#v got %#v", *page, p)
+	}
+}

+ 192 - 0
uuid/uuid.go

@@ -0,0 +1,192 @@
+// Copyright (c) 2012 by Christoph Hack <christoph@tux21b.org>
+// All rights reserved. Distributed under the Simplified BSD License.
+
+// The uuid package can be used to generate and parse universally unique
+// identifiers, a standardized format in the form of a 128 bit number.
+//
+// http://tools.ietf.org/html/rfc4122
+package uuid
+
+import (
+	"crypto/rand"
+	"fmt"
+	"io"
+	"net"
+	"time"
+)
+
+type UUID [16]byte
+
+var hardwareAddr []byte
+
+const (
+	VariantNCSCompat = 0
+	VariantIETF      = 2
+	VariantMicrosoft = 6
+	VariantFuture    = 7
+)
+
+func init() {
+	if interfaces, err := net.Interfaces(); err == nil {
+		for _, i := range interfaces {
+			if i.Flags&net.FlagLoopback == 0 && len(i.HardwareAddr) > 0 {
+				hardwareAddr = i.HardwareAddr
+				break
+			}
+		}
+	}
+	if hardwareAddr == nil {
+		// If we failed to obtain the MAC address of the current computer,
+		// we will use a randomly generated 6 byte sequence instead and set
+		// the multicast bit as recommended in RFC 4122.
+		hardwareAddr = make([]byte, 6)
+		_, err := io.ReadFull(rand.Reader, hardwareAddr)
+		if err != nil {
+			panic(err)
+		}
+		hardwareAddr[0] = hardwareAddr[0] | 0x01
+	}
+}
+
+// ParseUUID parses a 32 digit hexadecimal number (that might contain hypens)
+// represanting an UUID.
+func ParseUUID(input string) (UUID, error) {
+	var u UUID
+	j := 0
+	for _, r := range input {
+		switch {
+		case r == '-' && j&1 == 0:
+			continue
+		case r >= '0' && r <= '9' && j < 32:
+			u[j/2] |= byte(r-'0') << uint(4-j&1*4)
+		case r >= 'a' && r <= 'f' && j < 32:
+			u[j/2] |= byte(r-'a'+10) << uint(4-j&1*4)
+		case r >= 'A' && r <= 'F' && j < 32:
+			u[j/2] |= byte(r-'A'+10) << uint(4-j&1*4)
+		default:
+			return UUID{}, fmt.Errorf("invalid UUID %q", input)
+		}
+		j += 1
+	}
+	if j != 32 {
+		return UUID{}, fmt.Errorf("invalid UUID %q", input)
+	}
+	return u, nil
+}
+
+// FromBytes converts a raw byte slice to an UUID. It will panic if the slice
+// isn't exactly 16 bytes long.
+func FromBytes(input []byte) UUID {
+	var u UUID
+	if len(input) != 16 {
+		panic("UUIDs must be exactly 16 bytes long")
+	}
+	copy(u[:], input)
+	return u
+}
+
+// RandomUUID generates a totally random UUID (version 4) as described in
+// RFC 4122.
+func RandomUUID() UUID {
+	var u UUID
+	io.ReadFull(rand.Reader, u[:])
+	u[6] &= 0x0F // clear version
+	u[6] |= 0x40 // set version to 4 (random uuid)
+	u[8] &= 0x3F // clear variant
+	u[8] |= 0x80 // set to IETF variant
+	return u
+}
+
+var timeBase = time.Date(1582, time.October, 15, 0, 0, 0, 0, time.UTC).Unix()
+
+// TimeUUID generates a new time based UUID (version 1) as described in RFC
+// 4122. This UUID contains the MAC address of the node that generated the
+// UUID, a timestamp and a sequence number.
+func TimeUUID() UUID {
+	var u UUID
+
+	now := time.Now().In(time.UTC)
+	t := uint64(now.Unix()-timeBase)*10000000 + uint64(now.Nanosecond()/100)
+	u[0], u[1], u[2], u[3] = byte(t>>24), byte(t>>16), byte(t>>8), byte(t)
+	u[4], u[5] = byte(t>>40), byte(t>>32)
+	u[6], u[7] = byte(t>>56)&0x0F, byte(t>>48)
+
+	var clockSeq [2]byte
+	io.ReadFull(rand.Reader, clockSeq[:])
+	u[8] = clockSeq[1]
+	u[9] = clockSeq[0]
+
+	copy(u[10:], hardwareAddr)
+
+	u[6] |= 0x10 // set version to 1 (time based uuid)
+	u[8] &= 0x3F // clear variant
+	u[8] |= 0x80 // set to IETF variant
+
+	return u
+}
+
+// String returns the UUID in it's canonical form, a 32 digit hexadecimal
+// number in the form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
+func (u UUID) String() string {
+	return fmt.Sprintf("%x-%x-%x-%x-%x",
+		u[0:4], u[4:6], u[6:8], u[8:10], u[10:16])
+}
+
+// Bytes returns the raw byte slice for this UUID. A UUID is always 128 bits
+// (16 bytes) long.
+func (u UUID) Bytes() []byte {
+	return u[:]
+}
+
+// Variant returns the variant of this UUID. This package will only generate
+// UUIDs in the IETF variant.
+func (u UUID) Variant() int {
+	x := u[8]
+	if x&0x80 == 0 {
+		return VariantNCSCompat
+	}
+	if x&0x40 == 0 {
+		return VariantIETF
+	}
+	if x&0x20 == 0 {
+		return VariantMicrosoft
+	}
+	return VariantFuture
+}
+
+// Version extracts the version of this UUID variant. The RFC 4122 describes
+// five kinds of UUIDs.
+func (u UUID) Version() int {
+	return int(u[6] & 0xF0 >> 4)
+}
+
+// Node extracts the MAC address of the node who generated this UUID. It will
+// return nil if the UUID is not a time based UUID (version 1).
+func (u UUID) Node() []byte {
+	if u.Version() != 1 {
+		return nil
+	}
+	return u[10:]
+}
+
+// Timestamp extracts the timestamp information from a time based UUID
+// (version 1).
+func (u UUID) Timestamp() uint64 {
+	if u.Version() != 1 {
+		return 0
+	}
+	return uint64(u[0])<<24 + uint64(u[1])<<16 + uint64(u[2])<<8 +
+		uint64(u[3]) + uint64(u[4])<<40 + uint64(u[5])<<32 +
+		uint64(u[7])<<48 + uint64(u[6]&0x0F)<<56
+}
+
+// Time is like Timestamp, except that it returns a time.Time.
+func (u UUID) Time() time.Time {
+	t := u.Timestamp()
+	if t == 0 {
+		return time.Time{}
+	}
+	sec := t / 10000000
+	nsec := t - sec
+	return time.Unix(int64(sec)+timeBase, int64(nsec))
+}

+ 101 - 0
uuid/uuid_test.go

@@ -0,0 +1,101 @@
+// Copyright (c) 2012 by Christoph Hack <christoph@tux21b.org>
+// All rights reserved. Distributed under the Simplified BSD License.
+
+package uuid
+
+import (
+	"bytes"
+	"testing"
+)
+
+func TestNil(t *testing.T) {
+	var uuid UUID
+	want, got := "00000000-0000-0000-0000-000000000000", uuid.String()
+	if want != got {
+		t.Fatalf("TestNil: expected %q got %q", want, got)
+	}
+}
+
+var tests = []struct {
+	input   string
+	variant int
+	version int
+}{
+	{"b4f00409-cef8-4822-802c-deb20704c365", VariantIETF, 4},
+	{"f81d4fae-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
+	{"00000000-7dec-11d0-a765-00a0c91e6bf6", VariantIETF, 1},
+	{"3051a8d7-aea7-1801-e0bf-bc539dd60cf3", VariantFuture, 1},
+	{"3051a8d7-aea7-2801-e0bf-bc539dd60cf3", VariantFuture, 2},
+	{"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 3},
+	{"3051a8d7-aea7-4801-e0bf-bc539dd60cf3", VariantFuture, 4},
+	{"3051a8d7-aea7-3801-e0bf-bc539dd60cf3", VariantFuture, 5},
+	{"d0e817e1-e4b1-1801-3fe6-b4b60ccecf9d", VariantNCSCompat, 0},
+	{"d0e817e1-e4b1-1801-bfe6-b4b60ccecf9d", VariantIETF, 1},
+	{"d0e817e1-e4b1-1801-dfe6-b4b60ccecf9d", VariantMicrosoft, 0},
+	{"d0e817e1-e4b1-1801-ffe6-b4b60ccecf9d", VariantFuture, 0},
+}
+
+func TestPredefined(t *testing.T) {
+	for i := range tests {
+		uuid, err := ParseUUID(tests[i].input)
+		if err != nil {
+			t.Errorf("ParseUUID #%d: %v", i, err)
+			continue
+		}
+
+		if str := uuid.String(); str != tests[i].input {
+			t.Errorf("String #%d: expected %q got %q", i, tests[i].input, str)
+			continue
+		}
+
+		if variant := uuid.Variant(); variant != tests[i].variant {
+			t.Errorf("Variant #%d: expected %d got %d", i, tests[i].variant, variant)
+		}
+
+		if tests[i].variant == VariantIETF {
+			if version := uuid.Version(); version != tests[i].version {
+				t.Errorf("Version #%d: expected %d got %d", i, tests[i].version, version)
+			}
+		}
+	}
+}
+
+func TestRandomUUID(t *testing.T) {
+	for i := 0; i < 20; i++ {
+		uuid := RandomUUID()
+
+		if variant := uuid.Variant(); variant != VariantIETF {
+			t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant)
+		}
+		if version := uuid.Version(); version != 4 {
+			t.Errorf("wrong version. expected %d got %d", 4, version)
+		}
+	}
+}
+
+func TestTimeUUID(t *testing.T) {
+	var node []byte
+	timestamp := uint64(0)
+	for i := 0; i < 20; i++ {
+		uuid := TimeUUID()
+
+		if variant := uuid.Variant(); variant != VariantIETF {
+			t.Errorf("wrong variant. expected %d got %d", VariantIETF, variant)
+		}
+		if version := uuid.Version(); version != 1 {
+			t.Errorf("wrong version. expected %d got %d", 1, version)
+		}
+
+		if n := uuid.Node(); !bytes.Equal(n, node) && i > 0 {
+			t.Errorf("wrong node. expected %x, got %x", node, n)
+		} else if i == 0 {
+			node = n
+		}
+
+		ts := uuid.Timestamp()
+		if ts < timestamp {
+			t.Errorf("timestamps must grow")
+		}
+		timestamp = ts
+	}
+}