func ndtWeb100SyntheticUUID(fn string) string {
	return base64hash(fn)
}
ndtWeb100SyntheticUUID constructs a synthetic UUID for ndt web100 rows using
the test filename.  The filename is the same field used by gardener's dedup,
which guarantees uniqueness only for joining with annotations.
package parser
import (
	"crypto/md5"
	"encoding/base64"
	"fmt"
	"log"
	"net"
	"reflect"
	"time"
	"cloud.google.com/go/bigquery"
	"github.com/m-lab/annotation-service/api/v2"
	"github.com/m-lab/etl/etl"
	"github.com/m-lab/etl/metrics"
	"github.com/m-lab/etl/row"
	"github.com/m-lab/etl/web100"
)
func init() {
	initParserVersion()
	initParserGitCommit()
}
const uninitialized = "uninitialized"
var (
	gParserVersion   = uninitialized
	gParserGitCommit = uninitialized
)
func initParserVersion() string {
	release := etl.Version
	if release != "noversion" {
		gParserVersion = "https://github.com/m-lab/etl/tree/" + release
	} else {
		gParserVersion = "local development"
	}
	return gParserVersion
}
func initParserGitCommit() string {
	hash := etl.GitCommit
	if hash != "nocommit" {
		gParserGitCommit = hash
	}
	return gParserGitCommit
}
func Version() string {
	return gParserVersion
}
func GitCommit() string {
	return gParserGitCommit
}
func NormalizeIP(ip string) string {
	r, err := web100.FixIPv6(ip)
	if err != nil {
		return ip
	}
	n := net.ParseIP(r)
	if n == nil {
		return r
	}
	return n.String()
}
func NewSinkParser(dt etl.DataType, sink row.Sink, table string, ann api.Annotator) etl.Parser {
	switch dt {
	case etl.ANNOTATION:
		return NewAnnotationParser(sink, table, "", ann)
	case etl.HOPANNOTATION1:
		return NewHopAnnotation1Parser(sink, table, "", ann)
	case etl.NDT5:
		return NewNDT5ResultParser(sink, table, "", ann)
	case etl.NDT7:
		return NewNDT7ResultParser(sink, table, "", ann)
	case etl.TCPINFO:
		return NewTCPInfoParser(sink, table, "", ann)
	case etl.PCAP:
		return NewPCAPParser(sink, table, "", ann)
	case etl.SCAMPER1:
		return NewScamper1Parser(sink, table, "", ann)
	default:
		return nil
	}
}
func NewParser(dt etl.DataType, ins etl.Inserter) etl.Parser {
	switch dt {
	case etl.NDT:
		return NewNDTParser(ins)
	case etl.NDT5:
		sink, ok := ins.(row.Sink)
		if !ok {
			log.Printf("%v is not a Sink\n", ins)
			log.Println(reflect.TypeOf(ins))
			return nil
		}
		return NewNDT5ResultParser(sink, ins.TableBase(), ins.TableSuffix(), nil)
	case etl.SS:
		return NewDefaultSSParser(ins)  
	case etl.PT:
		return NewPTParser(ins)
	case etl.SW:
		return NewDiscoParser(ins)
	case etl.TCPINFO:
		sink, ok := ins.(row.Sink)
		if !ok {
			log.Printf("%v is not a Sink\n", ins)
			log.Println(reflect.TypeOf(ins))
			return nil
		}
		return NewTCPInfoParser(sink, ins.TableBase(), ins.TableSuffix(), nil)
	default:
		return nil
	}
}
type FakeRowStats struct {
}
func (s *FakeRowStats) RowsInBuffer() int {
	return 0
}
func (s *FakeRowStats) Accepted() int {
	return 0
}
func (s *FakeRowStats) Committed() int {
	return 0
}
func (s *FakeRowStats) Failed() int {
	return 0
}
type NullParser struct {
	FakeRowStats
}
func (np *NullParser) ParseAndInsert(meta map[string]bigquery.Value, testName string, test []byte) error {
	metrics.TestCount.WithLabelValues("table", "null", "ok").Inc()
	return nil
}
func (np *NullParser) TableName() string {
	return "null-table"
}
func (np *NullParser) TaskError() error {
	return nil
}
func base64hash(input string) string {
	b := md5.Sum([]byte(input))
	return base64.RawURLEncoding.EncodeToString(b[:])
}
func ptSyntheticUUID(t time.Time, srcIP, dstIP string) string {
	return base64hash(t.Format(time.RFC3339) + "-" + srcIP + "-" + dstIP)
}