func (sl *SnapLog) ChangeIndices(fieldName string) ([]int, error) {
	result := make([]int, 0, 100)
	field := sl.read.find(fieldName)
	if field == nil {
		return nil, errors.New("Field not found")
	}
	last := make([]byte, field.Size)
	var s Snapshot  
	for i := 0; i < sl.SnapCount(); i++ {
		offset := sl.bodyOffset + i*sl.read.Length
		begin := string(sl.raw[offset : offset+len(BEGIN_SNAP_DATA)])
		if begin != BEGIN_SNAP_DATA {
			return nil, nil
		}
		s.reset(sl.raw[offset+len(BEGIN_SNAP_DATA):offset+sl.read.Length], &sl.read)
		data := s.raw[field.Offset : field.Offset+field.Size]
		if bytes.Compare(data, last) != 0 {
			result = append(result, i)
		}
		last = data
	}
	return result, nil
}
ChangeIndices finds all snapshot indices where the specified field
changes value.
package web100
import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
)
type Saver interface {
	SetInt64(name string, value int64)
	SetString(name string, value string)
	SetBool(name string, value bool)
}
var CanonicalNames map[string]string
func init() {
	data, err := Asset("tcp-kis.txt")
	if err != nil {
		panic("tcp-kis.txt not found")
	}
	b := bytes.NewBuffer(data)
	CanonicalNames, err = ParseWeb100Definitions(b)
	if err != nil {
		panic("error parsing tcp-kis.txt")
	}
}
const (
	BEGIN_SNAP_DATA   = "----Begin-Snap-Data----\n"
	END_OF_HEADER     = "\x00----End-Of-Header---- -1 -1\n"
	GROUPNAME_LEN_MAX = 32
	VARNAME_LEN_MAX   = 32
)
type varType int
const (
	WEB100_TYPE_INTEGER varType = iota
	WEB100_TYPE_INTEGER32
	WEB100_TYPE_INET_ADDRESS_IPV4
	WEB100_TYPE_COUNTER32
	WEB100_TYPE_GAUGE32
	WEB100_TYPE_UNSIGNED32
	WEB100_TYPE_TIME_TICKS
	WEB100_TYPE_COUNTER64
	WEB100_TYPE_INET_PORT_NUMBER
	WEB100_TYPE_INET_ADDRESS
	WEB100_TYPE_INET_ADDRESS_IPV6
	WEB100_TYPE_STR32
	WEB100_TYPE_OCTET
	WEB100_NUM_TYPES
)
type addrType int
const (
	WEB100_ADDRTYPE_UNKNOWN addrType = iota
	WEB100_ADDRTYPE_IPV4
	WEB100_ADDRTYPE_IPV6
	WEB100_ADDRTYPE_DNS = 16
)
var web100Sizes = [WEB100_NUM_TYPES + 1]int{
	4,   
	4,   
	4,   
	4,   
	4,   
	4,   
	4,   
	8,   
	2,   
	17,  
	17,  
	32,  
	1,   
	0,
}
type Variable struct {
	Name   string   
	Offset int      
	Type   varType  
	Size   int      
}
func NewVariable(s string) (*Variable, error) {
	var name string
	var length, typ, offset int
	n, err := fmt.Sscanln(s, &name, &offset, &typ, &length)
	if err != nil {
		fmt.Printf("NewVariable Error %v, %d: %s\n", err, n, s)
		return nil, err
	}
	vt := varType(typ)
	if vt > WEB100_TYPE_OCTET || vt < WEB100_TYPE_INTEGER {
		return nil, fmt.Errorf("invalid type field: %d", typ)
	}
	if length != web100Sizes[vt] {
		return nil, fmt.Errorf("invalid length for %s field: %d",
			name, length)
	}
	return &Variable{name, offset, vt, length}, nil
}
func IPFromBytes(data []byte) (net.IP, error) {
	if len(data) != 17 {
		return net.IP{}, errors.New("Wrong number of bytes")
	}
	switch addrType(data[16]) {
	case WEB100_ADDRTYPE_IPV4:
		return net.IPv4(data[0], data[1], data[2], data[3]), nil
	case WEB100_ADDRTYPE_IPV6:
		return net.IP(data[:16]), nil
	case WEB100_ADDRTYPE_UNKNOWN:
		fallthrough
	default:
		return nil, errors.New("Invalid IP encoding")
	}
}
func (v *Variable) Save(data []byte, snapValues Saver) error {
	if v.Name[0] == '_' {
		return nil
	}
	canonicalName := v.Name
	if legacy, ok := CanonicalNames[canonicalName]; ok {
		canonicalName = legacy
	}
	switch v.Type {
	case WEB100_TYPE_INTEGER:
		fallthrough
	case WEB100_TYPE_INTEGER32:
		val := binary.LittleEndian.Uint32(data)
		if val >= 0x7FFFFFFF {
			snapValues.SetInt64(canonicalName, int64(val)-0x100000000)
		} else {
			snapValues.SetInt64(canonicalName, int64(val))
		}
	case WEB100_TYPE_INET_ADDRESS_IPV4:
		snapValues.SetString(canonicalName,
			fmt.Sprintf("%d.%d.%d.%d",
				data[0], data[1], data[2], data[3]))
	case WEB100_TYPE_COUNTER32:
		fallthrough
	case WEB100_TYPE_GAUGE32:
		fallthrough
	case WEB100_TYPE_UNSIGNED32:
		fallthrough
	case WEB100_TYPE_TIME_TICKS:
		snapValues.SetInt64(canonicalName, int64(binary.LittleEndian.Uint32(data)))
	case WEB100_TYPE_COUNTER64:
		snapValues.SetInt64(canonicalName, int64(binary.LittleEndian.Uint64(data)))
	case WEB100_TYPE_INET_PORT_NUMBER:
		snapValues.SetInt64(canonicalName, int64(binary.LittleEndian.Uint16(data)))
	case WEB100_TYPE_INET_ADDRESS:
		fallthrough
	case WEB100_TYPE_INET_ADDRESS_IPV6:
		ip, err := IPFromBytes(data)
		if err != nil {
			return err
		}
		snapValues.SetString(canonicalName, ip.String())
	case WEB100_TYPE_STR32:
		snapValues.SetString(canonicalName, strings.SplitN(string(data), "\000", 2)[0])
	case WEB100_TYPE_OCTET:
		snapValues.SetInt64(canonicalName, int64(data[0]))
	default:
		return errors.New("Invalid field type")
	}
	return nil
}
type fieldSet struct {
	Fields   []Variable
	FieldMap map[string]int  
	Length int
}
func (fs *fieldSet) find(name string) *Variable {
	index, ok := fs.FieldMap[name]
	if !ok {
		return nil
	}
	return &fs.Fields[index]
}
type connectionSpec struct {
	DestPort uint16
	SrcPort  uint16
	DestAddr []byte  
	SrcAddr  []byte
}
type SnapLog struct {
	raw []byte
	Version   string
	LogTime   uint32
	GroupName string
	connSpecOffset int  
	bodyOffset     int  
	spec           fieldSet
	read fieldSet
	tune fieldSet
	connSpec connectionSpec
}
func (sl *SnapLog) ConnectionSpecValues(saver Saver) {
	saver.SetInt64("local_af", int64(0))
	src := sl.connSpec.SrcAddr
	saver.SetString("local_ip", net.IPv4(src[0], src[1], src[2], src[3]).String())
	saver.SetInt64("local_port", int64(sl.connSpec.SrcPort))
	dst := sl.connSpec.DestAddr
	saver.SetString("remote_ip", net.IPv4(dst[0], dst[1], dst[2], dst[3]).String())
	saver.SetInt64("remote_port", int64(sl.connSpec.DestPort))
}
func (sl *SnapLog) SnapshotNumBytes() int {
	return sl.read.Length
}
func (sl *SnapLog) SnapshotNumFields() int {
	return len(sl.read.Fields)
}
func parseFields(buf *bytes.Buffer, preamble string, terminator string) (*fieldSet, error) {
	fields := new(fieldSet)
	fields.FieldMap = make(map[string]int)
	pre, err := buf.ReadString('\n')
	if err != nil {
		return nil, err
	}
	if pre != preamble {
		return nil, errors.New("Expected preamble: " +
			preamble[:len(preamble)-1] + " != " + pre[:len(pre)-1])
	}
	for {
		line, err := buf.ReadString('\n')
		if err != nil || len(line) > VARNAME_LEN_MAX+20 {
			if err == io.EOF {
				return nil, errors.New("Encountered EOF")
			}
			return nil, errors.New("Corrupted header")
		}
		if line == terminator {
			return fields, nil
		}
		v, err := NewVariable(line)
		if err != nil {
			return nil, err
		}
		if fields.Length != v.Offset {
			return nil, errors.New("Bad offset at " + line[:len(line)-2])
		}
		fields.FieldMap[v.Name] = len(fields.Fields)
		fields.Fields = append(fields.Fields, *v)
		fields.Length += v.Size
	}
}
func parseConnectionSpec(buf *bytes.Buffer) (connectionSpec, error) {
	raw := make([]byte, 16)
	n, err := buf.Read(raw)
	if err != nil || n < 16 {
		return connectionSpec{}, errors.New("Too few bytes for connection spec")
	}
	dstPort := binary.LittleEndian.Uint16(raw[0:2])
	dstAddr := raw[4:8]
	srcPort := binary.LittleEndian.Uint16(raw[8:10])
	srcAddr := raw[12:16]
	return connectionSpec{DestPort: dstPort, SrcPort: srcPort,
		DestAddr: dstAddr, SrcAddr: srcAddr}, nil
}
func NewSnapLog(raw []byte) (*SnapLog, error) {
	buf := bytes.NewBuffer(raw)
	version, err := buf.ReadString('\n')
	if err != nil {
		return nil, err
	}
	version = strings.Split(version, "\n")[0]
	empty, err := buf.ReadString('\n')
	if err != nil {
		return nil, err
	}
	if empty != "\n" {
		fmt.Printf("%v\n", []byte(empty))
		return nil, errors.New("Expected empty string")
	}
	spec, err := parseFields(buf, "/spec\n", "\n")
	if err != nil {
		return nil, err
	}
	read, err := parseFields(buf, "/read\n", "\n")
	if err != nil {
		return nil, err
	}
	read.Length += len(BEGIN_SNAP_DATA)
	tune, err := parseFields(buf, "/tune\n", END_OF_HEADER)
	if err != nil {
		return nil, err
	}
	t := make([]byte, 4)
	n, err := buf.Read(t)
	if err != nil || n < 4 {
		return nil, errors.New("Too few bytes for logTime")
	}
	logTime := binary.LittleEndian.Uint32(t)
	gn := make([]byte, GROUPNAME_LEN_MAX)
	n, err = buf.Read(gn)
	if err != nil || n != GROUPNAME_LEN_MAX {
		return nil, errors.New("Too few bytes for groupName")
	}
	groupName := strings.SplitN(string(gn), "\000", 2)[0]
	if groupName != "read" {
		fmt.Println(groupName)
		return nil, errors.New("Only 'read' group is supported")
	}
	connSpecOffset := len(raw) - buf.Len()
	connSpec, err := parseConnectionSpec(buf)
	if err != nil {
		return nil, err
	}
	bodyOffset := len(raw) - buf.Len()
	slog := SnapLog{raw: raw, Version: version, LogTime: logTime, GroupName: groupName,
		connSpecOffset: connSpecOffset, bodyOffset: bodyOffset,
		spec: *spec, read: *read, tune: *tune, connSpec: connSpec}
	return &slog, nil
}
func (sl *SnapLog) SnapCount() int {
	total := len(sl.raw) - sl.bodyOffset
	return total / sl.read.Length
}
func (sl *SnapLog) ValidateSnapshots() error {
	_, err := sl.Snapshot(0)
	if err != nil {
		return err
	}
	_, err = sl.Snapshot(sl.SnapCount() - 1)
	if err != nil {
		return err
	}
	total := len(sl.raw) - sl.bodyOffset
	if total%sl.read.Length != 0 {
		return errors.New("last snapshot truncated")
	}
	return nil
}
type Snapshot struct {
	raw    []byte     
	fields *fieldSet  
}
func (sl *SnapLog) Snapshot(n int) (Snapshot, error) {
	if n > sl.SnapCount()-1 {
		return Snapshot{}, fmt.Errorf("invalid snapshot index %d", n)
	}
	offset := sl.bodyOffset + n*sl.read.Length
	begin := string(sl.raw[offset : offset+len(BEGIN_SNAP_DATA)])
	if begin != BEGIN_SNAP_DATA {
		return Snapshot{}, errors.New("missing BeginSnapData")
	}
	return Snapshot{raw: sl.raw[offset+len(BEGIN_SNAP_DATA) : offset+sl.read.Length],
		fields: &sl.read}, nil
}
func (snap *Snapshot) reset(data []byte, fields *fieldSet) {
	snap.fields = fields
	snap.raw = data
}
func (snap *Snapshot) SnapshotValues(snapValues Saver) error {
	if snap.raw == nil {
		return errors.New("Empty/Invalid Snaplog")
	}
	var field Variable
	for _, field = range snap.fields.Fields {
		field.Save(snap.raw[field.Offset:field.Offset+field.Size], snapValues)
	}
	return nil
}
func (snap *Snapshot) SnapshotDeltas(other *Snapshot, snapValues Saver) error {
	if snap.raw == nil {
		return errors.New("Empty/Invalid Snaplog")
	}
	if other.raw == nil {
		return snap.SnapshotValues(snapValues)
	}
	var field Variable
	for _, field = range snap.fields.Fields {
		a := other.raw[field.Offset : field.Offset+field.Size]
		b := snap.raw[field.Offset : field.Offset+field.Size]
		if bytes.Compare(a, b) != 0 {
			field.Save(b, snapValues)
		}
	}
	return nil
}