func NewTLSHandshakesList(results oonitemplates.Results) TLSHandshakesList {
	var out TLSHandshakesList
	for _, in := range results.TLSHandshakes {
		out = append(out, TLSHandshake{
			CipherSuite:        tlsx.CipherSuiteString(in.ConnectionState.CipherSuite),
			ConnID:             in.ConnID,
			Failure:            makeFailure(in.Error),
			NegotiatedProtocol: in.ConnectionState.NegotiatedProtocol,
			PeerCertificates:   makePeerCerts(in.ConnectionState.PeerCertificates),
			T:                  in.DurationSinceBeginning.Seconds(),
			TLSVersion:         tlsx.VersionString(in.ConnectionState.Version),
			TransactionID:      in.TransactionID,
		})
	}
	return out
}
NewTLSHandshakesList creates a new TLSHandshakesList
package oonidatamodel
import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"net"
	"net/http"
	"strconv"
	"strings"
	"unicode/utf8"
	"github.com/ooni/probe-engine/internal/tlsx"
	"github.com/ooni/probe-engine/legacy/netx/modelx"
	"github.com/ooni/probe-engine/legacy/oonitemplates"
	"github.com/ooni/probe-engine/model"
	"github.com/ooni/probe-engine/netx/errorx"
)
type ExtSpec struct {
	Name string  
	V    int64   
}
func (spec ExtSpec) AddTo(m *model.Measurement) {
	if m.Extensions == nil {
		m.Extensions = make(map[string]int64)
	}
	m.Extensions[spec.Name] = spec.V
}
var (
	ExtDNS = ExtSpec{Name: "dnst", V: 0}
	ExtNetevents = ExtSpec{Name: "netevents", V: 0}
	ExtHTTP = ExtSpec{Name: "httpt", V: 0}
	ExtTCPConnect = ExtSpec{Name: "tcpconnect", V: 0}
	ExtTLSHandshake = ExtSpec{Name: "tlshandshake", V: 0}
)
type TCPConnectStatus struct {
	Failure *string `json:"failure"`
	Success bool    `json:"success"`
}
type TCPConnectEntry struct {
	ConnID        int64            `json:"conn_id,omitempty"`
	DialID        int64            `json:"dial_id,omitempty"`
	IP            string           `json:"ip"`
	Port          int              `json:"port"`
	Status        TCPConnectStatus `json:"status"`
	T             float64          `json:"t"`
	TransactionID int64            `json:"transaction_id,omitempty"`
}
type TCPConnectList []TCPConnectEntry
func NewTCPConnectList(results oonitemplates.Results) TCPConnectList {
	var out TCPConnectList
	for _, connect := range results.Connects {
		ip, sport, _ := net.SplitHostPort(connect.RemoteAddress)
		iport, _ := strconv.Atoi(sport)
		out = append(out, TCPConnectEntry{
			ConnID: connect.ConnID,
			DialID: connect.DialID,
			IP:     ip,
			Port:   iport,
			Status: TCPConnectStatus{
				Failure: makeFailure(connect.Error),
				Success: connect.Error == nil,
			},
			T:             connect.DurationSinceBeginning.Seconds(),
			TransactionID: connect.TransactionID,
		})
	}
	return out
}
func makeFailure(err error) (s *string) {
	if err != nil {
		serio := err.Error()
		s = &serio
	}
	return
}
type HTTPTor struct {
	ExitIP   *string `json:"exit_ip"`
	ExitName *string `json:"exit_name"`
	IsTor    bool    `json:"is_tor"`
}
type MaybeBinaryValue struct {
	Value string
}
func (hb MaybeBinaryValue) MarshalJSON() ([]byte, error) {
	if utf8.ValidString(hb.Value) {
		return json.Marshal(hb.Value)
	}
	er := make(map[string]string)
	er["format"] = "base64"
	er["data"] = base64.StdEncoding.EncodeToString([]byte(hb.Value))
	return json.Marshal(er)
}
func (hb *MaybeBinaryValue) UnmarshalJSON(d []byte) error {
	if err := json.Unmarshal(d, &hb.Value); err == nil {
		return nil
	}
	er := make(map[string]string)
	if err := json.Unmarshal(d, &er); err != nil {
		return err
	}
	if v, ok := er["format"]; !ok || v != "base64" {
		return errors.New("missing or invalid format field")
	}
	if _, ok := er["data"]; !ok {
		return errors.New("missing data field")
	}
	b64, err := base64.StdEncoding.DecodeString(er["data"])
	if err != nil {
		return err
	}
	hb.Value = string(b64)
	return nil
}
type HTTPBody = MaybeBinaryValue
type HTTPHeaders map[string]MaybeBinaryValue
type HTTPHeader struct {
	Key   string
	Value MaybeBinaryValue
}
func (hh HTTPHeader) MarshalJSON() ([]byte, error) {
	if utf8.ValidString(hh.Value.Value) {
		return json.Marshal([]string{hh.Key, hh.Value.Value})
	}
	value := make(map[string]string)
	value["format"] = "base64"
	value["data"] = base64.StdEncoding.EncodeToString([]byte(hh.Value.Value))
	return json.Marshal([]interface{}{hh.Key, value})
}
func (hh *HTTPHeader) UnmarshalJSON(d []byte) error {
	var pair []interface{}
	if err := json.Unmarshal(d, &pair); err != nil {
		return err
	}
	if len(pair) != 2 {
		return errors.New("unexpected pair length")
	}
	key, ok := pair[0].(string)
	if !ok {
		return errors.New("the key is not a string")
	}
	value, ok := pair[1].(string)
	if !ok {
		mapvalue, ok := pair[1].(map[string]interface{})
		if !ok {
			return errors.New("the value is neither a string nor a map[string]interface{}")
		}
		if _, ok := mapvalue["format"]; !ok {
			return errors.New("missing format")
		}
		if v, ok := mapvalue["format"].(string); !ok || v != "base64" {
			return errors.New("invalid format")
		}
		if _, ok := mapvalue["data"]; !ok {
			return errors.New("missing data field")
		}
		v, ok := mapvalue["data"].(string)
		if !ok {
			return errors.New("the data field is not a string")
		}
		b64, err := base64.StdEncoding.DecodeString(v)
		if err != nil {
			return err
		}
		value = string(b64)
	}
	hh.Key, hh.Value = key, MaybeBinaryValue{Value: value}
	return nil
}
type HTTPHeadersList []HTTPHeader
type HTTPRequest struct {
	Body            HTTPBody        `json:"body"`
	BodyIsTruncated bool            `json:"body_is_truncated"`
	HeadersList     HTTPHeadersList `json:"headers_list"`
	Headers         HTTPHeaders     `json:"headers"`
	Method          string          `json:"method"`
	Tor             HTTPTor         `json:"tor"`
	URL             string          `json:"url"`
}
type HTTPResponse struct {
	Body            HTTPBody        `json:"body"`
	BodyIsTruncated bool            `json:"body_is_truncated"`
	Code            int64           `json:"code"`
	HeadersList     HTTPHeadersList `json:"headers_list"`
	Headers         HTTPHeaders     `json:"headers"`
}
type RequestEntry struct {
	Failure       *string      `json:"failure"`
	Request       HTTPRequest  `json:"request"`
	Response      HTTPResponse `json:"response"`
	TransactionID int64        `json:"transaction_id,omitempty"`
}
type RequestList []RequestEntry
func addheaders(
	source http.Header,
	destList *HTTPHeadersList,
	destMap *HTTPHeaders,
) {
	for key, values := range source {
		for index, value := range values {
			value := MaybeBinaryValue{Value: value}
			if index == 0 {
				(*destMap)[key] = value
			}
			*destList = append(*destList, HTTPHeader{
				Key:   key,
				Value: value,
			})
		}
	}
}
func NewRequestList(results oonitemplates.Results) RequestList {
	var out RequestList
	in := results.HTTPRequests
	for idx := len(in) - 1; idx >= 0; idx-- {
		var entry RequestEntry
		entry.Failure = makeFailure(in[idx].Error)
		entry.Request.Headers = make(HTTPHeaders)
		addheaders(
			in[idx].RequestHeaders, &entry.Request.HeadersList,
			&entry.Request.Headers,
		)
		entry.Request.Method = in[idx].RequestMethod
		entry.Request.URL = in[idx].RequestURL
		entry.Request.Body.Value = string(in[idx].RequestBodySnap)
		entry.Request.BodyIsTruncated = in[idx].MaxBodySnapSize > 0 &&
			int64(len(in[idx].RequestBodySnap)) >= in[idx].MaxBodySnapSize
		entry.Response.Headers = make(HTTPHeaders)
		addheaders(
			in[idx].ResponseHeaders, &entry.Response.HeadersList,
			&entry.Response.Headers,
		)
		entry.Response.Code = in[idx].ResponseStatusCode
		entry.Response.Body.Value = string(in[idx].ResponseBodySnap)
		entry.Response.BodyIsTruncated = in[idx].MaxBodySnapSize > 0 &&
			int64(len(in[idx].ResponseBodySnap)) >= in[idx].MaxBodySnapSize
		entry.TransactionID = in[idx].TransactionID
		out = append(out, entry)
	}
	return out
}
type DNSAnswerEntry struct {
	AnswerType string  `json:"answer_type"`
	Hostname   string  `json:"hostname,omitempty"`
	IPv4       string  `json:"ipv4,omitempty"`
	IPv6       string  `json:"ipv6,omitempty"`
	TTL        *uint32 `json:"ttl"`
}
type DNSQueryEntry struct {
	Answers          []DNSAnswerEntry `json:"answers"`
	DialID           int64            `json:"dial_id,omitempty"`
	Engine           string           `json:"engine"`
	Failure          *string          `json:"failure"`
	Hostname         string           `json:"hostname"`
	QueryType        string           `json:"query_type"`
	ResolverHostname *string          `json:"resolver_hostname"`
	ResolverPort     *string          `json:"resolver_port"`
	ResolverAddress  string           `json:"resolver_address"`
	T                float64          `json:"t"`
	TransactionID    int64            `json:"transaction_id,omitempty"`
}
type (
	DNSQueriesList []DNSQueryEntry
	dnsQueryType   string
)
func NewDNSQueriesList(results oonitemplates.Results) DNSQueriesList {
	var out DNSQueriesList
	for _, resolve := range results.Resolves {
		for _, qtype := range []dnsQueryType{"A", "AAAA"} {
			entry := qtype.makequeryentry(resolve)
			for _, addr := range resolve.Addresses {
				if qtype.ipoftype(addr) {
					entry.Answers = append(entry.Answers, qtype.makeanswerentry(addr))
				}
			}
			out = append(out, entry)
		}
	}
	return out
}
func (qtype dnsQueryType) ipoftype(addr string) bool {
	switch qtype {
	case "A":
		return strings.Contains(addr, ":") == false
	case "AAAA":
		return strings.Contains(addr, ":") == true
	}
	return false
}
func (qtype dnsQueryType) makeanswerentry(addr string) DNSAnswerEntry {
	answer := DNSAnswerEntry{AnswerType: string(qtype)}
	switch qtype {
	case "A":
		answer.IPv4 = addr
	case "AAAA":
		answer.IPv6 = addr
	}
	return answer
}
func (qtype dnsQueryType) makequeryentry(resolve *modelx.ResolveDoneEvent) DNSQueryEntry {
	return DNSQueryEntry{
		DialID:          resolve.DialID,
		Engine:          resolve.TransportNetwork,
		Failure:         makeFailure(resolve.Error),
		Hostname:        resolve.Hostname,
		QueryType:       string(qtype),
		ResolverAddress: resolve.TransportAddress,
		T:               resolve.DurationSinceBeginning.Seconds(),
		TransactionID:   resolve.TransactionID,
	}
}
type NetworkEvent struct {
	Address       string  `json:"address,omitempty"`
	ConnID        int64   `json:"conn_id,omitempty"`
	DialID        int64   `json:"dial_id,omitempty"`
	Failure       *string `json:"failure"`
	NumBytes      int64   `json:"num_bytes,omitempty"`
	Operation     string  `json:"operation"`
	Proto         string  `json:"proto"`
	T             float64 `json:"t"`
	TransactionID int64   `json:"transaction_id,omitempty"`
}
type NetworkEventsList []*NetworkEvent
var protocolName = map[bool]string{
	true:  "tcp",
	false: "udp",
}
func NewNetworkEventsList(results oonitemplates.Results) NetworkEventsList {
	var out NetworkEventsList
	for _, in := range results.NetworkEvents {
		if in.Connect != nil {
			out = append(out, &NetworkEvent{
				Address:       in.Connect.RemoteAddress,
				ConnID:        in.Connect.ConnID,
				DialID:        in.Connect.DialID,
				Failure:       makeFailure(in.Connect.Error),
				Operation:     errorx.ConnectOperation,
				Proto:         protocolName[in.Connect.ConnID >= 0],
				T:             in.Connect.DurationSinceBeginning.Seconds(),
				TransactionID: in.Connect.TransactionID,
			})
		}
		if in.Read != nil {
			out = append(out, &NetworkEvent{
				ConnID:    in.Read.ConnID,
				Failure:   makeFailure(in.Read.Error),
				Operation: errorx.ReadOperation,
				NumBytes:  in.Read.NumBytes,
				Proto:     protocolName[in.Read.ConnID >= 0],
				T:         in.Read.DurationSinceBeginning.Seconds(),
			})
		}
		if in.Write != nil {
			out = append(out, &NetworkEvent{
				ConnID:    in.Write.ConnID,
				Failure:   makeFailure(in.Write.Error),
				Operation: errorx.WriteOperation,
				NumBytes:  in.Write.NumBytes,
				Proto:     protocolName[in.Write.ConnID >= 0],
				T:         in.Write.DurationSinceBeginning.Seconds(),
			})
		}
	}
	return out
}
type TLSHandshake struct {
	CipherSuite        string             `json:"cipher_suite"`
	ConnID             int64              `json:"conn_id,omitempty"`
	Failure            *string            `json:"failure"`
	NegotiatedProtocol string             `json:"negotiated_protocol"`
	PeerCertificates   []MaybeBinaryValue `json:"peer_certificates"`
	T                  float64            `json:"t"`
	TLSVersion         string             `json:"tls_version"`
	TransactionID      int64              `json:"transaction_id,omitempty"`
}
type TLSHandshakesList []TLSHandshake