diff --git a/bpf/vm_bpf_test.go b/bpf/vm_bpf_test.go index 137eea160..559e5dd8e 100644 --- a/bpf/vm_bpf_test.go +++ b/bpf/vm_bpf_test.go @@ -14,6 +14,7 @@ import ( "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" "golang.org/x/net/nettest" + "golang.org/x/sys/cpu" ) // A virtualMachine is a BPF virtual machine which can process an @@ -22,18 +23,6 @@ type virtualMachine interface { Run(in []byte) (int, error) } -// canUseOSVM indicates if the OS BPF VM is available on this platform. -func canUseOSVM() bool { - // OS BPF VM can only be used on platforms where x/net/ipv4 supports - // attaching a BPF program to a socket. - switch runtime.GOOS { - case "linux": - return true - } - - return false -} - // All BPF tests against both the Go VM and OS VM are assumed to // be used with a UDP socket. As a result, the entire contents // of a UDP datagram is sent through the BPF program, but only @@ -55,11 +44,10 @@ func testVM(t *testing.T, filter []bpf.Instruction) (virtualMachine, func(), err t: t, } - // If available, add the OS VM for tests which verify that both the Go - // VM and OS VM have exactly the same output for the same input program - // and packet. + // For linux with a little endian CPU, the Go VM and OS VM have exactly the + // same output for the same input program and packet. Compare both. done := func() {} - if canUseOSVM() { + if runtime.GOOS == "linux" && !cpu.IsBigEndian { osVM, osVMDone := testOSVM(t, filter) done = func() { osVMDone() } mvm.osVM = osVM diff --git a/dns/dnsmessage/message.go b/dns/dnsmessage/message.go index ffdf19d5d..b6b4f9c19 100644 --- a/dns/dnsmessage/message.go +++ b/dns/dnsmessage/message.go @@ -260,9 +260,11 @@ var ( errReserved = errors.New("segment prefix is reserved") errTooManyPtr = errors.New("too many pointers (>10)") errInvalidPtr = errors.New("invalid pointer") + errInvalidName = errors.New("invalid dns name") errNilResouceBody = errors.New("nil resource body") errResourceLen = errors.New("insufficient data for resource body length") errSegTooLong = errors.New("segment length too long") + errNameTooLong = errors.New("name too long") errZeroSegLen = errors.New("zero length segment") errResTooLong = errors.New("resource length too long") errTooManyQuestions = errors.New("too many Questions to pack (>65535)") @@ -359,6 +361,8 @@ func (m *Header) GoString() string { "Truncated: " + printBool(m.Truncated) + ", " + "RecursionDesired: " + printBool(m.RecursionDesired) + ", " + "RecursionAvailable: " + printBool(m.RecursionAvailable) + ", " + + "AuthenticData: " + printBool(m.AuthenticData) + ", " + + "CheckingDisabled: " + printBool(m.CheckingDisabled) + ", " + "RCode: " + m.RCode.GoString() + "}" } @@ -488,7 +492,7 @@ func (r *Resource) GoString() string { // A ResourceBody is a DNS resource record minus the header. type ResourceBody interface { // pack packs a Resource except for its header. - pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) + pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) // realType returns the actual type of the Resource. This is used to // fill in the header Type field. @@ -499,7 +503,7 @@ type ResourceBody interface { } // pack appends the wire format of the Resource to msg. -func (r *Resource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *Resource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { if r.Body == nil { return msg, errNilResouceBody } @@ -525,22 +529,26 @@ func (r *Resource) pack(msg []byte, compression map[string]int, compressionOff i // When parsing is started, the Header is parsed. Next, each Question can be // either parsed or skipped. Alternatively, all Questions can be skipped at // once. When all Questions have been parsed, attempting to parse Questions -// will return (nil, nil) and attempting to skip Questions will return -// (true, nil). After all Questions have been either parsed or skipped, all +// will return the [ErrSectionDone] error. +// After all Questions have been either parsed or skipped, all // Answers, Authorities and Additionals can be either parsed or skipped in the // same way, and each type of Resource must be fully parsed or skipped before // proceeding to the next type of Resource. // +// Parser is safe to copy to preserve the parsing state. +// // Note that there is no requirement to fully skip or parse the message. type Parser struct { msg []byte header header - section section - off int - index int - resHeaderValid bool - resHeader ResourceHeader + section section + off int + index int + resHeaderValid bool + resHeaderOffset int + resHeaderType Type + resHeaderLength uint16 } // Start parses the header and enables the parsing of Questions. @@ -591,8 +599,9 @@ func (p *Parser) resource(sec section) (Resource, error) { func (p *Parser) resourceHeader(sec section) (ResourceHeader, error) { if p.resHeaderValid { - return p.resHeader, nil + p.off = p.resHeaderOffset } + if err := p.checkAdvance(sec); err != nil { return ResourceHeader{}, err } @@ -602,14 +611,16 @@ func (p *Parser) resourceHeader(sec section) (ResourceHeader, error) { return ResourceHeader{}, err } p.resHeaderValid = true - p.resHeader = hdr + p.resHeaderOffset = p.off + p.resHeaderType = hdr.Type + p.resHeaderLength = hdr.Length p.off = off return hdr, nil } func (p *Parser) skipResource(sec section) error { - if p.resHeaderValid { - newOff := p.off + int(p.resHeader.Length) + if p.resHeaderValid && p.section == sec { + newOff := p.off + int(p.resHeaderLength) if newOff > len(p.msg) { return errResourceLen } @@ -860,14 +871,14 @@ func (p *Parser) SkipAllAdditionals() error { // One of the XXXHeader methods must have been called before calling this // method. func (p *Parser) CNAMEResource() (CNAMEResource, error) { - if !p.resHeaderValid || p.resHeader.Type != TypeCNAME { + if !p.resHeaderValid || p.resHeaderType != TypeCNAME { return CNAMEResource{}, ErrNotStarted } r, err := unpackCNAMEResource(p.msg, p.off) if err != nil { return CNAMEResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -878,14 +889,14 @@ func (p *Parser) CNAMEResource() (CNAMEResource, error) { // One of the XXXHeader methods must have been called before calling this // method. func (p *Parser) MXResource() (MXResource, error) { - if !p.resHeaderValid || p.resHeader.Type != TypeMX { + if !p.resHeaderValid || p.resHeaderType != TypeMX { return MXResource{}, ErrNotStarted } r, err := unpackMXResource(p.msg, p.off) if err != nil { return MXResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -896,14 +907,14 @@ func (p *Parser) MXResource() (MXResource, error) { // One of the XXXHeader methods must have been called before calling this // method. func (p *Parser) NSResource() (NSResource, error) { - if !p.resHeaderValid || p.resHeader.Type != TypeNS { + if !p.resHeaderValid || p.resHeaderType != TypeNS { return NSResource{}, ErrNotStarted } r, err := unpackNSResource(p.msg, p.off) if err != nil { return NSResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -914,14 +925,14 @@ func (p *Parser) NSResource() (NSResource, error) { // One of the XXXHeader methods must have been called before calling this // method. func (p *Parser) PTRResource() (PTRResource, error) { - if !p.resHeaderValid || p.resHeader.Type != TypePTR { + if !p.resHeaderValid || p.resHeaderType != TypePTR { return PTRResource{}, ErrNotStarted } r, err := unpackPTRResource(p.msg, p.off) if err != nil { return PTRResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -932,14 +943,14 @@ func (p *Parser) PTRResource() (PTRResource, error) { // One of the XXXHeader methods must have been called before calling this // method. func (p *Parser) SOAResource() (SOAResource, error) { - if !p.resHeaderValid || p.resHeader.Type != TypeSOA { + if !p.resHeaderValid || p.resHeaderType != TypeSOA { return SOAResource{}, ErrNotStarted } r, err := unpackSOAResource(p.msg, p.off) if err != nil { return SOAResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -950,14 +961,14 @@ func (p *Parser) SOAResource() (SOAResource, error) { // One of the XXXHeader methods must have been called before calling this // method. func (p *Parser) TXTResource() (TXTResource, error) { - if !p.resHeaderValid || p.resHeader.Type != TypeTXT { + if !p.resHeaderValid || p.resHeaderType != TypeTXT { return TXTResource{}, ErrNotStarted } - r, err := unpackTXTResource(p.msg, p.off, p.resHeader.Length) + r, err := unpackTXTResource(p.msg, p.off, p.resHeaderLength) if err != nil { return TXTResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -968,14 +979,14 @@ func (p *Parser) TXTResource() (TXTResource, error) { // One of the XXXHeader methods must have been called before calling this // method. func (p *Parser) SRVResource() (SRVResource, error) { - if !p.resHeaderValid || p.resHeader.Type != TypeSRV { + if !p.resHeaderValid || p.resHeaderType != TypeSRV { return SRVResource{}, ErrNotStarted } r, err := unpackSRVResource(p.msg, p.off) if err != nil { return SRVResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -986,14 +997,14 @@ func (p *Parser) SRVResource() (SRVResource, error) { // One of the XXXHeader methods must have been called before calling this // method. func (p *Parser) AResource() (AResource, error) { - if !p.resHeaderValid || p.resHeader.Type != TypeA { + if !p.resHeaderValid || p.resHeaderType != TypeA { return AResource{}, ErrNotStarted } r, err := unpackAResource(p.msg, p.off) if err != nil { return AResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -1004,14 +1015,14 @@ func (p *Parser) AResource() (AResource, error) { // One of the XXXHeader methods must have been called before calling this // method. func (p *Parser) AAAAResource() (AAAAResource, error) { - if !p.resHeaderValid || p.resHeader.Type != TypeAAAA { + if !p.resHeaderValid || p.resHeaderType != TypeAAAA { return AAAAResource{}, ErrNotStarted } r, err := unpackAAAAResource(p.msg, p.off) if err != nil { return AAAAResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -1022,14 +1033,14 @@ func (p *Parser) AAAAResource() (AAAAResource, error) { // One of the XXXHeader methods must have been called before calling this // method. func (p *Parser) OPTResource() (OPTResource, error) { - if !p.resHeaderValid || p.resHeader.Type != TypeOPT { + if !p.resHeaderValid || p.resHeaderType != TypeOPT { return OPTResource{}, ErrNotStarted } - r, err := unpackOPTResource(p.msg, p.off, p.resHeader.Length) + r, err := unpackOPTResource(p.msg, p.off, p.resHeaderLength) if err != nil { return OPTResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -1043,11 +1054,11 @@ func (p *Parser) UnknownResource() (UnknownResource, error) { if !p.resHeaderValid { return UnknownResource{}, ErrNotStarted } - r, err := unpackUnknownResource(p.resHeader.Type, p.msg, p.off, p.resHeader.Length) + r, err := unpackUnknownResource(p.resHeaderType, p.msg, p.off, p.resHeaderLength) if err != nil { return UnknownResource{}, err } - p.off += int(p.resHeader.Length) + p.off += int(p.resHeaderLength) p.resHeaderValid = false p.index++ return r, nil @@ -1118,7 +1129,7 @@ func (m *Message) AppendPack(b []byte) ([]byte, error) { // DNS messages can be a maximum of 512 bytes long. Without compression, // many DNS response messages are over this limit, so enabling // compression will help ensure compliance. - compression := map[string]int{} + compression := map[string]uint16{} for i := range m.Questions { var err error @@ -1209,7 +1220,7 @@ type Builder struct { // compression is a mapping from name suffixes to their starting index // in msg. - compression map[string]int + compression map[string]uint16 } // NewBuilder creates a new builder with compression disabled. @@ -1246,7 +1257,7 @@ func NewBuilder(buf []byte, h Header) Builder { // // Compression should be enabled before any sections are added for best results. func (b *Builder) EnableCompression() { - b.compression = map[string]int{} + b.compression = map[string]uint16{} } func (b *Builder) startCheck(s section) error { @@ -1662,7 +1673,7 @@ func (h *ResourceHeader) GoString() string { // pack appends the wire format of the ResourceHeader to oldMsg. // // lenOff is the offset in msg where the Length field was packed. -func (h *ResourceHeader) pack(oldMsg []byte, compression map[string]int, compressionOff int) (msg []byte, lenOff int, err error) { +func (h *ResourceHeader) pack(oldMsg []byte, compression map[string]uint16, compressionOff int) (msg []byte, lenOff int, err error) { msg = oldMsg if msg, err = h.Name.pack(msg, compression, compressionOff); err != nil { return oldMsg, 0, &nestedError{"Name", err} @@ -1728,7 +1739,7 @@ const ( // // The provided extRCode must be an extended RCode. func (h *ResourceHeader) SetEDNS0(udpPayloadLen int, extRCode RCode, dnssecOK bool) error { - h.Name = Name{Data: [nameLen]byte{'.'}, Length: 1} // RFC 6891 section 6.1.2 + h.Name = Name{Data: [255]byte{'.'}, Length: 1} // RFC 6891 section 6.1.2 h.Type = TypeOPT h.Class = Class(udpPayloadLen) h.TTL = uint32(extRCode) >> 4 << 24 @@ -1888,21 +1899,21 @@ func unpackBytes(msg []byte, off int, field []byte) (int, error) { return newOff, nil } -const nameLen = 255 +const nonEncodedNameMax = 254 -// A Name is a non-encoded domain name. It is used instead of strings to avoid +// A Name is a non-encoded and non-escaped domain name. It is used instead of strings to avoid // allocations. type Name struct { - Data [nameLen]byte // 255 bytes + Data [255]byte Length uint8 } // NewName creates a new Name from a string. func NewName(name string) (Name, error) { - if len(name) > nameLen { + n := Name{Length: uint8(len(name))} + if len(name) > len(n.Data) { return Name{}, errCalcLen } - n := Name{Length: uint8(len(name))} copy(n.Data[:], name) return n, nil } @@ -1917,6 +1928,8 @@ func MustNewName(name string) Name { } // String implements fmt.Stringer.String. +// +// Note: characters inside the labels are not escaped in any way. func (n Name) String() string { return string(n.Data[:n.Length]) } @@ -1933,9 +1946,13 @@ func (n *Name) GoString() string { // // The compression map will be updated with new domain suffixes. If compression // is nil, compression will not be used. -func (n *Name) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (n *Name) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { oldMsg := msg + if n.Length > nonEncodedNameMax { + return nil, errNameTooLong + } + // Add a trailing dot to canonicalize name. if n.Length == 0 || n.Data[n.Length-1] != '.' { return oldMsg, errNonCanonicalName @@ -1946,6 +1963,8 @@ func (n *Name) pack(msg []byte, compression map[string]int, compressionOff int) return append(msg, 0), nil } + var nameAsStr string + // Emit sequence of counted strings, chopping at dots. for i, begin := 0, 0; i < int(n.Length); i++ { // Check for the end of the segment. @@ -1976,16 +1995,22 @@ func (n *Name) pack(msg []byte, compression map[string]int, compressionOff int) // segment. A pointer is two bytes with the two most significant // bits set to 1 to indicate that it is a pointer. if (i == 0 || n.Data[i-1] == '.') && compression != nil { - if ptr, ok := compression[string(n.Data[i:])]; ok { + if ptr, ok := compression[string(n.Data[i:n.Length])]; ok { // Hit. Emit a pointer instead of the rest of // the domain. return append(msg, byte(ptr>>8|0xC0), byte(ptr)), nil } // Miss. Add the suffix to the compression table if the - // offset can be stored in the available 14 bytes. - if len(msg) <= int(^uint16(0)>>2) { - compression[string(n.Data[i:])] = len(msg) - compressionOff + // offset can be stored in the available 14 bits. + newPtr := len(msg) - compressionOff + if newPtr <= int(^uint16(0)>>2) { + if nameAsStr == "" { + // allocate n.Data on the heap once, to avoid allocating it + // multiple times (for next labels). + nameAsStr = string(n.Data[:n.Length]) + } + compression[nameAsStr[i:]] = uint16(newPtr) } } } @@ -2029,6 +2054,15 @@ Loop: if endOff > len(msg) { return off, errCalcLen } + + // Reject names containing dots. + // See issue golang/go#56246 + for _, v := range msg[currOff:endOff] { + if v == '.' { + return off, errInvalidName + } + } + name = append(name, msg[currOff:endOff]...) name = append(name, '.') currOff = endOff @@ -2057,8 +2091,8 @@ Loop: if len(name) == 0 { name = append(name, '.') } - if len(name) > len(n.Data) { - return off, errCalcLen + if len(name) > nonEncodedNameMax { + return off, errNameTooLong } n.Length = uint8(len(name)) if ptr == 0 { @@ -2116,7 +2150,7 @@ type Question struct { } // pack appends the wire format of the Question to msg. -func (q *Question) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (q *Question) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { msg, err := q.Name.pack(msg, compression, compressionOff) if err != nil { return msg, &nestedError{"Name", err} @@ -2212,7 +2246,7 @@ func (r *CNAMEResource) realType() Type { } // pack appends the wire format of the CNAMEResource to msg. -func (r *CNAMEResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *CNAMEResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { return r.CNAME.pack(msg, compression, compressionOff) } @@ -2240,7 +2274,7 @@ func (r *MXResource) realType() Type { } // pack appends the wire format of the MXResource to msg. -func (r *MXResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *MXResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { oldMsg := msg msg = packUint16(msg, r.Pref) msg, err := r.MX.pack(msg, compression, compressionOff) @@ -2279,7 +2313,7 @@ func (r *NSResource) realType() Type { } // pack appends the wire format of the NSResource to msg. -func (r *NSResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *NSResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { return r.NS.pack(msg, compression, compressionOff) } @@ -2306,7 +2340,7 @@ func (r *PTRResource) realType() Type { } // pack appends the wire format of the PTRResource to msg. -func (r *PTRResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *PTRResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { return r.PTR.pack(msg, compression, compressionOff) } @@ -2343,7 +2377,7 @@ func (r *SOAResource) realType() Type { } // pack appends the wire format of the SOAResource to msg. -func (r *SOAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *SOAResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { oldMsg := msg msg, err := r.NS.pack(msg, compression, compressionOff) if err != nil { @@ -2415,7 +2449,7 @@ func (r *TXTResource) realType() Type { } // pack appends the wire format of the TXTResource to msg. -func (r *TXTResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *TXTResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { oldMsg := msg for _, s := range r.TXT { var err error @@ -2471,7 +2505,7 @@ func (r *SRVResource) realType() Type { } // pack appends the wire format of the SRVResource to msg. -func (r *SRVResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *SRVResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { oldMsg := msg msg = packUint16(msg, r.Priority) msg = packUint16(msg, r.Weight) @@ -2522,7 +2556,7 @@ func (r *AResource) realType() Type { } // pack appends the wire format of the AResource to msg. -func (r *AResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *AResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { return packBytes(msg, r.A[:]), nil } @@ -2556,7 +2590,7 @@ func (r *AAAAResource) GoString() string { } // pack appends the wire format of the AAAAResource to msg. -func (r *AAAAResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *AAAAResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { return packBytes(msg, r.AAAA[:]), nil } @@ -2596,7 +2630,7 @@ func (r *OPTResource) realType() Type { return TypeOPT } -func (r *OPTResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *OPTResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { for _, opt := range r.Options { msg = packUint16(msg, opt.Code) l := uint16(len(opt.Data)) @@ -2654,7 +2688,7 @@ func (r *UnknownResource) realType() Type { } // pack appends the wire format of the UnknownResource to msg. -func (r *UnknownResource) pack(msg []byte, compression map[string]int, compressionOff int) ([]byte, error) { +func (r *UnknownResource) pack(msg []byte, compression map[string]uint16, compressionOff int) ([]byte, error) { return packBytes(msg, r.Data[:]), nil } diff --git a/dns/dnsmessage/message_test.go b/dns/dnsmessage/message_test.go index 3cddfca99..c84d5a3aa 100644 --- a/dns/dnsmessage/message_test.go +++ b/dns/dnsmessage/message_test.go @@ -164,7 +164,7 @@ func TestQuestionPackUnpack(t *testing.T) { Type: TypeA, Class: ClassINET, } - buf, err := want.pack(make([]byte, 1, 50), map[string]int{}, 1) + buf, err := want.pack(make([]byte, 1, 50), map[string]uint16{}, 1) if err != nil { t.Fatal("Question.pack() =", err) } @@ -211,27 +211,39 @@ func TestName(t *testing.T) { } } +func TestNameWithDotsUnpack(t *testing.T) { + name := []byte{3, 'w', '.', 'w', 2, 'g', 'o', 3, 'd', 'e', 'v', 0} + var n Name + _, err := n.unpack(name, 0) + if err != errInvalidName { + t.Fatalf("expected %v, got %v", errInvalidName, err) + } +} + func TestNamePackUnpack(t *testing.T) { + const suffix = ".go.dev." + var longDNSPrefix = strings.Repeat("verylongdomainlabel.", 20) + tests := []struct { - in string - want string - err error + in string + err error }{ - {"", "", errNonCanonicalName}, - {".", ".", nil}, - {"google..com", "", errNonCanonicalName}, - {"google.com", "", errNonCanonicalName}, - {"google..com.", "", errZeroSegLen}, - {"google.com.", "google.com.", nil}, - {".google.com.", "", errZeroSegLen}, - {"www..google.com.", "", errZeroSegLen}, - {"www.google.com.", "www.google.com.", nil}, + {"", errNonCanonicalName}, + {".", nil}, + {"google..com", errNonCanonicalName}, + {"google.com", errNonCanonicalName}, + {"google..com.", errZeroSegLen}, + {"google.com.", nil}, + {".google.com.", errZeroSegLen}, + {"www..google.com.", errZeroSegLen}, + {"www.google.com.", nil}, + {in: longDNSPrefix[:254-len(suffix)] + suffix}, // 254B name, with ending dot. + {in: longDNSPrefix[:255-len(suffix)] + suffix, err: errNameTooLong}, // 255B name, with ending dot. } for _, test := range tests { in := MustNewName(test.in) - want := MustNewName(test.want) - buf, err := in.pack(make([]byte, 0, 30), map[string]int{}, 0) + buf, err := in.pack(make([]byte, 0, 30), map[string]uint16{}, 0) if err != test.err { t.Errorf("got %q.pack() = %v, want = %v", test.in, err, test.err) continue @@ -253,15 +265,47 @@ func TestNamePackUnpack(t *testing.T) { len(buf), ) } - if got != want { - t.Errorf("unpacking packing of %q: got = %#v, want = %#v", test.in, got, want) + if got != in { + t.Errorf("unpacking packing of %q: got = %#v, want = %#v", test.in, got, in) + } + } +} + +func TestNameUnpackTooLongName(t *testing.T) { + var suffix = []byte{2, 'g', 'o', 3, 'd', 'e', 'v', 0} + + const label = "longdnslabel" + labelBinary := append([]byte{byte(len(label))}, []byte(label)...) + var longDNSPrefix = bytes.Repeat(labelBinary, 18) + longDNSPrefix = longDNSPrefix[:len(longDNSPrefix):len(longDNSPrefix)] + + prepName := func(length int) []byte { + missing := length - (len(longDNSPrefix) + len(suffix) + 1) + name := append(longDNSPrefix, byte(missing)) + name = append(name, bytes.Repeat([]byte{'a'}, missing)...) + return append(name, suffix...) + } + + tests := []struct { + name []byte + err error + }{ + {name: prepName(255)}, + {name: prepName(256), err: errNameTooLong}, + } + + for i, test := range tests { + var got Name + _, err := got.unpack(test.name, 0) + if err != test.err { + t.Errorf("%v: %v: expected error: %v, got %v", i, test.name, test.err, err) } } } func TestIncompressibleName(t *testing.T) { name := MustNewName("example.com.") - compression := map[string]int{} + compression := map[string]uint16{} buf, err := name.pack(make([]byte, 0, 100), compression, 0) if err != nil { t.Fatal("first Name.pack() =", err) @@ -579,7 +623,7 @@ func TestVeryLongTxt(t *testing.T) { strings.Repeat(".", 255), }}, } - buf, err := want.pack(make([]byte, 0, 8000), map[string]int{}, 0) + buf, err := want.pack(make([]byte, 0, 8000), map[string]uint16{}, 0) if err != nil { t.Fatal("Resource.pack() =", err) } @@ -603,7 +647,7 @@ func TestVeryLongTxt(t *testing.T) { func TestTooLongTxt(t *testing.T) { rb := TXTResource{[]string{strings.Repeat(".", 256)}} - if _, err := rb.pack(make([]byte, 0, 8000), map[string]int{}, 0); err != errStringTooLong { + if _, err := rb.pack(make([]byte, 0, 8000), map[string]uint16{}, 0); err != errStringTooLong { t.Errorf("packing TXTResource with 256 character string: got err = %v, want = %v", err, errStringTooLong) } } @@ -1141,8 +1185,7 @@ func TestGoString(t *testing.T) { t.Error("Message.GoString lost information or largeTestMsg changed: msg != largeTestMsg()") } got := msg.GoString() - - want := `dnsmessage.Message{Header: dnsmessage.Header{ID: 0, Response: true, OpCode: 0, Authoritative: true, Truncated: false, RecursionDesired: false, RecursionAvailable: false, RCode: dnsmessage.RCodeSuccess}, Questions: []dnsmessage.Question{dnsmessage.Question{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET}}, Answers: []dnsmessage.Resource{dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.AResource{A: [4]byte{127, 0, 0, 1}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.AResource{A: [4]byte{127, 0, 0, 2}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeAAAA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.AAAAResource{AAAA: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeCNAME, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.CNAMEResource{CNAME: dnsmessage.MustNewName("alias.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeSOA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.SOAResource{NS: dnsmessage.MustNewName("ns1.example.com."), MBox: dnsmessage.MustNewName("mb.example.com."), Serial: 1, Refresh: 2, Retry: 3, Expire: 4, MinTTL: 5}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypePTR, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.PTRResource{PTR: dnsmessage.MustNewName("ptr.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeMX, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.MXResource{Pref: 7, MX: dnsmessage.MustNewName("mx.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeSRV, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.SRVResource{Priority: 8, Weight: 9, Port: 11, Target: dnsmessage.MustNewName("srv.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: 65362, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.UnknownResource{Type: 65362, Data: []byte{42, 0, 43, 44}}}}, Authorities: []dnsmessage.Resource{dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeNS, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.NSResource{NS: dnsmessage.MustNewName("ns1.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeNS, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.NSResource{NS: dnsmessage.MustNewName("ns2.example.com.")}}}, Additionals: []dnsmessage.Resource{dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeTXT, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.TXTResource{TXT: []string{"So Long\x2c and Thanks for All the Fish"}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeTXT, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.TXTResource{TXT: []string{"Hamster Huey and the Gooey Kablooie"}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("."), Type: dnsmessage.TypeOPT, Class: 4096, TTL: 4261412864, Length: 0}, Body: &dnsmessage.OPTResource{Options: []dnsmessage.Option{dnsmessage.Option{Code: 10, Data: []byte{1, 35, 69, 103, 137, 171, 205, 239}}}}}}}` + want := `dnsmessage.Message{Header: dnsmessage.Header{ID: 0, Response: true, OpCode: 0, Authoritative: true, Truncated: false, RecursionDesired: false, RecursionAvailable: false, AuthenticData: false, CheckingDisabled: false, RCode: dnsmessage.RCodeSuccess}, Questions: []dnsmessage.Question{dnsmessage.Question{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET}}, Answers: []dnsmessage.Resource{dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.AResource{A: [4]byte{127, 0, 0, 1}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.AResource{A: [4]byte{127, 0, 0, 2}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeAAAA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.AAAAResource{AAAA: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeCNAME, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.CNAMEResource{CNAME: dnsmessage.MustNewName("alias.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeSOA, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.SOAResource{NS: dnsmessage.MustNewName("ns1.example.com."), MBox: dnsmessage.MustNewName("mb.example.com."), Serial: 1, Refresh: 2, Retry: 3, Expire: 4, MinTTL: 5}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypePTR, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.PTRResource{PTR: dnsmessage.MustNewName("ptr.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeMX, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.MXResource{Pref: 7, MX: dnsmessage.MustNewName("mx.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeSRV, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.SRVResource{Priority: 8, Weight: 9, Port: 11, Target: dnsmessage.MustNewName("srv.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: 65362, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.UnknownResource{Type: 65362, Data: []byte{42, 0, 43, 44}}}}, Authorities: []dnsmessage.Resource{dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeNS, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.NSResource{NS: dnsmessage.MustNewName("ns1.example.com.")}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeNS, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.NSResource{NS: dnsmessage.MustNewName("ns2.example.com.")}}}, Additionals: []dnsmessage.Resource{dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeTXT, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.TXTResource{TXT: []string{"So Long\x2c and Thanks for All the Fish"}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("foo.bar.example.com."), Type: dnsmessage.TypeTXT, Class: dnsmessage.ClassINET, TTL: 0, Length: 0}, Body: &dnsmessage.TXTResource{TXT: []string{"Hamster Huey and the Gooey Kablooie"}}}, dnsmessage.Resource{Header: dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName("."), Type: dnsmessage.TypeOPT, Class: 4096, TTL: 4261412864, Length: 0}, Body: &dnsmessage.OPTResource{Options: []dnsmessage.Option{dnsmessage.Option{Code: 10, Data: []byte{1, 35, 69, 103, 137, 171, 205, 239}}}}}}}` if got != want { t.Errorf("got msg1.GoString() = %s\nwant = %s", got, want) @@ -1599,3 +1642,235 @@ func TestNoFmt(t *testing.T) { } } } + +func FuzzUnpackPack(f *testing.F) { + for _, msg := range []Message{smallTestMsg(), largeTestMsg()} { + bytes, _ := msg.Pack() + f.Add(bytes) + } + + f.Fuzz(func(t *testing.T, msg []byte) { + var m Message + if err := m.Unpack(msg); err != nil { + return + } + + msgPacked, err := m.Pack() + if err != nil { + t.Fatalf("failed to pack message that was succesfully unpacked: %v", err) + } + + var m2 Message + if err := m2.Unpack(msgPacked); err != nil { + t.Fatalf("failed to unpack message that was succesfully packed: %v", err) + } + + if !reflect.DeepEqual(m, m2) { + t.Fatal("unpack(msg) is not deep equal to unpack(pack(unpack(msg)))") + } + }) +} + +func TestParseResourceHeaderMultipleTimes(t *testing.T) { + msg := Message{ + Header: Header{Response: true, Authoritative: true}, + Answers: []Resource{ + { + ResourceHeader{ + Name: MustNewName("go.dev."), + Type: TypeA, + Class: ClassINET, + }, + &AResource{[4]byte{127, 0, 0, 1}}, + }, + }, + Authorities: []Resource{ + { + ResourceHeader{ + Name: MustNewName("go.dev."), + Type: TypeA, + Class: ClassINET, + }, + &AResource{[4]byte{127, 0, 0, 1}}, + }, + }, + } + + raw, err := msg.Pack() + if err != nil { + t.Fatal(err) + } + + var p Parser + + if _, err := p.Start(raw); err != nil { + t.Fatal(err) + } + + if err := p.SkipAllQuestions(); err != nil { + t.Fatal(err) + } + + hdr1, err := p.AnswerHeader() + if err != nil { + t.Fatal(err) + } + + hdr2, err := p.AnswerHeader() + if err != nil { + t.Fatal(err) + } + + if hdr1 != hdr2 { + t.Fatal("AnswerHeader called multiple times without parsing the RData returned different headers") + } + + if _, err := p.AResource(); err != nil { + t.Fatal(err) + } + + if _, err := p.AnswerHeader(); err != ErrSectionDone { + t.Fatalf("unexpected error: %v, want: %v", err, ErrSectionDone) + } + + hdr3, err := p.AuthorityHeader() + if err != nil { + t.Fatal(err) + } + + hdr4, err := p.AuthorityHeader() + if err != nil { + t.Fatal(err) + } + + if hdr3 != hdr4 { + t.Fatal("AuthorityHeader called multiple times without parsing the RData returned different headers") + } + + if _, err := p.AResource(); err != nil { + t.Fatal(err) + } + + if _, err := p.AuthorityHeader(); err != ErrSectionDone { + t.Fatalf("unexpected error: %v, want: %v", err, ErrSectionDone) + } +} + +func TestParseDifferentResourceHeadersWithoutParsingRData(t *testing.T) { + msg := smallTestMsg() + raw, err := msg.Pack() + if err != nil { + t.Fatal(err) + } + + var p Parser + if _, err := p.Start(raw); err != nil { + t.Fatal(err) + } + + if err := p.SkipAllQuestions(); err != nil { + t.Fatal(err) + } + + if _, err := p.AnswerHeader(); err != nil { + t.Fatal(err) + } + + if _, err := p.AdditionalHeader(); err == nil { + t.Errorf("p.AdditionalHeader() unexpected success") + } + + if _, err := p.AuthorityHeader(); err == nil { + t.Errorf("p.AuthorityHeader() unexpected success") + } +} + +func TestParseWrongSection(t *testing.T) { + msg := smallTestMsg() + raw, err := msg.Pack() + if err != nil { + t.Fatal(err) + } + + var p Parser + if _, err := p.Start(raw); err != nil { + t.Fatal(err) + } + + if err := p.SkipAllQuestions(); err != nil { + t.Fatalf("p.SkipAllQuestions() = %v", err) + } + if _, err := p.AnswerHeader(); err != nil { + t.Fatalf("p.AnswerHeader() = %v", err) + } + if _, err := p.AuthorityHeader(); err == nil { + t.Fatalf("p.AuthorityHeader(): unexpected success in Answer section") + } + if err := p.SkipAuthority(); err == nil { + t.Fatalf("p.SkipAuthority(): unexpected success in Answer section") + } + if err := p.SkipAllAuthorities(); err == nil { + t.Fatalf("p.SkipAllAuthorities(): unexpected success in Answer section") + } +} + +func TestBuilderNameCompressionWithNonZeroedName(t *testing.T) { + b := NewBuilder(nil, Header{}) + b.EnableCompression() + if err := b.StartQuestions(); err != nil { + t.Fatalf("b.StartQuestions() unexpected error: %v", err) + } + + name := MustNewName("go.dev.") + if err := b.Question(Question{Name: name}); err != nil { + t.Fatalf("b.Question() unexpected error: %v", err) + } + + // Character that is not part of the name (name.Data[:name.Length]), + // shouldn't affect the compression algorithm. + name.Data[name.Length] = '1' + if err := b.Question(Question{Name: name}); err != nil { + t.Fatalf("b.Question() unexpected error: %v", err) + } + + msg, err := b.Finish() + if err != nil { + t.Fatalf("b.Finish() unexpected error: %v", err) + } + + expect := []byte{ + 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, // header + 2, 'g', 'o', 3, 'd', 'e', 'v', 0, 0, 0, 0, 0, // question 1 + 0xC0, 12, 0, 0, 0, 0, // question 2 + } + if !bytes.Equal(msg, expect) { + t.Fatalf("b.Finish() = %v, want: %v", msg, expect) + } +} + +func TestBuilderCompressionInAppendMode(t *testing.T) { + maxPtr := int(^uint16(0) >> 2) + b := NewBuilder(make([]byte, maxPtr, maxPtr+512), Header{}) + b.EnableCompression() + if err := b.StartQuestions(); err != nil { + t.Fatalf("b.StartQuestions() unexpected error: %v", err) + } + if err := b.Question(Question{Name: MustNewName("go.dev.")}); err != nil { + t.Fatalf("b.Question() unexpected error: %v", err) + } + if err := b.Question(Question{Name: MustNewName("go.dev.")}); err != nil { + t.Fatalf("b.Question() unexpected error: %v", err) + } + msg, err := b.Finish() + if err != nil { + t.Fatalf("b.Finish() unexpected error: %v", err) + } + expect := []byte{ + 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, // header + 2, 'g', 'o', 3, 'd', 'e', 'v', 0, 0, 0, 0, 0, // question 1 + 0xC0, 12, 0, 0, 0, 0, // question 2 + } + if !bytes.Equal(msg[maxPtr:], expect) { + t.Fatalf("msg[maxPtr:] = %v, want: %v", msg[maxPtr:], expect) + } +} diff --git a/go.mod b/go.mod index 4d8f58967..38ac82b44 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module golang.org/x/net go 1.17 require ( - golang.org/x/sys v0.5.0 - golang.org/x/term v0.5.0 - golang.org/x/text v0.7.0 + golang.org/x/crypto v0.14.0 + golang.org/x/sys v0.13.0 + golang.org/x/term v0.13.0 + golang.org/x/text v0.13.0 ) diff --git a/go.sum b/go.sum index bcd80060d..dc4dc125c 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,42 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/html/comment_test.go b/html/comment_test.go index 2c80bc748..fd47de877 100644 --- a/html/comment_test.go +++ b/html/comment_test.go @@ -6,12 +6,13 @@ package html import ( "bytes" + "strings" "testing" ) // TestComments exhaustively tests every 'interesting' N-byte string is -// correctly parsed as a comment. N ranges from 4+1 to 4+suffixLen inclusive, -// where 4 is the length of the ""); err != nil { @@ -194,9 +194,8 @@ func render1(w writer, n *Node) error { } } - // Render any child nodes. - switch n.Data { - case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp": + // Render any child nodes + if childTextNodesAreLiteral(n) { for c := n.FirstChild; c != nil; c = c.NextSibling { if c.Type == TextNode { if _, err := w.WriteString(c.Data); err != nil { @@ -213,7 +212,7 @@ func render1(w writer, n *Node) error { // last element in the file, with no closing tag. return plaintextAbort } - default: + } else { for c := n.FirstChild; c != nil; c = c.NextSibling { if err := render1(w, c); err != nil { return err @@ -231,6 +230,27 @@ func render1(w writer, n *Node) error { return w.WriteByte('>') } +func childTextNodesAreLiteral(n *Node) bool { + // Per WHATWG HTML 13.3, if the parent of the current node is a style, + // script, xmp, iframe, noembed, noframes, or plaintext element, and the + // current node is a text node, append the value of the node's data + // literally. The specification is not explicit about it, but we only + // enforce this if we are in the HTML namespace (i.e. when the namespace is + // ""). + // NOTE: we also always include noscript elements, although the + // specification states that they should only be rendered as such if + // scripting is enabled for the node (which is not something we track). + if n.Namespace != "" { + return false + } + switch n.Data { + case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp": + return true + default: + return false + } +} + // writeQuoted writes s to w surrounded by quotes. Normally it will use double // quotes, but if s contains a double quote, it will use single quotes. // It is used for writing the identifiers in a doctype declaration. diff --git a/html/render_test.go b/html/render_test.go index 08e592be2..22d08641a 100644 --- a/html/render_test.go +++ b/html/render_test.go @@ -6,6 +6,8 @@ package html import ( "bytes" + "fmt" + "strings" "testing" ) @@ -108,16 +110,16 @@ func TestRenderer(t *testing.T) { // just commentary. The "0:" prefixes are for easy cross-reference with // the nodes array. treeAsText := [...]string{ - 0: ``, - 1: `. `, - 2: `. `, - 3: `. . "0<1"`, - 4: `. .

`, - 5: `. . . "2"`, - 6: `. . . `, - 7: `. . . . "3"`, - 8: `. . . `, - 9: `. . . . "&4"`, + 0: ``, + 1: `. `, + 2: `. `, + 3: `. . "0<1"`, + 4: `. .

`, + 5: `. . . "2"`, + 6: `. . . `, + 7: `. . . . "3"`, + 8: `. . . `, + 9: `. . . . "&4"`, 10: `. . "5"`, 11: `. .

`, 12: `. .
`, @@ -169,3 +171,37 @@ func TestRenderer(t *testing.T) { t.Errorf("got vs want:\n%s\n%s\n", got, want) } } + +func TestRenderTextNodes(t *testing.T) { + elements := []string{"style", "script", "xmp", "iframe", "noembed", "noframes", "plaintext", "noscript"} + for _, namespace := range []string{ + "", // html + "svg", + "math", + } { + for _, e := range elements { + var namespaceOpen, namespaceClose string + if namespace != "" { + namespaceOpen, namespaceClose = fmt.Sprintf("<%s>", namespace), fmt.Sprintf("", namespace) + } + doc := fmt.Sprintf(`%s<%s>&%s`, namespaceOpen, e, e, namespaceClose) + n, err := Parse(strings.NewReader(doc)) + if err != nil { + t.Fatal(err) + } + b := bytes.NewBuffer(nil) + if err := Render(b, n); err != nil { + t.Fatal(err) + } + + expected := doc + if namespace != "" { + expected = strings.Replace(expected, "&", "&", 1) + } + + if b.String() != expected { + t.Errorf("unexpected output: got %q, want %q", b.String(), expected) + } + } + } +} diff --git a/html/token.go b/html/token.go index 50f7c6aac..de67f938a 100644 --- a/html/token.go +++ b/html/token.go @@ -110,7 +110,7 @@ func (t Token) String() string { case SelfClosingTagToken: return "<" + t.tagString() + "/>" case CommentToken: - return "" + return "" case DoctypeToken: return "" } @@ -598,10 +598,10 @@ scriptDataDoubleEscapeEnd: // readComment reads the next comment token starting with "` + type tokenTest struct { // A short description of the test case. desc string @@ -314,7 +324,7 @@ var tokenTests = []tokenTest{ { "comment3", "az", - "a$$z", + "a$$z", }, { "comment4", @@ -334,7 +344,7 @@ var tokenTests = []tokenTest{ { "comment7", "a", + "a$", }, { "comment8", @@ -389,12 +399,12 @@ var tokenTests = []tokenTest{ { "comment18", "az", - "a$$z", + "a$$z", }, { "comment19", "a", + "a$", }, { "comment20", @@ -409,7 +419,89 @@ var tokenTests = []tokenTest{ { "comment22", "az", - "a$$z", + "a$$z", + }, + { + "comment23", + "az", + "a$$z", + }, + { + "comment24", + "a", + }, + { + "comment25", + "a", + }, + { + "comment26", + "a", + }, + { + "comment27", + "az", + "a$$z", + }, + { + "comment28", + "az", + "a$$z", + }, + { + "comment29", + "az", + "a$$z", + }, + { + "comment30", + "az", + "a$$z", + }, + { + "comment31", + "az", + "a$$z", + }, + { + "comment32", + "az", + "a$$z", + }, + // https://stackoverflow.design/email/base/mso/#targeting-specific-outlook-versions + // says "[For] Windows Outlook 2003 and above... conditional comments allow + // us to add bits of HTML that are only read by the Word-based versions of + // Outlook". These comments (with angle brackets) should pass through + // unchanged (by this Go package) when rendering. + // + // We should also still escape ">" as ">" when necessary. + // https://github.com/golang/go/issues/48237 + // + // The "your code" example below comes from that stackoverflow.design link + // above but note that it can contain angle-bracket-rich XML. + // https://github.com/golang/go/issues/58246 + { + "issue48237CommentWithAmpgtsemi1", + "az", + "a$$z", + }, + { + "issue48237CommentWithAmpgtsemi2", + "az", + "a$$z", + }, + { + "issue58246MicrosoftOutlookComment1", + "az", + "a$$z", + }, + { + "issue58246MicrosoftOutlookComment2", + "a" + issue58246 + "z", + "a$" + issue58246 + "$z", }, // An attribute with a backslash. { @@ -498,6 +590,17 @@ var tokenTests = []tokenTest{ `

`, `

$

`, }, + // WHATWG 13.2.5.32 equals sign before attribute name state + { + "equals sign before attribute name", + `

`, + `

`, + }, + { + "equals sign before attribute name, extra cruft", + `

`, + `

`, + }, } func TestTokenizer(t *testing.T) { diff --git a/http2/Dockerfile b/http2/Dockerfile deleted file mode 100644 index 851224595..000000000 --- a/http2/Dockerfile +++ /dev/null @@ -1,51 +0,0 @@ -# -# This Dockerfile builds a recent curl with HTTP/2 client support, using -# a recent nghttp2 build. -# -# See the Makefile for how to tag it. If Docker and that image is found, the -# Go tests use this curl binary for integration tests. -# - -FROM ubuntu:trusty - -RUN apt-get update && \ - apt-get upgrade -y && \ - apt-get install -y git-core build-essential wget - -RUN apt-get install -y --no-install-recommends \ - autotools-dev libtool pkg-config zlib1g-dev \ - libcunit1-dev libssl-dev libxml2-dev libevent-dev \ - automake autoconf - -# The list of packages nghttp2 recommends for h2load: -RUN apt-get install -y --no-install-recommends make binutils \ - autoconf automake autotools-dev \ - libtool pkg-config zlib1g-dev libcunit1-dev libssl-dev libxml2-dev \ - libev-dev libevent-dev libjansson-dev libjemalloc-dev \ - cython python3.4-dev python-setuptools - -# Note: setting NGHTTP2_VER before the git clone, so an old git clone isn't cached: -ENV NGHTTP2_VER 895da9a -RUN cd /root && git clone https://github.com/tatsuhiro-t/nghttp2.git - -WORKDIR /root/nghttp2 -RUN git reset --hard $NGHTTP2_VER -RUN autoreconf -i -RUN automake -RUN autoconf -RUN ./configure -RUN make -RUN make install - -WORKDIR /root -RUN wget https://curl.se/download/curl-7.45.0.tar.gz -RUN tar -zxvf curl-7.45.0.tar.gz -WORKDIR /root/curl-7.45.0 -RUN ./configure --with-ssl --with-nghttp2=/usr/local -RUN make -RUN make install -RUN ldconfig - -CMD ["-h"] -ENTRYPOINT ["/usr/local/bin/curl"] - diff --git a/http2/Makefile b/http2/Makefile deleted file mode 100644 index 55fd826f7..000000000 --- a/http2/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -curlimage: - docker build -t gohttp2/curl . - diff --git a/http2/h2c/h2c.go b/http2/h2c/h2c.go index a72bbed1b..2d6bf861b 100644 --- a/http2/h2c/h2c.go +++ b/http2/h2c/h2c.go @@ -44,7 +44,7 @@ func init() { // HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to // h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to // h2c. When either of those situations occur we hijack the HTTP/1 connection, -// convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn. +// convert it to an HTTP/2 connection and pass the net.Conn to http2.ServeConn. type h2cHandler struct { Handler http.Handler s *http2.Server diff --git a/http2/http2_test.go b/http2/http2_test.go index f77c08a10..a16774b7f 100644 --- a/http2/http2_test.go +++ b/http2/http2_test.go @@ -6,16 +6,13 @@ package http2 import ( "bytes" - "errors" "flag" "fmt" "io/ioutil" "net/http" "os" - "os/exec" "path/filepath" "regexp" - "strconv" "strings" "testing" "time" @@ -85,44 +82,6 @@ func encodeHeaderNoImplicit(t *testing.T, headers ...string) []byte { return buf.Bytes() } -// Verify that curl has http2. -func requireCurl(t *testing.T) { - out, err := dockerLogs(curl(t, "--version")) - if err != nil { - t.Skipf("failed to determine curl features; skipping test") - } - if !strings.Contains(string(out), "HTTP2") { - t.Skip("curl doesn't support HTTP2; skipping test") - } -} - -func curl(t *testing.T, args ...string) (container string) { - out, err := exec.Command("docker", append([]string{"run", "-d", "--net=host", "gohttp2/curl"}, args...)...).Output() - if err != nil { - t.Skipf("Failed to run curl in docker: %v, %s", err, out) - } - return strings.TrimSpace(string(out)) -} - -// Verify that h2load exists. -func requireH2load(t *testing.T) { - out, err := dockerLogs(h2load(t, "--version")) - if err != nil { - t.Skipf("failed to probe h2load; skipping test: %s", out) - } - if !strings.Contains(string(out), "h2load nghttp2/") { - t.Skipf("h2load not present; skipping test. (Output=%q)", out) - } -} - -func h2load(t *testing.T, args ...string) (container string) { - out, err := exec.Command("docker", append([]string{"run", "-d", "--net=host", "--entrypoint=/usr/local/bin/h2load", "gohttp2/curl"}, args...)...).Output() - if err != nil { - t.Skipf("Failed to run h2load in docker: %v, %s", err, out) - } - return strings.TrimSpace(string(out)) -} - type puppetCommand struct { fn func(w http.ResponseWriter, r *http.Request) done chan<- bool @@ -151,27 +110,6 @@ func (p *handlerPuppet) do(fn func(http.ResponseWriter, *http.Request)) { p.ch <- puppetCommand{fn, done} <-done } -func dockerLogs(container string) ([]byte, error) { - out, err := exec.Command("docker", "wait", container).CombinedOutput() - if err != nil { - return out, err - } - exitStatus, err := strconv.Atoi(strings.TrimSpace(string(out))) - if err != nil { - return out, errors.New("unexpected exit status from docker wait") - } - out, err = exec.Command("docker", "logs", container).CombinedOutput() - exec.Command("docker", "rm", container).Run() - if err == nil && exitStatus != 0 { - err = fmt.Errorf("exit status %d: %s", exitStatus, out) - } - return out, err -} - -func kill(container string) { - exec.Command("docker", "kill", container).Run() - exec.Command("docker", "rm", container).Run() -} func cleanDate(res *http.Response) { if d := res.Header["Date"]; len(d) == 1 { diff --git a/http2/pipe.go b/http2/pipe.go index c15b8a771..684d984fd 100644 --- a/http2/pipe.go +++ b/http2/pipe.go @@ -88,13 +88,9 @@ func (p *pipe) Write(d []byte) (n int, err error) { p.c.L = &p.mu } defer p.c.Signal() - if p.err != nil { + if p.err != nil || p.breakErr != nil { return 0, errClosedPipeWrite } - if p.breakErr != nil { - p.unread += len(d) - return len(d), nil // discard when there is no reader - } return p.b.Write(d) } diff --git a/http2/pipe_test.go b/http2/pipe_test.go index 83d2dfd27..67562a92a 100644 --- a/http2/pipe_test.go +++ b/http2/pipe_test.go @@ -125,14 +125,14 @@ func TestPipeBreakWithError(t *testing.T) { if p.Len() != 3 { t.Errorf("pipe should have 3 unread bytes") } - // Write should succeed silently. - if n, err := p.Write([]byte("abc")); err != nil || n != 3 { - t.Errorf("Write(abc) after break\ngot %v, %v\nwant 0, nil", n, err) + // Write should fail. + if n, err := p.Write([]byte("abc")); err != errClosedPipeWrite || n != 0 { + t.Errorf("Write(abc) after break\ngot %v, %v\nwant 0, errClosedPipeWrite", n, err) } if p.b != nil { t.Errorf("buffer should be nil after Write") } - if p.Len() != 6 { + if p.Len() != 3 { t.Errorf("pipe should have 6 unread bytes") } // Read should fail. diff --git a/http2/server.go b/http2/server.go index 8cb14f3c9..02c88b6b3 100644 --- a/http2/server.go +++ b/http2/server.go @@ -441,7 +441,7 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) { if s.NewWriteScheduler != nil { sc.writeSched = s.NewWriteScheduler() } else { - sc.writeSched = NewPriorityWriteScheduler(nil) + sc.writeSched = newRoundRobinWriteScheduler() } // These start at the RFC-specified defaults. If there is a higher @@ -581,9 +581,11 @@ type serverConn struct { advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client curClientStreams uint32 // number of open streams initiated by the client curPushedStreams uint32 // number of open streams initiated by server push + curHandlers uint32 // number of running handler goroutines maxClientStreamID uint32 // max ever seen from client (odd), or 0 if there have been no client requests maxPushPromiseID uint32 // ID of the last push promise (even), or 0 if there have been no pushes streams map[uint32]*stream + unstartedHandlers []unstartedHandler initialStreamSendWindowSize int32 maxFrameSize int32 peerMaxHeaderListSize uint32 // zero means unknown (default) @@ -981,6 +983,8 @@ func (sc *serverConn) serve() { return case gracefulShutdownMsg: sc.startGracefulShutdownInternal() + case handlerDoneMsg: + sc.handlerDone() default: panic("unknown timer") } @@ -1012,14 +1016,6 @@ func (sc *serverConn) serve() { } } -func (sc *serverConn) awaitGracefulShutdown(sharedCh <-chan struct{}, privateCh chan struct{}) { - select { - case <-sc.doneServing: - case <-sharedCh: - close(privateCh) - } -} - type serverMessage int // Message values sent to serveMsgCh. @@ -1028,6 +1024,7 @@ var ( idleTimerMsg = new(serverMessage) shutdownTimerMsg = new(serverMessage) gracefulShutdownMsg = new(serverMessage) + handlerDoneMsg = new(serverMessage) ) func (sc *serverConn) onSettingsTimer() { sc.sendServeMsg(settingsTimerMsg) } @@ -1822,15 +1819,18 @@ func (sc *serverConn) processData(f *DataFrame) error { } if len(data) > 0 { + st.bodyBytes += int64(len(data)) wrote, err := st.body.Write(data) if err != nil { + // The handler has closed the request body. + // Return the connection-level flow control for the discarded data, + // but not the stream-level flow control. sc.sendWindowUpdate(nil, int(f.Length)-wrote) - return sc.countError("body_write_err", streamError(id, ErrCodeStreamClosed)) + return nil } if wrote != len(data) { panic("internal error: bad Writer") } - st.bodyBytes += int64(len(data)) } // Return any padded flow control now, since we won't @@ -1897,9 +1897,11 @@ func (st *stream) copyTrailersToHandlerRequest() { // onReadTimeout is run on its own goroutine (from time.AfterFunc) // when the stream's ReadTimeout has fired. func (st *stream) onReadTimeout() { - // Wrap the ErrDeadlineExceeded to avoid callers depending on us - // returning the bare error. - st.body.CloseWithError(fmt.Errorf("%w", os.ErrDeadlineExceeded)) + if st.body != nil { + // Wrap the ErrDeadlineExceeded to avoid callers depending on us + // returning the bare error. + st.body.CloseWithError(fmt.Errorf("%w", os.ErrDeadlineExceeded)) + } } // onWriteTimeout is run on its own goroutine (from time.AfterFunc) @@ -2017,13 +2019,10 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { // (in Go 1.8), though. That's a more sane option anyway. if sc.hs.ReadTimeout != 0 { sc.conn.SetReadDeadline(time.Time{}) - if st.body != nil { - st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout) - } + st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout) } - go sc.runHandler(rw, req, handler) - return nil + return sc.scheduleHandler(id, rw, req, handler) } func (sc *serverConn) upgradeRequest(req *http.Request) { @@ -2043,6 +2042,10 @@ func (sc *serverConn) upgradeRequest(req *http.Request) { sc.conn.SetReadDeadline(time.Time{}) } + // This is the first request on the connection, + // so start the handler directly rather than going + // through scheduleHandler. + sc.curHandlers++ go sc.runHandler(rw, req, sc.handler.ServeHTTP) } @@ -2283,8 +2286,62 @@ func (sc *serverConn) newResponseWriter(st *stream, req *http.Request) *response return &responseWriter{rws: rws} } +type unstartedHandler struct { + streamID uint32 + rw *responseWriter + req *http.Request + handler func(http.ResponseWriter, *http.Request) +} + +// scheduleHandler starts a handler goroutine, +// or schedules one to start as soon as an existing handler finishes. +func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) error { + sc.serveG.check() + maxHandlers := sc.advMaxStreams + if sc.curHandlers < maxHandlers { + sc.curHandlers++ + go sc.runHandler(rw, req, handler) + return nil + } + if len(sc.unstartedHandlers) > int(4*sc.advMaxStreams) { + return sc.countError("too_many_early_resets", ConnectionError(ErrCodeEnhanceYourCalm)) + } + sc.unstartedHandlers = append(sc.unstartedHandlers, unstartedHandler{ + streamID: streamID, + rw: rw, + req: req, + handler: handler, + }) + return nil +} + +func (sc *serverConn) handlerDone() { + sc.serveG.check() + sc.curHandlers-- + i := 0 + maxHandlers := sc.advMaxStreams + for ; i < len(sc.unstartedHandlers); i++ { + u := sc.unstartedHandlers[i] + if sc.streams[u.streamID] == nil { + // This stream was reset before its goroutine had a chance to start. + continue + } + if sc.curHandlers >= maxHandlers { + break + } + sc.curHandlers++ + go sc.runHandler(u.rw, u.req, u.handler) + sc.unstartedHandlers[i] = unstartedHandler{} // don't retain references + } + sc.unstartedHandlers = sc.unstartedHandlers[i:] + if len(sc.unstartedHandlers) == 0 { + sc.unstartedHandlers = nil + } +} + // Run on its own goroutine. func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) { + defer sc.sendServeMsg(handlerDoneMsg) didPanic := true defer func() { rw.rws.stream.cancelCtx() @@ -2426,7 +2483,7 @@ type requestBody struct { conn *serverConn closeOnce sync.Once // for use by Close only sawEOF bool // for use by Read only - pipe *pipe // non-nil if we have a HTTP entity message body + pipe *pipe // non-nil if we have an HTTP entity message body needsContinue bool // need to send a 100-continue } @@ -2566,7 +2623,8 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) { clen = "" } } - if clen == "" && rws.handlerDone && bodyAllowedForStatus(rws.status) && (len(p) > 0 || !isHeadResp) { + _, hasContentLength := rws.snapHeader["Content-Length"] + if !hasContentLength && clen == "" && rws.handlerDone && bodyAllowedForStatus(rws.status) && (len(p) > 0 || !isHeadResp) { clen = strconv.Itoa(len(p)) } _, hasContentType := rws.snapHeader["Content-Type"] @@ -2771,7 +2829,7 @@ func (w *responseWriter) FlushError() error { err = rws.bw.Flush() } else { // The bufio.Writer won't call chunkWriter.Write - // (writeChunk with zero bytes, so we have to do it + // (writeChunk with zero bytes), so we have to do it // ourselves to force the HTTP response header and/or // final DATA frame (with END_STREAM) to be sent. _, err = chunkWriter{rws}.Write(nil) diff --git a/http2/server_test.go b/http2/server_test.go index d32b2d85b..22657cbfe 100644 --- a/http2/server_test.go +++ b/http2/server_test.go @@ -20,13 +20,11 @@ import ( "net/http" "net/http/httptest" "os" - "os/exec" "reflect" "runtime" "strconv" "strings" "sync" - "sync/atomic" "testing" "time" @@ -2704,96 +2702,6 @@ func readBodyHandler(t *testing.T, want string) func(w http.ResponseWriter, r *h } } -// TestServerWithCurl currently fails, hence the LenientCipherSuites test. See: -// -// https://github.com/tatsuhiro-t/nghttp2/issues/140 & -// http://sourceforge.net/p/curl/bugs/1472/ -func TestServerWithCurl(t *testing.T) { testServerWithCurl(t, false) } -func TestServerWithCurl_LenientCipherSuites(t *testing.T) { testServerWithCurl(t, true) } - -func testServerWithCurl(t *testing.T, permitProhibitedCipherSuites bool) { - if runtime.GOOS != "linux" { - t.Skip("skipping Docker test when not on Linux; requires --net which won't work with boot2docker anyway") - } - if testing.Short() { - t.Skip("skipping curl test in short mode") - } - requireCurl(t) - var gotConn int32 - testHookOnConn = func() { atomic.StoreInt32(&gotConn, 1) } - - const msg = "Hello from curl!\n" - ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Foo", "Bar") - w.Header().Set("Client-Proto", r.Proto) - io.WriteString(w, msg) - })) - ConfigureServer(ts.Config, &Server{ - PermitProhibitedCipherSuites: permitProhibitedCipherSuites, - }) - ts.TLS = ts.Config.TLSConfig // the httptest.Server has its own copy of this TLS config - ts.StartTLS() - defer ts.Close() - - t.Logf("Running test server for curl to hit at: %s", ts.URL) - container := curl(t, "--silent", "--http2", "--insecure", "-v", ts.URL) - defer kill(container) - res, err := dockerLogs(container) - if err != nil { - t.Fatal(err) - } - - body := string(res) - // Search for both "key: value" and "key:value", since curl changed their format - // Our Dockerfile contains the latest version (no space), but just in case people - // didn't rebuild, check both. - if !strings.Contains(body, "foo: Bar") && !strings.Contains(body, "foo:Bar") { - t.Errorf("didn't see foo: Bar header") - t.Logf("Got: %s", body) - } - if !strings.Contains(body, "client-proto: HTTP/2") && !strings.Contains(body, "client-proto:HTTP/2") { - t.Errorf("didn't see client-proto: HTTP/2 header") - t.Logf("Got: %s", res) - } - if !strings.Contains(string(res), msg) { - t.Errorf("didn't see %q content", msg) - t.Logf("Got: %s", res) - } - - if atomic.LoadInt32(&gotConn) == 0 { - t.Error("never saw an http2 connection") - } -} - -var doh2load = flag.Bool("h2load", false, "Run h2load test") - -func TestServerWithH2Load(t *testing.T) { - if !*doh2load { - t.Skip("Skipping without --h2load flag.") - } - if runtime.GOOS != "linux" { - t.Skip("skipping Docker test when not on Linux; requires --net which won't work with boot2docker anyway") - } - requireH2load(t) - - msg := strings.Repeat("Hello, h2load!\n", 5000) - ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, msg) - w.(http.Flusher).Flush() - io.WriteString(w, msg) - })) - ts.StartTLS() - defer ts.Close() - - cmd := exec.Command("docker", "run", "--net=host", "--entrypoint=/usr/local/bin/h2load", "gohttp2/curl", - "-n100000", "-c100", "-m100", ts.URL) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - t.Fatal(err) - } -} - func TestServer_MaxDecoderHeaderTableSize(t *testing.T) { wantHeaderTableSize := uint32(initialHeaderTableSize * 2) st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {}, func(s *Server) { @@ -3555,6 +3463,30 @@ func TestServerNoDuplicateContentType(t *testing.T) { } } +func TestServerContentLengthCanBeDisabled(t *testing.T) { + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + w.Header()["Content-Length"] = nil + fmt.Fprintf(w, "OK") + }) + defer st.Close() + st.greet() + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, + }) + h := st.wantHeaders() + headers := st.decodeHeader(h.HeaderBlockFragment()) + want := [][2]string{ + {":status", "200"}, + {"content-type", "text/plain; charset=utf-8"}, + } + if !reflect.DeepEqual(headers, want) { + t.Errorf("Headers mismatch.\n got: %q\nwant: %q\n", headers, want) + } +} + func disableGoroutineTracking() (restore func()) { old := DebugGoroutines DebugGoroutines = false @@ -3906,6 +3838,32 @@ func TestUnreadFlowControlReturned_Server(t *testing.T) { } } +func TestServerReturnsStreamAndConnFlowControlOnBodyClose(t *testing.T) { + unblockHandler := make(chan struct{}) + defer close(unblockHandler) + + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + r.Body.Close() + w.WriteHeader(200) + w.(http.Flusher).Flush() + <-unblockHandler + }) + defer st.Close() + + st.greet() + st.writeHeaders(HeadersFrameParam{ + StreamID: 1, + BlockFragment: st.encodeHeader(), + EndHeaders: true, + }) + st.wantHeaders() + const size = inflowMinRefresh // enough to trigger flow control return + st.writeData(1, false, make([]byte, size)) + st.wantWindowUpdate(0, size) // conn-level flow control is returned + unblockHandler <- struct{}{} + st.wantData() +} + func TestServerIdleTimeout(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") @@ -4706,3 +4664,116 @@ func TestServerWriteDoesNotRetainBufferAfterServerClose(t *testing.T) { st.ts.Config.Close() <-donec } + +func TestServerMaxHandlerGoroutines(t *testing.T) { + const maxHandlers = 10 + handlerc := make(chan chan bool) + donec := make(chan struct{}) + defer close(donec) + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + stopc := make(chan bool, 1) + select { + case handlerc <- stopc: + case <-donec: + } + select { + case shouldPanic := <-stopc: + if shouldPanic { + panic(http.ErrAbortHandler) + } + case <-donec: + } + }, func(s *Server) { + s.MaxConcurrentStreams = maxHandlers + }) + defer st.Close() + + st.writePreface() + st.writeInitialSettings() + st.writeSettingsAck() + + // Make maxHandlers concurrent requests. + // Reset them all, but only after the handler goroutines have started. + var stops []chan bool + streamID := uint32(1) + for i := 0; i < maxHandlers; i++ { + st.writeHeaders(HeadersFrameParam{ + StreamID: streamID, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, + }) + stops = append(stops, <-handlerc) + st.fr.WriteRSTStream(streamID, ErrCodeCancel) + streamID += 2 + } + + // Start another request, and immediately reset it. + st.writeHeaders(HeadersFrameParam{ + StreamID: streamID, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, + }) + st.fr.WriteRSTStream(streamID, ErrCodeCancel) + streamID += 2 + + // Start another two requests. Don't reset these. + for i := 0; i < 2; i++ { + st.writeHeaders(HeadersFrameParam{ + StreamID: streamID, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, + }) + streamID += 2 + } + + // The initial maxHandlers handlers are still executing, + // so the last two requests don't start any new handlers. + select { + case <-handlerc: + t.Errorf("handler unexpectedly started while maxHandlers are already running") + case <-time.After(1 * time.Millisecond): + } + + // Tell two handlers to exit. + // The pending requests which weren't reset start handlers. + stops[0] <- false // normal exit + stops[1] <- true // panic + stops = stops[2:] + stops = append(stops, <-handlerc) + stops = append(stops, <-handlerc) + + // Make a bunch more requests. + // Eventually, the server tells us to go away. + for i := 0; i < 5*maxHandlers; i++ { + st.writeHeaders(HeadersFrameParam{ + StreamID: streamID, + BlockFragment: st.encodeHeader(), + EndStream: true, + EndHeaders: true, + }) + st.fr.WriteRSTStream(streamID, ErrCodeCancel) + streamID += 2 + } +Frames: + for { + f, err := st.readFrame() + if err != nil { + st.t.Fatal(err) + } + switch f := f.(type) { + case *GoAwayFrame: + if f.ErrCode != ErrCodeEnhanceYourCalm { + t.Errorf("err code = %v; want %v", f.ErrCode, ErrCodeEnhanceYourCalm) + } + break Frames + default: + } + } + + for _, s := range stops { + close(s) + } +} diff --git a/http2/transport.go b/http2/transport.go index 05ba23d3d..4515b22c4 100644 --- a/http2/transport.go +++ b/http2/transport.go @@ -19,6 +19,7 @@ import ( "io/fs" "log" "math" + "math/bits" mathrand "math/rand" "net" "net/http" @@ -290,8 +291,7 @@ func (t *Transport) initConnPool() { // HTTP/2 server. type ClientConn struct { t *Transport - tconn net.Conn // usually *tls.Conn, except specialized impls - tconnClosed bool + tconn net.Conn // usually *tls.Conn, except specialized impls tlsState *tls.ConnectionState // nil only for specialized impls reused uint32 // whether conn is being reused; atomic singleUse bool // whether being used for a single http.Request @@ -518,11 +518,14 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { func authorityAddr(scheme string, authority string) (addr string) { host, port, err := net.SplitHostPort(authority) if err != nil { // authority didn't have a port + host = authority + port = "" + } + if port == "" { // authority's port was empty port = "443" if scheme == "http" { port = "80" } - host = authority } if a, err := idna.ToASCII(host); err == nil { host = a @@ -560,10 +563,11 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res traceGotConn(req, cc, reused) res, err := cc.RoundTrip(req) if err != nil && retry <= 6 { + roundTripErr := err if req, err = shouldRetryRequest(req, err); err == nil { // After the first retry, do exponential backoff with 10% jitter. if retry == 0 { - t.vlogf("RoundTrip retrying after failure: %v", err) + t.vlogf("RoundTrip retrying after failure: %v", roundTripErr) continue } backoff := float64(uint(1) << (uint(retry) - 1)) @@ -572,7 +576,7 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res timer := backoffNewTimer(d) select { case <-timer.C: - t.vlogf("RoundTrip retrying after failure: %v", err) + t.vlogf("RoundTrip retrying after failure: %v", roundTripErr) continue case <-req.Context().Done(): timer.Stop() @@ -1265,6 +1269,29 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { return res, nil } + cancelRequest := func(cs *clientStream, err error) error { + cs.cc.mu.Lock() + bodyClosed := cs.reqBodyClosed + cs.cc.mu.Unlock() + // Wait for the request body to be closed. + // + // If nothing closed the body before now, abortStreamLocked + // will have started a goroutine to close it. + // + // Closing the body before returning avoids a race condition + // with net/http checking its readTrackingBody to see if the + // body was read from or closed. See golang/go#60041. + // + // The body is closed in a separate goroutine without the + // connection mutex held, but dropping the mutex before waiting + // will keep us from holding it indefinitely if the body + // close is slow for some reason. + if bodyClosed != nil { + <-bodyClosed + } + return err + } + for { select { case <-cs.respHeaderRecv: @@ -1284,10 +1311,10 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) { case <-ctx.Done(): err := ctx.Err() cs.abortStream(err) - return nil, err + return nil, cancelRequest(cs, err) case <-cs.reqCancel: cs.abortStream(errRequestCanceled) - return nil, errRequestCanceled + return nil, cancelRequest(cs, errRequestCanceled) } } } @@ -1653,7 +1680,27 @@ func (cs *clientStream) frameScratchBufferLen(maxFrameSize int) int { return int(n) // doesn't truncate; max is 512K } -var bufPool sync.Pool // of *[]byte +// Seven bufPools manage different frame sizes. This helps to avoid scenarios where long-running +// streaming requests using small frame sizes occupy large buffers initially allocated for prior +// requests needing big buffers. The size ranges are as follows: +// {0 KB, 16 KB], {16 KB, 32 KB], {32 KB, 64 KB], {64 KB, 128 KB], {128 KB, 256 KB], +// {256 KB, 512 KB], {512 KB, infinity} +// In practice, the maximum scratch buffer size should not exceed 512 KB due to +// frameScratchBufferLen(maxFrameSize), thus the "infinity pool" should never be used. +// It exists mainly as a safety measure, for potential future increases in max buffer size. +var bufPools [7]sync.Pool // of *[]byte +func bufPoolIndex(size int) int { + if size <= 16384 { + return 0 + } + size -= 1 + bits := bits.Len(uint(size)) + index := bits - 14 + if index >= len(bufPools) { + return len(bufPools) - 1 + } + return index +} func (cs *clientStream) writeRequestBody(req *http.Request) (err error) { cc := cs.cc @@ -1671,12 +1718,13 @@ func (cs *clientStream) writeRequestBody(req *http.Request) (err error) { // Scratch buffer for reading into & writing from. scratchLen := cs.frameScratchBufferLen(maxFrameSize) var buf []byte - if bp, ok := bufPool.Get().(*[]byte); ok && len(*bp) >= scratchLen { - defer bufPool.Put(bp) + index := bufPoolIndex(scratchLen) + if bp, ok := bufPools[index].Get().(*[]byte); ok && len(*bp) >= scratchLen { + defer bufPools[index].Put(bp) buf = *bp } else { buf = make([]byte, scratchLen) - defer bufPool.Put(&buf) + defer bufPools[index].Put(&buf) } var sawEOF bool @@ -1844,6 +1892,9 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail if err != nil { return nil, err } + if !httpguts.ValidHostHeader(host) { + return nil, errors.New("http2: invalid Host header") + } var path string if req.Method != "CONNECT" { @@ -1880,7 +1931,7 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail // 8.1.2.3 Request Pseudo-Header Fields // The :path pseudo-header field includes the path and query parts of the // target URI (the path-absolute production and optionally a '?' character - // followed by the query production (see Sections 3.3 and 3.4 of + // followed by the query production, see Sections 3.3 and 3.4 of // [RFC3986]). f(":authority", host) m := req.Method @@ -2555,6 +2606,9 @@ func (b transportResponseBody) Close() error { cs := b.cs cc := cs.cc + cs.bufPipe.BreakWithError(errClosedResponseBody) + cs.abortStream(errClosedResponseBody) + unread := cs.bufPipe.Len() if unread > 0 { cc.mu.Lock() @@ -2573,9 +2627,6 @@ func (b transportResponseBody) Close() error { cc.wmu.Unlock() } - cs.bufPipe.BreakWithError(errClosedResponseBody) - cs.abortStream(errClosedResponseBody) - select { case <-cs.donec: case <-cs.ctx.Done(): diff --git a/http2/transport_test.go b/http2/transport_test.go index 5adef4292..99848485b 100644 --- a/http2/transport_test.go +++ b/http2/transport_test.go @@ -775,7 +775,6 @@ func newClientTester(t *testing.T) *clientTester { cc, err := net.Dial("tcp", ln.Addr().String()) if err != nil { t.Fatal(err) - } sc, err := ln.Accept() if err != nil { @@ -1765,6 +1764,18 @@ func TestTransportChecksRequestHeaderListSize(t *testing.T) { defer tr.CloseIdleConnections() checkRoundTrip := func(req *http.Request, wantErr error, desc string) { + // Make an arbitrary request to ensure we get the server's + // settings frame and initialize peerMaxHeaderListSize. + req0, err := http.NewRequest("GET", st.ts.URL, nil) + if err != nil { + t.Fatalf("newRequest: NewRequest: %v", err) + } + res0, err := tr.RoundTrip(req0) + if err != nil { + t.Errorf("%v: Initial RoundTrip err = %v", desc, err) + } + res0.Body.Close() + res, err := tr.RoundTrip(req) if err != wantErr { if res != nil { @@ -1825,13 +1836,9 @@ func TestTransportChecksRequestHeaderListSize(t *testing.T) { return req } - // Make an arbitrary request to ensure we get the server's - // settings frame and initialize peerMaxHeaderListSize. + // Validate peerMaxHeaderListSize. req := newRequest() checkRoundTrip(req, nil, "Initial request") - - // Get the ClientConn associated with the request and validate - // peerMaxHeaderListSize. addr := authorityAddr(req.URL.Scheme, req.URL.Host) cc, err := tr.connPool().GetClientConn(req, addr) if err != nil { @@ -3738,35 +3745,33 @@ func testTransportPingWhenReading(t *testing.T, readIdleTimeout, deadline time.D ct.run() } -func TestTransportRetryAfterGOAWAY(t *testing.T) { - var dialer struct { - sync.Mutex - count int - } - ct1 := make(chan *clientTester) - ct2 := make(chan *clientTester) - +func testClientMultipleDials(t *testing.T, client func(*Transport), server func(int, *clientTester)) { ln := newLocalListener(t) defer ln.Close() + var ( + mu sync.Mutex + count int + conns []net.Conn + ) + var wg sync.WaitGroup tr := &Transport{ TLSClientConfig: tlsConfigInsecure, } tr.DialTLS = func(network, addr string, cfg *tls.Config) (net.Conn, error) { - dialer.Lock() - defer dialer.Unlock() - dialer.count++ - if dialer.count == 3 { - return nil, errors.New("unexpected number of dials") - } + mu.Lock() + defer mu.Unlock() + count++ cc, err := net.Dial("tcp", ln.Addr().String()) if err != nil { return nil, fmt.Errorf("dial error: %v", err) } + conns = append(conns, cc) sc, err := ln.Accept() if err != nil { return nil, fmt.Errorf("accept error: %v", err) } + conns = append(conns, sc) ct := &clientTester{ t: t, tr: tr, @@ -3774,19 +3779,25 @@ func TestTransportRetryAfterGOAWAY(t *testing.T) { sc: sc, fr: NewFramer(sc, sc), } - switch dialer.count { - case 1: - ct1 <- ct - case 2: - ct2 <- ct - } + wg.Add(1) + go func(count int) { + defer wg.Done() + server(count, ct) + }(count) return cc, nil } - errs := make(chan error, 3) + client(tr) + tr.CloseIdleConnections() + ln.Close() + for _, c := range conns { + c.Close() + } + wg.Wait() +} - // Client. - go func() { +func TestTransportRetryAfterGOAWAY(t *testing.T) { + client := func(tr *Transport) { req, _ := http.NewRequest("GET", "https://dummy.tld/", nil) res, err := tr.RoundTrip(req) if res != nil { @@ -3796,102 +3807,77 @@ func TestTransportRetryAfterGOAWAY(t *testing.T) { } } if err != nil { - err = fmt.Errorf("RoundTrip: %v", err) + t.Errorf("RoundTrip: %v", err) } - errs <- err - }() - - connToClose := make(chan io.Closer, 2) - - // Server for the first request. - go func() { - ct := <-ct1 - - connToClose <- ct.cc - ct.greet() - hf, err := ct.firstHeaders() - if err != nil { - errs <- fmt.Errorf("server1 failed reading HEADERS: %v", err) - return - } - t.Logf("server1 got %v", hf) - if err := ct.fr.WriteGoAway(0 /*max id*/, ErrCodeNo, nil); err != nil { - errs <- fmt.Errorf("server1 failed writing GOAWAY: %v", err) - return - } - errs <- nil - }() + } - // Server for the second request. - go func() { - ct := <-ct2 + server := func(count int, ct *clientTester) { + switch count { + case 1: + ct.greet() + hf, err := ct.firstHeaders() + if err != nil { + t.Errorf("server1 failed reading HEADERS: %v", err) + return + } + t.Logf("server1 got %v", hf) + if err := ct.fr.WriteGoAway(0 /*max id*/, ErrCodeNo, nil); err != nil { + t.Errorf("server1 failed writing GOAWAY: %v", err) + return + } + case 2: + ct.greet() + hf, err := ct.firstHeaders() + if err != nil { + t.Errorf("server2 failed reading HEADERS: %v", err) + return + } + t.Logf("server2 got %v", hf) - connToClose <- ct.cc - ct.greet() - hf, err := ct.firstHeaders() - if err != nil { - errs <- fmt.Errorf("server2 failed reading HEADERS: %v", err) + var buf bytes.Buffer + enc := hpack.NewEncoder(&buf) + enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"}) + enc.WriteField(hpack.HeaderField{Name: "foo", Value: "bar"}) + err = ct.fr.WriteHeaders(HeadersFrameParam{ + StreamID: hf.StreamID, + EndHeaders: true, + EndStream: false, + BlockFragment: buf.Bytes(), + }) + if err != nil { + t.Errorf("server2 failed writing response HEADERS: %v", err) + } + default: + t.Errorf("unexpected number of dials") return } - t.Logf("server2 got %v", hf) - - var buf bytes.Buffer - enc := hpack.NewEncoder(&buf) - enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"}) - enc.WriteField(hpack.HeaderField{Name: "foo", Value: "bar"}) - err = ct.fr.WriteHeaders(HeadersFrameParam{ - StreamID: hf.StreamID, - EndHeaders: true, - EndStream: false, - BlockFragment: buf.Bytes(), - }) - if err != nil { - errs <- fmt.Errorf("server2 failed writing response HEADERS: %v", err) - } else { - errs <- nil - } - }() - - for k := 0; k < 3; k++ { - err := <-errs - if err != nil { - t.Error(err) - } } - close(connToClose) - for c := range connToClose { - c.Close() - } + testClientMultipleDials(t, client, server) } func TestTransportRetryAfterRefusedStream(t *testing.T) { clientDone := make(chan struct{}) - ct := newClientTester(t) - ct.client = func() error { - defer ct.cc.(*net.TCPConn).CloseWrite() - if runtime.GOOS == "plan9" { - // CloseWrite not supported on Plan 9; Issue 17906 - defer ct.cc.(*net.TCPConn).Close() - } + client := func(tr *Transport) { defer close(clientDone) req, _ := http.NewRequest("GET", "https://dummy.tld/", nil) - resp, err := ct.tr.RoundTrip(req) + resp, err := tr.RoundTrip(req) if err != nil { - return fmt.Errorf("RoundTrip: %v", err) + t.Errorf("RoundTrip: %v", err) + return } resp.Body.Close() if resp.StatusCode != 204 { - return fmt.Errorf("Status = %v; want 204", resp.StatusCode) + t.Errorf("Status = %v; want 204", resp.StatusCode) + return } - return nil } - ct.server = func() error { + + server := func(_ int, ct *clientTester) { ct.greet() var buf bytes.Buffer enc := hpack.NewEncoder(&buf) - nreq := 0 - + var count int for { f, err := ct.fr.ReadFrame() if err != nil { @@ -3900,19 +3886,20 @@ func TestTransportRetryAfterRefusedStream(t *testing.T) { // If the client's done, it // will have reported any // errors on its side. - return nil default: - return err + t.Error(err) } + return } switch f := f.(type) { case *WindowUpdateFrame, *SettingsFrame: case *HeadersFrame: if !f.HeadersEnded() { - return fmt.Errorf("headers should have END_HEADERS be ended: %v", f) + t.Errorf("headers should have END_HEADERS be ended: %v", f) + return } - nreq++ - if nreq == 1 { + count++ + if count == 1 { ct.fr.WriteRSTStream(f.StreamID, ErrCodeRefusedStream) } else { enc.WriteField(hpack.HeaderField{Name: ":status", Value: "204"}) @@ -3924,11 +3911,13 @@ func TestTransportRetryAfterRefusedStream(t *testing.T) { }) } default: - return fmt.Errorf("Unexpected client frame %v", f) + t.Errorf("Unexpected client frame %v", f) + return } } } - ct.run() + + testClientMultipleDials(t, client, server) } func TestTransportRetryHasLimit(t *testing.T) { @@ -4143,6 +4132,7 @@ func TestTransportRequestsStallAtServerLimit(t *testing.T) { greet := make(chan struct{}) // server sends initial SETTINGS frame gotRequest := make(chan struct{}) // server received a request clientDone := make(chan struct{}) + cancelClientRequest := make(chan struct{}) // Collect errors from goroutines. var wg sync.WaitGroup @@ -4221,9 +4211,8 @@ func TestTransportRequestsStallAtServerLimit(t *testing.T) { req, _ := http.NewRequest("GET", fmt.Sprintf("https://dummy.tld/%d", k), body) if k == maxConcurrent { // This request will be canceled. - cancel := make(chan struct{}) - req.Cancel = cancel - close(cancel) + req.Cancel = cancelClientRequest + close(cancelClientRequest) _, err := ct.tr.RoundTrip(req) close(clientRequestCancelled) if err == nil { @@ -4467,11 +4456,14 @@ func TestAuthorityAddr(t *testing.T) { }{ {"http", "foo.com", "foo.com:80"}, {"https", "foo.com", "foo.com:443"}, + {"https", "foo.com:", "foo.com:443"}, {"https", "foo.com:1234", "foo.com:1234"}, {"https", "1.2.3.4:1234", "1.2.3.4:1234"}, {"https", "1.2.3.4", "1.2.3.4:443"}, + {"https", "1.2.3.4:", "1.2.3.4:443"}, {"https", "[::1]:1234", "[::1]:1234"}, {"https", "[::1]", "[::1]:443"}, + {"https", "[::1]:", "[::1]:443"}, } for _, tt := range tests { got := authorityAddr(tt.scheme, tt.authority) @@ -5986,14 +5978,21 @@ func TestTransportRetriesOnStreamProtocolError(t *testing.T) { } func TestClientConnReservations(t *testing.T) { - cc := &ClientConn{ - reqHeaderMu: make(chan struct{}, 1), - streams: make(map[uint32]*clientStream), - maxConcurrentStreams: initialMaxConcurrentStreams, - nextStreamID: 1, - t: &Transport{}, + st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) { + }, func(s *Server) { + s.MaxConcurrentStreams = initialMaxConcurrentStreams + }) + defer st.Close() + + tr := &Transport{TLSClientConfig: tlsConfigInsecure} + defer tr.CloseIdleConnections() + + cc, err := tr.newClientConn(st.cc, false) + if err != nil { + t.Fatal(err) } - cc.cond = sync.NewCond(&cc.mu) + + req, _ := http.NewRequest("GET", st.ts.URL, nil) n := 0 for n <= initialMaxConcurrentStreams && cc.ReserveNewRequest() { n++ @@ -6001,8 +6000,8 @@ func TestClientConnReservations(t *testing.T) { if n != initialMaxConcurrentStreams { t.Errorf("did %v reservations; want %v", n, initialMaxConcurrentStreams) } - if _, err := cc.RoundTrip(new(http.Request)); !errors.Is(err, errNilRequestURL) { - t.Fatalf("RoundTrip error = %v; want errNilRequestURL", err) + if _, err := cc.RoundTrip(req); err != nil { + t.Fatalf("RoundTrip error = %v", err) } n2 := 0 for n2 <= 5 && cc.ReserveNewRequest() { @@ -6014,7 +6013,7 @@ func TestClientConnReservations(t *testing.T) { // Use up all the reservations for i := 0; i < n; i++ { - cc.RoundTrip(new(http.Request)) + cc.RoundTrip(req) } n2 = 0 diff --git a/http2/writesched.go b/http2/writesched.go index c7cd00173..cc893adc2 100644 --- a/http2/writesched.go +++ b/http2/writesched.go @@ -184,7 +184,8 @@ func (wr *FrameWriteRequest) replyToWriter(err error) { // writeQueue is used by implementations of WriteScheduler. type writeQueue struct { - s []FrameWriteRequest + s []FrameWriteRequest + prev, next *writeQueue } func (q *writeQueue) empty() bool { return len(q.s) == 0 } diff --git a/http2/writesched_roundrobin.go b/http2/writesched_roundrobin.go new file mode 100644 index 000000000..54fe86322 --- /dev/null +++ b/http2/writesched_roundrobin.go @@ -0,0 +1,119 @@ +// Copyright 2023 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 http2 + +import ( + "fmt" + "math" +) + +type roundRobinWriteScheduler struct { + // control contains control frames (SETTINGS, PING, etc.). + control writeQueue + + // streams maps stream ID to a queue. + streams map[uint32]*writeQueue + + // stream queues are stored in a circular linked list. + // head is the next stream to write, or nil if there are no streams open. + head *writeQueue + + // pool of empty queues for reuse. + queuePool writeQueuePool +} + +// newRoundRobinWriteScheduler constructs a new write scheduler. +// The round robin scheduler priorizes control frames +// like SETTINGS and PING over DATA frames. +// When there are no control frames to send, it performs a round-robin +// selection from the ready streams. +func newRoundRobinWriteScheduler() WriteScheduler { + ws := &roundRobinWriteScheduler{ + streams: make(map[uint32]*writeQueue), + } + return ws +} + +func (ws *roundRobinWriteScheduler) OpenStream(streamID uint32, options OpenStreamOptions) { + if ws.streams[streamID] != nil { + panic(fmt.Errorf("stream %d already opened", streamID)) + } + q := ws.queuePool.get() + ws.streams[streamID] = q + if ws.head == nil { + ws.head = q + q.next = q + q.prev = q + } else { + // Queues are stored in a ring. + // Insert the new stream before ws.head, putting it at the end of the list. + q.prev = ws.head.prev + q.next = ws.head + q.prev.next = q + q.next.prev = q + } +} + +func (ws *roundRobinWriteScheduler) CloseStream(streamID uint32) { + q := ws.streams[streamID] + if q == nil { + return + } + if q.next == q { + // This was the only open stream. + ws.head = nil + } else { + q.prev.next = q.next + q.next.prev = q.prev + if ws.head == q { + ws.head = q.next + } + } + delete(ws.streams, streamID) + ws.queuePool.put(q) +} + +func (ws *roundRobinWriteScheduler) AdjustStream(streamID uint32, priority PriorityParam) {} + +func (ws *roundRobinWriteScheduler) Push(wr FrameWriteRequest) { + if wr.isControl() { + ws.control.push(wr) + return + } + q := ws.streams[wr.StreamID()] + if q == nil { + // This is a closed stream. + // wr should not be a HEADERS or DATA frame. + // We push the request onto the control queue. + if wr.DataSize() > 0 { + panic("add DATA on non-open stream") + } + ws.control.push(wr) + return + } + q.push(wr) +} + +func (ws *roundRobinWriteScheduler) Pop() (FrameWriteRequest, bool) { + // Control and RST_STREAM frames first. + if !ws.control.empty() { + return ws.control.shift(), true + } + if ws.head == nil { + return FrameWriteRequest{}, false + } + q := ws.head + for { + if wr, ok := q.consume(math.MaxInt32); ok { + ws.head = q.next + return wr, true + } + q = q.next + if q == ws.head { + break + } + } + return FrameWriteRequest{}, false +} diff --git a/http2/writesched_roundrobin_test.go b/http2/writesched_roundrobin_test.go new file mode 100644 index 000000000..032b2bc6c --- /dev/null +++ b/http2/writesched_roundrobin_test.go @@ -0,0 +1,65 @@ +// Copyright 2023 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 http2 + +import ( + "reflect" + "testing" +) + +func TestRoundRobinScheduler(t *testing.T) { + const maxFrameSize = 16 + sc := &serverConn{maxFrameSize: maxFrameSize} + ws := newRoundRobinWriteScheduler() + streams := make([]*stream, 4) + for i := range streams { + streamID := uint32(i) + 1 + streams[i] = &stream{ + id: streamID, + sc: sc, + } + streams[i].flow.add(1 << 20) // arbitrary large value + ws.OpenStream(streamID, OpenStreamOptions{}) + wr := FrameWriteRequest{ + write: &writeData{ + streamID: streamID, + p: make([]byte, maxFrameSize*(i+1)), + endStream: false, + }, + stream: streams[i], + } + ws.Push(wr) + } + const controlFrames = 2 + for i := 0; i < controlFrames; i++ { + ws.Push(makeWriteNonStreamRequest()) + } + + // We should get the control frames first. + for i := 0; i < controlFrames; i++ { + wr, ok := ws.Pop() + if !ok || wr.StreamID() != 0 { + t.Fatalf("wr.Pop() = stream %v, %v; want 0, true", wr.StreamID(), ok) + } + } + + // Each stream should write maxFrameSize bytes until it runs out of data. + // Stream 1 has one frame of data, 2 has two frames, etc. + want := []uint32{1, 2, 3, 4, 2, 3, 4, 3, 4, 4} + var got []uint32 + for { + wr, ok := ws.Pop() + if !ok { + break + } + if wr.DataSize() != maxFrameSize { + t.Fatalf("wr.Pop() = %v data bytes, want %v", wr.DataSize(), maxFrameSize) + } + got = append(got, wr.StreamID()) + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("popped streams %v, want %v", got, want) + } +} diff --git a/idna/idna9.0.0.go b/idna/idna9.0.0.go index aae6aac87..ee1698cef 100644 --- a/idna/idna9.0.0.go +++ b/idna/idna9.0.0.go @@ -121,7 +121,7 @@ func CheckJoiners(enable bool) Option { } } -// StrictDomainName limits the set of permissable ASCII characters to those +// StrictDomainName limits the set of permissible ASCII characters to those // allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the // hyphen). This is set by default for MapForLookup and ValidateForRegistration, // but is only useful if ValidateLabels is set. diff --git a/idna/tables13.0.0.go b/idna/tables13.0.0.go index 390c5e56d..66701eadf 100644 --- a/idna/tables13.0.0.go +++ b/idna/tables13.0.0.go @@ -1,151 +1,294 @@ // Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. -//go:build go1.16 -// +build go1.16 +//go:build go1.16 && !go1.21 +// +build go1.16,!go1.21 package idna // UnicodeVersion is the Unicode version from which the tables in this package are derived. const UnicodeVersion = "13.0.0" -var mappings string = "" + // Size: 8188 bytes - "\x00\x01 \x03 ̈\x01a\x03 ̄\x012\x013\x03 ́\x03 ̧\x011\x01o\x051⁄4\x051⁄2" + - "\x053⁄4\x03i̇\x03l·\x03ʼn\x01s\x03dž\x03ⱥ\x03ⱦ\x01h\x01j\x01r\x01w\x01y" + - "\x03 ̆\x03 ̇\x03 ̊\x03 ̨\x03 ̃\x03 ̋\x01l\x01x\x04̈́\x03 ι\x01;\x05 ̈́" + - "\x04եւ\x04اٴ\x04وٴ\x04ۇٴ\x04يٴ\x06क़\x06ख़\x06ग़\x06ज़\x06ड़\x06ढ़\x06फ़" + - "\x06य़\x06ড়\x06ঢ়\x06য়\x06ਲ਼\x06ਸ਼\x06ਖ਼\x06ਗ਼\x06ਜ਼\x06ਫ਼\x06ଡ଼\x06ଢ଼" + - "\x06ํา\x06ໍາ\x06ຫນ\x06ຫມ\x06གྷ\x06ཌྷ\x06དྷ\x06བྷ\x06ཛྷ\x06ཀྵ\x06ཱི\x06ཱུ" + - "\x06ྲྀ\x09ྲཱྀ\x06ླྀ\x09ླཱྀ\x06ཱྀ\x06ྒྷ\x06ྜྷ\x06ྡྷ\x06ྦྷ\x06ྫྷ\x06ྐྵ\x02" + - "в\x02д\x02о\x02с\x02т\x02ъ\x02ѣ\x02æ\x01b\x01d\x01e\x02ǝ\x01g\x01i\x01k" + - "\x01m\x01n\x02ȣ\x01p\x01t\x01u\x02ɐ\x02ɑ\x02ə\x02ɛ\x02ɜ\x02ŋ\x02ɔ\x02ɯ" + - "\x01v\x02β\x02γ\x02δ\x02φ\x02χ\x02ρ\x02н\x02ɒ\x01c\x02ɕ\x02ð\x01f\x02ɟ" + - "\x02ɡ\x02ɥ\x02ɨ\x02ɩ\x02ɪ\x02ʝ\x02ɭ\x02ʟ\x02ɱ\x02ɰ\x02ɲ\x02ɳ\x02ɴ\x02ɵ" + - "\x02ɸ\x02ʂ\x02ʃ\x02ƫ\x02ʉ\x02ʊ\x02ʋ\x02ʌ\x01z\x02ʐ\x02ʑ\x02ʒ\x02θ\x02ss" + - "\x02ά\x02έ\x02ή\x02ί\x02ό\x02ύ\x02ώ\x05ἀι\x05ἁι\x05ἂι\x05ἃι\x05ἄι\x05ἅι" + - "\x05ἆι\x05ἇι\x05ἠι\x05ἡι\x05ἢι\x05ἣι\x05ἤι\x05ἥι\x05ἦι\x05ἧι\x05ὠι\x05ὡι" + - "\x05ὢι\x05ὣι\x05ὤι\x05ὥι\x05ὦι\x05ὧι\x05ὰι\x04αι\x04άι\x05ᾶι\x02ι\x05 ̈͂" + - "\x05ὴι\x04ηι\x04ήι\x05ῆι\x05 ̓̀\x05 ̓́\x05 ̓͂\x02ΐ\x05 ̔̀\x05 ̔́\x05 ̔͂" + - "\x02ΰ\x05 ̈̀\x01`\x05ὼι\x04ωι\x04ώι\x05ῶι\x06′′\x09′′′\x06‵‵\x09‵‵‵\x02!" + - "!\x02??\x02?!\x02!?\x0c′′′′\x010\x014\x015\x016\x017\x018\x019\x01+\x01=" + - "\x01(\x01)\x02rs\x02ħ\x02no\x01q\x02sm\x02tm\x02ω\x02å\x02א\x02ב\x02ג" + - "\x02ד\x02π\x051⁄7\x051⁄9\x061⁄10\x051⁄3\x052⁄3\x051⁄5\x052⁄5\x053⁄5\x054" + - "⁄5\x051⁄6\x055⁄6\x051⁄8\x053⁄8\x055⁄8\x057⁄8\x041⁄\x02ii\x02iv\x02vi" + - "\x04viii\x02ix\x02xi\x050⁄3\x06∫∫\x09∫∫∫\x06∮∮\x09∮∮∮\x0210\x0211\x0212" + - "\x0213\x0214\x0215\x0216\x0217\x0218\x0219\x0220\x04(10)\x04(11)\x04(12)" + - "\x04(13)\x04(14)\x04(15)\x04(16)\x04(17)\x04(18)\x04(19)\x04(20)\x0c∫∫∫∫" + - "\x02==\x05⫝̸\x02ɫ\x02ɽ\x02ȿ\x02ɀ\x01.\x04 ゙\x04 ゚\x06より\x06コト\x05(ᄀ)\x05" + - "(ᄂ)\x05(ᄃ)\x05(ᄅ)\x05(ᄆ)\x05(ᄇ)\x05(ᄉ)\x05(ᄋ)\x05(ᄌ)\x05(ᄎ)\x05(ᄏ)\x05(ᄐ" + - ")\x05(ᄑ)\x05(ᄒ)\x05(가)\x05(나)\x05(다)\x05(라)\x05(마)\x05(바)\x05(사)\x05(아)" + - "\x05(자)\x05(차)\x05(카)\x05(타)\x05(파)\x05(하)\x05(주)\x08(오전)\x08(오후)\x05(一)" + - "\x05(二)\x05(三)\x05(四)\x05(五)\x05(六)\x05(七)\x05(八)\x05(九)\x05(十)\x05(月)" + - "\x05(火)\x05(水)\x05(木)\x05(金)\x05(土)\x05(日)\x05(株)\x05(有)\x05(社)\x05(名)" + - "\x05(特)\x05(財)\x05(祝)\x05(労)\x05(代)\x05(呼)\x05(学)\x05(監)\x05(企)\x05(資)" + - "\x05(協)\x05(祭)\x05(休)\x05(自)\x05(至)\x0221\x0222\x0223\x0224\x0225\x0226" + - "\x0227\x0228\x0229\x0230\x0231\x0232\x0233\x0234\x0235\x06참고\x06주의\x0236" + - "\x0237\x0238\x0239\x0240\x0241\x0242\x0243\x0244\x0245\x0246\x0247\x0248" + - "\x0249\x0250\x041月\x042月\x043月\x044月\x045月\x046月\x047月\x048月\x049月\x0510" + - "月\x0511月\x0512月\x02hg\x02ev\x06令和\x0cアパート\x0cアルファ\x0cアンペア\x09アール\x0cイニ" + - "ング\x09インチ\x09ウォン\x0fエスクード\x0cエーカー\x09オンス\x09オーム\x09カイリ\x0cカラット\x0cカロリー" + - "\x09ガロン\x09ガンマ\x06ギガ\x09ギニー\x0cキュリー\x0cギルダー\x06キロ\x0fキログラム\x12キロメートル\x0f" + - "キロワット\x09グラム\x0fグラムトン\x0fクルゼイロ\x0cクローネ\x09ケース\x09コルナ\x09コーポ\x0cサイクル" + - "\x0fサンチーム\x0cシリング\x09センチ\x09セント\x09ダース\x06デシ\x06ドル\x06トン\x06ナノ\x09ノット" + - "\x09ハイツ\x0fパーセント\x09パーツ\x0cバーレル\x0fピアストル\x09ピクル\x06ピコ\x06ビル\x0fファラッド\x0c" + - "フィート\x0fブッシェル\x09フラン\x0fヘクタール\x06ペソ\x09ペニヒ\x09ヘルツ\x09ペンス\x09ページ\x09ベータ" + - "\x0cポイント\x09ボルト\x06ホン\x09ポンド\x09ホール\x09ホーン\x0cマイクロ\x09マイル\x09マッハ\x09マルク" + - "\x0fマンション\x0cミクロン\x06ミリ\x0fミリバール\x06メガ\x0cメガトン\x0cメートル\x09ヤード\x09ヤール\x09" + - "ユアン\x0cリットル\x06リラ\x09ルピー\x0cルーブル\x06レム\x0fレントゲン\x09ワット\x040点\x041点\x04" + - "2点\x043点\x044点\x045点\x046点\x047点\x048点\x049点\x0510点\x0511点\x0512点\x0513点" + - "\x0514点\x0515点\x0516点\x0517点\x0518点\x0519点\x0520点\x0521点\x0522点\x0523点" + - "\x0524点\x02da\x02au\x02ov\x02pc\x02dm\x02iu\x06平成\x06昭和\x06大正\x06明治\x0c株" + - "式会社\x02pa\x02na\x02ma\x02ka\x02kb\x02mb\x02gb\x04kcal\x02pf\x02nf\x02m" + - "g\x02kg\x02hz\x02ml\x02dl\x02kl\x02fm\x02nm\x02mm\x02cm\x02km\x02m2\x02m" + - "3\x05m∕s\x06m∕s2\x07rad∕s\x08rad∕s2\x02ps\x02ns\x02ms\x02pv\x02nv\x02mv" + - "\x02kv\x02pw\x02nw\x02mw\x02kw\x02bq\x02cc\x02cd\x06c∕kg\x02db\x02gy\x02" + - "ha\x02hp\x02in\x02kk\x02kt\x02lm\x02ln\x02lx\x02ph\x02pr\x02sr\x02sv\x02" + - "wb\x05v∕m\x05a∕m\x041日\x042日\x043日\x044日\x045日\x046日\x047日\x048日\x049日" + - "\x0510日\x0511日\x0512日\x0513日\x0514日\x0515日\x0516日\x0517日\x0518日\x0519日" + - "\x0520日\x0521日\x0522日\x0523日\x0524日\x0525日\x0526日\x0527日\x0528日\x0529日" + - "\x0530日\x0531日\x02ь\x02ɦ\x02ɬ\x02ʞ\x02ʇ\x02œ\x02ʍ\x04𤋮\x04𢡊\x04𢡄\x04𣏕" + - "\x04𥉉\x04𥳐\x04𧻓\x02ff\x02fi\x02fl\x02st\x04մն\x04մե\x04մի\x04վն\x04մխ" + - "\x04יִ\x04ײַ\x02ע\x02ה\x02כ\x02ל\x02ם\x02ר\x02ת\x04שׁ\x04שׂ\x06שּׁ\x06שּ" + - "ׂ\x04אַ\x04אָ\x04אּ\x04בּ\x04גּ\x04דּ\x04הּ\x04וּ\x04זּ\x04טּ\x04יּ\x04" + - "ךּ\x04כּ\x04לּ\x04מּ\x04נּ\x04סּ\x04ףּ\x04פּ\x04צּ\x04קּ\x04רּ\x04שּ" + - "\x04תּ\x04וֹ\x04בֿ\x04כֿ\x04פֿ\x04אל\x02ٱ\x02ٻ\x02پ\x02ڀ\x02ٺ\x02ٿ\x02ٹ" + - "\x02ڤ\x02ڦ\x02ڄ\x02ڃ\x02چ\x02ڇ\x02ڍ\x02ڌ\x02ڎ\x02ڈ\x02ژ\x02ڑ\x02ک\x02گ" + - "\x02ڳ\x02ڱ\x02ں\x02ڻ\x02ۀ\x02ہ\x02ھ\x02ے\x02ۓ\x02ڭ\x02ۇ\x02ۆ\x02ۈ\x02ۋ" + - "\x02ۅ\x02ۉ\x02ې\x02ى\x04ئا\x04ئە\x04ئو\x04ئۇ\x04ئۆ\x04ئۈ\x04ئې\x04ئى\x02" + - "ی\x04ئج\x04ئح\x04ئم\x04ئي\x04بج\x04بح\x04بخ\x04بم\x04بى\x04بي\x04تج\x04" + - "تح\x04تخ\x04تم\x04تى\x04تي\x04ثج\x04ثم\x04ثى\x04ثي\x04جح\x04جم\x04حج" + - "\x04حم\x04خج\x04خح\x04خم\x04سج\x04سح\x04سخ\x04سم\x04صح\x04صم\x04ضج\x04ضح" + - "\x04ضخ\x04ضم\x04طح\x04طم\x04ظم\x04عج\x04عم\x04غج\x04غم\x04فج\x04فح\x04فخ" + - "\x04فم\x04فى\x04في\x04قح\x04قم\x04قى\x04قي\x04كا\x04كج\x04كح\x04كخ\x04كل" + - "\x04كم\x04كى\x04كي\x04لج\x04لح\x04لخ\x04لم\x04لى\x04لي\x04مج\x04مح\x04مخ" + - "\x04مم\x04مى\x04مي\x04نج\x04نح\x04نخ\x04نم\x04نى\x04ني\x04هج\x04هم\x04هى" + - "\x04هي\x04يج\x04يح\x04يخ\x04يم\x04يى\x04يي\x04ذٰ\x04رٰ\x04ىٰ\x05 ٌّ\x05 " + - "ٍّ\x05 َّ\x05 ُّ\x05 ِّ\x05 ّٰ\x04ئر\x04ئز\x04ئن\x04بر\x04بز\x04بن\x04ت" + - "ر\x04تز\x04تن\x04ثر\x04ثز\x04ثن\x04ما\x04نر\x04نز\x04نن\x04ير\x04يز\x04" + - "ين\x04ئخ\x04ئه\x04به\x04ته\x04صخ\x04له\x04نه\x04هٰ\x04يه\x04ثه\x04سه" + - "\x04شم\x04شه\x06ـَّ\x06ـُّ\x06ـِّ\x04طى\x04طي\x04عى\x04عي\x04غى\x04غي" + - "\x04سى\x04سي\x04شى\x04شي\x04حى\x04حي\x04جى\x04جي\x04خى\x04خي\x04صى\x04صي" + - "\x04ضى\x04ضي\x04شج\x04شح\x04شخ\x04شر\x04سر\x04صر\x04ضر\x04اً\x06تجم\x06ت" + - "حج\x06تحم\x06تخم\x06تمج\x06تمح\x06تمخ\x06جمح\x06حمي\x06حمى\x06سحج\x06سج" + - "ح\x06سجى\x06سمح\x06سمج\x06سمم\x06صحح\x06صمم\x06شحم\x06شجي\x06شمخ\x06شمم" + - "\x06ضحى\x06ضخم\x06طمح\x06طمم\x06طمي\x06عجم\x06عمم\x06عمى\x06غمم\x06غمي" + - "\x06غمى\x06فخم\x06قمح\x06قمم\x06لحم\x06لحي\x06لحى\x06لجج\x06لخم\x06لمح" + - "\x06محج\x06محم\x06محي\x06مجح\x06مجم\x06مخج\x06مخم\x06مجخ\x06همج\x06همم" + - "\x06نحم\x06نحى\x06نجم\x06نجى\x06نمي\x06نمى\x06يمم\x06بخي\x06تجي\x06تجى" + - "\x06تخي\x06تخى\x06تمي\x06تمى\x06جمي\x06جحى\x06جمى\x06سخى\x06صحي\x06شحي" + - "\x06ضحي\x06لجي\x06لمي\x06يحي\x06يجي\x06يمي\x06ممي\x06قمي\x06نحي\x06عمي" + - "\x06كمي\x06نجح\x06مخي\x06لجم\x06كمم\x06جحي\x06حجي\x06مجي\x06فمي\x06بحي" + - "\x06سخي\x06نجي\x06صلے\x06قلے\x08الله\x08اكبر\x08محمد\x08صلعم\x08رسول\x08" + - "عليه\x08وسلم\x06صلى!صلى الله عليه وسلم\x0fجل جلاله\x08ریال\x01,\x01:" + - "\x01!\x01?\x01_\x01{\x01}\x01[\x01]\x01#\x01&\x01*\x01-\x01<\x01>\x01\\" + - "\x01$\x01%\x01@\x04ـً\x04ـَ\x04ـُ\x04ـِ\x04ـّ\x04ـْ\x02ء\x02آ\x02أ\x02ؤ" + - "\x02إ\x02ئ\x02ا\x02ب\x02ة\x02ت\x02ث\x02ج\x02ح\x02خ\x02د\x02ذ\x02ر\x02ز" + - "\x02س\x02ش\x02ص\x02ض\x02ط\x02ظ\x02ع\x02غ\x02ف\x02ق\x02ك\x02ل\x02م\x02ن" + - "\x02ه\x02و\x02ي\x04لآ\x04لأ\x04لإ\x04لا\x01\x22\x01'\x01/\x01^\x01|\x01~" + - "\x02¢\x02£\x02¬\x02¦\x02¥\x08𝅗𝅥\x08𝅘𝅥\x0c𝅘𝅥𝅮\x0c𝅘𝅥𝅯\x0c𝅘𝅥𝅰\x0c𝅘𝅥𝅱\x0c𝅘𝅥𝅲" + - "\x08𝆹𝅥\x08𝆺𝅥\x0c𝆹𝅥𝅮\x0c𝆺𝅥𝅮\x0c𝆹𝅥𝅯\x0c𝆺𝅥𝅯\x02ı\x02ȷ\x02α\x02ε\x02ζ\x02η" + - "\x02κ\x02λ\x02μ\x02ν\x02ξ\x02ο\x02σ\x02τ\x02υ\x02ψ\x03∇\x03∂\x02ϝ\x02ٮ" + - "\x02ڡ\x02ٯ\x020,\x021,\x022,\x023,\x024,\x025,\x026,\x027,\x028,\x029," + - "\x03(a)\x03(b)\x03(c)\x03(d)\x03(e)\x03(f)\x03(g)\x03(h)\x03(i)\x03(j)" + - "\x03(k)\x03(l)\x03(m)\x03(n)\x03(o)\x03(p)\x03(q)\x03(r)\x03(s)\x03(t)" + - "\x03(u)\x03(v)\x03(w)\x03(x)\x03(y)\x03(z)\x07〔s〕\x02wz\x02hv\x02sd\x03p" + - "pv\x02wc\x02mc\x02md\x02mr\x02dj\x06ほか\x06ココ\x03サ\x03手\x03字\x03双\x03デ" + - "\x03二\x03多\x03解\x03天\x03交\x03映\x03無\x03料\x03前\x03後\x03再\x03新\x03初\x03終" + - "\x03生\x03販\x03声\x03吹\x03演\x03投\x03捕\x03一\x03三\x03遊\x03左\x03中\x03右\x03指" + - "\x03走\x03打\x03禁\x03空\x03合\x03満\x03有\x03月\x03申\x03割\x03営\x03配\x09〔本〕\x09〔" + - "三〕\x09〔二〕\x09〔安〕\x09〔点〕\x09〔打〕\x09〔盗〕\x09〔勝〕\x09〔敗〕\x03得\x03可\x03丽\x03" + - "丸\x03乁\x03你\x03侮\x03侻\x03倂\x03偺\x03備\x03僧\x03像\x03㒞\x03免\x03兔\x03兤\x03" + - "具\x03㒹\x03內\x03冗\x03冤\x03仌\x03冬\x03况\x03凵\x03刃\x03㓟\x03刻\x03剆\x03剷\x03" + - "㔕\x03勇\x03勉\x03勤\x03勺\x03包\x03匆\x03北\x03卉\x03卑\x03博\x03即\x03卽\x03卿\x03" + - "灰\x03及\x03叟\x03叫\x03叱\x03吆\x03咞\x03吸\x03呈\x03周\x03咢\x03哶\x03唐\x03啓\x03" + - "啣\x03善\x03喙\x03喫\x03喳\x03嗂\x03圖\x03嘆\x03圗\x03噑\x03噴\x03切\x03壮\x03城\x03" + - "埴\x03堍\x03型\x03堲\x03報\x03墬\x03売\x03壷\x03夆\x03夢\x03奢\x03姬\x03娛\x03娧\x03" + - "姘\x03婦\x03㛮\x03嬈\x03嬾\x03寃\x03寘\x03寧\x03寳\x03寿\x03将\x03尢\x03㞁\x03屠\x03" + - "屮\x03峀\x03岍\x03嵃\x03嵮\x03嵫\x03嵼\x03巡\x03巢\x03㠯\x03巽\x03帨\x03帽\x03幩\x03" + - "㡢\x03㡼\x03庰\x03庳\x03庶\x03廊\x03廾\x03舁\x03弢\x03㣇\x03形\x03彫\x03㣣\x03徚\x03" + - "忍\x03志\x03忹\x03悁\x03㤺\x03㤜\x03悔\x03惇\x03慈\x03慌\x03慎\x03慺\x03憎\x03憲\x03" + - "憤\x03憯\x03懞\x03懲\x03懶\x03成\x03戛\x03扝\x03抱\x03拔\x03捐\x03挽\x03拼\x03捨\x03" + - "掃\x03揤\x03搢\x03揅\x03掩\x03㨮\x03摩\x03摾\x03撝\x03摷\x03㩬\x03敏\x03敬\x03旣\x03" + - "書\x03晉\x03㬙\x03暑\x03㬈\x03㫤\x03冒\x03冕\x03最\x03暜\x03肭\x03䏙\x03朗\x03望\x03" + - "朡\x03杞\x03杓\x03㭉\x03柺\x03枅\x03桒\x03梅\x03梎\x03栟\x03椔\x03㮝\x03楂\x03榣\x03" + - "槪\x03檨\x03櫛\x03㰘\x03次\x03歔\x03㱎\x03歲\x03殟\x03殺\x03殻\x03汎\x03沿\x03泍\x03" + - "汧\x03洖\x03派\x03海\x03流\x03浩\x03浸\x03涅\x03洴\x03港\x03湮\x03㴳\x03滋\x03滇\x03" + - "淹\x03潮\x03濆\x03瀹\x03瀞\x03瀛\x03㶖\x03灊\x03災\x03灷\x03炭\x03煅\x03熜\x03爨\x03" + - "爵\x03牐\x03犀\x03犕\x03獺\x03王\x03㺬\x03玥\x03㺸\x03瑇\x03瑜\x03瑱\x03璅\x03瓊\x03" + - "㼛\x03甤\x03甾\x03異\x03瘐\x03㿼\x03䀈\x03直\x03眞\x03真\x03睊\x03䀹\x03瞋\x03䁆\x03" + - "䂖\x03硎\x03碌\x03磌\x03䃣\x03祖\x03福\x03秫\x03䄯\x03穀\x03穊\x03穏\x03䈂\x03篆\x03" + - "築\x03䈧\x03糒\x03䊠\x03糨\x03糣\x03紀\x03絣\x03䌁\x03緇\x03縂\x03繅\x03䌴\x03䍙\x03" + - "罺\x03羕\x03翺\x03者\x03聠\x03聰\x03䏕\x03育\x03脃\x03䐋\x03脾\x03媵\x03舄\x03辞\x03" + - "䑫\x03芑\x03芋\x03芝\x03劳\x03花\x03芳\x03芽\x03苦\x03若\x03茝\x03荣\x03莭\x03茣\x03" + - "莽\x03菧\x03著\x03荓\x03菊\x03菌\x03菜\x03䔫\x03蓱\x03蓳\x03蔖\x03蕤\x03䕝\x03䕡\x03" + - "䕫\x03虐\x03虜\x03虧\x03虩\x03蚩\x03蚈\x03蜎\x03蛢\x03蝹\x03蜨\x03蝫\x03螆\x03蟡\x03" + - "蠁\x03䗹\x03衠\x03衣\x03裗\x03裞\x03䘵\x03裺\x03㒻\x03䚾\x03䛇\x03誠\x03諭\x03變\x03" + - "豕\x03貫\x03賁\x03贛\x03起\x03跋\x03趼\x03跰\x03軔\x03輸\x03邔\x03郱\x03鄑\x03鄛\x03" + - "鈸\x03鋗\x03鋘\x03鉼\x03鏹\x03鐕\x03開\x03䦕\x03閷\x03䧦\x03雃\x03嶲\x03霣\x03䩮\x03" + - "䩶\x03韠\x03䪲\x03頋\x03頩\x03飢\x03䬳\x03餩\x03馧\x03駂\x03駾\x03䯎\x03鬒\x03鱀\x03" + - "鳽\x03䳎\x03䳭\x03鵧\x03䳸\x03麻\x03䵖\x03黹\x03黾\x03鼅\x03鼏\x03鼖\x03鼻" +var mappings string = "" + // Size: 6539 bytes + " ̈a ̄23 ́ ̧1o1⁄41⁄23⁄4i̇l·ʼnsdžⱥⱦhjrwy ̆ ̇ ̊ ̨ ̃ ̋lẍ́ ι; ̈́եւاٴوٴۇٴيٴक" + + "़ख़ग़ज़ड़ढ़फ़य़ড়ঢ়য়ਲ਼ਸ਼ਖ਼ਗ਼ਜ਼ਫ਼ଡ଼ଢ଼ําໍາຫນຫມགྷཌྷདྷབྷཛྷཀྵཱཱིུྲྀྲཱྀླྀླཱ" + + "ཱྀྀྒྷྜྷྡྷྦྷྫྷྐྵвдостъѣæbdeǝgikmnȣptuɐɑəɛɜŋɔɯvβγδφχρнɒcɕðfɟɡɥɨɩɪʝɭʟɱɰɲɳ" + + "ɴɵɸʂʃƫʉʊʋʌzʐʑʒθssάέήίόύώἀιἁιἂιἃιἄιἅιἆιἇιἠιἡιἢιἣιἤιἥιἦιἧιὠιὡιὢιὣιὤιὥιὦιὧ" + + "ιὰιαιάιᾶιι ̈͂ὴιηιήιῆι ̓̀ ̓́ ̓͂ΐ ̔̀ ̔́ ̔͂ΰ ̈̀`ὼιωιώιῶι′′′′′‵‵‵‵‵!!???!!?" + + "′′′′0456789+=()rsħnoqsmtmωåאבגדπ1⁄71⁄91⁄101⁄32⁄31⁄52⁄53⁄54⁄51⁄65⁄61⁄83" + + "⁄85⁄87⁄81⁄iiivviviiiixxi0⁄3∫∫∫∫∫∮∮∮∮∮1011121314151617181920(10)(11)(12" + + ")(13)(14)(15)(16)(17)(18)(19)(20)∫∫∫∫==⫝̸ɫɽȿɀ. ゙ ゚よりコト(ᄀ)(ᄂ)(ᄃ)(ᄅ)(ᄆ)(ᄇ)" + + "(ᄉ)(ᄋ)(ᄌ)(ᄎ)(ᄏ)(ᄐ)(ᄑ)(ᄒ)(가)(나)(다)(라)(마)(바)(사)(아)(자)(차)(카)(타)(파)(하)(주)(오전" + + ")(오후)(一)(二)(三)(四)(五)(六)(七)(八)(九)(十)(月)(火)(水)(木)(金)(土)(日)(株)(有)(社)(名)(特)(" + + "財)(祝)(労)(代)(呼)(学)(監)(企)(資)(協)(祭)(休)(自)(至)21222324252627282930313233343" + + "5참고주의3637383940414243444546474849501月2月3月4月5月6月7月8月9月10月11月12月hgev令和アパート" + + "アルファアンペアアールイニングインチウォンエスクードエーカーオンスオームカイリカラットカロリーガロンガンマギガギニーキュリーギルダーキロキロ" + + "グラムキロメートルキロワットグラムグラムトンクルゼイロクローネケースコルナコーポサイクルサンチームシリングセンチセントダースデシドルトンナノ" + + "ノットハイツパーセントパーツバーレルピアストルピクルピコビルファラッドフィートブッシェルフランヘクタールペソペニヒヘルツペンスページベータポ" + + "イントボルトホンポンドホールホーンマイクロマイルマッハマルクマンションミクロンミリミリバールメガメガトンメートルヤードヤールユアンリットルリ" + + "ラルピールーブルレムレントゲンワット0点1点2点3点4点5点6点7点8点9点10点11点12点13点14点15点16点17点18点19点20" + + "点21点22点23点24点daauovpcdmiu平成昭和大正明治株式会社panamakakbmbgbkcalpfnfmgkghzmldlk" + + "lfmnmmmcmkmm2m3m∕sm∕s2rad∕srad∕s2psnsmspvnvmvkvpwnwmwkwbqcccdc∕kgdbgyhah" + + "pinkkktlmlnlxphprsrsvwbv∕ma∕m1日2日3日4日5日6日7日8日9日10日11日12日13日14日15日16日17日1" + + "8日19日20日21日22日23日24日25日26日27日28日29日30日31日ьɦɬʞʇœʍ𤋮𢡊𢡄𣏕𥉉𥳐𧻓fffiflstմնմեմիվնմ" + + "խיִײַעהכלםרתשׁשׂשּׁשּׂאַאָאּבּגּדּהּוּזּטּיּךּכּלּמּנּסּףּפּצּקּרּשּתּו" + + "ֹבֿכֿפֿאלٱٻپڀٺٿٹڤڦڄڃچڇڍڌڎڈژڑکگڳڱںڻۀہھےۓڭۇۆۈۋۅۉېىئائەئوئۇئۆئۈئېئىیئجئحئم" + + "ئيبجبحبخبمبىبيتجتحتختمتىتيثجثمثىثيجحجمحجحمخجخحخمسجسحسخسمصحصمضجضحضخضمطحط" + + "مظمعجعمغجغمفجفحفخفمفىفيقحقمقىقيكاكجكحكخكلكمكىكيلجلحلخلملىليمجمحمخمممىمي" + + "نجنحنخنمنىنيهجهمهىهييجيحيخيميىييذٰرٰىٰ ٌّ ٍّ َّ ُّ ِّ ّٰئرئزئنبربزبنترت" + + "زتنثرثزثنمانرنزننيريزينئخئهبهتهصخلهنههٰيهثهسهشمشهـَّـُّـِّطىطيعىعيغىغيس" + + "ىسيشىشيحىحيجىجيخىخيصىصيضىضيشجشحشخشرسرصرضراًتجمتحجتحمتخمتمجتمحتمخجمححميح" + + "مىسحجسجحسجىسمحسمجسممصححصممشحمشجيشمخشممضحىضخمطمحطممطميعجمعممعمىغممغميغمى" + + "فخمقمحقمملحملحيلحىلججلخملمحمحجمحممحيمجحمجممخجمخممجخهمجهممنحمنحىنجمنجىنم" + + "ينمىيممبخيتجيتجىتخيتخىتميتمىجميجحىجمىسخىصحيشحيضحيلجيلمييحييجييميمميقمين" + + "حيعميكمينجحمخيلجمكممجحيحجيمجيفميبحيسخينجيصلےقلےاللهاكبرمحمدصلعمرسولعليه" + + "وسلمصلىصلى الله عليه وسلمجل جلالهریال,:!?_{}[]#&*-<>\\$%@ـًـَـُـِـّـْءآ" + + "أؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهويلآلألإلا\x22'/^|~¢£¬¦¥𝅗𝅥𝅘𝅥𝅘𝅥𝅮𝅘𝅥𝅯𝅘𝅥𝅰𝅘𝅥𝅱" + + "𝅘𝅥𝅲𝆹𝅥𝆺𝅥𝆹𝅥𝅮𝆺𝅥𝅮𝆹𝅥𝅯𝆺𝅥𝅯ıȷαεζηκλμνξοστυψ∇∂ϝٮڡٯ0,1,2,3,4,5,6,7,8,9,(a)(b)(c" + + ")(d)(e)(f)(g)(h)(i)(j)(k)(l)(m)(n)(o)(p)(q)(r)(s)(t)(u)(v)(w)(x)(y)(z)〔s" + + "〕wzhvsdppvwcmcmdmrdjほかココサ手字双デ二多解天交映無料前後再新初終生販声吹演投捕一三遊左中右指走打禁空合満有月申割営配〔" + + "本〕〔三〕〔二〕〔安〕〔点〕〔打〕〔盗〕〔勝〕〔敗〕得可丽丸乁你侮侻倂偺備僧像㒞免兔兤具㒹內冗冤仌冬况凵刃㓟刻剆剷㔕勇勉勤勺包匆北卉卑博即卽" + + "卿灰及叟叫叱吆咞吸呈周咢哶唐啓啣善喙喫喳嗂圖嘆圗噑噴切壮城埴堍型堲報墬売壷夆夢奢姬娛娧姘婦㛮嬈嬾寃寘寧寳寿将尢㞁屠屮峀岍嵃嵮嵫嵼巡巢㠯巽帨帽" + + "幩㡢㡼庰庳庶廊廾舁弢㣇形彫㣣徚忍志忹悁㤺㤜悔惇慈慌慎慺憎憲憤憯懞懲懶成戛扝抱拔捐挽拼捨掃揤搢揅掩㨮摩摾撝摷㩬敏敬旣書晉㬙暑㬈㫤冒冕最暜肭䏙朗" + + "望朡杞杓㭉柺枅桒梅梎栟椔㮝楂榣槪檨櫛㰘次歔㱎歲殟殺殻汎沿泍汧洖派海流浩浸涅洴港湮㴳滋滇淹潮濆瀹瀞瀛㶖灊災灷炭煅熜爨爵牐犀犕獺王㺬玥㺸瑇瑜瑱璅" + + "瓊㼛甤甾異瘐㿼䀈直眞真睊䀹瞋䁆䂖硎碌磌䃣祖福秫䄯穀穊穏䈂篆築䈧糒䊠糨糣紀絣䌁緇縂繅䌴䍙罺羕翺者聠聰䏕育脃䐋脾媵舄辞䑫芑芋芝劳花芳芽苦若茝荣莭" + + "茣莽菧著荓菊菌菜䔫蓱蓳蔖蕤䕝䕡䕫虐虜虧虩蚩蚈蜎蛢蝹蜨蝫螆蟡蠁䗹衠衣裗裞䘵裺㒻䚾䛇誠諭變豕貫賁贛起跋趼跰軔輸邔郱鄑鄛鈸鋗鋘鉼鏹鐕開䦕閷䧦雃嶲霣" + + "䩮䩶韠䪲頋頩飢䬳餩馧駂駾䯎鬒鱀鳽䳎䳭鵧䳸麻䵖黹黾鼅鼏鼖鼻" + +var mappingIndex = []uint16{ // 1650 elements + // Entry 0 - 3F + 0x0000, 0x0000, 0x0001, 0x0004, 0x0005, 0x0008, 0x0009, 0x000a, + 0x000d, 0x0010, 0x0011, 0x0012, 0x0017, 0x001c, 0x0021, 0x0024, + 0x0027, 0x002a, 0x002b, 0x002e, 0x0031, 0x0034, 0x0035, 0x0036, + 0x0037, 0x0038, 0x0039, 0x003c, 0x003f, 0x0042, 0x0045, 0x0048, + 0x004b, 0x004c, 0x004d, 0x0051, 0x0054, 0x0055, 0x005a, 0x005e, + 0x0062, 0x0066, 0x006a, 0x006e, 0x0074, 0x007a, 0x0080, 0x0086, + 0x008c, 0x0092, 0x0098, 0x009e, 0x00a4, 0x00aa, 0x00b0, 0x00b6, + 0x00bc, 0x00c2, 0x00c8, 0x00ce, 0x00d4, 0x00da, 0x00e0, 0x00e6, + // Entry 40 - 7F + 0x00ec, 0x00f2, 0x00f8, 0x00fe, 0x0104, 0x010a, 0x0110, 0x0116, + 0x011c, 0x0122, 0x0128, 0x012e, 0x0137, 0x013d, 0x0146, 0x014c, + 0x0152, 0x0158, 0x015e, 0x0164, 0x016a, 0x0170, 0x0172, 0x0174, + 0x0176, 0x0178, 0x017a, 0x017c, 0x017e, 0x0180, 0x0181, 0x0182, + 0x0183, 0x0185, 0x0186, 0x0187, 0x0188, 0x0189, 0x018a, 0x018c, + 0x018d, 0x018e, 0x018f, 0x0191, 0x0193, 0x0195, 0x0197, 0x0199, + 0x019b, 0x019d, 0x019f, 0x01a0, 0x01a2, 0x01a4, 0x01a6, 0x01a8, + 0x01aa, 0x01ac, 0x01ae, 0x01b0, 0x01b1, 0x01b3, 0x01b5, 0x01b6, + // Entry 80 - BF + 0x01b8, 0x01ba, 0x01bc, 0x01be, 0x01c0, 0x01c2, 0x01c4, 0x01c6, + 0x01c8, 0x01ca, 0x01cc, 0x01ce, 0x01d0, 0x01d2, 0x01d4, 0x01d6, + 0x01d8, 0x01da, 0x01dc, 0x01de, 0x01e0, 0x01e2, 0x01e4, 0x01e5, + 0x01e7, 0x01e9, 0x01eb, 0x01ed, 0x01ef, 0x01f1, 0x01f3, 0x01f5, + 0x01f7, 0x01f9, 0x01fb, 0x01fd, 0x0202, 0x0207, 0x020c, 0x0211, + 0x0216, 0x021b, 0x0220, 0x0225, 0x022a, 0x022f, 0x0234, 0x0239, + 0x023e, 0x0243, 0x0248, 0x024d, 0x0252, 0x0257, 0x025c, 0x0261, + 0x0266, 0x026b, 0x0270, 0x0275, 0x027a, 0x027e, 0x0282, 0x0287, + // Entry C0 - FF + 0x0289, 0x028e, 0x0293, 0x0297, 0x029b, 0x02a0, 0x02a5, 0x02aa, + 0x02af, 0x02b1, 0x02b6, 0x02bb, 0x02c0, 0x02c2, 0x02c7, 0x02c8, + 0x02cd, 0x02d1, 0x02d5, 0x02da, 0x02e0, 0x02e9, 0x02ef, 0x02f8, + 0x02fa, 0x02fc, 0x02fe, 0x0300, 0x030c, 0x030d, 0x030e, 0x030f, + 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, + 0x0319, 0x031b, 0x031d, 0x031e, 0x0320, 0x0322, 0x0324, 0x0326, + 0x0328, 0x032a, 0x032c, 0x032e, 0x0330, 0x0335, 0x033a, 0x0340, + 0x0345, 0x034a, 0x034f, 0x0354, 0x0359, 0x035e, 0x0363, 0x0368, + // Entry 100 - 13F + 0x036d, 0x0372, 0x0377, 0x037c, 0x0380, 0x0382, 0x0384, 0x0386, + 0x038a, 0x038c, 0x038e, 0x0393, 0x0399, 0x03a2, 0x03a8, 0x03b1, + 0x03b3, 0x03b5, 0x03b7, 0x03b9, 0x03bb, 0x03bd, 0x03bf, 0x03c1, + 0x03c3, 0x03c5, 0x03c7, 0x03cb, 0x03cf, 0x03d3, 0x03d7, 0x03db, + 0x03df, 0x03e3, 0x03e7, 0x03eb, 0x03ef, 0x03f3, 0x03ff, 0x0401, + 0x0406, 0x0408, 0x040a, 0x040c, 0x040e, 0x040f, 0x0413, 0x0417, + 0x041d, 0x0423, 0x0428, 0x042d, 0x0432, 0x0437, 0x043c, 0x0441, + 0x0446, 0x044b, 0x0450, 0x0455, 0x045a, 0x045f, 0x0464, 0x0469, + // Entry 140 - 17F + 0x046e, 0x0473, 0x0478, 0x047d, 0x0482, 0x0487, 0x048c, 0x0491, + 0x0496, 0x049b, 0x04a0, 0x04a5, 0x04aa, 0x04af, 0x04b4, 0x04bc, + 0x04c4, 0x04c9, 0x04ce, 0x04d3, 0x04d8, 0x04dd, 0x04e2, 0x04e7, + 0x04ec, 0x04f1, 0x04f6, 0x04fb, 0x0500, 0x0505, 0x050a, 0x050f, + 0x0514, 0x0519, 0x051e, 0x0523, 0x0528, 0x052d, 0x0532, 0x0537, + 0x053c, 0x0541, 0x0546, 0x054b, 0x0550, 0x0555, 0x055a, 0x055f, + 0x0564, 0x0569, 0x056e, 0x0573, 0x0578, 0x057a, 0x057c, 0x057e, + 0x0580, 0x0582, 0x0584, 0x0586, 0x0588, 0x058a, 0x058c, 0x058e, + // Entry 180 - 1BF + 0x0590, 0x0592, 0x0594, 0x0596, 0x059c, 0x05a2, 0x05a4, 0x05a6, + 0x05a8, 0x05aa, 0x05ac, 0x05ae, 0x05b0, 0x05b2, 0x05b4, 0x05b6, + 0x05b8, 0x05ba, 0x05bc, 0x05be, 0x05c0, 0x05c4, 0x05c8, 0x05cc, + 0x05d0, 0x05d4, 0x05d8, 0x05dc, 0x05e0, 0x05e4, 0x05e9, 0x05ee, + 0x05f3, 0x05f5, 0x05f7, 0x05fd, 0x0609, 0x0615, 0x0621, 0x062a, + 0x0636, 0x063f, 0x0648, 0x0657, 0x0663, 0x066c, 0x0675, 0x067e, + 0x068a, 0x0696, 0x069f, 0x06a8, 0x06ae, 0x06b7, 0x06c3, 0x06cf, + 0x06d5, 0x06e4, 0x06f6, 0x0705, 0x070e, 0x071d, 0x072c, 0x0738, + // Entry 1C0 - 1FF + 0x0741, 0x074a, 0x0753, 0x075f, 0x076e, 0x077a, 0x0783, 0x078c, + 0x0795, 0x079b, 0x07a1, 0x07a7, 0x07ad, 0x07b6, 0x07bf, 0x07ce, + 0x07d7, 0x07e3, 0x07f2, 0x07fb, 0x0801, 0x0807, 0x0816, 0x0822, + 0x0831, 0x083a, 0x0849, 0x084f, 0x0858, 0x0861, 0x086a, 0x0873, + 0x087c, 0x0888, 0x0891, 0x0897, 0x08a0, 0x08a9, 0x08b2, 0x08be, + 0x08c7, 0x08d0, 0x08d9, 0x08e8, 0x08f4, 0x08fa, 0x0909, 0x090f, + 0x091b, 0x0927, 0x0930, 0x0939, 0x0942, 0x094e, 0x0954, 0x095d, + 0x0969, 0x096f, 0x097e, 0x0987, 0x098b, 0x098f, 0x0993, 0x0997, + // Entry 200 - 23F + 0x099b, 0x099f, 0x09a3, 0x09a7, 0x09ab, 0x09af, 0x09b4, 0x09b9, + 0x09be, 0x09c3, 0x09c8, 0x09cd, 0x09d2, 0x09d7, 0x09dc, 0x09e1, + 0x09e6, 0x09eb, 0x09f0, 0x09f5, 0x09fa, 0x09fc, 0x09fe, 0x0a00, + 0x0a02, 0x0a04, 0x0a06, 0x0a0c, 0x0a12, 0x0a18, 0x0a1e, 0x0a2a, + 0x0a2c, 0x0a2e, 0x0a30, 0x0a32, 0x0a34, 0x0a36, 0x0a38, 0x0a3c, + 0x0a3e, 0x0a40, 0x0a42, 0x0a44, 0x0a46, 0x0a48, 0x0a4a, 0x0a4c, + 0x0a4e, 0x0a50, 0x0a52, 0x0a54, 0x0a56, 0x0a58, 0x0a5a, 0x0a5f, + 0x0a65, 0x0a6c, 0x0a74, 0x0a76, 0x0a78, 0x0a7a, 0x0a7c, 0x0a7e, + // Entry 240 - 27F + 0x0a80, 0x0a82, 0x0a84, 0x0a86, 0x0a88, 0x0a8a, 0x0a8c, 0x0a8e, + 0x0a90, 0x0a96, 0x0a98, 0x0a9a, 0x0a9c, 0x0a9e, 0x0aa0, 0x0aa2, + 0x0aa4, 0x0aa6, 0x0aa8, 0x0aaa, 0x0aac, 0x0aae, 0x0ab0, 0x0ab2, + 0x0ab4, 0x0ab9, 0x0abe, 0x0ac2, 0x0ac6, 0x0aca, 0x0ace, 0x0ad2, + 0x0ad6, 0x0ada, 0x0ade, 0x0ae2, 0x0ae7, 0x0aec, 0x0af1, 0x0af6, + 0x0afb, 0x0b00, 0x0b05, 0x0b0a, 0x0b0f, 0x0b14, 0x0b19, 0x0b1e, + 0x0b23, 0x0b28, 0x0b2d, 0x0b32, 0x0b37, 0x0b3c, 0x0b41, 0x0b46, + 0x0b4b, 0x0b50, 0x0b52, 0x0b54, 0x0b56, 0x0b58, 0x0b5a, 0x0b5c, + // Entry 280 - 2BF + 0x0b5e, 0x0b62, 0x0b66, 0x0b6a, 0x0b6e, 0x0b72, 0x0b76, 0x0b7a, + 0x0b7c, 0x0b7e, 0x0b80, 0x0b82, 0x0b86, 0x0b8a, 0x0b8e, 0x0b92, + 0x0b96, 0x0b9a, 0x0b9e, 0x0ba0, 0x0ba2, 0x0ba4, 0x0ba6, 0x0ba8, + 0x0baa, 0x0bac, 0x0bb0, 0x0bb4, 0x0bba, 0x0bc0, 0x0bc4, 0x0bc8, + 0x0bcc, 0x0bd0, 0x0bd4, 0x0bd8, 0x0bdc, 0x0be0, 0x0be4, 0x0be8, + 0x0bec, 0x0bf0, 0x0bf4, 0x0bf8, 0x0bfc, 0x0c00, 0x0c04, 0x0c08, + 0x0c0c, 0x0c10, 0x0c14, 0x0c18, 0x0c1c, 0x0c20, 0x0c24, 0x0c28, + 0x0c2c, 0x0c30, 0x0c34, 0x0c36, 0x0c38, 0x0c3a, 0x0c3c, 0x0c3e, + // Entry 2C0 - 2FF + 0x0c40, 0x0c42, 0x0c44, 0x0c46, 0x0c48, 0x0c4a, 0x0c4c, 0x0c4e, + 0x0c50, 0x0c52, 0x0c54, 0x0c56, 0x0c58, 0x0c5a, 0x0c5c, 0x0c5e, + 0x0c60, 0x0c62, 0x0c64, 0x0c66, 0x0c68, 0x0c6a, 0x0c6c, 0x0c6e, + 0x0c70, 0x0c72, 0x0c74, 0x0c76, 0x0c78, 0x0c7a, 0x0c7c, 0x0c7e, + 0x0c80, 0x0c82, 0x0c86, 0x0c8a, 0x0c8e, 0x0c92, 0x0c96, 0x0c9a, + 0x0c9e, 0x0ca2, 0x0ca4, 0x0ca8, 0x0cac, 0x0cb0, 0x0cb4, 0x0cb8, + 0x0cbc, 0x0cc0, 0x0cc4, 0x0cc8, 0x0ccc, 0x0cd0, 0x0cd4, 0x0cd8, + 0x0cdc, 0x0ce0, 0x0ce4, 0x0ce8, 0x0cec, 0x0cf0, 0x0cf4, 0x0cf8, + // Entry 300 - 33F + 0x0cfc, 0x0d00, 0x0d04, 0x0d08, 0x0d0c, 0x0d10, 0x0d14, 0x0d18, + 0x0d1c, 0x0d20, 0x0d24, 0x0d28, 0x0d2c, 0x0d30, 0x0d34, 0x0d38, + 0x0d3c, 0x0d40, 0x0d44, 0x0d48, 0x0d4c, 0x0d50, 0x0d54, 0x0d58, + 0x0d5c, 0x0d60, 0x0d64, 0x0d68, 0x0d6c, 0x0d70, 0x0d74, 0x0d78, + 0x0d7c, 0x0d80, 0x0d84, 0x0d88, 0x0d8c, 0x0d90, 0x0d94, 0x0d98, + 0x0d9c, 0x0da0, 0x0da4, 0x0da8, 0x0dac, 0x0db0, 0x0db4, 0x0db8, + 0x0dbc, 0x0dc0, 0x0dc4, 0x0dc8, 0x0dcc, 0x0dd0, 0x0dd4, 0x0dd8, + 0x0ddc, 0x0de0, 0x0de4, 0x0de8, 0x0dec, 0x0df0, 0x0df4, 0x0df8, + // Entry 340 - 37F + 0x0dfc, 0x0e00, 0x0e04, 0x0e08, 0x0e0c, 0x0e10, 0x0e14, 0x0e18, + 0x0e1d, 0x0e22, 0x0e27, 0x0e2c, 0x0e31, 0x0e36, 0x0e3a, 0x0e3e, + 0x0e42, 0x0e46, 0x0e4a, 0x0e4e, 0x0e52, 0x0e56, 0x0e5a, 0x0e5e, + 0x0e62, 0x0e66, 0x0e6a, 0x0e6e, 0x0e72, 0x0e76, 0x0e7a, 0x0e7e, + 0x0e82, 0x0e86, 0x0e8a, 0x0e8e, 0x0e92, 0x0e96, 0x0e9a, 0x0e9e, + 0x0ea2, 0x0ea6, 0x0eaa, 0x0eae, 0x0eb2, 0x0eb6, 0x0ebc, 0x0ec2, + 0x0ec8, 0x0ecc, 0x0ed0, 0x0ed4, 0x0ed8, 0x0edc, 0x0ee0, 0x0ee4, + 0x0ee8, 0x0eec, 0x0ef0, 0x0ef4, 0x0ef8, 0x0efc, 0x0f00, 0x0f04, + // Entry 380 - 3BF + 0x0f08, 0x0f0c, 0x0f10, 0x0f14, 0x0f18, 0x0f1c, 0x0f20, 0x0f24, + 0x0f28, 0x0f2c, 0x0f30, 0x0f34, 0x0f38, 0x0f3e, 0x0f44, 0x0f4a, + 0x0f50, 0x0f56, 0x0f5c, 0x0f62, 0x0f68, 0x0f6e, 0x0f74, 0x0f7a, + 0x0f80, 0x0f86, 0x0f8c, 0x0f92, 0x0f98, 0x0f9e, 0x0fa4, 0x0faa, + 0x0fb0, 0x0fb6, 0x0fbc, 0x0fc2, 0x0fc8, 0x0fce, 0x0fd4, 0x0fda, + 0x0fe0, 0x0fe6, 0x0fec, 0x0ff2, 0x0ff8, 0x0ffe, 0x1004, 0x100a, + 0x1010, 0x1016, 0x101c, 0x1022, 0x1028, 0x102e, 0x1034, 0x103a, + 0x1040, 0x1046, 0x104c, 0x1052, 0x1058, 0x105e, 0x1064, 0x106a, + // Entry 3C0 - 3FF + 0x1070, 0x1076, 0x107c, 0x1082, 0x1088, 0x108e, 0x1094, 0x109a, + 0x10a0, 0x10a6, 0x10ac, 0x10b2, 0x10b8, 0x10be, 0x10c4, 0x10ca, + 0x10d0, 0x10d6, 0x10dc, 0x10e2, 0x10e8, 0x10ee, 0x10f4, 0x10fa, + 0x1100, 0x1106, 0x110c, 0x1112, 0x1118, 0x111e, 0x1124, 0x112a, + 0x1130, 0x1136, 0x113c, 0x1142, 0x1148, 0x114e, 0x1154, 0x115a, + 0x1160, 0x1166, 0x116c, 0x1172, 0x1178, 0x1180, 0x1188, 0x1190, + 0x1198, 0x11a0, 0x11a8, 0x11b0, 0x11b6, 0x11d7, 0x11e6, 0x11ee, + 0x11ef, 0x11f0, 0x11f1, 0x11f2, 0x11f3, 0x11f4, 0x11f5, 0x11f6, + // Entry 400 - 43F + 0x11f7, 0x11f8, 0x11f9, 0x11fa, 0x11fb, 0x11fc, 0x11fd, 0x11fe, + 0x11ff, 0x1200, 0x1201, 0x1205, 0x1209, 0x120d, 0x1211, 0x1215, + 0x1219, 0x121b, 0x121d, 0x121f, 0x1221, 0x1223, 0x1225, 0x1227, + 0x1229, 0x122b, 0x122d, 0x122f, 0x1231, 0x1233, 0x1235, 0x1237, + 0x1239, 0x123b, 0x123d, 0x123f, 0x1241, 0x1243, 0x1245, 0x1247, + 0x1249, 0x124b, 0x124d, 0x124f, 0x1251, 0x1253, 0x1255, 0x1257, + 0x1259, 0x125b, 0x125d, 0x125f, 0x1263, 0x1267, 0x126b, 0x126f, + 0x1270, 0x1271, 0x1272, 0x1273, 0x1274, 0x1275, 0x1277, 0x1279, + // Entry 440 - 47F + 0x127b, 0x127d, 0x127f, 0x1287, 0x128f, 0x129b, 0x12a7, 0x12b3, + 0x12bf, 0x12cb, 0x12d3, 0x12db, 0x12e7, 0x12f3, 0x12ff, 0x130b, + 0x130d, 0x130f, 0x1311, 0x1313, 0x1315, 0x1317, 0x1319, 0x131b, + 0x131d, 0x131f, 0x1321, 0x1323, 0x1325, 0x1327, 0x1329, 0x132b, + 0x132e, 0x1331, 0x1333, 0x1335, 0x1337, 0x1339, 0x133b, 0x133d, + 0x133f, 0x1341, 0x1343, 0x1345, 0x1347, 0x1349, 0x134b, 0x134d, + 0x1350, 0x1353, 0x1356, 0x1359, 0x135c, 0x135f, 0x1362, 0x1365, + 0x1368, 0x136b, 0x136e, 0x1371, 0x1374, 0x1377, 0x137a, 0x137d, + // Entry 480 - 4BF + 0x1380, 0x1383, 0x1386, 0x1389, 0x138c, 0x138f, 0x1392, 0x1395, + 0x1398, 0x139b, 0x13a2, 0x13a4, 0x13a6, 0x13a8, 0x13ab, 0x13ad, + 0x13af, 0x13b1, 0x13b3, 0x13b5, 0x13bb, 0x13c1, 0x13c4, 0x13c7, + 0x13ca, 0x13cd, 0x13d0, 0x13d3, 0x13d6, 0x13d9, 0x13dc, 0x13df, + 0x13e2, 0x13e5, 0x13e8, 0x13eb, 0x13ee, 0x13f1, 0x13f4, 0x13f7, + 0x13fa, 0x13fd, 0x1400, 0x1403, 0x1406, 0x1409, 0x140c, 0x140f, + 0x1412, 0x1415, 0x1418, 0x141b, 0x141e, 0x1421, 0x1424, 0x1427, + 0x142a, 0x142d, 0x1430, 0x1433, 0x1436, 0x1439, 0x143c, 0x143f, + // Entry 4C0 - 4FF + 0x1442, 0x1445, 0x1448, 0x1451, 0x145a, 0x1463, 0x146c, 0x1475, + 0x147e, 0x1487, 0x1490, 0x1499, 0x149c, 0x149f, 0x14a2, 0x14a5, + 0x14a8, 0x14ab, 0x14ae, 0x14b1, 0x14b4, 0x14b7, 0x14ba, 0x14bd, + 0x14c0, 0x14c3, 0x14c6, 0x14c9, 0x14cc, 0x14cf, 0x14d2, 0x14d5, + 0x14d8, 0x14db, 0x14de, 0x14e1, 0x14e4, 0x14e7, 0x14ea, 0x14ed, + 0x14f0, 0x14f3, 0x14f6, 0x14f9, 0x14fc, 0x14ff, 0x1502, 0x1505, + 0x1508, 0x150b, 0x150e, 0x1511, 0x1514, 0x1517, 0x151a, 0x151d, + 0x1520, 0x1523, 0x1526, 0x1529, 0x152c, 0x152f, 0x1532, 0x1535, + // Entry 500 - 53F + 0x1538, 0x153b, 0x153e, 0x1541, 0x1544, 0x1547, 0x154a, 0x154d, + 0x1550, 0x1553, 0x1556, 0x1559, 0x155c, 0x155f, 0x1562, 0x1565, + 0x1568, 0x156b, 0x156e, 0x1571, 0x1574, 0x1577, 0x157a, 0x157d, + 0x1580, 0x1583, 0x1586, 0x1589, 0x158c, 0x158f, 0x1592, 0x1595, + 0x1598, 0x159b, 0x159e, 0x15a1, 0x15a4, 0x15a7, 0x15aa, 0x15ad, + 0x15b0, 0x15b3, 0x15b6, 0x15b9, 0x15bc, 0x15bf, 0x15c2, 0x15c5, + 0x15c8, 0x15cb, 0x15ce, 0x15d1, 0x15d4, 0x15d7, 0x15da, 0x15dd, + 0x15e0, 0x15e3, 0x15e6, 0x15e9, 0x15ec, 0x15ef, 0x15f2, 0x15f5, + // Entry 540 - 57F + 0x15f8, 0x15fb, 0x15fe, 0x1601, 0x1604, 0x1607, 0x160a, 0x160d, + 0x1610, 0x1613, 0x1616, 0x1619, 0x161c, 0x161f, 0x1622, 0x1625, + 0x1628, 0x162b, 0x162e, 0x1631, 0x1634, 0x1637, 0x163a, 0x163d, + 0x1640, 0x1643, 0x1646, 0x1649, 0x164c, 0x164f, 0x1652, 0x1655, + 0x1658, 0x165b, 0x165e, 0x1661, 0x1664, 0x1667, 0x166a, 0x166d, + 0x1670, 0x1673, 0x1676, 0x1679, 0x167c, 0x167f, 0x1682, 0x1685, + 0x1688, 0x168b, 0x168e, 0x1691, 0x1694, 0x1697, 0x169a, 0x169d, + 0x16a0, 0x16a3, 0x16a6, 0x16a9, 0x16ac, 0x16af, 0x16b2, 0x16b5, + // Entry 580 - 5BF + 0x16b8, 0x16bb, 0x16be, 0x16c1, 0x16c4, 0x16c7, 0x16ca, 0x16cd, + 0x16d0, 0x16d3, 0x16d6, 0x16d9, 0x16dc, 0x16df, 0x16e2, 0x16e5, + 0x16e8, 0x16eb, 0x16ee, 0x16f1, 0x16f4, 0x16f7, 0x16fa, 0x16fd, + 0x1700, 0x1703, 0x1706, 0x1709, 0x170c, 0x170f, 0x1712, 0x1715, + 0x1718, 0x171b, 0x171e, 0x1721, 0x1724, 0x1727, 0x172a, 0x172d, + 0x1730, 0x1733, 0x1736, 0x1739, 0x173c, 0x173f, 0x1742, 0x1745, + 0x1748, 0x174b, 0x174e, 0x1751, 0x1754, 0x1757, 0x175a, 0x175d, + 0x1760, 0x1763, 0x1766, 0x1769, 0x176c, 0x176f, 0x1772, 0x1775, + // Entry 5C0 - 5FF + 0x1778, 0x177b, 0x177e, 0x1781, 0x1784, 0x1787, 0x178a, 0x178d, + 0x1790, 0x1793, 0x1796, 0x1799, 0x179c, 0x179f, 0x17a2, 0x17a5, + 0x17a8, 0x17ab, 0x17ae, 0x17b1, 0x17b4, 0x17b7, 0x17ba, 0x17bd, + 0x17c0, 0x17c3, 0x17c6, 0x17c9, 0x17cc, 0x17cf, 0x17d2, 0x17d5, + 0x17d8, 0x17db, 0x17de, 0x17e1, 0x17e4, 0x17e7, 0x17ea, 0x17ed, + 0x17f0, 0x17f3, 0x17f6, 0x17f9, 0x17fc, 0x17ff, 0x1802, 0x1805, + 0x1808, 0x180b, 0x180e, 0x1811, 0x1814, 0x1817, 0x181a, 0x181d, + 0x1820, 0x1823, 0x1826, 0x1829, 0x182c, 0x182f, 0x1832, 0x1835, + // Entry 600 - 63F + 0x1838, 0x183b, 0x183e, 0x1841, 0x1844, 0x1847, 0x184a, 0x184d, + 0x1850, 0x1853, 0x1856, 0x1859, 0x185c, 0x185f, 0x1862, 0x1865, + 0x1868, 0x186b, 0x186e, 0x1871, 0x1874, 0x1877, 0x187a, 0x187d, + 0x1880, 0x1883, 0x1886, 0x1889, 0x188c, 0x188f, 0x1892, 0x1895, + 0x1898, 0x189b, 0x189e, 0x18a1, 0x18a4, 0x18a7, 0x18aa, 0x18ad, + 0x18b0, 0x18b3, 0x18b6, 0x18b9, 0x18bc, 0x18bf, 0x18c2, 0x18c5, + 0x18c8, 0x18cb, 0x18ce, 0x18d1, 0x18d4, 0x18d7, 0x18da, 0x18dd, + 0x18e0, 0x18e3, 0x18e6, 0x18e9, 0x18ec, 0x18ef, 0x18f2, 0x18f5, + // Entry 640 - 67F + 0x18f8, 0x18fb, 0x18fe, 0x1901, 0x1904, 0x1907, 0x190a, 0x190d, + 0x1910, 0x1913, 0x1916, 0x1919, 0x191c, 0x191f, 0x1922, 0x1925, + 0x1928, 0x192b, 0x192e, 0x1931, 0x1934, 0x1937, 0x193a, 0x193d, + 0x1940, 0x1943, 0x1946, 0x1949, 0x194c, 0x194f, 0x1952, 0x1955, + 0x1958, 0x195b, 0x195e, 0x1961, 0x1964, 0x1967, 0x196a, 0x196d, + 0x1970, 0x1973, 0x1976, 0x1979, 0x197c, 0x197f, 0x1982, 0x1985, + 0x1988, 0x198b, +} // Size: 3324 bytes var xorData string = "" + // Size: 4862 bytes "\x02\x0c\x09\x02\xb0\xec\x02\xad\xd8\x02\xad\xd9\x02\x06\x07\x02\x0f\x12" + @@ -547,7 +690,7 @@ func (t *idnaTrie) lookupStringUnsafe(s string) uint16 { return 0 } -// idnaTrie. Total size: 30288 bytes (29.58 KiB). Checksum: c0cd84404a2f6f19. +// idnaTrie. Total size: 30196 bytes (29.49 KiB). Checksum: e2ae95a945f04016. type idnaTrie struct{} func newIdnaTrie(i int) *idnaTrie { @@ -600,11 +743,11 @@ var idnaValues = [8192]uint16{ 0xd2: 0x0040, 0xd3: 0x0040, 0xd4: 0x0040, 0xd5: 0x0040, 0xd6: 0x0040, 0xd7: 0x0040, 0xd8: 0x0040, 0xd9: 0x0040, 0xda: 0x0040, 0xdb: 0x0040, 0xdc: 0x0040, 0xdd: 0x0040, 0xde: 0x0040, 0xdf: 0x0040, 0xe0: 0x000a, 0xe1: 0x0018, 0xe2: 0x0018, 0xe3: 0x0018, - 0xe4: 0x0018, 0xe5: 0x0018, 0xe6: 0x0018, 0xe7: 0x0018, 0xe8: 0x001a, 0xe9: 0x0018, - 0xea: 0x0039, 0xeb: 0x0018, 0xec: 0x0018, 0xed: 0x03c0, 0xee: 0x0018, 0xef: 0x004a, - 0xf0: 0x0018, 0xf1: 0x0018, 0xf2: 0x0069, 0xf3: 0x0079, 0xf4: 0x008a, 0xf5: 0x0005, - 0xf6: 0x0018, 0xf7: 0x0008, 0xf8: 0x00aa, 0xf9: 0x00c9, 0xfa: 0x00d9, 0xfb: 0x0018, - 0xfc: 0x00e9, 0xfd: 0x0119, 0xfe: 0x0149, 0xff: 0x0018, + 0xe4: 0x0018, 0xe5: 0x0018, 0xe6: 0x0018, 0xe7: 0x0018, 0xe8: 0x0012, 0xe9: 0x0018, + 0xea: 0x0019, 0xeb: 0x0018, 0xec: 0x0018, 0xed: 0x03c0, 0xee: 0x0018, 0xef: 0x0022, + 0xf0: 0x0018, 0xf1: 0x0018, 0xf2: 0x0029, 0xf3: 0x0031, 0xf4: 0x003a, 0xf5: 0x0005, + 0xf6: 0x0018, 0xf7: 0x0008, 0xf8: 0x0042, 0xf9: 0x0049, 0xfa: 0x0051, 0xfb: 0x0018, + 0xfc: 0x0059, 0xfd: 0x0061, 0xfe: 0x0069, 0xff: 0x0018, // Block 0x4, offset 0x100 0x100: 0xe00d, 0x101: 0x0008, 0x102: 0xe00d, 0x103: 0x0008, 0x104: 0xe00d, 0x105: 0x0008, 0x106: 0xe00d, 0x107: 0x0008, 0x108: 0xe00d, 0x109: 0x0008, 0x10a: 0xe00d, 0x10b: 0x0008, @@ -614,12 +757,12 @@ var idnaValues = [8192]uint16{ 0x11e: 0xe00d, 0x11f: 0x0008, 0x120: 0xe00d, 0x121: 0x0008, 0x122: 0xe00d, 0x123: 0x0008, 0x124: 0xe00d, 0x125: 0x0008, 0x126: 0xe00d, 0x127: 0x0008, 0x128: 0xe00d, 0x129: 0x0008, 0x12a: 0xe00d, 0x12b: 0x0008, 0x12c: 0xe00d, 0x12d: 0x0008, 0x12e: 0xe00d, 0x12f: 0x0008, - 0x130: 0x0179, 0x131: 0x0008, 0x132: 0x0035, 0x133: 0x004d, 0x134: 0xe00d, 0x135: 0x0008, + 0x130: 0x0071, 0x131: 0x0008, 0x132: 0x0035, 0x133: 0x004d, 0x134: 0xe00d, 0x135: 0x0008, 0x136: 0xe00d, 0x137: 0x0008, 0x138: 0x0008, 0x139: 0xe01d, 0x13a: 0x0008, 0x13b: 0xe03d, - 0x13c: 0x0008, 0x13d: 0xe01d, 0x13e: 0x0008, 0x13f: 0x0199, + 0x13c: 0x0008, 0x13d: 0xe01d, 0x13e: 0x0008, 0x13f: 0x0079, // Block 0x5, offset 0x140 - 0x140: 0x0199, 0x141: 0xe01d, 0x142: 0x0008, 0x143: 0xe03d, 0x144: 0x0008, 0x145: 0xe01d, - 0x146: 0x0008, 0x147: 0xe07d, 0x148: 0x0008, 0x149: 0x01b9, 0x14a: 0xe00d, 0x14b: 0x0008, + 0x140: 0x0079, 0x141: 0xe01d, 0x142: 0x0008, 0x143: 0xe03d, 0x144: 0x0008, 0x145: 0xe01d, + 0x146: 0x0008, 0x147: 0xe07d, 0x148: 0x0008, 0x149: 0x0081, 0x14a: 0xe00d, 0x14b: 0x0008, 0x14c: 0xe00d, 0x14d: 0x0008, 0x14e: 0xe00d, 0x14f: 0x0008, 0x150: 0xe00d, 0x151: 0x0008, 0x152: 0xe00d, 0x153: 0x0008, 0x154: 0xe00d, 0x155: 0x0008, 0x156: 0xe00d, 0x157: 0x0008, 0x158: 0xe00d, 0x159: 0x0008, 0x15a: 0xe00d, 0x15b: 0x0008, 0x15c: 0xe00d, 0x15d: 0x0008, @@ -628,7 +771,7 @@ var idnaValues = [8192]uint16{ 0x16a: 0xe00d, 0x16b: 0x0008, 0x16c: 0xe00d, 0x16d: 0x0008, 0x16e: 0xe00d, 0x16f: 0x0008, 0x170: 0xe00d, 0x171: 0x0008, 0x172: 0xe00d, 0x173: 0x0008, 0x174: 0xe00d, 0x175: 0x0008, 0x176: 0xe00d, 0x177: 0x0008, 0x178: 0x0065, 0x179: 0xe01d, 0x17a: 0x0008, 0x17b: 0xe03d, - 0x17c: 0x0008, 0x17d: 0xe01d, 0x17e: 0x0008, 0x17f: 0x01d9, + 0x17c: 0x0008, 0x17d: 0xe01d, 0x17e: 0x0008, 0x17f: 0x0089, // Block 0x6, offset 0x180 0x180: 0x0008, 0x181: 0x007d, 0x182: 0xe00d, 0x183: 0x0008, 0x184: 0xe00d, 0x185: 0x0008, 0x186: 0x007d, 0x187: 0xe07d, 0x188: 0x0008, 0x189: 0x0095, 0x18a: 0x00ad, 0x18b: 0xe03d, @@ -642,8 +785,8 @@ var idnaValues = [8192]uint16{ 0x1b6: 0x0008, 0x1b7: 0x01e5, 0x1b8: 0xe00d, 0x1b9: 0x0008, 0x1ba: 0x0008, 0x1bb: 0x0008, 0x1bc: 0xe00d, 0x1bd: 0x0008, 0x1be: 0x0008, 0x1bf: 0x0008, // Block 0x7, offset 0x1c0 - 0x1c0: 0x0008, 0x1c1: 0x0008, 0x1c2: 0x0008, 0x1c3: 0x0008, 0x1c4: 0x01e9, 0x1c5: 0x01e9, - 0x1c6: 0x01e9, 0x1c7: 0x01fd, 0x1c8: 0x0215, 0x1c9: 0x022d, 0x1ca: 0x0245, 0x1cb: 0x025d, + 0x1c0: 0x0008, 0x1c1: 0x0008, 0x1c2: 0x0008, 0x1c3: 0x0008, 0x1c4: 0x0091, 0x1c5: 0x0091, + 0x1c6: 0x0091, 0x1c7: 0x01fd, 0x1c8: 0x0215, 0x1c9: 0x022d, 0x1ca: 0x0245, 0x1cb: 0x025d, 0x1cc: 0x0275, 0x1cd: 0xe01d, 0x1ce: 0x0008, 0x1cf: 0xe0fd, 0x1d0: 0x0008, 0x1d1: 0xe01d, 0x1d2: 0x0008, 0x1d3: 0xe03d, 0x1d4: 0x0008, 0x1d5: 0xe01d, 0x1d6: 0x0008, 0x1d7: 0xe07d, 0x1d8: 0x0008, 0x1d9: 0xe01d, 0x1da: 0x0008, 0x1db: 0xe03d, 0x1dc: 0x0008, 0x1dd: 0x0008, @@ -663,22 +806,22 @@ var idnaValues = [8192]uint16{ 0x224: 0xe00d, 0x225: 0x0008, 0x226: 0xe00d, 0x227: 0x0008, 0x228: 0xe00d, 0x229: 0x0008, 0x22a: 0xe00d, 0x22b: 0x0008, 0x22c: 0xe00d, 0x22d: 0x0008, 0x22e: 0xe00d, 0x22f: 0x0008, 0x230: 0xe00d, 0x231: 0x0008, 0x232: 0xe00d, 0x233: 0x0008, 0x234: 0x0008, 0x235: 0x0008, - 0x236: 0x0008, 0x237: 0x0008, 0x238: 0x0008, 0x239: 0x0008, 0x23a: 0x0209, 0x23b: 0xe03d, - 0x23c: 0x0008, 0x23d: 0x031d, 0x23e: 0x0229, 0x23f: 0x0008, + 0x236: 0x0008, 0x237: 0x0008, 0x238: 0x0008, 0x239: 0x0008, 0x23a: 0x0099, 0x23b: 0xe03d, + 0x23c: 0x0008, 0x23d: 0x031d, 0x23e: 0x00a1, 0x23f: 0x0008, // Block 0x9, offset 0x240 0x240: 0x0008, 0x241: 0x0008, 0x242: 0x0018, 0x243: 0x0018, 0x244: 0x0018, 0x245: 0x0018, 0x246: 0x0008, 0x247: 0x0008, 0x248: 0x0008, 0x249: 0x0008, 0x24a: 0x0008, 0x24b: 0x0008, 0x24c: 0x0008, 0x24d: 0x0008, 0x24e: 0x0008, 0x24f: 0x0008, 0x250: 0x0008, 0x251: 0x0008, 0x252: 0x0018, 0x253: 0x0018, 0x254: 0x0018, 0x255: 0x0018, 0x256: 0x0018, 0x257: 0x0018, - 0x258: 0x029a, 0x259: 0x02ba, 0x25a: 0x02da, 0x25b: 0x02fa, 0x25c: 0x031a, 0x25d: 0x033a, - 0x25e: 0x0018, 0x25f: 0x0018, 0x260: 0x03ad, 0x261: 0x0359, 0x262: 0x01d9, 0x263: 0x0369, + 0x258: 0x00d2, 0x259: 0x00da, 0x25a: 0x00e2, 0x25b: 0x00ea, 0x25c: 0x00f2, 0x25d: 0x00fa, + 0x25e: 0x0018, 0x25f: 0x0018, 0x260: 0x03ad, 0x261: 0x0101, 0x262: 0x0089, 0x263: 0x0109, 0x264: 0x03c5, 0x265: 0x0018, 0x266: 0x0018, 0x267: 0x0018, 0x268: 0x0018, 0x269: 0x0018, 0x26a: 0x0018, 0x26b: 0x0018, 0x26c: 0x0008, 0x26d: 0x0018, 0x26e: 0x0008, 0x26f: 0x0018, 0x270: 0x0018, 0x271: 0x0018, 0x272: 0x0018, 0x273: 0x0018, 0x274: 0x0018, 0x275: 0x0018, 0x276: 0x0018, 0x277: 0x0018, 0x278: 0x0018, 0x279: 0x0018, 0x27a: 0x0018, 0x27b: 0x0018, 0x27c: 0x0018, 0x27d: 0x0018, 0x27e: 0x0018, 0x27f: 0x0018, // Block 0xa, offset 0x280 - 0x280: 0x03dd, 0x281: 0x03dd, 0x282: 0x3308, 0x283: 0x03f5, 0x284: 0x0379, 0x285: 0x040d, + 0x280: 0x03dd, 0x281: 0x03dd, 0x282: 0x3308, 0x283: 0x03f5, 0x284: 0x0111, 0x285: 0x040d, 0x286: 0x3308, 0x287: 0x3308, 0x288: 0x3308, 0x289: 0x3308, 0x28a: 0x3308, 0x28b: 0x3308, 0x28c: 0x3308, 0x28d: 0x3308, 0x28e: 0x3308, 0x28f: 0x33c0, 0x290: 0x3308, 0x291: 0x3308, 0x292: 0x3308, 0x293: 0x3308, 0x294: 0x3308, 0x295: 0x3308, 0x296: 0x3308, 0x297: 0x3308, @@ -687,10 +830,10 @@ var idnaValues = [8192]uint16{ 0x2a4: 0x3308, 0x2a5: 0x3308, 0x2a6: 0x3308, 0x2a7: 0x3308, 0x2a8: 0x3308, 0x2a9: 0x3308, 0x2aa: 0x3308, 0x2ab: 0x3308, 0x2ac: 0x3308, 0x2ad: 0x3308, 0x2ae: 0x3308, 0x2af: 0x3308, 0x2b0: 0xe00d, 0x2b1: 0x0008, 0x2b2: 0xe00d, 0x2b3: 0x0008, 0x2b4: 0x0425, 0x2b5: 0x0008, - 0x2b6: 0xe00d, 0x2b7: 0x0008, 0x2b8: 0x0040, 0x2b9: 0x0040, 0x2ba: 0x03a2, 0x2bb: 0x0008, - 0x2bc: 0x0008, 0x2bd: 0x0008, 0x2be: 0x03c2, 0x2bf: 0x043d, + 0x2b6: 0xe00d, 0x2b7: 0x0008, 0x2b8: 0x0040, 0x2b9: 0x0040, 0x2ba: 0x011a, 0x2bb: 0x0008, + 0x2bc: 0x0008, 0x2bd: 0x0008, 0x2be: 0x0122, 0x2bf: 0x043d, // Block 0xb, offset 0x2c0 - 0x2c0: 0x0040, 0x2c1: 0x0040, 0x2c2: 0x0040, 0x2c3: 0x0040, 0x2c4: 0x008a, 0x2c5: 0x03d2, + 0x2c0: 0x0040, 0x2c1: 0x0040, 0x2c2: 0x0040, 0x2c3: 0x0040, 0x2c4: 0x003a, 0x2c5: 0x012a, 0x2c6: 0xe155, 0x2c7: 0x0455, 0x2c8: 0xe12d, 0x2c9: 0xe13d, 0x2ca: 0xe12d, 0x2cb: 0x0040, 0x2cc: 0x03dd, 0x2cd: 0x0040, 0x2ce: 0x046d, 0x2cf: 0x0485, 0x2d0: 0x0008, 0x2d1: 0xe105, 0x2d2: 0xe105, 0x2d3: 0xe105, 0x2d4: 0xe105, 0x2d5: 0xe105, 0x2d6: 0xe105, 0x2d7: 0xe105, @@ -782,8 +925,8 @@ var idnaValues = [8192]uint16{ 0x49e: 0x3308, 0x49f: 0x3308, 0x4a0: 0x0808, 0x4a1: 0x0808, 0x4a2: 0x0808, 0x4a3: 0x0808, 0x4a4: 0x0808, 0x4a5: 0x0808, 0x4a6: 0x0808, 0x4a7: 0x0808, 0x4a8: 0x0808, 0x4a9: 0x0808, 0x4aa: 0x0018, 0x4ab: 0x0818, 0x4ac: 0x0818, 0x4ad: 0x0818, 0x4ae: 0x0a08, 0x4af: 0x0a08, - 0x4b0: 0x3308, 0x4b1: 0x0c08, 0x4b2: 0x0c08, 0x4b3: 0x0c08, 0x4b4: 0x0808, 0x4b5: 0x0429, - 0x4b6: 0x0451, 0x4b7: 0x0479, 0x4b8: 0x04a1, 0x4b9: 0x0a08, 0x4ba: 0x0a08, 0x4bb: 0x0a08, + 0x4b0: 0x3308, 0x4b1: 0x0c08, 0x4b2: 0x0c08, 0x4b3: 0x0c08, 0x4b4: 0x0808, 0x4b5: 0x0139, + 0x4b6: 0x0141, 0x4b7: 0x0149, 0x4b8: 0x0151, 0x4b9: 0x0a08, 0x4ba: 0x0a08, 0x4bb: 0x0a08, 0x4bc: 0x0a08, 0x4bd: 0x0a08, 0x4be: 0x0a08, 0x4bf: 0x0a08, // Block 0x13, offset 0x4c0 0x4c0: 0x0c08, 0x4c1: 0x0a08, 0x4c2: 0x0a08, 0x4c3: 0x0c08, 0x4c4: 0x0c08, 0x4c5: 0x0c08, @@ -826,8 +969,8 @@ var idnaValues = [8192]uint16{ 0x586: 0x3308, 0x587: 0x3308, 0x588: 0x3308, 0x589: 0x3008, 0x58a: 0x3008, 0x58b: 0x3008, 0x58c: 0x3008, 0x58d: 0x3b08, 0x58e: 0x3008, 0x58f: 0x3008, 0x590: 0x0008, 0x591: 0x3308, 0x592: 0x3308, 0x593: 0x3308, 0x594: 0x3308, 0x595: 0x3308, 0x596: 0x3308, 0x597: 0x3308, - 0x598: 0x04c9, 0x599: 0x0501, 0x59a: 0x0539, 0x59b: 0x0571, 0x59c: 0x05a9, 0x59d: 0x05e1, - 0x59e: 0x0619, 0x59f: 0x0651, 0x5a0: 0x0008, 0x5a1: 0x0008, 0x5a2: 0x3308, 0x5a3: 0x3308, + 0x598: 0x0159, 0x599: 0x0161, 0x59a: 0x0169, 0x59b: 0x0171, 0x59c: 0x0179, 0x59d: 0x0181, + 0x59e: 0x0189, 0x59f: 0x0191, 0x5a0: 0x0008, 0x5a1: 0x0008, 0x5a2: 0x3308, 0x5a3: 0x3308, 0x5a4: 0x0018, 0x5a5: 0x0018, 0x5a6: 0x0008, 0x5a7: 0x0008, 0x5a8: 0x0008, 0x5a9: 0x0008, 0x5aa: 0x0008, 0x5ab: 0x0008, 0x5ac: 0x0008, 0x5ad: 0x0008, 0x5ae: 0x0008, 0x5af: 0x0008, 0x5b0: 0x0018, 0x5b1: 0x0008, 0x5b2: 0x0008, 0x5b3: 0x0008, 0x5b4: 0x0008, 0x5b5: 0x0008, @@ -850,8 +993,8 @@ var idnaValues = [8192]uint16{ 0x606: 0x0040, 0x607: 0x3008, 0x608: 0x3008, 0x609: 0x0040, 0x60a: 0x0040, 0x60b: 0x3008, 0x60c: 0x3008, 0x60d: 0x3b08, 0x60e: 0x0008, 0x60f: 0x0040, 0x610: 0x0040, 0x611: 0x0040, 0x612: 0x0040, 0x613: 0x0040, 0x614: 0x0040, 0x615: 0x0040, 0x616: 0x0040, 0x617: 0x3008, - 0x618: 0x0040, 0x619: 0x0040, 0x61a: 0x0040, 0x61b: 0x0040, 0x61c: 0x0689, 0x61d: 0x06c1, - 0x61e: 0x0040, 0x61f: 0x06f9, 0x620: 0x0008, 0x621: 0x0008, 0x622: 0x3308, 0x623: 0x3308, + 0x618: 0x0040, 0x619: 0x0040, 0x61a: 0x0040, 0x61b: 0x0040, 0x61c: 0x0199, 0x61d: 0x01a1, + 0x61e: 0x0040, 0x61f: 0x01a9, 0x620: 0x0008, 0x621: 0x0008, 0x622: 0x3308, 0x623: 0x3308, 0x624: 0x0040, 0x625: 0x0040, 0x626: 0x0008, 0x627: 0x0008, 0x628: 0x0008, 0x629: 0x0008, 0x62a: 0x0008, 0x62b: 0x0008, 0x62c: 0x0008, 0x62d: 0x0008, 0x62e: 0x0008, 0x62f: 0x0008, 0x630: 0x0008, 0x631: 0x0008, 0x632: 0x0018, 0x633: 0x0018, 0x634: 0x0018, 0x635: 0x0018, @@ -866,16 +1009,16 @@ var idnaValues = [8192]uint16{ 0x65e: 0x0008, 0x65f: 0x0008, 0x660: 0x0008, 0x661: 0x0008, 0x662: 0x0008, 0x663: 0x0008, 0x664: 0x0008, 0x665: 0x0008, 0x666: 0x0008, 0x667: 0x0008, 0x668: 0x0008, 0x669: 0x0040, 0x66a: 0x0008, 0x66b: 0x0008, 0x66c: 0x0008, 0x66d: 0x0008, 0x66e: 0x0008, 0x66f: 0x0008, - 0x670: 0x0008, 0x671: 0x0040, 0x672: 0x0008, 0x673: 0x0731, 0x674: 0x0040, 0x675: 0x0008, - 0x676: 0x0769, 0x677: 0x0040, 0x678: 0x0008, 0x679: 0x0008, 0x67a: 0x0040, 0x67b: 0x0040, + 0x670: 0x0008, 0x671: 0x0040, 0x672: 0x0008, 0x673: 0x01b1, 0x674: 0x0040, 0x675: 0x0008, + 0x676: 0x01b9, 0x677: 0x0040, 0x678: 0x0008, 0x679: 0x0008, 0x67a: 0x0040, 0x67b: 0x0040, 0x67c: 0x3308, 0x67d: 0x0040, 0x67e: 0x3008, 0x67f: 0x3008, // Block 0x1a, offset 0x680 0x680: 0x3008, 0x681: 0x3308, 0x682: 0x3308, 0x683: 0x0040, 0x684: 0x0040, 0x685: 0x0040, 0x686: 0x0040, 0x687: 0x3308, 0x688: 0x3308, 0x689: 0x0040, 0x68a: 0x0040, 0x68b: 0x3308, 0x68c: 0x3308, 0x68d: 0x3b08, 0x68e: 0x0040, 0x68f: 0x0040, 0x690: 0x0040, 0x691: 0x3308, 0x692: 0x0040, 0x693: 0x0040, 0x694: 0x0040, 0x695: 0x0040, 0x696: 0x0040, 0x697: 0x0040, - 0x698: 0x0040, 0x699: 0x07a1, 0x69a: 0x07d9, 0x69b: 0x0811, 0x69c: 0x0008, 0x69d: 0x0040, - 0x69e: 0x0849, 0x69f: 0x0040, 0x6a0: 0x0040, 0x6a1: 0x0040, 0x6a2: 0x0040, 0x6a3: 0x0040, + 0x698: 0x0040, 0x699: 0x01c1, 0x69a: 0x01c9, 0x69b: 0x01d1, 0x69c: 0x0008, 0x69d: 0x0040, + 0x69e: 0x01d9, 0x69f: 0x0040, 0x6a0: 0x0040, 0x6a1: 0x0040, 0x6a2: 0x0040, 0x6a3: 0x0040, 0x6a4: 0x0040, 0x6a5: 0x0040, 0x6a6: 0x0008, 0x6a7: 0x0008, 0x6a8: 0x0008, 0x6a9: 0x0008, 0x6aa: 0x0008, 0x6ab: 0x0008, 0x6ac: 0x0008, 0x6ad: 0x0008, 0x6ae: 0x0008, 0x6af: 0x0008, 0x6b0: 0x3308, 0x6b1: 0x3308, 0x6b2: 0x0008, 0x6b3: 0x0008, 0x6b4: 0x0008, 0x6b5: 0x3308, @@ -922,7 +1065,7 @@ var idnaValues = [8192]uint16{ 0x786: 0x0040, 0x787: 0x3008, 0x788: 0x3008, 0x789: 0x0040, 0x78a: 0x0040, 0x78b: 0x3008, 0x78c: 0x3008, 0x78d: 0x3b08, 0x78e: 0x0040, 0x78f: 0x0040, 0x790: 0x0040, 0x791: 0x0040, 0x792: 0x0040, 0x793: 0x0040, 0x794: 0x0040, 0x795: 0x3308, 0x796: 0x3308, 0x797: 0x3008, - 0x798: 0x0040, 0x799: 0x0040, 0x79a: 0x0040, 0x79b: 0x0040, 0x79c: 0x0881, 0x79d: 0x08b9, + 0x798: 0x0040, 0x799: 0x0040, 0x79a: 0x0040, 0x79b: 0x0040, 0x79c: 0x01e1, 0x79d: 0x01e9, 0x79e: 0x0040, 0x79f: 0x0008, 0x7a0: 0x0008, 0x7a1: 0x0008, 0x7a2: 0x3308, 0x7a3: 0x3308, 0x7a4: 0x0040, 0x7a5: 0x0040, 0x7a6: 0x0008, 0x7a7: 0x0008, 0x7a8: 0x0008, 0x7a9: 0x0008, 0x7aa: 0x0008, 0x7ab: 0x0008, 0x7ac: 0x0008, 0x7ad: 0x0008, 0x7ae: 0x0008, 0x7af: 0x0008, @@ -998,32 +1141,32 @@ var idnaValues = [8192]uint16{ 0x91e: 0x0008, 0x91f: 0x0008, 0x920: 0x0008, 0x921: 0x0008, 0x922: 0x0008, 0x923: 0x0008, 0x924: 0x0040, 0x925: 0x0008, 0x926: 0x0040, 0x927: 0x0008, 0x928: 0x0008, 0x929: 0x0008, 0x92a: 0x0008, 0x92b: 0x0008, 0x92c: 0x0008, 0x92d: 0x0008, 0x92e: 0x0008, 0x92f: 0x0008, - 0x930: 0x0008, 0x931: 0x3308, 0x932: 0x0008, 0x933: 0x0929, 0x934: 0x3308, 0x935: 0x3308, + 0x930: 0x0008, 0x931: 0x3308, 0x932: 0x0008, 0x933: 0x01f9, 0x934: 0x3308, 0x935: 0x3308, 0x936: 0x3308, 0x937: 0x3308, 0x938: 0x3308, 0x939: 0x3308, 0x93a: 0x3b08, 0x93b: 0x3308, 0x93c: 0x3308, 0x93d: 0x0008, 0x93e: 0x0040, 0x93f: 0x0040, // Block 0x25, offset 0x940 - 0x940: 0x0008, 0x941: 0x0008, 0x942: 0x0008, 0x943: 0x09d1, 0x944: 0x0008, 0x945: 0x0008, + 0x940: 0x0008, 0x941: 0x0008, 0x942: 0x0008, 0x943: 0x0211, 0x944: 0x0008, 0x945: 0x0008, 0x946: 0x0008, 0x947: 0x0008, 0x948: 0x0040, 0x949: 0x0008, 0x94a: 0x0008, 0x94b: 0x0008, - 0x94c: 0x0008, 0x94d: 0x0a09, 0x94e: 0x0008, 0x94f: 0x0008, 0x950: 0x0008, 0x951: 0x0008, - 0x952: 0x0a41, 0x953: 0x0008, 0x954: 0x0008, 0x955: 0x0008, 0x956: 0x0008, 0x957: 0x0a79, - 0x958: 0x0008, 0x959: 0x0008, 0x95a: 0x0008, 0x95b: 0x0008, 0x95c: 0x0ab1, 0x95d: 0x0008, + 0x94c: 0x0008, 0x94d: 0x0219, 0x94e: 0x0008, 0x94f: 0x0008, 0x950: 0x0008, 0x951: 0x0008, + 0x952: 0x0221, 0x953: 0x0008, 0x954: 0x0008, 0x955: 0x0008, 0x956: 0x0008, 0x957: 0x0229, + 0x958: 0x0008, 0x959: 0x0008, 0x95a: 0x0008, 0x95b: 0x0008, 0x95c: 0x0231, 0x95d: 0x0008, 0x95e: 0x0008, 0x95f: 0x0008, 0x960: 0x0008, 0x961: 0x0008, 0x962: 0x0008, 0x963: 0x0008, - 0x964: 0x0008, 0x965: 0x0008, 0x966: 0x0008, 0x967: 0x0008, 0x968: 0x0008, 0x969: 0x0ae9, + 0x964: 0x0008, 0x965: 0x0008, 0x966: 0x0008, 0x967: 0x0008, 0x968: 0x0008, 0x969: 0x0239, 0x96a: 0x0008, 0x96b: 0x0008, 0x96c: 0x0008, 0x96d: 0x0040, 0x96e: 0x0040, 0x96f: 0x0040, - 0x970: 0x0040, 0x971: 0x3308, 0x972: 0x3308, 0x973: 0x0b21, 0x974: 0x3308, 0x975: 0x0b59, - 0x976: 0x0b91, 0x977: 0x0bc9, 0x978: 0x0c19, 0x979: 0x0c51, 0x97a: 0x3308, 0x97b: 0x3308, + 0x970: 0x0040, 0x971: 0x3308, 0x972: 0x3308, 0x973: 0x0241, 0x974: 0x3308, 0x975: 0x0249, + 0x976: 0x0251, 0x977: 0x0259, 0x978: 0x0261, 0x979: 0x0269, 0x97a: 0x3308, 0x97b: 0x3308, 0x97c: 0x3308, 0x97d: 0x3308, 0x97e: 0x3308, 0x97f: 0x3008, // Block 0x26, offset 0x980 - 0x980: 0x3308, 0x981: 0x0ca1, 0x982: 0x3308, 0x983: 0x3308, 0x984: 0x3b08, 0x985: 0x0018, + 0x980: 0x3308, 0x981: 0x0271, 0x982: 0x3308, 0x983: 0x3308, 0x984: 0x3b08, 0x985: 0x0018, 0x986: 0x3308, 0x987: 0x3308, 0x988: 0x0008, 0x989: 0x0008, 0x98a: 0x0008, 0x98b: 0x0008, 0x98c: 0x0008, 0x98d: 0x3308, 0x98e: 0x3308, 0x98f: 0x3308, 0x990: 0x3308, 0x991: 0x3308, - 0x992: 0x3308, 0x993: 0x0cd9, 0x994: 0x3308, 0x995: 0x3308, 0x996: 0x3308, 0x997: 0x3308, - 0x998: 0x0040, 0x999: 0x3308, 0x99a: 0x3308, 0x99b: 0x3308, 0x99c: 0x3308, 0x99d: 0x0d11, - 0x99e: 0x3308, 0x99f: 0x3308, 0x9a0: 0x3308, 0x9a1: 0x3308, 0x9a2: 0x0d49, 0x9a3: 0x3308, - 0x9a4: 0x3308, 0x9a5: 0x3308, 0x9a6: 0x3308, 0x9a7: 0x0d81, 0x9a8: 0x3308, 0x9a9: 0x3308, - 0x9aa: 0x3308, 0x9ab: 0x3308, 0x9ac: 0x0db9, 0x9ad: 0x3308, 0x9ae: 0x3308, 0x9af: 0x3308, + 0x992: 0x3308, 0x993: 0x0279, 0x994: 0x3308, 0x995: 0x3308, 0x996: 0x3308, 0x997: 0x3308, + 0x998: 0x0040, 0x999: 0x3308, 0x99a: 0x3308, 0x99b: 0x3308, 0x99c: 0x3308, 0x99d: 0x0281, + 0x99e: 0x3308, 0x99f: 0x3308, 0x9a0: 0x3308, 0x9a1: 0x3308, 0x9a2: 0x0289, 0x9a3: 0x3308, + 0x9a4: 0x3308, 0x9a5: 0x3308, 0x9a6: 0x3308, 0x9a7: 0x0291, 0x9a8: 0x3308, 0x9a9: 0x3308, + 0x9aa: 0x3308, 0x9ab: 0x3308, 0x9ac: 0x0299, 0x9ad: 0x3308, 0x9ae: 0x3308, 0x9af: 0x3308, 0x9b0: 0x3308, 0x9b1: 0x3308, 0x9b2: 0x3308, 0x9b3: 0x3308, 0x9b4: 0x3308, 0x9b5: 0x3308, - 0x9b6: 0x3308, 0x9b7: 0x3308, 0x9b8: 0x3308, 0x9b9: 0x0df1, 0x9ba: 0x3308, 0x9bb: 0x3308, + 0x9b6: 0x3308, 0x9b7: 0x3308, 0x9b8: 0x3308, 0x9b9: 0x02a1, 0x9ba: 0x3308, 0x9bb: 0x3308, 0x9bc: 0x3308, 0x9bd: 0x0040, 0x9be: 0x0018, 0x9bf: 0x0018, // Block 0x27, offset 0x9c0 0x9c0: 0x0008, 0x9c1: 0x0008, 0x9c2: 0x0008, 0x9c3: 0x0008, 0x9c4: 0x0008, 0x9c5: 0x0008, @@ -1033,34 +1176,34 @@ var idnaValues = [8192]uint16{ 0x9d8: 0x0008, 0x9d9: 0x0008, 0x9da: 0x0008, 0x9db: 0x0008, 0x9dc: 0x0008, 0x9dd: 0x0008, 0x9de: 0x0008, 0x9df: 0x0008, 0x9e0: 0x0008, 0x9e1: 0x0008, 0x9e2: 0x0008, 0x9e3: 0x0008, 0x9e4: 0x0008, 0x9e5: 0x0008, 0x9e6: 0x0008, 0x9e7: 0x0008, 0x9e8: 0x0008, 0x9e9: 0x0008, - 0x9ea: 0x0008, 0x9eb: 0x0008, 0x9ec: 0x0039, 0x9ed: 0x0ed1, 0x9ee: 0x0ee9, 0x9ef: 0x0008, - 0x9f0: 0x0ef9, 0x9f1: 0x0f09, 0x9f2: 0x0f19, 0x9f3: 0x0f31, 0x9f4: 0x0249, 0x9f5: 0x0f41, - 0x9f6: 0x0259, 0x9f7: 0x0f51, 0x9f8: 0x0359, 0x9f9: 0x0f61, 0x9fa: 0x0f71, 0x9fb: 0x0008, - 0x9fc: 0x00d9, 0x9fd: 0x0f81, 0x9fe: 0x0f99, 0x9ff: 0x0269, + 0x9ea: 0x0008, 0x9eb: 0x0008, 0x9ec: 0x0019, 0x9ed: 0x02e1, 0x9ee: 0x02e9, 0x9ef: 0x0008, + 0x9f0: 0x02f1, 0x9f1: 0x02f9, 0x9f2: 0x0301, 0x9f3: 0x0309, 0x9f4: 0x00a9, 0x9f5: 0x0311, + 0x9f6: 0x00b1, 0x9f7: 0x0319, 0x9f8: 0x0101, 0x9f9: 0x0321, 0x9fa: 0x0329, 0x9fb: 0x0008, + 0x9fc: 0x0051, 0x9fd: 0x0331, 0x9fe: 0x0339, 0x9ff: 0x00b9, // Block 0x28, offset 0xa00 - 0xa00: 0x0fa9, 0xa01: 0x0fb9, 0xa02: 0x0279, 0xa03: 0x0039, 0xa04: 0x0fc9, 0xa05: 0x0fe1, - 0xa06: 0x05b5, 0xa07: 0x0ee9, 0xa08: 0x0ef9, 0xa09: 0x0f09, 0xa0a: 0x0ff9, 0xa0b: 0x1011, - 0xa0c: 0x1029, 0xa0d: 0x0f31, 0xa0e: 0x0008, 0xa0f: 0x0f51, 0xa10: 0x0f61, 0xa11: 0x1041, - 0xa12: 0x00d9, 0xa13: 0x1059, 0xa14: 0x05cd, 0xa15: 0x05cd, 0xa16: 0x0f99, 0xa17: 0x0fa9, - 0xa18: 0x0fb9, 0xa19: 0x05b5, 0xa1a: 0x1071, 0xa1b: 0x1089, 0xa1c: 0x05e5, 0xa1d: 0x1099, - 0xa1e: 0x10b1, 0xa1f: 0x10c9, 0xa20: 0x10e1, 0xa21: 0x10f9, 0xa22: 0x0f41, 0xa23: 0x0269, - 0xa24: 0x0fb9, 0xa25: 0x1089, 0xa26: 0x1099, 0xa27: 0x10b1, 0xa28: 0x1111, 0xa29: 0x10e1, - 0xa2a: 0x10f9, 0xa2b: 0x0008, 0xa2c: 0x0008, 0xa2d: 0x0008, 0xa2e: 0x0008, 0xa2f: 0x0008, + 0xa00: 0x0341, 0xa01: 0x0349, 0xa02: 0x00c1, 0xa03: 0x0019, 0xa04: 0x0351, 0xa05: 0x0359, + 0xa06: 0x05b5, 0xa07: 0x02e9, 0xa08: 0x02f1, 0xa09: 0x02f9, 0xa0a: 0x0361, 0xa0b: 0x0369, + 0xa0c: 0x0371, 0xa0d: 0x0309, 0xa0e: 0x0008, 0xa0f: 0x0319, 0xa10: 0x0321, 0xa11: 0x0379, + 0xa12: 0x0051, 0xa13: 0x0381, 0xa14: 0x05cd, 0xa15: 0x05cd, 0xa16: 0x0339, 0xa17: 0x0341, + 0xa18: 0x0349, 0xa19: 0x05b5, 0xa1a: 0x0389, 0xa1b: 0x0391, 0xa1c: 0x05e5, 0xa1d: 0x0399, + 0xa1e: 0x03a1, 0xa1f: 0x03a9, 0xa20: 0x03b1, 0xa21: 0x03b9, 0xa22: 0x0311, 0xa23: 0x00b9, + 0xa24: 0x0349, 0xa25: 0x0391, 0xa26: 0x0399, 0xa27: 0x03a1, 0xa28: 0x03c1, 0xa29: 0x03b1, + 0xa2a: 0x03b9, 0xa2b: 0x0008, 0xa2c: 0x0008, 0xa2d: 0x0008, 0xa2e: 0x0008, 0xa2f: 0x0008, 0xa30: 0x0008, 0xa31: 0x0008, 0xa32: 0x0008, 0xa33: 0x0008, 0xa34: 0x0008, 0xa35: 0x0008, - 0xa36: 0x0008, 0xa37: 0x0008, 0xa38: 0x1129, 0xa39: 0x0008, 0xa3a: 0x0008, 0xa3b: 0x0008, + 0xa36: 0x0008, 0xa37: 0x0008, 0xa38: 0x03c9, 0xa39: 0x0008, 0xa3a: 0x0008, 0xa3b: 0x0008, 0xa3c: 0x0008, 0xa3d: 0x0008, 0xa3e: 0x0008, 0xa3f: 0x0008, // Block 0x29, offset 0xa40 0xa40: 0x0008, 0xa41: 0x0008, 0xa42: 0x0008, 0xa43: 0x0008, 0xa44: 0x0008, 0xa45: 0x0008, 0xa46: 0x0008, 0xa47: 0x0008, 0xa48: 0x0008, 0xa49: 0x0008, 0xa4a: 0x0008, 0xa4b: 0x0008, 0xa4c: 0x0008, 0xa4d: 0x0008, 0xa4e: 0x0008, 0xa4f: 0x0008, 0xa50: 0x0008, 0xa51: 0x0008, 0xa52: 0x0008, 0xa53: 0x0008, 0xa54: 0x0008, 0xa55: 0x0008, 0xa56: 0x0008, 0xa57: 0x0008, - 0xa58: 0x0008, 0xa59: 0x0008, 0xa5a: 0x0008, 0xa5b: 0x1141, 0xa5c: 0x1159, 0xa5d: 0x1169, - 0xa5e: 0x1181, 0xa5f: 0x1029, 0xa60: 0x1199, 0xa61: 0x11a9, 0xa62: 0x11c1, 0xa63: 0x11d9, - 0xa64: 0x11f1, 0xa65: 0x1209, 0xa66: 0x1221, 0xa67: 0x05fd, 0xa68: 0x1239, 0xa69: 0x1251, - 0xa6a: 0xe17d, 0xa6b: 0x1269, 0xa6c: 0x1281, 0xa6d: 0x1299, 0xa6e: 0x12b1, 0xa6f: 0x12c9, - 0xa70: 0x12e1, 0xa71: 0x12f9, 0xa72: 0x1311, 0xa73: 0x1329, 0xa74: 0x1341, 0xa75: 0x1359, - 0xa76: 0x1371, 0xa77: 0x1389, 0xa78: 0x0615, 0xa79: 0x13a1, 0xa7a: 0x13b9, 0xa7b: 0x13d1, - 0xa7c: 0x13e1, 0xa7d: 0x13f9, 0xa7e: 0x1411, 0xa7f: 0x1429, + 0xa58: 0x0008, 0xa59: 0x0008, 0xa5a: 0x0008, 0xa5b: 0x03d1, 0xa5c: 0x03d9, 0xa5d: 0x03e1, + 0xa5e: 0x03e9, 0xa5f: 0x0371, 0xa60: 0x03f1, 0xa61: 0x03f9, 0xa62: 0x0401, 0xa63: 0x0409, + 0xa64: 0x0411, 0xa65: 0x0419, 0xa66: 0x0421, 0xa67: 0x05fd, 0xa68: 0x0429, 0xa69: 0x0431, + 0xa6a: 0xe17d, 0xa6b: 0x0439, 0xa6c: 0x0441, 0xa6d: 0x0449, 0xa6e: 0x0451, 0xa6f: 0x0459, + 0xa70: 0x0461, 0xa71: 0x0469, 0xa72: 0x0471, 0xa73: 0x0479, 0xa74: 0x0481, 0xa75: 0x0489, + 0xa76: 0x0491, 0xa77: 0x0499, 0xa78: 0x0615, 0xa79: 0x04a1, 0xa7a: 0x04a9, 0xa7b: 0x04b1, + 0xa7c: 0x04b9, 0xa7d: 0x04c1, 0xa7e: 0x04c9, 0xa7f: 0x04d1, // Block 0x2a, offset 0xa80 0xa80: 0xe00d, 0xa81: 0x0008, 0xa82: 0xe00d, 0xa83: 0x0008, 0xa84: 0xe00d, 0xa85: 0x0008, 0xa86: 0xe00d, 0xa87: 0x0008, 0xa88: 0xe00d, 0xa89: 0x0008, 0xa8a: 0xe00d, 0xa8b: 0x0008, @@ -1079,7 +1222,7 @@ var idnaValues = [8192]uint16{ 0xacc: 0xe00d, 0xacd: 0x0008, 0xace: 0xe00d, 0xacf: 0x0008, 0xad0: 0xe00d, 0xad1: 0x0008, 0xad2: 0xe00d, 0xad3: 0x0008, 0xad4: 0xe00d, 0xad5: 0x0008, 0xad6: 0x0008, 0xad7: 0x0008, 0xad8: 0x0008, 0xad9: 0x0008, 0xada: 0x062d, 0xadb: 0x064d, 0xadc: 0x0008, 0xadd: 0x0008, - 0xade: 0x1441, 0xadf: 0x0008, 0xae0: 0xe00d, 0xae1: 0x0008, 0xae2: 0xe00d, 0xae3: 0x0008, + 0xade: 0x04d9, 0xadf: 0x0008, 0xae0: 0xe00d, 0xae1: 0x0008, 0xae2: 0xe00d, 0xae3: 0x0008, 0xae4: 0xe00d, 0xae5: 0x0008, 0xae6: 0xe00d, 0xae7: 0x0008, 0xae8: 0xe00d, 0xae9: 0x0008, 0xaea: 0xe00d, 0xaeb: 0x0008, 0xaec: 0xe00d, 0xaed: 0x0008, 0xaee: 0xe00d, 0xaef: 0x0008, 0xaf0: 0xe00d, 0xaf1: 0x0008, 0xaf2: 0xe00d, 0xaf3: 0x0008, 0xaf4: 0xe00d, 0xaf5: 0x0008, @@ -1094,33 +1237,33 @@ var idnaValues = [8192]uint16{ 0xb1e: 0x0040, 0xb1f: 0xe045, 0xb20: 0x0008, 0xb21: 0x0008, 0xb22: 0x0008, 0xb23: 0x0008, 0xb24: 0x0008, 0xb25: 0x0008, 0xb26: 0x0008, 0xb27: 0x0008, 0xb28: 0xe045, 0xb29: 0xe045, 0xb2a: 0xe045, 0xb2b: 0xe045, 0xb2c: 0xe045, 0xb2d: 0xe045, 0xb2e: 0xe045, 0xb2f: 0xe045, - 0xb30: 0x0008, 0xb31: 0x1459, 0xb32: 0x0008, 0xb33: 0x1471, 0xb34: 0x0008, 0xb35: 0x1489, - 0xb36: 0x0008, 0xb37: 0x14a1, 0xb38: 0x0008, 0xb39: 0x14b9, 0xb3a: 0x0008, 0xb3b: 0x14d1, - 0xb3c: 0x0008, 0xb3d: 0x14e9, 0xb3e: 0x0040, 0xb3f: 0x0040, + 0xb30: 0x0008, 0xb31: 0x04e1, 0xb32: 0x0008, 0xb33: 0x04e9, 0xb34: 0x0008, 0xb35: 0x04f1, + 0xb36: 0x0008, 0xb37: 0x04f9, 0xb38: 0x0008, 0xb39: 0x0501, 0xb3a: 0x0008, 0xb3b: 0x0509, + 0xb3c: 0x0008, 0xb3d: 0x0511, 0xb3e: 0x0040, 0xb3f: 0x0040, // Block 0x2d, offset 0xb40 - 0xb40: 0x1501, 0xb41: 0x1531, 0xb42: 0x1561, 0xb43: 0x1591, 0xb44: 0x15c1, 0xb45: 0x15f1, - 0xb46: 0x1621, 0xb47: 0x1651, 0xb48: 0x1501, 0xb49: 0x1531, 0xb4a: 0x1561, 0xb4b: 0x1591, - 0xb4c: 0x15c1, 0xb4d: 0x15f1, 0xb4e: 0x1621, 0xb4f: 0x1651, 0xb50: 0x1681, 0xb51: 0x16b1, - 0xb52: 0x16e1, 0xb53: 0x1711, 0xb54: 0x1741, 0xb55: 0x1771, 0xb56: 0x17a1, 0xb57: 0x17d1, - 0xb58: 0x1681, 0xb59: 0x16b1, 0xb5a: 0x16e1, 0xb5b: 0x1711, 0xb5c: 0x1741, 0xb5d: 0x1771, - 0xb5e: 0x17a1, 0xb5f: 0x17d1, 0xb60: 0x1801, 0xb61: 0x1831, 0xb62: 0x1861, 0xb63: 0x1891, - 0xb64: 0x18c1, 0xb65: 0x18f1, 0xb66: 0x1921, 0xb67: 0x1951, 0xb68: 0x1801, 0xb69: 0x1831, - 0xb6a: 0x1861, 0xb6b: 0x1891, 0xb6c: 0x18c1, 0xb6d: 0x18f1, 0xb6e: 0x1921, 0xb6f: 0x1951, - 0xb70: 0x0008, 0xb71: 0x0008, 0xb72: 0x1981, 0xb73: 0x19b1, 0xb74: 0x19d9, 0xb75: 0x0040, - 0xb76: 0x0008, 0xb77: 0x1a01, 0xb78: 0xe045, 0xb79: 0xe045, 0xb7a: 0x0665, 0xb7b: 0x1459, - 0xb7c: 0x19b1, 0xb7d: 0x067e, 0xb7e: 0x1a31, 0xb7f: 0x069e, + 0xb40: 0x0519, 0xb41: 0x0521, 0xb42: 0x0529, 0xb43: 0x0531, 0xb44: 0x0539, 0xb45: 0x0541, + 0xb46: 0x0549, 0xb47: 0x0551, 0xb48: 0x0519, 0xb49: 0x0521, 0xb4a: 0x0529, 0xb4b: 0x0531, + 0xb4c: 0x0539, 0xb4d: 0x0541, 0xb4e: 0x0549, 0xb4f: 0x0551, 0xb50: 0x0559, 0xb51: 0x0561, + 0xb52: 0x0569, 0xb53: 0x0571, 0xb54: 0x0579, 0xb55: 0x0581, 0xb56: 0x0589, 0xb57: 0x0591, + 0xb58: 0x0559, 0xb59: 0x0561, 0xb5a: 0x0569, 0xb5b: 0x0571, 0xb5c: 0x0579, 0xb5d: 0x0581, + 0xb5e: 0x0589, 0xb5f: 0x0591, 0xb60: 0x0599, 0xb61: 0x05a1, 0xb62: 0x05a9, 0xb63: 0x05b1, + 0xb64: 0x05b9, 0xb65: 0x05c1, 0xb66: 0x05c9, 0xb67: 0x05d1, 0xb68: 0x0599, 0xb69: 0x05a1, + 0xb6a: 0x05a9, 0xb6b: 0x05b1, 0xb6c: 0x05b9, 0xb6d: 0x05c1, 0xb6e: 0x05c9, 0xb6f: 0x05d1, + 0xb70: 0x0008, 0xb71: 0x0008, 0xb72: 0x05d9, 0xb73: 0x05e1, 0xb74: 0x05e9, 0xb75: 0x0040, + 0xb76: 0x0008, 0xb77: 0x05f1, 0xb78: 0xe045, 0xb79: 0xe045, 0xb7a: 0x0665, 0xb7b: 0x04e1, + 0xb7c: 0x05e1, 0xb7d: 0x067e, 0xb7e: 0x05f9, 0xb7f: 0x069e, // Block 0x2e, offset 0xb80 - 0xb80: 0x06be, 0xb81: 0x1a4a, 0xb82: 0x1a79, 0xb83: 0x1aa9, 0xb84: 0x1ad1, 0xb85: 0x0040, - 0xb86: 0x0008, 0xb87: 0x1af9, 0xb88: 0x06dd, 0xb89: 0x1471, 0xb8a: 0x06f5, 0xb8b: 0x1489, - 0xb8c: 0x1aa9, 0xb8d: 0x1b2a, 0xb8e: 0x1b5a, 0xb8f: 0x1b8a, 0xb90: 0x0008, 0xb91: 0x0008, - 0xb92: 0x0008, 0xb93: 0x1bb9, 0xb94: 0x0040, 0xb95: 0x0040, 0xb96: 0x0008, 0xb97: 0x0008, - 0xb98: 0xe045, 0xb99: 0xe045, 0xb9a: 0x070d, 0xb9b: 0x14a1, 0xb9c: 0x0040, 0xb9d: 0x1bd2, - 0xb9e: 0x1c02, 0xb9f: 0x1c32, 0xba0: 0x0008, 0xba1: 0x0008, 0xba2: 0x0008, 0xba3: 0x1c61, + 0xb80: 0x06be, 0xb81: 0x0602, 0xb82: 0x0609, 0xb83: 0x0611, 0xb84: 0x0619, 0xb85: 0x0040, + 0xb86: 0x0008, 0xb87: 0x0621, 0xb88: 0x06dd, 0xb89: 0x04e9, 0xb8a: 0x06f5, 0xb8b: 0x04f1, + 0xb8c: 0x0611, 0xb8d: 0x062a, 0xb8e: 0x0632, 0xb8f: 0x063a, 0xb90: 0x0008, 0xb91: 0x0008, + 0xb92: 0x0008, 0xb93: 0x0641, 0xb94: 0x0040, 0xb95: 0x0040, 0xb96: 0x0008, 0xb97: 0x0008, + 0xb98: 0xe045, 0xb99: 0xe045, 0xb9a: 0x070d, 0xb9b: 0x04f9, 0xb9c: 0x0040, 0xb9d: 0x064a, + 0xb9e: 0x0652, 0xb9f: 0x065a, 0xba0: 0x0008, 0xba1: 0x0008, 0xba2: 0x0008, 0xba3: 0x0661, 0xba4: 0x0008, 0xba5: 0x0008, 0xba6: 0x0008, 0xba7: 0x0008, 0xba8: 0xe045, 0xba9: 0xe045, - 0xbaa: 0x0725, 0xbab: 0x14d1, 0xbac: 0xe04d, 0xbad: 0x1c7a, 0xbae: 0x03d2, 0xbaf: 0x1caa, - 0xbb0: 0x0040, 0xbb1: 0x0040, 0xbb2: 0x1cb9, 0xbb3: 0x1ce9, 0xbb4: 0x1d11, 0xbb5: 0x0040, - 0xbb6: 0x0008, 0xbb7: 0x1d39, 0xbb8: 0x073d, 0xbb9: 0x14b9, 0xbba: 0x0515, 0xbbb: 0x14e9, - 0xbbc: 0x1ce9, 0xbbd: 0x0756, 0xbbe: 0x0776, 0xbbf: 0x0040, + 0xbaa: 0x0725, 0xbab: 0x0509, 0xbac: 0xe04d, 0xbad: 0x066a, 0xbae: 0x012a, 0xbaf: 0x0672, + 0xbb0: 0x0040, 0xbb1: 0x0040, 0xbb2: 0x0679, 0xbb3: 0x0681, 0xbb4: 0x0689, 0xbb5: 0x0040, + 0xbb6: 0x0008, 0xbb7: 0x0691, 0xbb8: 0x073d, 0xbb9: 0x0501, 0xbba: 0x0515, 0xbbb: 0x0511, + 0xbbc: 0x0681, 0xbbd: 0x0756, 0xbbe: 0x0776, 0xbbf: 0x0040, // Block 0x2f, offset 0xbc0 0xbc0: 0x000a, 0xbc1: 0x000a, 0xbc2: 0x000a, 0xbc3: 0x000a, 0xbc4: 0x000a, 0xbc5: 0x000a, 0xbc6: 0x000a, 0xbc7: 0x000a, 0xbc8: 0x000a, 0xbc9: 0x000a, 0xbca: 0x000a, 0xbcb: 0x03c0, @@ -1130,72 +1273,72 @@ var idnaValues = [8192]uint16{ 0xbde: 0x0018, 0xbdf: 0x0018, 0xbe0: 0x0018, 0xbe1: 0x0018, 0xbe2: 0x0018, 0xbe3: 0x0018, 0xbe4: 0x0040, 0xbe5: 0x0040, 0xbe6: 0x0040, 0xbe7: 0x0018, 0xbe8: 0x0040, 0xbe9: 0x0040, 0xbea: 0x0340, 0xbeb: 0x0340, 0xbec: 0x0340, 0xbed: 0x0340, 0xbee: 0x0340, 0xbef: 0x000a, - 0xbf0: 0x0018, 0xbf1: 0x0018, 0xbf2: 0x0018, 0xbf3: 0x1d69, 0xbf4: 0x1da1, 0xbf5: 0x0018, - 0xbf6: 0x1df1, 0xbf7: 0x1e29, 0xbf8: 0x0018, 0xbf9: 0x0018, 0xbfa: 0x0018, 0xbfb: 0x0018, - 0xbfc: 0x1e7a, 0xbfd: 0x0018, 0xbfe: 0x07b6, 0xbff: 0x0018, + 0xbf0: 0x0018, 0xbf1: 0x0018, 0xbf2: 0x0018, 0xbf3: 0x0699, 0xbf4: 0x06a1, 0xbf5: 0x0018, + 0xbf6: 0x06a9, 0xbf7: 0x06b1, 0xbf8: 0x0018, 0xbf9: 0x0018, 0xbfa: 0x0018, 0xbfb: 0x0018, + 0xbfc: 0x06ba, 0xbfd: 0x0018, 0xbfe: 0x07b6, 0xbff: 0x0018, // Block 0x30, offset 0xc00 0xc00: 0x0018, 0xc01: 0x0018, 0xc02: 0x0018, 0xc03: 0x0018, 0xc04: 0x0018, 0xc05: 0x0018, - 0xc06: 0x0018, 0xc07: 0x1e92, 0xc08: 0x1eaa, 0xc09: 0x1ec2, 0xc0a: 0x0018, 0xc0b: 0x0018, + 0xc06: 0x0018, 0xc07: 0x06c2, 0xc08: 0x06ca, 0xc09: 0x06d2, 0xc0a: 0x0018, 0xc0b: 0x0018, 0xc0c: 0x0018, 0xc0d: 0x0018, 0xc0e: 0x0018, 0xc0f: 0x0018, 0xc10: 0x0018, 0xc11: 0x0018, - 0xc12: 0x0018, 0xc13: 0x0018, 0xc14: 0x0018, 0xc15: 0x0018, 0xc16: 0x0018, 0xc17: 0x1ed9, + 0xc12: 0x0018, 0xc13: 0x0018, 0xc14: 0x0018, 0xc15: 0x0018, 0xc16: 0x0018, 0xc17: 0x06d9, 0xc18: 0x0018, 0xc19: 0x0018, 0xc1a: 0x0018, 0xc1b: 0x0018, 0xc1c: 0x0018, 0xc1d: 0x0018, 0xc1e: 0x0018, 0xc1f: 0x000a, 0xc20: 0x03c0, 0xc21: 0x0340, 0xc22: 0x0340, 0xc23: 0x0340, 0xc24: 0x03c0, 0xc25: 0x0040, 0xc26: 0x0040, 0xc27: 0x0040, 0xc28: 0x0040, 0xc29: 0x0040, 0xc2a: 0x0340, 0xc2b: 0x0340, 0xc2c: 0x0340, 0xc2d: 0x0340, 0xc2e: 0x0340, 0xc2f: 0x0340, - 0xc30: 0x1f41, 0xc31: 0x0f41, 0xc32: 0x0040, 0xc33: 0x0040, 0xc34: 0x1f51, 0xc35: 0x1f61, - 0xc36: 0x1f71, 0xc37: 0x1f81, 0xc38: 0x1f91, 0xc39: 0x1fa1, 0xc3a: 0x1fb2, 0xc3b: 0x07d5, - 0xc3c: 0x1fc2, 0xc3d: 0x1fd2, 0xc3e: 0x1fe2, 0xc3f: 0x0f71, + 0xc30: 0x06e1, 0xc31: 0x0311, 0xc32: 0x0040, 0xc33: 0x0040, 0xc34: 0x06e9, 0xc35: 0x06f1, + 0xc36: 0x06f9, 0xc37: 0x0701, 0xc38: 0x0709, 0xc39: 0x0711, 0xc3a: 0x071a, 0xc3b: 0x07d5, + 0xc3c: 0x0722, 0xc3d: 0x072a, 0xc3e: 0x0732, 0xc3f: 0x0329, // Block 0x31, offset 0xc40 - 0xc40: 0x1f41, 0xc41: 0x00c9, 0xc42: 0x0069, 0xc43: 0x0079, 0xc44: 0x1f51, 0xc45: 0x1f61, - 0xc46: 0x1f71, 0xc47: 0x1f81, 0xc48: 0x1f91, 0xc49: 0x1fa1, 0xc4a: 0x1fb2, 0xc4b: 0x07ed, - 0xc4c: 0x1fc2, 0xc4d: 0x1fd2, 0xc4e: 0x1fe2, 0xc4f: 0x0040, 0xc50: 0x0039, 0xc51: 0x0f09, - 0xc52: 0x00d9, 0xc53: 0x0369, 0xc54: 0x0ff9, 0xc55: 0x0249, 0xc56: 0x0f51, 0xc57: 0x0359, - 0xc58: 0x0f61, 0xc59: 0x0f71, 0xc5a: 0x0f99, 0xc5b: 0x01d9, 0xc5c: 0x0fa9, 0xc5d: 0x0040, + 0xc40: 0x06e1, 0xc41: 0x0049, 0xc42: 0x0029, 0xc43: 0x0031, 0xc44: 0x06e9, 0xc45: 0x06f1, + 0xc46: 0x06f9, 0xc47: 0x0701, 0xc48: 0x0709, 0xc49: 0x0711, 0xc4a: 0x071a, 0xc4b: 0x07ed, + 0xc4c: 0x0722, 0xc4d: 0x072a, 0xc4e: 0x0732, 0xc4f: 0x0040, 0xc50: 0x0019, 0xc51: 0x02f9, + 0xc52: 0x0051, 0xc53: 0x0109, 0xc54: 0x0361, 0xc55: 0x00a9, 0xc56: 0x0319, 0xc57: 0x0101, + 0xc58: 0x0321, 0xc59: 0x0329, 0xc5a: 0x0339, 0xc5b: 0x0089, 0xc5c: 0x0341, 0xc5d: 0x0040, 0xc5e: 0x0040, 0xc5f: 0x0040, 0xc60: 0x0018, 0xc61: 0x0018, 0xc62: 0x0018, 0xc63: 0x0018, - 0xc64: 0x0018, 0xc65: 0x0018, 0xc66: 0x0018, 0xc67: 0x0018, 0xc68: 0x1ff1, 0xc69: 0x0018, + 0xc64: 0x0018, 0xc65: 0x0018, 0xc66: 0x0018, 0xc67: 0x0018, 0xc68: 0x0739, 0xc69: 0x0018, 0xc6a: 0x0018, 0xc6b: 0x0018, 0xc6c: 0x0018, 0xc6d: 0x0018, 0xc6e: 0x0018, 0xc6f: 0x0018, 0xc70: 0x0018, 0xc71: 0x0018, 0xc72: 0x0018, 0xc73: 0x0018, 0xc74: 0x0018, 0xc75: 0x0018, 0xc76: 0x0018, 0xc77: 0x0018, 0xc78: 0x0018, 0xc79: 0x0018, 0xc7a: 0x0018, 0xc7b: 0x0018, 0xc7c: 0x0018, 0xc7d: 0x0018, 0xc7e: 0x0018, 0xc7f: 0x0018, // Block 0x32, offset 0xc80 - 0xc80: 0x0806, 0xc81: 0x0826, 0xc82: 0x1159, 0xc83: 0x0845, 0xc84: 0x0018, 0xc85: 0x0866, - 0xc86: 0x0886, 0xc87: 0x1011, 0xc88: 0x0018, 0xc89: 0x08a5, 0xc8a: 0x0f31, 0xc8b: 0x0249, - 0xc8c: 0x0249, 0xc8d: 0x0249, 0xc8e: 0x0249, 0xc8f: 0x2009, 0xc90: 0x0f41, 0xc91: 0x0f41, - 0xc92: 0x0359, 0xc93: 0x0359, 0xc94: 0x0018, 0xc95: 0x0f71, 0xc96: 0x2021, 0xc97: 0x0018, - 0xc98: 0x0018, 0xc99: 0x0f99, 0xc9a: 0x2039, 0xc9b: 0x0269, 0xc9c: 0x0269, 0xc9d: 0x0269, - 0xc9e: 0x0018, 0xc9f: 0x0018, 0xca0: 0x2049, 0xca1: 0x08c5, 0xca2: 0x2061, 0xca3: 0x0018, - 0xca4: 0x13d1, 0xca5: 0x0018, 0xca6: 0x2079, 0xca7: 0x0018, 0xca8: 0x13d1, 0xca9: 0x0018, - 0xcaa: 0x0f51, 0xcab: 0x2091, 0xcac: 0x0ee9, 0xcad: 0x1159, 0xcae: 0x0018, 0xcaf: 0x0f09, - 0xcb0: 0x0f09, 0xcb1: 0x1199, 0xcb2: 0x0040, 0xcb3: 0x0f61, 0xcb4: 0x00d9, 0xcb5: 0x20a9, - 0xcb6: 0x20c1, 0xcb7: 0x20d9, 0xcb8: 0x20f1, 0xcb9: 0x0f41, 0xcba: 0x0018, 0xcbb: 0x08e5, - 0xcbc: 0x2109, 0xcbd: 0x10b1, 0xcbe: 0x10b1, 0xcbf: 0x2109, + 0xc80: 0x0806, 0xc81: 0x0826, 0xc82: 0x03d9, 0xc83: 0x0845, 0xc84: 0x0018, 0xc85: 0x0866, + 0xc86: 0x0886, 0xc87: 0x0369, 0xc88: 0x0018, 0xc89: 0x08a5, 0xc8a: 0x0309, 0xc8b: 0x00a9, + 0xc8c: 0x00a9, 0xc8d: 0x00a9, 0xc8e: 0x00a9, 0xc8f: 0x0741, 0xc90: 0x0311, 0xc91: 0x0311, + 0xc92: 0x0101, 0xc93: 0x0101, 0xc94: 0x0018, 0xc95: 0x0329, 0xc96: 0x0749, 0xc97: 0x0018, + 0xc98: 0x0018, 0xc99: 0x0339, 0xc9a: 0x0751, 0xc9b: 0x00b9, 0xc9c: 0x00b9, 0xc9d: 0x00b9, + 0xc9e: 0x0018, 0xc9f: 0x0018, 0xca0: 0x0759, 0xca1: 0x08c5, 0xca2: 0x0761, 0xca3: 0x0018, + 0xca4: 0x04b1, 0xca5: 0x0018, 0xca6: 0x0769, 0xca7: 0x0018, 0xca8: 0x04b1, 0xca9: 0x0018, + 0xcaa: 0x0319, 0xcab: 0x0771, 0xcac: 0x02e9, 0xcad: 0x03d9, 0xcae: 0x0018, 0xcaf: 0x02f9, + 0xcb0: 0x02f9, 0xcb1: 0x03f1, 0xcb2: 0x0040, 0xcb3: 0x0321, 0xcb4: 0x0051, 0xcb5: 0x0779, + 0xcb6: 0x0781, 0xcb7: 0x0789, 0xcb8: 0x0791, 0xcb9: 0x0311, 0xcba: 0x0018, 0xcbb: 0x08e5, + 0xcbc: 0x0799, 0xcbd: 0x03a1, 0xcbe: 0x03a1, 0xcbf: 0x0799, // Block 0x33, offset 0xcc0 - 0xcc0: 0x0905, 0xcc1: 0x0018, 0xcc2: 0x0018, 0xcc3: 0x0018, 0xcc4: 0x0018, 0xcc5: 0x0ef9, - 0xcc6: 0x0ef9, 0xcc7: 0x0f09, 0xcc8: 0x0f41, 0xcc9: 0x0259, 0xcca: 0x0018, 0xccb: 0x0018, - 0xccc: 0x0018, 0xccd: 0x0018, 0xcce: 0x0008, 0xccf: 0x0018, 0xcd0: 0x2121, 0xcd1: 0x2151, - 0xcd2: 0x2181, 0xcd3: 0x21b9, 0xcd4: 0x21e9, 0xcd5: 0x2219, 0xcd6: 0x2249, 0xcd7: 0x2279, - 0xcd8: 0x22a9, 0xcd9: 0x22d9, 0xcda: 0x2309, 0xcdb: 0x2339, 0xcdc: 0x2369, 0xcdd: 0x2399, - 0xcde: 0x23c9, 0xcdf: 0x23f9, 0xce0: 0x0f41, 0xce1: 0x2421, 0xce2: 0x091d, 0xce3: 0x2439, - 0xce4: 0x1089, 0xce5: 0x2451, 0xce6: 0x093d, 0xce7: 0x2469, 0xce8: 0x2491, 0xce9: 0x0369, - 0xcea: 0x24a9, 0xceb: 0x095d, 0xcec: 0x0359, 0xced: 0x1159, 0xcee: 0x0ef9, 0xcef: 0x0f61, - 0xcf0: 0x0f41, 0xcf1: 0x2421, 0xcf2: 0x097d, 0xcf3: 0x2439, 0xcf4: 0x1089, 0xcf5: 0x2451, - 0xcf6: 0x099d, 0xcf7: 0x2469, 0xcf8: 0x2491, 0xcf9: 0x0369, 0xcfa: 0x24a9, 0xcfb: 0x09bd, - 0xcfc: 0x0359, 0xcfd: 0x1159, 0xcfe: 0x0ef9, 0xcff: 0x0f61, + 0xcc0: 0x0905, 0xcc1: 0x0018, 0xcc2: 0x0018, 0xcc3: 0x0018, 0xcc4: 0x0018, 0xcc5: 0x02f1, + 0xcc6: 0x02f1, 0xcc7: 0x02f9, 0xcc8: 0x0311, 0xcc9: 0x00b1, 0xcca: 0x0018, 0xccb: 0x0018, + 0xccc: 0x0018, 0xccd: 0x0018, 0xcce: 0x0008, 0xccf: 0x0018, 0xcd0: 0x07a1, 0xcd1: 0x07a9, + 0xcd2: 0x07b1, 0xcd3: 0x07b9, 0xcd4: 0x07c1, 0xcd5: 0x07c9, 0xcd6: 0x07d1, 0xcd7: 0x07d9, + 0xcd8: 0x07e1, 0xcd9: 0x07e9, 0xcda: 0x07f1, 0xcdb: 0x07f9, 0xcdc: 0x0801, 0xcdd: 0x0809, + 0xcde: 0x0811, 0xcdf: 0x0819, 0xce0: 0x0311, 0xce1: 0x0821, 0xce2: 0x091d, 0xce3: 0x0829, + 0xce4: 0x0391, 0xce5: 0x0831, 0xce6: 0x093d, 0xce7: 0x0839, 0xce8: 0x0841, 0xce9: 0x0109, + 0xcea: 0x0849, 0xceb: 0x095d, 0xcec: 0x0101, 0xced: 0x03d9, 0xcee: 0x02f1, 0xcef: 0x0321, + 0xcf0: 0x0311, 0xcf1: 0x0821, 0xcf2: 0x097d, 0xcf3: 0x0829, 0xcf4: 0x0391, 0xcf5: 0x0831, + 0xcf6: 0x099d, 0xcf7: 0x0839, 0xcf8: 0x0841, 0xcf9: 0x0109, 0xcfa: 0x0849, 0xcfb: 0x09bd, + 0xcfc: 0x0101, 0xcfd: 0x03d9, 0xcfe: 0x02f1, 0xcff: 0x0321, // Block 0x34, offset 0xd00 0xd00: 0x0018, 0xd01: 0x0018, 0xd02: 0x0018, 0xd03: 0x0018, 0xd04: 0x0018, 0xd05: 0x0018, 0xd06: 0x0018, 0xd07: 0x0018, 0xd08: 0x0018, 0xd09: 0x0018, 0xd0a: 0x0018, 0xd0b: 0x0040, 0xd0c: 0x0040, 0xd0d: 0x0040, 0xd0e: 0x0040, 0xd0f: 0x0040, 0xd10: 0x0040, 0xd11: 0x0040, 0xd12: 0x0040, 0xd13: 0x0040, 0xd14: 0x0040, 0xd15: 0x0040, 0xd16: 0x0040, 0xd17: 0x0040, 0xd18: 0x0040, 0xd19: 0x0040, 0xd1a: 0x0040, 0xd1b: 0x0040, 0xd1c: 0x0040, 0xd1d: 0x0040, - 0xd1e: 0x0040, 0xd1f: 0x0040, 0xd20: 0x00c9, 0xd21: 0x0069, 0xd22: 0x0079, 0xd23: 0x1f51, - 0xd24: 0x1f61, 0xd25: 0x1f71, 0xd26: 0x1f81, 0xd27: 0x1f91, 0xd28: 0x1fa1, 0xd29: 0x2601, - 0xd2a: 0x2619, 0xd2b: 0x2631, 0xd2c: 0x2649, 0xd2d: 0x2661, 0xd2e: 0x2679, 0xd2f: 0x2691, - 0xd30: 0x26a9, 0xd31: 0x26c1, 0xd32: 0x26d9, 0xd33: 0x26f1, 0xd34: 0x0a1e, 0xd35: 0x0a3e, + 0xd1e: 0x0040, 0xd1f: 0x0040, 0xd20: 0x0049, 0xd21: 0x0029, 0xd22: 0x0031, 0xd23: 0x06e9, + 0xd24: 0x06f1, 0xd25: 0x06f9, 0xd26: 0x0701, 0xd27: 0x0709, 0xd28: 0x0711, 0xd29: 0x0879, + 0xd2a: 0x0881, 0xd2b: 0x0889, 0xd2c: 0x0891, 0xd2d: 0x0899, 0xd2e: 0x08a1, 0xd2f: 0x08a9, + 0xd30: 0x08b1, 0xd31: 0x08b9, 0xd32: 0x08c1, 0xd33: 0x08c9, 0xd34: 0x0a1e, 0xd35: 0x0a3e, 0xd36: 0x0a5e, 0xd37: 0x0a7e, 0xd38: 0x0a9e, 0xd39: 0x0abe, 0xd3a: 0x0ade, 0xd3b: 0x0afe, - 0xd3c: 0x0b1e, 0xd3d: 0x270a, 0xd3e: 0x2732, 0xd3f: 0x275a, + 0xd3c: 0x0b1e, 0xd3d: 0x08d2, 0xd3e: 0x08da, 0xd3f: 0x08e2, // Block 0x35, offset 0xd40 - 0xd40: 0x2782, 0xd41: 0x27aa, 0xd42: 0x27d2, 0xd43: 0x27fa, 0xd44: 0x2822, 0xd45: 0x284a, - 0xd46: 0x2872, 0xd47: 0x289a, 0xd48: 0x0040, 0xd49: 0x0040, 0xd4a: 0x0040, 0xd4b: 0x0040, + 0xd40: 0x08ea, 0xd41: 0x08f2, 0xd42: 0x08fa, 0xd43: 0x0902, 0xd44: 0x090a, 0xd45: 0x0912, + 0xd46: 0x091a, 0xd47: 0x0922, 0xd48: 0x0040, 0xd49: 0x0040, 0xd4a: 0x0040, 0xd4b: 0x0040, 0xd4c: 0x0040, 0xd4d: 0x0040, 0xd4e: 0x0040, 0xd4f: 0x0040, 0xd50: 0x0040, 0xd51: 0x0040, 0xd52: 0x0040, 0xd53: 0x0040, 0xd54: 0x0040, 0xd55: 0x0040, 0xd56: 0x0040, 0xd57: 0x0040, 0xd58: 0x0040, 0xd59: 0x0040, 0xd5a: 0x0040, 0xd5b: 0x0040, 0xd5c: 0x0b3e, 0xd5d: 0x0b5e, @@ -1203,17 +1346,17 @@ var idnaValues = [8192]uint16{ 0xd64: 0x0c3e, 0xd65: 0x0c5e, 0xd66: 0x0c7e, 0xd67: 0x0c9e, 0xd68: 0x0cbe, 0xd69: 0x0cde, 0xd6a: 0x0cfe, 0xd6b: 0x0d1e, 0xd6c: 0x0d3e, 0xd6d: 0x0d5e, 0xd6e: 0x0d7e, 0xd6f: 0x0d9e, 0xd70: 0x0dbe, 0xd71: 0x0dde, 0xd72: 0x0dfe, 0xd73: 0x0e1e, 0xd74: 0x0e3e, 0xd75: 0x0e5e, - 0xd76: 0x0039, 0xd77: 0x0ee9, 0xd78: 0x1159, 0xd79: 0x0ef9, 0xd7a: 0x0f09, 0xd7b: 0x1199, - 0xd7c: 0x0f31, 0xd7d: 0x0249, 0xd7e: 0x0f41, 0xd7f: 0x0259, + 0xd76: 0x0019, 0xd77: 0x02e9, 0xd78: 0x03d9, 0xd79: 0x02f1, 0xd7a: 0x02f9, 0xd7b: 0x03f1, + 0xd7c: 0x0309, 0xd7d: 0x00a9, 0xd7e: 0x0311, 0xd7f: 0x00b1, // Block 0x36, offset 0xd80 - 0xd80: 0x0f51, 0xd81: 0x0359, 0xd82: 0x0f61, 0xd83: 0x0f71, 0xd84: 0x00d9, 0xd85: 0x0f99, - 0xd86: 0x2039, 0xd87: 0x0269, 0xd88: 0x01d9, 0xd89: 0x0fa9, 0xd8a: 0x0fb9, 0xd8b: 0x1089, - 0xd8c: 0x0279, 0xd8d: 0x0369, 0xd8e: 0x0289, 0xd8f: 0x13d1, 0xd90: 0x0039, 0xd91: 0x0ee9, - 0xd92: 0x1159, 0xd93: 0x0ef9, 0xd94: 0x0f09, 0xd95: 0x1199, 0xd96: 0x0f31, 0xd97: 0x0249, - 0xd98: 0x0f41, 0xd99: 0x0259, 0xd9a: 0x0f51, 0xd9b: 0x0359, 0xd9c: 0x0f61, 0xd9d: 0x0f71, - 0xd9e: 0x00d9, 0xd9f: 0x0f99, 0xda0: 0x2039, 0xda1: 0x0269, 0xda2: 0x01d9, 0xda3: 0x0fa9, - 0xda4: 0x0fb9, 0xda5: 0x1089, 0xda6: 0x0279, 0xda7: 0x0369, 0xda8: 0x0289, 0xda9: 0x13d1, - 0xdaa: 0x1f41, 0xdab: 0x0018, 0xdac: 0x0018, 0xdad: 0x0018, 0xdae: 0x0018, 0xdaf: 0x0018, + 0xd80: 0x0319, 0xd81: 0x0101, 0xd82: 0x0321, 0xd83: 0x0329, 0xd84: 0x0051, 0xd85: 0x0339, + 0xd86: 0x0751, 0xd87: 0x00b9, 0xd88: 0x0089, 0xd89: 0x0341, 0xd8a: 0x0349, 0xd8b: 0x0391, + 0xd8c: 0x00c1, 0xd8d: 0x0109, 0xd8e: 0x00c9, 0xd8f: 0x04b1, 0xd90: 0x0019, 0xd91: 0x02e9, + 0xd92: 0x03d9, 0xd93: 0x02f1, 0xd94: 0x02f9, 0xd95: 0x03f1, 0xd96: 0x0309, 0xd97: 0x00a9, + 0xd98: 0x0311, 0xd99: 0x00b1, 0xd9a: 0x0319, 0xd9b: 0x0101, 0xd9c: 0x0321, 0xd9d: 0x0329, + 0xd9e: 0x0051, 0xd9f: 0x0339, 0xda0: 0x0751, 0xda1: 0x00b9, 0xda2: 0x0089, 0xda3: 0x0341, + 0xda4: 0x0349, 0xda5: 0x0391, 0xda6: 0x00c1, 0xda7: 0x0109, 0xda8: 0x00c9, 0xda9: 0x04b1, + 0xdaa: 0x06e1, 0xdab: 0x0018, 0xdac: 0x0018, 0xdad: 0x0018, 0xdae: 0x0018, 0xdaf: 0x0018, 0xdb0: 0x0018, 0xdb1: 0x0018, 0xdb2: 0x0018, 0xdb3: 0x0018, 0xdb4: 0x0018, 0xdb5: 0x0018, 0xdb6: 0x0018, 0xdb7: 0x0018, 0xdb8: 0x0018, 0xdb9: 0x0018, 0xdba: 0x0018, 0xdbb: 0x0018, 0xdbc: 0x0018, 0xdbd: 0x0018, 0xdbe: 0x0018, 0xdbf: 0x0018, @@ -1223,12 +1366,12 @@ var idnaValues = [8192]uint16{ 0xdcc: 0x0008, 0xdcd: 0x0008, 0xdce: 0x0008, 0xdcf: 0x0008, 0xdd0: 0x0008, 0xdd1: 0x0008, 0xdd2: 0x0008, 0xdd3: 0x0008, 0xdd4: 0x0008, 0xdd5: 0x0008, 0xdd6: 0x0008, 0xdd7: 0x0008, 0xdd8: 0x0008, 0xdd9: 0x0008, 0xdda: 0x0008, 0xddb: 0x0008, 0xddc: 0x0008, 0xddd: 0x0008, - 0xdde: 0x0008, 0xddf: 0x0040, 0xde0: 0xe00d, 0xde1: 0x0008, 0xde2: 0x2971, 0xde3: 0x0ed5, - 0xde4: 0x2989, 0xde5: 0x0008, 0xde6: 0x0008, 0xde7: 0xe07d, 0xde8: 0x0008, 0xde9: 0xe01d, - 0xdea: 0x0008, 0xdeb: 0xe03d, 0xdec: 0x0008, 0xded: 0x0fe1, 0xdee: 0x1281, 0xdef: 0x0fc9, - 0xdf0: 0x1141, 0xdf1: 0x0008, 0xdf2: 0xe00d, 0xdf3: 0x0008, 0xdf4: 0x0008, 0xdf5: 0xe01d, + 0xdde: 0x0008, 0xddf: 0x0040, 0xde0: 0xe00d, 0xde1: 0x0008, 0xde2: 0x0941, 0xde3: 0x0ed5, + 0xde4: 0x0949, 0xde5: 0x0008, 0xde6: 0x0008, 0xde7: 0xe07d, 0xde8: 0x0008, 0xde9: 0xe01d, + 0xdea: 0x0008, 0xdeb: 0xe03d, 0xdec: 0x0008, 0xded: 0x0359, 0xdee: 0x0441, 0xdef: 0x0351, + 0xdf0: 0x03d1, 0xdf1: 0x0008, 0xdf2: 0xe00d, 0xdf3: 0x0008, 0xdf4: 0x0008, 0xdf5: 0xe01d, 0xdf6: 0x0008, 0xdf7: 0x0008, 0xdf8: 0x0008, 0xdf9: 0x0008, 0xdfa: 0x0008, 0xdfb: 0x0008, - 0xdfc: 0x0259, 0xdfd: 0x1089, 0xdfe: 0x29a1, 0xdff: 0x29b9, + 0xdfc: 0x00b1, 0xdfd: 0x0391, 0xdfe: 0x0951, 0xdff: 0x0959, // Block 0x38, offset 0xe00 0xe00: 0xe00d, 0xe01: 0x0008, 0xe02: 0xe00d, 0xe03: 0x0008, 0xe04: 0xe00d, 0xe05: 0x0008, 0xe06: 0xe00d, 0xe07: 0x0008, 0xe08: 0xe00d, 0xe09: 0x0008, 0xe0a: 0xe00d, 0xe0b: 0x0008, @@ -1254,7 +1397,7 @@ var idnaValues = [8192]uint16{ 0xe76: 0x0040, 0xe77: 0x0040, 0xe78: 0x0040, 0xe79: 0x0040, 0xe7a: 0x0040, 0xe7b: 0x0040, 0xe7c: 0x0040, 0xe7d: 0x0040, 0xe7e: 0x0040, 0xe7f: 0x0040, // Block 0x3a, offset 0xe80 - 0xe80: 0x000a, 0xe81: 0x0018, 0xe82: 0x29d1, 0xe83: 0x0018, 0xe84: 0x0018, 0xe85: 0x0008, + 0xe80: 0x000a, 0xe81: 0x0018, 0xe82: 0x0961, 0xe83: 0x0018, 0xe84: 0x0018, 0xe85: 0x0008, 0xe86: 0x0008, 0xe87: 0x0008, 0xe88: 0x0018, 0xe89: 0x0018, 0xe8a: 0x0018, 0xe8b: 0x0018, 0xe8c: 0x0018, 0xe8d: 0x0018, 0xe8e: 0x0018, 0xe8f: 0x0018, 0xe90: 0x0018, 0xe91: 0x0018, 0xe92: 0x0018, 0xe93: 0x0018, 0xe94: 0x0018, 0xe95: 0x0018, 0xe96: 0x0018, 0xe97: 0x0018, @@ -1290,17 +1433,17 @@ var idnaValues = [8192]uint16{ 0xf36: 0x0008, 0xf37: 0x0008, 0xf38: 0x0008, 0xf39: 0x0008, 0xf3a: 0x0008, 0xf3b: 0x0008, 0xf3c: 0x0008, 0xf3d: 0x0008, 0xf3e: 0x0008, 0xf3f: 0x0008, // Block 0x3d, offset 0xf40 - 0xf40: 0x36a2, 0xf41: 0x36d2, 0xf42: 0x3702, 0xf43: 0x3732, 0xf44: 0x32d5, 0xf45: 0x32f5, + 0xf40: 0x0b82, 0xf41: 0x0b8a, 0xf42: 0x0b92, 0xf43: 0x0b9a, 0xf44: 0x32d5, 0xf45: 0x32f5, 0xf46: 0x3315, 0xf47: 0x3335, 0xf48: 0x0018, 0xf49: 0x0018, 0xf4a: 0x0018, 0xf4b: 0x0018, - 0xf4c: 0x0018, 0xf4d: 0x0018, 0xf4e: 0x0018, 0xf4f: 0x0018, 0xf50: 0x3355, 0xf51: 0x3761, - 0xf52: 0x3779, 0xf53: 0x3791, 0xf54: 0x37a9, 0xf55: 0x37c1, 0xf56: 0x37d9, 0xf57: 0x37f1, - 0xf58: 0x3809, 0xf59: 0x3821, 0xf5a: 0x3839, 0xf5b: 0x3851, 0xf5c: 0x3869, 0xf5d: 0x3881, - 0xf5e: 0x3899, 0xf5f: 0x38b1, 0xf60: 0x3375, 0xf61: 0x3395, 0xf62: 0x33b5, 0xf63: 0x33d5, + 0xf4c: 0x0018, 0xf4d: 0x0018, 0xf4e: 0x0018, 0xf4f: 0x0018, 0xf50: 0x3355, 0xf51: 0x0ba1, + 0xf52: 0x0ba9, 0xf53: 0x0bb1, 0xf54: 0x0bb9, 0xf55: 0x0bc1, 0xf56: 0x0bc9, 0xf57: 0x0bd1, + 0xf58: 0x0bd9, 0xf59: 0x0be1, 0xf5a: 0x0be9, 0xf5b: 0x0bf1, 0xf5c: 0x0bf9, 0xf5d: 0x0c01, + 0xf5e: 0x0c09, 0xf5f: 0x0c11, 0xf60: 0x3375, 0xf61: 0x3395, 0xf62: 0x33b5, 0xf63: 0x33d5, 0xf64: 0x33f5, 0xf65: 0x33f5, 0xf66: 0x3415, 0xf67: 0x3435, 0xf68: 0x3455, 0xf69: 0x3475, 0xf6a: 0x3495, 0xf6b: 0x34b5, 0xf6c: 0x34d5, 0xf6d: 0x34f5, 0xf6e: 0x3515, 0xf6f: 0x3535, 0xf70: 0x3555, 0xf71: 0x3575, 0xf72: 0x3595, 0xf73: 0x35b5, 0xf74: 0x35d5, 0xf75: 0x35f5, 0xf76: 0x3615, 0xf77: 0x3635, 0xf78: 0x3655, 0xf79: 0x3675, 0xf7a: 0x3695, 0xf7b: 0x36b5, - 0xf7c: 0x38c9, 0xf7d: 0x3901, 0xf7e: 0x36d5, 0xf7f: 0x0018, + 0xf7c: 0x0c19, 0xf7d: 0x0c21, 0xf7e: 0x36d5, 0xf7f: 0x0018, // Block 0x3e, offset 0xf80 0xf80: 0x36f5, 0xf81: 0x3715, 0xf82: 0x3735, 0xf83: 0x3755, 0xf84: 0x3775, 0xf85: 0x3795, 0xf86: 0x37b5, 0xf87: 0x37d5, 0xf88: 0x37f5, 0xf89: 0x3815, 0xf8a: 0x3835, 0xf8b: 0x3855, @@ -1310,13 +1453,13 @@ var idnaValues = [8192]uint16{ 0xf9e: 0x3ab5, 0xf9f: 0x3ad5, 0xfa0: 0x3af5, 0xfa1: 0x3b15, 0xfa2: 0x3b35, 0xfa3: 0x3b55, 0xfa4: 0x3b75, 0xfa5: 0x3b95, 0xfa6: 0x1295, 0xfa7: 0x3bb5, 0xfa8: 0x3bd5, 0xfa9: 0x3bf5, 0xfaa: 0x3c15, 0xfab: 0x3c35, 0xfac: 0x3c55, 0xfad: 0x3c75, 0xfae: 0x23b5, 0xfaf: 0x3c95, - 0xfb0: 0x3cb5, 0xfb1: 0x3939, 0xfb2: 0x3951, 0xfb3: 0x3969, 0xfb4: 0x3981, 0xfb5: 0x3999, - 0xfb6: 0x39b1, 0xfb7: 0x39c9, 0xfb8: 0x39e1, 0xfb9: 0x39f9, 0xfba: 0x3a11, 0xfbb: 0x3a29, - 0xfbc: 0x3a41, 0xfbd: 0x3a59, 0xfbe: 0x3a71, 0xfbf: 0x3a89, + 0xfb0: 0x3cb5, 0xfb1: 0x0c29, 0xfb2: 0x0c31, 0xfb3: 0x0c39, 0xfb4: 0x0c41, 0xfb5: 0x0c49, + 0xfb6: 0x0c51, 0xfb7: 0x0c59, 0xfb8: 0x0c61, 0xfb9: 0x0c69, 0xfba: 0x0c71, 0xfbb: 0x0c79, + 0xfbc: 0x0c81, 0xfbd: 0x0c89, 0xfbe: 0x0c91, 0xfbf: 0x0c99, // Block 0x3f, offset 0xfc0 - 0xfc0: 0x3aa1, 0xfc1: 0x3ac9, 0xfc2: 0x3af1, 0xfc3: 0x3b19, 0xfc4: 0x3b41, 0xfc5: 0x3b69, - 0xfc6: 0x3b91, 0xfc7: 0x3bb9, 0xfc8: 0x3be1, 0xfc9: 0x3c09, 0xfca: 0x3c39, 0xfcb: 0x3c69, - 0xfcc: 0x3c99, 0xfcd: 0x3cd5, 0xfce: 0x3cb1, 0xfcf: 0x3cf5, 0xfd0: 0x3d15, 0xfd1: 0x3d2d, + 0xfc0: 0x0ca1, 0xfc1: 0x0ca9, 0xfc2: 0x0cb1, 0xfc3: 0x0cb9, 0xfc4: 0x0cc1, 0xfc5: 0x0cc9, + 0xfc6: 0x0cd1, 0xfc7: 0x0cd9, 0xfc8: 0x0ce1, 0xfc9: 0x0ce9, 0xfca: 0x0cf1, 0xfcb: 0x0cf9, + 0xfcc: 0x0d01, 0xfcd: 0x3cd5, 0xfce: 0x0d09, 0xfcf: 0x3cf5, 0xfd0: 0x3d15, 0xfd1: 0x3d2d, 0xfd2: 0x3d45, 0xfd3: 0x3d5d, 0xfd4: 0x3d75, 0xfd5: 0x3d75, 0xfd6: 0x3d5d, 0xfd7: 0x3d8d, 0xfd8: 0x07d5, 0xfd9: 0x3da5, 0xfda: 0x3dbd, 0xfdb: 0x3dd5, 0xfdc: 0x3ded, 0xfdd: 0x3e05, 0xfde: 0x3e1d, 0xfdf: 0x3e35, 0xfe0: 0x3e4d, 0xfe1: 0x3e65, 0xfe2: 0x3e7d, 0xfe3: 0x3e95, @@ -1324,769 +1467,769 @@ var idnaValues = [8192]uint16{ 0xfea: 0x3ef5, 0xfeb: 0x3f0d, 0xfec: 0x3f25, 0xfed: 0x3f3d, 0xfee: 0x3f55, 0xfef: 0x3f55, 0xff0: 0x3f6d, 0xff1: 0x3f6d, 0xff2: 0x3f6d, 0xff3: 0x3f85, 0xff4: 0x3f9d, 0xff5: 0x3fb5, 0xff6: 0x3fcd, 0xff7: 0x3fb5, 0xff8: 0x3fe5, 0xff9: 0x3ffd, 0xffa: 0x3f85, 0xffb: 0x4015, - 0xffc: 0x402d, 0xffd: 0x402d, 0xffe: 0x402d, 0xfff: 0x3cc9, + 0xffc: 0x402d, 0xffd: 0x402d, 0xffe: 0x402d, 0xfff: 0x0d11, // Block 0x40, offset 0x1000 - 0x1000: 0x3d01, 0x1001: 0x3d69, 0x1002: 0x3dd1, 0x1003: 0x3e39, 0x1004: 0x3e89, 0x1005: 0x3ef1, - 0x1006: 0x3f41, 0x1007: 0x3f91, 0x1008: 0x4011, 0x1009: 0x4079, 0x100a: 0x40c9, 0x100b: 0x4119, - 0x100c: 0x4169, 0x100d: 0x41d1, 0x100e: 0x4239, 0x100f: 0x4289, 0x1010: 0x42d9, 0x1011: 0x4311, - 0x1012: 0x4361, 0x1013: 0x43c9, 0x1014: 0x4431, 0x1015: 0x4469, 0x1016: 0x44e9, 0x1017: 0x4581, - 0x1018: 0x4601, 0x1019: 0x4651, 0x101a: 0x46d1, 0x101b: 0x4751, 0x101c: 0x47b9, 0x101d: 0x4809, - 0x101e: 0x4859, 0x101f: 0x48a9, 0x1020: 0x4911, 0x1021: 0x4991, 0x1022: 0x49f9, 0x1023: 0x4a49, - 0x1024: 0x4a99, 0x1025: 0x4ae9, 0x1026: 0x4b21, 0x1027: 0x4b59, 0x1028: 0x4b91, 0x1029: 0x4bc9, - 0x102a: 0x4c19, 0x102b: 0x4c69, 0x102c: 0x4ce9, 0x102d: 0x4d39, 0x102e: 0x4da1, 0x102f: 0x4e21, - 0x1030: 0x4e71, 0x1031: 0x4ea9, 0x1032: 0x4ee1, 0x1033: 0x4f61, 0x1034: 0x4fc9, 0x1035: 0x5049, - 0x1036: 0x5099, 0x1037: 0x5119, 0x1038: 0x5151, 0x1039: 0x51a1, 0x103a: 0x51f1, 0x103b: 0x5241, - 0x103c: 0x5291, 0x103d: 0x52e1, 0x103e: 0x5349, 0x103f: 0x5399, + 0x1000: 0x10f9, 0x1001: 0x1101, 0x1002: 0x40a5, 0x1003: 0x1109, 0x1004: 0x1111, 0x1005: 0x1119, + 0x1006: 0x1121, 0x1007: 0x1129, 0x1008: 0x40c5, 0x1009: 0x1131, 0x100a: 0x1139, 0x100b: 0x1141, + 0x100c: 0x40e5, 0x100d: 0x40e5, 0x100e: 0x1149, 0x100f: 0x1151, 0x1010: 0x1159, 0x1011: 0x4105, + 0x1012: 0x4125, 0x1013: 0x4145, 0x1014: 0x4165, 0x1015: 0x4185, 0x1016: 0x1161, 0x1017: 0x1169, + 0x1018: 0x1171, 0x1019: 0x1179, 0x101a: 0x1181, 0x101b: 0x41a5, 0x101c: 0x1189, 0x101d: 0x1191, + 0x101e: 0x1199, 0x101f: 0x41c5, 0x1020: 0x41e5, 0x1021: 0x11a1, 0x1022: 0x4205, 0x1023: 0x4225, + 0x1024: 0x4245, 0x1025: 0x11a9, 0x1026: 0x4265, 0x1027: 0x11b1, 0x1028: 0x11b9, 0x1029: 0x10f9, + 0x102a: 0x4285, 0x102b: 0x42a5, 0x102c: 0x42c5, 0x102d: 0x42e5, 0x102e: 0x11c1, 0x102f: 0x11c9, + 0x1030: 0x11d1, 0x1031: 0x11d9, 0x1032: 0x4305, 0x1033: 0x11e1, 0x1034: 0x11e9, 0x1035: 0x11f1, + 0x1036: 0x4325, 0x1037: 0x11f9, 0x1038: 0x1201, 0x1039: 0x11f9, 0x103a: 0x1209, 0x103b: 0x1211, + 0x103c: 0x4345, 0x103d: 0x1219, 0x103e: 0x1221, 0x103f: 0x1219, // Block 0x41, offset 0x1040 - 0x1040: 0x53d1, 0x1041: 0x5421, 0x1042: 0x5471, 0x1043: 0x54c1, 0x1044: 0x5529, 0x1045: 0x5579, - 0x1046: 0x55c9, 0x1047: 0x5619, 0x1048: 0x5699, 0x1049: 0x5701, 0x104a: 0x5739, 0x104b: 0x57b9, - 0x104c: 0x57f1, 0x104d: 0x5859, 0x104e: 0x58c1, 0x104f: 0x5911, 0x1050: 0x5961, 0x1051: 0x59b1, - 0x1052: 0x5a19, 0x1053: 0x5a51, 0x1054: 0x5aa1, 0x1055: 0x5b09, 0x1056: 0x5b41, 0x1057: 0x5bc1, - 0x1058: 0x5c11, 0x1059: 0x5c39, 0x105a: 0x5c61, 0x105b: 0x5c89, 0x105c: 0x5cb1, 0x105d: 0x5cd9, - 0x105e: 0x5d01, 0x105f: 0x5d29, 0x1060: 0x5d51, 0x1061: 0x5d79, 0x1062: 0x5da1, 0x1063: 0x5dd1, - 0x1064: 0x5e01, 0x1065: 0x5e31, 0x1066: 0x5e61, 0x1067: 0x5e91, 0x1068: 0x5ec1, 0x1069: 0x5ef1, - 0x106a: 0x5f21, 0x106b: 0x5f51, 0x106c: 0x5f81, 0x106d: 0x5fb1, 0x106e: 0x5fe1, 0x106f: 0x6011, - 0x1070: 0x6041, 0x1071: 0x4045, 0x1072: 0x6071, 0x1073: 0x6089, 0x1074: 0x4065, 0x1075: 0x60a1, - 0x1076: 0x60b9, 0x1077: 0x60d1, 0x1078: 0x4085, 0x1079: 0x4085, 0x107a: 0x60e9, 0x107b: 0x6101, - 0x107c: 0x6139, 0x107d: 0x6171, 0x107e: 0x61a9, 0x107f: 0x61e1, + 0x1040: 0x4365, 0x1041: 0x4385, 0x1042: 0x0040, 0x1043: 0x1229, 0x1044: 0x1231, 0x1045: 0x1239, + 0x1046: 0x1241, 0x1047: 0x0040, 0x1048: 0x1249, 0x1049: 0x1251, 0x104a: 0x1259, 0x104b: 0x1261, + 0x104c: 0x1269, 0x104d: 0x1271, 0x104e: 0x1199, 0x104f: 0x1279, 0x1050: 0x1281, 0x1051: 0x1289, + 0x1052: 0x43a5, 0x1053: 0x1291, 0x1054: 0x1121, 0x1055: 0x43c5, 0x1056: 0x43e5, 0x1057: 0x1299, + 0x1058: 0x0040, 0x1059: 0x4405, 0x105a: 0x12a1, 0x105b: 0x12a9, 0x105c: 0x12b1, 0x105d: 0x12b9, + 0x105e: 0x12c1, 0x105f: 0x12c9, 0x1060: 0x12d1, 0x1061: 0x12d9, 0x1062: 0x12e1, 0x1063: 0x12e9, + 0x1064: 0x12f1, 0x1065: 0x12f9, 0x1066: 0x1301, 0x1067: 0x1309, 0x1068: 0x1311, 0x1069: 0x1319, + 0x106a: 0x1321, 0x106b: 0x1329, 0x106c: 0x1331, 0x106d: 0x1339, 0x106e: 0x1341, 0x106f: 0x1349, + 0x1070: 0x1351, 0x1071: 0x1359, 0x1072: 0x1361, 0x1073: 0x1369, 0x1074: 0x1371, 0x1075: 0x1379, + 0x1076: 0x1381, 0x1077: 0x1389, 0x1078: 0x1391, 0x1079: 0x1399, 0x107a: 0x13a1, 0x107b: 0x13a9, + 0x107c: 0x13b1, 0x107d: 0x13b9, 0x107e: 0x13c1, 0x107f: 0x4425, // Block 0x42, offset 0x1080 - 0x1080: 0x6249, 0x1081: 0x6261, 0x1082: 0x40a5, 0x1083: 0x6279, 0x1084: 0x6291, 0x1085: 0x62a9, - 0x1086: 0x62c1, 0x1087: 0x62d9, 0x1088: 0x40c5, 0x1089: 0x62f1, 0x108a: 0x6319, 0x108b: 0x6331, - 0x108c: 0x40e5, 0x108d: 0x40e5, 0x108e: 0x6349, 0x108f: 0x6361, 0x1090: 0x6379, 0x1091: 0x4105, - 0x1092: 0x4125, 0x1093: 0x4145, 0x1094: 0x4165, 0x1095: 0x4185, 0x1096: 0x6391, 0x1097: 0x63a9, - 0x1098: 0x63c1, 0x1099: 0x63d9, 0x109a: 0x63f1, 0x109b: 0x41a5, 0x109c: 0x6409, 0x109d: 0x6421, - 0x109e: 0x6439, 0x109f: 0x41c5, 0x10a0: 0x41e5, 0x10a1: 0x6451, 0x10a2: 0x4205, 0x10a3: 0x4225, - 0x10a4: 0x4245, 0x10a5: 0x6469, 0x10a6: 0x4265, 0x10a7: 0x6481, 0x10a8: 0x64b1, 0x10a9: 0x6249, - 0x10aa: 0x4285, 0x10ab: 0x42a5, 0x10ac: 0x42c5, 0x10ad: 0x42e5, 0x10ae: 0x64e9, 0x10af: 0x6529, - 0x10b0: 0x6571, 0x10b1: 0x6589, 0x10b2: 0x4305, 0x10b3: 0x65a1, 0x10b4: 0x65b9, 0x10b5: 0x65d1, - 0x10b6: 0x4325, 0x10b7: 0x65e9, 0x10b8: 0x6601, 0x10b9: 0x65e9, 0x10ba: 0x6619, 0x10bb: 0x6631, - 0x10bc: 0x4345, 0x10bd: 0x6649, 0x10be: 0x6661, 0x10bf: 0x6649, + 0x1080: 0xe00d, 0x1081: 0x0008, 0x1082: 0xe00d, 0x1083: 0x0008, 0x1084: 0xe00d, 0x1085: 0x0008, + 0x1086: 0xe00d, 0x1087: 0x0008, 0x1088: 0xe00d, 0x1089: 0x0008, 0x108a: 0xe00d, 0x108b: 0x0008, + 0x108c: 0xe00d, 0x108d: 0x0008, 0x108e: 0xe00d, 0x108f: 0x0008, 0x1090: 0xe00d, 0x1091: 0x0008, + 0x1092: 0xe00d, 0x1093: 0x0008, 0x1094: 0xe00d, 0x1095: 0x0008, 0x1096: 0xe00d, 0x1097: 0x0008, + 0x1098: 0xe00d, 0x1099: 0x0008, 0x109a: 0xe00d, 0x109b: 0x0008, 0x109c: 0xe00d, 0x109d: 0x0008, + 0x109e: 0xe00d, 0x109f: 0x0008, 0x10a0: 0xe00d, 0x10a1: 0x0008, 0x10a2: 0xe00d, 0x10a3: 0x0008, + 0x10a4: 0xe00d, 0x10a5: 0x0008, 0x10a6: 0xe00d, 0x10a7: 0x0008, 0x10a8: 0xe00d, 0x10a9: 0x0008, + 0x10aa: 0xe00d, 0x10ab: 0x0008, 0x10ac: 0xe00d, 0x10ad: 0x0008, 0x10ae: 0x0008, 0x10af: 0x3308, + 0x10b0: 0x3318, 0x10b1: 0x3318, 0x10b2: 0x3318, 0x10b3: 0x0018, 0x10b4: 0x3308, 0x10b5: 0x3308, + 0x10b6: 0x3308, 0x10b7: 0x3308, 0x10b8: 0x3308, 0x10b9: 0x3308, 0x10ba: 0x3308, 0x10bb: 0x3308, + 0x10bc: 0x3308, 0x10bd: 0x3308, 0x10be: 0x0018, 0x10bf: 0x0008, // Block 0x43, offset 0x10c0 - 0x10c0: 0x4365, 0x10c1: 0x4385, 0x10c2: 0x0040, 0x10c3: 0x6679, 0x10c4: 0x6691, 0x10c5: 0x66a9, - 0x10c6: 0x66c1, 0x10c7: 0x0040, 0x10c8: 0x66f9, 0x10c9: 0x6711, 0x10ca: 0x6729, 0x10cb: 0x6741, - 0x10cc: 0x6759, 0x10cd: 0x6771, 0x10ce: 0x6439, 0x10cf: 0x6789, 0x10d0: 0x67a1, 0x10d1: 0x67b9, - 0x10d2: 0x43a5, 0x10d3: 0x67d1, 0x10d4: 0x62c1, 0x10d5: 0x43c5, 0x10d6: 0x43e5, 0x10d7: 0x67e9, - 0x10d8: 0x0040, 0x10d9: 0x4405, 0x10da: 0x6801, 0x10db: 0x6819, 0x10dc: 0x6831, 0x10dd: 0x6849, - 0x10de: 0x6861, 0x10df: 0x6891, 0x10e0: 0x68c1, 0x10e1: 0x68e9, 0x10e2: 0x6911, 0x10e3: 0x6939, - 0x10e4: 0x6961, 0x10e5: 0x6989, 0x10e6: 0x69b1, 0x10e7: 0x69d9, 0x10e8: 0x6a01, 0x10e9: 0x6a29, - 0x10ea: 0x6a59, 0x10eb: 0x6a89, 0x10ec: 0x6ab9, 0x10ed: 0x6ae9, 0x10ee: 0x6b19, 0x10ef: 0x6b49, - 0x10f0: 0x6b79, 0x10f1: 0x6ba9, 0x10f2: 0x6bd9, 0x10f3: 0x6c09, 0x10f4: 0x6c39, 0x10f5: 0x6c69, - 0x10f6: 0x6c99, 0x10f7: 0x6cc9, 0x10f8: 0x6cf9, 0x10f9: 0x6d29, 0x10fa: 0x6d59, 0x10fb: 0x6d89, - 0x10fc: 0x6db9, 0x10fd: 0x6de9, 0x10fe: 0x6e19, 0x10ff: 0x4425, + 0x10c0: 0xe00d, 0x10c1: 0x0008, 0x10c2: 0xe00d, 0x10c3: 0x0008, 0x10c4: 0xe00d, 0x10c5: 0x0008, + 0x10c6: 0xe00d, 0x10c7: 0x0008, 0x10c8: 0xe00d, 0x10c9: 0x0008, 0x10ca: 0xe00d, 0x10cb: 0x0008, + 0x10cc: 0xe00d, 0x10cd: 0x0008, 0x10ce: 0xe00d, 0x10cf: 0x0008, 0x10d0: 0xe00d, 0x10d1: 0x0008, + 0x10d2: 0xe00d, 0x10d3: 0x0008, 0x10d4: 0xe00d, 0x10d5: 0x0008, 0x10d6: 0xe00d, 0x10d7: 0x0008, + 0x10d8: 0xe00d, 0x10d9: 0x0008, 0x10da: 0xe00d, 0x10db: 0x0008, 0x10dc: 0x02d1, 0x10dd: 0x13c9, + 0x10de: 0x3308, 0x10df: 0x3308, 0x10e0: 0x0008, 0x10e1: 0x0008, 0x10e2: 0x0008, 0x10e3: 0x0008, + 0x10e4: 0x0008, 0x10e5: 0x0008, 0x10e6: 0x0008, 0x10e7: 0x0008, 0x10e8: 0x0008, 0x10e9: 0x0008, + 0x10ea: 0x0008, 0x10eb: 0x0008, 0x10ec: 0x0008, 0x10ed: 0x0008, 0x10ee: 0x0008, 0x10ef: 0x0008, + 0x10f0: 0x0008, 0x10f1: 0x0008, 0x10f2: 0x0008, 0x10f3: 0x0008, 0x10f4: 0x0008, 0x10f5: 0x0008, + 0x10f6: 0x0008, 0x10f7: 0x0008, 0x10f8: 0x0008, 0x10f9: 0x0008, 0x10fa: 0x0008, 0x10fb: 0x0008, + 0x10fc: 0x0008, 0x10fd: 0x0008, 0x10fe: 0x0008, 0x10ff: 0x0008, // Block 0x44, offset 0x1100 - 0x1100: 0xe00d, 0x1101: 0x0008, 0x1102: 0xe00d, 0x1103: 0x0008, 0x1104: 0xe00d, 0x1105: 0x0008, - 0x1106: 0xe00d, 0x1107: 0x0008, 0x1108: 0xe00d, 0x1109: 0x0008, 0x110a: 0xe00d, 0x110b: 0x0008, - 0x110c: 0xe00d, 0x110d: 0x0008, 0x110e: 0xe00d, 0x110f: 0x0008, 0x1110: 0xe00d, 0x1111: 0x0008, - 0x1112: 0xe00d, 0x1113: 0x0008, 0x1114: 0xe00d, 0x1115: 0x0008, 0x1116: 0xe00d, 0x1117: 0x0008, - 0x1118: 0xe00d, 0x1119: 0x0008, 0x111a: 0xe00d, 0x111b: 0x0008, 0x111c: 0xe00d, 0x111d: 0x0008, - 0x111e: 0xe00d, 0x111f: 0x0008, 0x1120: 0xe00d, 0x1121: 0x0008, 0x1122: 0xe00d, 0x1123: 0x0008, + 0x1100: 0x0018, 0x1101: 0x0018, 0x1102: 0x0018, 0x1103: 0x0018, 0x1104: 0x0018, 0x1105: 0x0018, + 0x1106: 0x0018, 0x1107: 0x0018, 0x1108: 0x0018, 0x1109: 0x0018, 0x110a: 0x0018, 0x110b: 0x0018, + 0x110c: 0x0018, 0x110d: 0x0018, 0x110e: 0x0018, 0x110f: 0x0018, 0x1110: 0x0018, 0x1111: 0x0018, + 0x1112: 0x0018, 0x1113: 0x0018, 0x1114: 0x0018, 0x1115: 0x0018, 0x1116: 0x0018, 0x1117: 0x0008, + 0x1118: 0x0008, 0x1119: 0x0008, 0x111a: 0x0008, 0x111b: 0x0008, 0x111c: 0x0008, 0x111d: 0x0008, + 0x111e: 0x0008, 0x111f: 0x0008, 0x1120: 0x0018, 0x1121: 0x0018, 0x1122: 0xe00d, 0x1123: 0x0008, 0x1124: 0xe00d, 0x1125: 0x0008, 0x1126: 0xe00d, 0x1127: 0x0008, 0x1128: 0xe00d, 0x1129: 0x0008, - 0x112a: 0xe00d, 0x112b: 0x0008, 0x112c: 0xe00d, 0x112d: 0x0008, 0x112e: 0x0008, 0x112f: 0x3308, - 0x1130: 0x3318, 0x1131: 0x3318, 0x1132: 0x3318, 0x1133: 0x0018, 0x1134: 0x3308, 0x1135: 0x3308, - 0x1136: 0x3308, 0x1137: 0x3308, 0x1138: 0x3308, 0x1139: 0x3308, 0x113a: 0x3308, 0x113b: 0x3308, - 0x113c: 0x3308, 0x113d: 0x3308, 0x113e: 0x0018, 0x113f: 0x0008, + 0x112a: 0xe00d, 0x112b: 0x0008, 0x112c: 0xe00d, 0x112d: 0x0008, 0x112e: 0xe00d, 0x112f: 0x0008, + 0x1130: 0x0008, 0x1131: 0x0008, 0x1132: 0xe00d, 0x1133: 0x0008, 0x1134: 0xe00d, 0x1135: 0x0008, + 0x1136: 0xe00d, 0x1137: 0x0008, 0x1138: 0xe00d, 0x1139: 0x0008, 0x113a: 0xe00d, 0x113b: 0x0008, + 0x113c: 0xe00d, 0x113d: 0x0008, 0x113e: 0xe00d, 0x113f: 0x0008, // Block 0x45, offset 0x1140 0x1140: 0xe00d, 0x1141: 0x0008, 0x1142: 0xe00d, 0x1143: 0x0008, 0x1144: 0xe00d, 0x1145: 0x0008, 0x1146: 0xe00d, 0x1147: 0x0008, 0x1148: 0xe00d, 0x1149: 0x0008, 0x114a: 0xe00d, 0x114b: 0x0008, 0x114c: 0xe00d, 0x114d: 0x0008, 0x114e: 0xe00d, 0x114f: 0x0008, 0x1150: 0xe00d, 0x1151: 0x0008, 0x1152: 0xe00d, 0x1153: 0x0008, 0x1154: 0xe00d, 0x1155: 0x0008, 0x1156: 0xe00d, 0x1157: 0x0008, - 0x1158: 0xe00d, 0x1159: 0x0008, 0x115a: 0xe00d, 0x115b: 0x0008, 0x115c: 0x0ea1, 0x115d: 0x6e49, - 0x115e: 0x3308, 0x115f: 0x3308, 0x1160: 0x0008, 0x1161: 0x0008, 0x1162: 0x0008, 0x1163: 0x0008, - 0x1164: 0x0008, 0x1165: 0x0008, 0x1166: 0x0008, 0x1167: 0x0008, 0x1168: 0x0008, 0x1169: 0x0008, - 0x116a: 0x0008, 0x116b: 0x0008, 0x116c: 0x0008, 0x116d: 0x0008, 0x116e: 0x0008, 0x116f: 0x0008, - 0x1170: 0x0008, 0x1171: 0x0008, 0x1172: 0x0008, 0x1173: 0x0008, 0x1174: 0x0008, 0x1175: 0x0008, - 0x1176: 0x0008, 0x1177: 0x0008, 0x1178: 0x0008, 0x1179: 0x0008, 0x117a: 0x0008, 0x117b: 0x0008, - 0x117c: 0x0008, 0x117d: 0x0008, 0x117e: 0x0008, 0x117f: 0x0008, + 0x1158: 0xe00d, 0x1159: 0x0008, 0x115a: 0xe00d, 0x115b: 0x0008, 0x115c: 0xe00d, 0x115d: 0x0008, + 0x115e: 0xe00d, 0x115f: 0x0008, 0x1160: 0xe00d, 0x1161: 0x0008, 0x1162: 0xe00d, 0x1163: 0x0008, + 0x1164: 0xe00d, 0x1165: 0x0008, 0x1166: 0xe00d, 0x1167: 0x0008, 0x1168: 0xe00d, 0x1169: 0x0008, + 0x116a: 0xe00d, 0x116b: 0x0008, 0x116c: 0xe00d, 0x116d: 0x0008, 0x116e: 0xe00d, 0x116f: 0x0008, + 0x1170: 0xe0fd, 0x1171: 0x0008, 0x1172: 0x0008, 0x1173: 0x0008, 0x1174: 0x0008, 0x1175: 0x0008, + 0x1176: 0x0008, 0x1177: 0x0008, 0x1178: 0x0008, 0x1179: 0xe01d, 0x117a: 0x0008, 0x117b: 0xe03d, + 0x117c: 0x0008, 0x117d: 0x4445, 0x117e: 0xe00d, 0x117f: 0x0008, // Block 0x46, offset 0x1180 - 0x1180: 0x0018, 0x1181: 0x0018, 0x1182: 0x0018, 0x1183: 0x0018, 0x1184: 0x0018, 0x1185: 0x0018, - 0x1186: 0x0018, 0x1187: 0x0018, 0x1188: 0x0018, 0x1189: 0x0018, 0x118a: 0x0018, 0x118b: 0x0018, - 0x118c: 0x0018, 0x118d: 0x0018, 0x118e: 0x0018, 0x118f: 0x0018, 0x1190: 0x0018, 0x1191: 0x0018, - 0x1192: 0x0018, 0x1193: 0x0018, 0x1194: 0x0018, 0x1195: 0x0018, 0x1196: 0x0018, 0x1197: 0x0008, - 0x1198: 0x0008, 0x1199: 0x0008, 0x119a: 0x0008, 0x119b: 0x0008, 0x119c: 0x0008, 0x119d: 0x0008, - 0x119e: 0x0008, 0x119f: 0x0008, 0x11a0: 0x0018, 0x11a1: 0x0018, 0x11a2: 0xe00d, 0x11a3: 0x0008, + 0x1180: 0xe00d, 0x1181: 0x0008, 0x1182: 0xe00d, 0x1183: 0x0008, 0x1184: 0xe00d, 0x1185: 0x0008, + 0x1186: 0xe00d, 0x1187: 0x0008, 0x1188: 0x0008, 0x1189: 0x0018, 0x118a: 0x0018, 0x118b: 0xe03d, + 0x118c: 0x0008, 0x118d: 0x0409, 0x118e: 0x0008, 0x118f: 0x0008, 0x1190: 0xe00d, 0x1191: 0x0008, + 0x1192: 0xe00d, 0x1193: 0x0008, 0x1194: 0x0008, 0x1195: 0x0008, 0x1196: 0xe00d, 0x1197: 0x0008, + 0x1198: 0xe00d, 0x1199: 0x0008, 0x119a: 0xe00d, 0x119b: 0x0008, 0x119c: 0xe00d, 0x119d: 0x0008, + 0x119e: 0xe00d, 0x119f: 0x0008, 0x11a0: 0xe00d, 0x11a1: 0x0008, 0x11a2: 0xe00d, 0x11a3: 0x0008, 0x11a4: 0xe00d, 0x11a5: 0x0008, 0x11a6: 0xe00d, 0x11a7: 0x0008, 0x11a8: 0xe00d, 0x11a9: 0x0008, - 0x11aa: 0xe00d, 0x11ab: 0x0008, 0x11ac: 0xe00d, 0x11ad: 0x0008, 0x11ae: 0xe00d, 0x11af: 0x0008, - 0x11b0: 0x0008, 0x11b1: 0x0008, 0x11b2: 0xe00d, 0x11b3: 0x0008, 0x11b4: 0xe00d, 0x11b5: 0x0008, + 0x11aa: 0x13d1, 0x11ab: 0x0371, 0x11ac: 0x0401, 0x11ad: 0x13d9, 0x11ae: 0x0421, 0x11af: 0x0008, + 0x11b0: 0x13e1, 0x11b1: 0x13e9, 0x11b2: 0x0429, 0x11b3: 0x4465, 0x11b4: 0xe00d, 0x11b5: 0x0008, 0x11b6: 0xe00d, 0x11b7: 0x0008, 0x11b8: 0xe00d, 0x11b9: 0x0008, 0x11ba: 0xe00d, 0x11bb: 0x0008, 0x11bc: 0xe00d, 0x11bd: 0x0008, 0x11be: 0xe00d, 0x11bf: 0x0008, // Block 0x47, offset 0x11c0 - 0x11c0: 0xe00d, 0x11c1: 0x0008, 0x11c2: 0xe00d, 0x11c3: 0x0008, 0x11c4: 0xe00d, 0x11c5: 0x0008, - 0x11c6: 0xe00d, 0x11c7: 0x0008, 0x11c8: 0xe00d, 0x11c9: 0x0008, 0x11ca: 0xe00d, 0x11cb: 0x0008, - 0x11cc: 0xe00d, 0x11cd: 0x0008, 0x11ce: 0xe00d, 0x11cf: 0x0008, 0x11d0: 0xe00d, 0x11d1: 0x0008, - 0x11d2: 0xe00d, 0x11d3: 0x0008, 0x11d4: 0xe00d, 0x11d5: 0x0008, 0x11d6: 0xe00d, 0x11d7: 0x0008, - 0x11d8: 0xe00d, 0x11d9: 0x0008, 0x11da: 0xe00d, 0x11db: 0x0008, 0x11dc: 0xe00d, 0x11dd: 0x0008, - 0x11de: 0xe00d, 0x11df: 0x0008, 0x11e0: 0xe00d, 0x11e1: 0x0008, 0x11e2: 0xe00d, 0x11e3: 0x0008, - 0x11e4: 0xe00d, 0x11e5: 0x0008, 0x11e6: 0xe00d, 0x11e7: 0x0008, 0x11e8: 0xe00d, 0x11e9: 0x0008, - 0x11ea: 0xe00d, 0x11eb: 0x0008, 0x11ec: 0xe00d, 0x11ed: 0x0008, 0x11ee: 0xe00d, 0x11ef: 0x0008, - 0x11f0: 0xe0fd, 0x11f1: 0x0008, 0x11f2: 0x0008, 0x11f3: 0x0008, 0x11f4: 0x0008, 0x11f5: 0x0008, - 0x11f6: 0x0008, 0x11f7: 0x0008, 0x11f8: 0x0008, 0x11f9: 0xe01d, 0x11fa: 0x0008, 0x11fb: 0xe03d, - 0x11fc: 0x0008, 0x11fd: 0x4445, 0x11fe: 0xe00d, 0x11ff: 0x0008, + 0x11c0: 0x650d, 0x11c1: 0x652d, 0x11c2: 0x654d, 0x11c3: 0x656d, 0x11c4: 0x658d, 0x11c5: 0x65ad, + 0x11c6: 0x65cd, 0x11c7: 0x65ed, 0x11c8: 0x660d, 0x11c9: 0x662d, 0x11ca: 0x664d, 0x11cb: 0x666d, + 0x11cc: 0x668d, 0x11cd: 0x66ad, 0x11ce: 0x0008, 0x11cf: 0x0008, 0x11d0: 0x66cd, 0x11d1: 0x0008, + 0x11d2: 0x66ed, 0x11d3: 0x0008, 0x11d4: 0x0008, 0x11d5: 0x670d, 0x11d6: 0x672d, 0x11d7: 0x674d, + 0x11d8: 0x676d, 0x11d9: 0x678d, 0x11da: 0x67ad, 0x11db: 0x67cd, 0x11dc: 0x67ed, 0x11dd: 0x680d, + 0x11de: 0x682d, 0x11df: 0x0008, 0x11e0: 0x684d, 0x11e1: 0x0008, 0x11e2: 0x686d, 0x11e3: 0x0008, + 0x11e4: 0x0008, 0x11e5: 0x688d, 0x11e6: 0x68ad, 0x11e7: 0x0008, 0x11e8: 0x0008, 0x11e9: 0x0008, + 0x11ea: 0x68cd, 0x11eb: 0x68ed, 0x11ec: 0x690d, 0x11ed: 0x692d, 0x11ee: 0x694d, 0x11ef: 0x696d, + 0x11f0: 0x698d, 0x11f1: 0x69ad, 0x11f2: 0x69cd, 0x11f3: 0x69ed, 0x11f4: 0x6a0d, 0x11f5: 0x6a2d, + 0x11f6: 0x6a4d, 0x11f7: 0x6a6d, 0x11f8: 0x6a8d, 0x11f9: 0x6aad, 0x11fa: 0x6acd, 0x11fb: 0x6aed, + 0x11fc: 0x6b0d, 0x11fd: 0x6b2d, 0x11fe: 0x6b4d, 0x11ff: 0x6b6d, // Block 0x48, offset 0x1200 - 0x1200: 0xe00d, 0x1201: 0x0008, 0x1202: 0xe00d, 0x1203: 0x0008, 0x1204: 0xe00d, 0x1205: 0x0008, - 0x1206: 0xe00d, 0x1207: 0x0008, 0x1208: 0x0008, 0x1209: 0x0018, 0x120a: 0x0018, 0x120b: 0xe03d, - 0x120c: 0x0008, 0x120d: 0x11d9, 0x120e: 0x0008, 0x120f: 0x0008, 0x1210: 0xe00d, 0x1211: 0x0008, - 0x1212: 0xe00d, 0x1213: 0x0008, 0x1214: 0x0008, 0x1215: 0x0008, 0x1216: 0xe00d, 0x1217: 0x0008, - 0x1218: 0xe00d, 0x1219: 0x0008, 0x121a: 0xe00d, 0x121b: 0x0008, 0x121c: 0xe00d, 0x121d: 0x0008, - 0x121e: 0xe00d, 0x121f: 0x0008, 0x1220: 0xe00d, 0x1221: 0x0008, 0x1222: 0xe00d, 0x1223: 0x0008, - 0x1224: 0xe00d, 0x1225: 0x0008, 0x1226: 0xe00d, 0x1227: 0x0008, 0x1228: 0xe00d, 0x1229: 0x0008, - 0x122a: 0x6e61, 0x122b: 0x1029, 0x122c: 0x11c1, 0x122d: 0x6e79, 0x122e: 0x1221, 0x122f: 0x0008, - 0x1230: 0x6e91, 0x1231: 0x6ea9, 0x1232: 0x1239, 0x1233: 0x4465, 0x1234: 0xe00d, 0x1235: 0x0008, - 0x1236: 0xe00d, 0x1237: 0x0008, 0x1238: 0xe00d, 0x1239: 0x0008, 0x123a: 0xe00d, 0x123b: 0x0008, - 0x123c: 0xe00d, 0x123d: 0x0008, 0x123e: 0xe00d, 0x123f: 0x0008, + 0x1200: 0x7acd, 0x1201: 0x7aed, 0x1202: 0x7b0d, 0x1203: 0x7b2d, 0x1204: 0x7b4d, 0x1205: 0x7b6d, + 0x1206: 0x7b8d, 0x1207: 0x7bad, 0x1208: 0x7bcd, 0x1209: 0x7bed, 0x120a: 0x7c0d, 0x120b: 0x7c2d, + 0x120c: 0x7c4d, 0x120d: 0x7c6d, 0x120e: 0x7c8d, 0x120f: 0x1409, 0x1210: 0x1411, 0x1211: 0x1419, + 0x1212: 0x7cad, 0x1213: 0x7ccd, 0x1214: 0x7ced, 0x1215: 0x1421, 0x1216: 0x1429, 0x1217: 0x1431, + 0x1218: 0x7d0d, 0x1219: 0x7d2d, 0x121a: 0x0040, 0x121b: 0x0040, 0x121c: 0x0040, 0x121d: 0x0040, + 0x121e: 0x0040, 0x121f: 0x0040, 0x1220: 0x0040, 0x1221: 0x0040, 0x1222: 0x0040, 0x1223: 0x0040, + 0x1224: 0x0040, 0x1225: 0x0040, 0x1226: 0x0040, 0x1227: 0x0040, 0x1228: 0x0040, 0x1229: 0x0040, + 0x122a: 0x0040, 0x122b: 0x0040, 0x122c: 0x0040, 0x122d: 0x0040, 0x122e: 0x0040, 0x122f: 0x0040, + 0x1230: 0x0040, 0x1231: 0x0040, 0x1232: 0x0040, 0x1233: 0x0040, 0x1234: 0x0040, 0x1235: 0x0040, + 0x1236: 0x0040, 0x1237: 0x0040, 0x1238: 0x0040, 0x1239: 0x0040, 0x123a: 0x0040, 0x123b: 0x0040, + 0x123c: 0x0040, 0x123d: 0x0040, 0x123e: 0x0040, 0x123f: 0x0040, // Block 0x49, offset 0x1240 - 0x1240: 0x650d, 0x1241: 0x652d, 0x1242: 0x654d, 0x1243: 0x656d, 0x1244: 0x658d, 0x1245: 0x65ad, - 0x1246: 0x65cd, 0x1247: 0x65ed, 0x1248: 0x660d, 0x1249: 0x662d, 0x124a: 0x664d, 0x124b: 0x666d, - 0x124c: 0x668d, 0x124d: 0x66ad, 0x124e: 0x0008, 0x124f: 0x0008, 0x1250: 0x66cd, 0x1251: 0x0008, - 0x1252: 0x66ed, 0x1253: 0x0008, 0x1254: 0x0008, 0x1255: 0x670d, 0x1256: 0x672d, 0x1257: 0x674d, - 0x1258: 0x676d, 0x1259: 0x678d, 0x125a: 0x67ad, 0x125b: 0x67cd, 0x125c: 0x67ed, 0x125d: 0x680d, - 0x125e: 0x682d, 0x125f: 0x0008, 0x1260: 0x684d, 0x1261: 0x0008, 0x1262: 0x686d, 0x1263: 0x0008, - 0x1264: 0x0008, 0x1265: 0x688d, 0x1266: 0x68ad, 0x1267: 0x0008, 0x1268: 0x0008, 0x1269: 0x0008, - 0x126a: 0x68cd, 0x126b: 0x68ed, 0x126c: 0x690d, 0x126d: 0x692d, 0x126e: 0x694d, 0x126f: 0x696d, - 0x1270: 0x698d, 0x1271: 0x69ad, 0x1272: 0x69cd, 0x1273: 0x69ed, 0x1274: 0x6a0d, 0x1275: 0x6a2d, - 0x1276: 0x6a4d, 0x1277: 0x6a6d, 0x1278: 0x6a8d, 0x1279: 0x6aad, 0x127a: 0x6acd, 0x127b: 0x6aed, - 0x127c: 0x6b0d, 0x127d: 0x6b2d, 0x127e: 0x6b4d, 0x127f: 0x6b6d, + 0x1240: 0x1439, 0x1241: 0x1441, 0x1242: 0x1449, 0x1243: 0x7d4d, 0x1244: 0x7d6d, 0x1245: 0x1451, + 0x1246: 0x1451, 0x1247: 0x0040, 0x1248: 0x0040, 0x1249: 0x0040, 0x124a: 0x0040, 0x124b: 0x0040, + 0x124c: 0x0040, 0x124d: 0x0040, 0x124e: 0x0040, 0x124f: 0x0040, 0x1250: 0x0040, 0x1251: 0x0040, + 0x1252: 0x0040, 0x1253: 0x1459, 0x1254: 0x1461, 0x1255: 0x1469, 0x1256: 0x1471, 0x1257: 0x1479, + 0x1258: 0x0040, 0x1259: 0x0040, 0x125a: 0x0040, 0x125b: 0x0040, 0x125c: 0x0040, 0x125d: 0x1481, + 0x125e: 0x3308, 0x125f: 0x1489, 0x1260: 0x1491, 0x1261: 0x0779, 0x1262: 0x0791, 0x1263: 0x1499, + 0x1264: 0x14a1, 0x1265: 0x14a9, 0x1266: 0x14b1, 0x1267: 0x14b9, 0x1268: 0x14c1, 0x1269: 0x071a, + 0x126a: 0x14c9, 0x126b: 0x14d1, 0x126c: 0x14d9, 0x126d: 0x14e1, 0x126e: 0x14e9, 0x126f: 0x14f1, + 0x1270: 0x14f9, 0x1271: 0x1501, 0x1272: 0x1509, 0x1273: 0x1511, 0x1274: 0x1519, 0x1275: 0x1521, + 0x1276: 0x1529, 0x1277: 0x0040, 0x1278: 0x1531, 0x1279: 0x1539, 0x127a: 0x1541, 0x127b: 0x1549, + 0x127c: 0x1551, 0x127d: 0x0040, 0x127e: 0x1559, 0x127f: 0x0040, // Block 0x4a, offset 0x1280 - 0x1280: 0x7acd, 0x1281: 0x7aed, 0x1282: 0x7b0d, 0x1283: 0x7b2d, 0x1284: 0x7b4d, 0x1285: 0x7b6d, - 0x1286: 0x7b8d, 0x1287: 0x7bad, 0x1288: 0x7bcd, 0x1289: 0x7bed, 0x128a: 0x7c0d, 0x128b: 0x7c2d, - 0x128c: 0x7c4d, 0x128d: 0x7c6d, 0x128e: 0x7c8d, 0x128f: 0x6f19, 0x1290: 0x6f41, 0x1291: 0x6f69, - 0x1292: 0x7cad, 0x1293: 0x7ccd, 0x1294: 0x7ced, 0x1295: 0x6f91, 0x1296: 0x6fb9, 0x1297: 0x6fe1, - 0x1298: 0x7d0d, 0x1299: 0x7d2d, 0x129a: 0x0040, 0x129b: 0x0040, 0x129c: 0x0040, 0x129d: 0x0040, - 0x129e: 0x0040, 0x129f: 0x0040, 0x12a0: 0x0040, 0x12a1: 0x0040, 0x12a2: 0x0040, 0x12a3: 0x0040, - 0x12a4: 0x0040, 0x12a5: 0x0040, 0x12a6: 0x0040, 0x12a7: 0x0040, 0x12a8: 0x0040, 0x12a9: 0x0040, - 0x12aa: 0x0040, 0x12ab: 0x0040, 0x12ac: 0x0040, 0x12ad: 0x0040, 0x12ae: 0x0040, 0x12af: 0x0040, - 0x12b0: 0x0040, 0x12b1: 0x0040, 0x12b2: 0x0040, 0x12b3: 0x0040, 0x12b4: 0x0040, 0x12b5: 0x0040, - 0x12b6: 0x0040, 0x12b7: 0x0040, 0x12b8: 0x0040, 0x12b9: 0x0040, 0x12ba: 0x0040, 0x12bb: 0x0040, - 0x12bc: 0x0040, 0x12bd: 0x0040, 0x12be: 0x0040, 0x12bf: 0x0040, + 0x1280: 0x1561, 0x1281: 0x1569, 0x1282: 0x0040, 0x1283: 0x1571, 0x1284: 0x1579, 0x1285: 0x0040, + 0x1286: 0x1581, 0x1287: 0x1589, 0x1288: 0x1591, 0x1289: 0x1599, 0x128a: 0x15a1, 0x128b: 0x15a9, + 0x128c: 0x15b1, 0x128d: 0x15b9, 0x128e: 0x15c1, 0x128f: 0x15c9, 0x1290: 0x15d1, 0x1291: 0x15d1, + 0x1292: 0x15d9, 0x1293: 0x15d9, 0x1294: 0x15d9, 0x1295: 0x15d9, 0x1296: 0x15e1, 0x1297: 0x15e1, + 0x1298: 0x15e1, 0x1299: 0x15e1, 0x129a: 0x15e9, 0x129b: 0x15e9, 0x129c: 0x15e9, 0x129d: 0x15e9, + 0x129e: 0x15f1, 0x129f: 0x15f1, 0x12a0: 0x15f1, 0x12a1: 0x15f1, 0x12a2: 0x15f9, 0x12a3: 0x15f9, + 0x12a4: 0x15f9, 0x12a5: 0x15f9, 0x12a6: 0x1601, 0x12a7: 0x1601, 0x12a8: 0x1601, 0x12a9: 0x1601, + 0x12aa: 0x1609, 0x12ab: 0x1609, 0x12ac: 0x1609, 0x12ad: 0x1609, 0x12ae: 0x1611, 0x12af: 0x1611, + 0x12b0: 0x1611, 0x12b1: 0x1611, 0x12b2: 0x1619, 0x12b3: 0x1619, 0x12b4: 0x1619, 0x12b5: 0x1619, + 0x12b6: 0x1621, 0x12b7: 0x1621, 0x12b8: 0x1621, 0x12b9: 0x1621, 0x12ba: 0x1629, 0x12bb: 0x1629, + 0x12bc: 0x1629, 0x12bd: 0x1629, 0x12be: 0x1631, 0x12bf: 0x1631, // Block 0x4b, offset 0x12c0 - 0x12c0: 0x7009, 0x12c1: 0x7021, 0x12c2: 0x7039, 0x12c3: 0x7d4d, 0x12c4: 0x7d6d, 0x12c5: 0x7051, - 0x12c6: 0x7051, 0x12c7: 0x0040, 0x12c8: 0x0040, 0x12c9: 0x0040, 0x12ca: 0x0040, 0x12cb: 0x0040, - 0x12cc: 0x0040, 0x12cd: 0x0040, 0x12ce: 0x0040, 0x12cf: 0x0040, 0x12d0: 0x0040, 0x12d1: 0x0040, - 0x12d2: 0x0040, 0x12d3: 0x7069, 0x12d4: 0x7091, 0x12d5: 0x70b9, 0x12d6: 0x70e1, 0x12d7: 0x7109, - 0x12d8: 0x0040, 0x12d9: 0x0040, 0x12da: 0x0040, 0x12db: 0x0040, 0x12dc: 0x0040, 0x12dd: 0x7131, - 0x12de: 0x3308, 0x12df: 0x7159, 0x12e0: 0x7181, 0x12e1: 0x20a9, 0x12e2: 0x20f1, 0x12e3: 0x7199, - 0x12e4: 0x71b1, 0x12e5: 0x71c9, 0x12e6: 0x71e1, 0x12e7: 0x71f9, 0x12e8: 0x7211, 0x12e9: 0x1fb2, - 0x12ea: 0x7229, 0x12eb: 0x7251, 0x12ec: 0x7279, 0x12ed: 0x72b1, 0x12ee: 0x72e9, 0x12ef: 0x7311, - 0x12f0: 0x7339, 0x12f1: 0x7361, 0x12f2: 0x7389, 0x12f3: 0x73b1, 0x12f4: 0x73d9, 0x12f5: 0x7401, - 0x12f6: 0x7429, 0x12f7: 0x0040, 0x12f8: 0x7451, 0x12f9: 0x7479, 0x12fa: 0x74a1, 0x12fb: 0x74c9, - 0x12fc: 0x74f1, 0x12fd: 0x0040, 0x12fe: 0x7519, 0x12ff: 0x0040, + 0x12c0: 0x1631, 0x12c1: 0x1631, 0x12c2: 0x1639, 0x12c3: 0x1639, 0x12c4: 0x1641, 0x12c5: 0x1641, + 0x12c6: 0x1649, 0x12c7: 0x1649, 0x12c8: 0x1651, 0x12c9: 0x1651, 0x12ca: 0x1659, 0x12cb: 0x1659, + 0x12cc: 0x1661, 0x12cd: 0x1661, 0x12ce: 0x1669, 0x12cf: 0x1669, 0x12d0: 0x1669, 0x12d1: 0x1669, + 0x12d2: 0x1671, 0x12d3: 0x1671, 0x12d4: 0x1671, 0x12d5: 0x1671, 0x12d6: 0x1679, 0x12d7: 0x1679, + 0x12d8: 0x1679, 0x12d9: 0x1679, 0x12da: 0x1681, 0x12db: 0x1681, 0x12dc: 0x1681, 0x12dd: 0x1681, + 0x12de: 0x1689, 0x12df: 0x1689, 0x12e0: 0x1691, 0x12e1: 0x1691, 0x12e2: 0x1691, 0x12e3: 0x1691, + 0x12e4: 0x1699, 0x12e5: 0x1699, 0x12e6: 0x16a1, 0x12e7: 0x16a1, 0x12e8: 0x16a1, 0x12e9: 0x16a1, + 0x12ea: 0x16a9, 0x12eb: 0x16a9, 0x12ec: 0x16a9, 0x12ed: 0x16a9, 0x12ee: 0x16b1, 0x12ef: 0x16b1, + 0x12f0: 0x16b9, 0x12f1: 0x16b9, 0x12f2: 0x0818, 0x12f3: 0x0818, 0x12f4: 0x0818, 0x12f5: 0x0818, + 0x12f6: 0x0818, 0x12f7: 0x0818, 0x12f8: 0x0818, 0x12f9: 0x0818, 0x12fa: 0x0818, 0x12fb: 0x0818, + 0x12fc: 0x0818, 0x12fd: 0x0818, 0x12fe: 0x0818, 0x12ff: 0x0818, // Block 0x4c, offset 0x1300 - 0x1300: 0x7541, 0x1301: 0x7569, 0x1302: 0x0040, 0x1303: 0x7591, 0x1304: 0x75b9, 0x1305: 0x0040, - 0x1306: 0x75e1, 0x1307: 0x7609, 0x1308: 0x7631, 0x1309: 0x7659, 0x130a: 0x7681, 0x130b: 0x76a9, - 0x130c: 0x76d1, 0x130d: 0x76f9, 0x130e: 0x7721, 0x130f: 0x7749, 0x1310: 0x7771, 0x1311: 0x7771, - 0x1312: 0x7789, 0x1313: 0x7789, 0x1314: 0x7789, 0x1315: 0x7789, 0x1316: 0x77a1, 0x1317: 0x77a1, - 0x1318: 0x77a1, 0x1319: 0x77a1, 0x131a: 0x77b9, 0x131b: 0x77b9, 0x131c: 0x77b9, 0x131d: 0x77b9, - 0x131e: 0x77d1, 0x131f: 0x77d1, 0x1320: 0x77d1, 0x1321: 0x77d1, 0x1322: 0x77e9, 0x1323: 0x77e9, - 0x1324: 0x77e9, 0x1325: 0x77e9, 0x1326: 0x7801, 0x1327: 0x7801, 0x1328: 0x7801, 0x1329: 0x7801, - 0x132a: 0x7819, 0x132b: 0x7819, 0x132c: 0x7819, 0x132d: 0x7819, 0x132e: 0x7831, 0x132f: 0x7831, - 0x1330: 0x7831, 0x1331: 0x7831, 0x1332: 0x7849, 0x1333: 0x7849, 0x1334: 0x7849, 0x1335: 0x7849, - 0x1336: 0x7861, 0x1337: 0x7861, 0x1338: 0x7861, 0x1339: 0x7861, 0x133a: 0x7879, 0x133b: 0x7879, - 0x133c: 0x7879, 0x133d: 0x7879, 0x133e: 0x7891, 0x133f: 0x7891, + 0x1300: 0x0818, 0x1301: 0x0818, 0x1302: 0x0040, 0x1303: 0x0040, 0x1304: 0x0040, 0x1305: 0x0040, + 0x1306: 0x0040, 0x1307: 0x0040, 0x1308: 0x0040, 0x1309: 0x0040, 0x130a: 0x0040, 0x130b: 0x0040, + 0x130c: 0x0040, 0x130d: 0x0040, 0x130e: 0x0040, 0x130f: 0x0040, 0x1310: 0x0040, 0x1311: 0x0040, + 0x1312: 0x0040, 0x1313: 0x16c1, 0x1314: 0x16c1, 0x1315: 0x16c1, 0x1316: 0x16c1, 0x1317: 0x16c9, + 0x1318: 0x16c9, 0x1319: 0x16d1, 0x131a: 0x16d1, 0x131b: 0x16d9, 0x131c: 0x16d9, 0x131d: 0x0149, + 0x131e: 0x16e1, 0x131f: 0x16e1, 0x1320: 0x16e9, 0x1321: 0x16e9, 0x1322: 0x16f1, 0x1323: 0x16f1, + 0x1324: 0x16f9, 0x1325: 0x16f9, 0x1326: 0x16f9, 0x1327: 0x16f9, 0x1328: 0x1701, 0x1329: 0x1701, + 0x132a: 0x1709, 0x132b: 0x1709, 0x132c: 0x1711, 0x132d: 0x1711, 0x132e: 0x1719, 0x132f: 0x1719, + 0x1330: 0x1721, 0x1331: 0x1721, 0x1332: 0x1729, 0x1333: 0x1729, 0x1334: 0x1731, 0x1335: 0x1731, + 0x1336: 0x1739, 0x1337: 0x1739, 0x1338: 0x1739, 0x1339: 0x1741, 0x133a: 0x1741, 0x133b: 0x1741, + 0x133c: 0x1749, 0x133d: 0x1749, 0x133e: 0x1749, 0x133f: 0x1749, // Block 0x4d, offset 0x1340 - 0x1340: 0x7891, 0x1341: 0x7891, 0x1342: 0x78a9, 0x1343: 0x78a9, 0x1344: 0x78c1, 0x1345: 0x78c1, - 0x1346: 0x78d9, 0x1347: 0x78d9, 0x1348: 0x78f1, 0x1349: 0x78f1, 0x134a: 0x7909, 0x134b: 0x7909, - 0x134c: 0x7921, 0x134d: 0x7921, 0x134e: 0x7939, 0x134f: 0x7939, 0x1350: 0x7939, 0x1351: 0x7939, - 0x1352: 0x7951, 0x1353: 0x7951, 0x1354: 0x7951, 0x1355: 0x7951, 0x1356: 0x7969, 0x1357: 0x7969, - 0x1358: 0x7969, 0x1359: 0x7969, 0x135a: 0x7981, 0x135b: 0x7981, 0x135c: 0x7981, 0x135d: 0x7981, - 0x135e: 0x7999, 0x135f: 0x7999, 0x1360: 0x79b1, 0x1361: 0x79b1, 0x1362: 0x79b1, 0x1363: 0x79b1, - 0x1364: 0x79c9, 0x1365: 0x79c9, 0x1366: 0x79e1, 0x1367: 0x79e1, 0x1368: 0x79e1, 0x1369: 0x79e1, - 0x136a: 0x79f9, 0x136b: 0x79f9, 0x136c: 0x79f9, 0x136d: 0x79f9, 0x136e: 0x7a11, 0x136f: 0x7a11, - 0x1370: 0x7a29, 0x1371: 0x7a29, 0x1372: 0x0818, 0x1373: 0x0818, 0x1374: 0x0818, 0x1375: 0x0818, - 0x1376: 0x0818, 0x1377: 0x0818, 0x1378: 0x0818, 0x1379: 0x0818, 0x137a: 0x0818, 0x137b: 0x0818, - 0x137c: 0x0818, 0x137d: 0x0818, 0x137e: 0x0818, 0x137f: 0x0818, + 0x1340: 0x1949, 0x1341: 0x1951, 0x1342: 0x1959, 0x1343: 0x1961, 0x1344: 0x1969, 0x1345: 0x1971, + 0x1346: 0x1979, 0x1347: 0x1981, 0x1348: 0x1989, 0x1349: 0x1991, 0x134a: 0x1999, 0x134b: 0x19a1, + 0x134c: 0x19a9, 0x134d: 0x19b1, 0x134e: 0x19b9, 0x134f: 0x19c1, 0x1350: 0x19c9, 0x1351: 0x19d1, + 0x1352: 0x19d9, 0x1353: 0x19e1, 0x1354: 0x19e9, 0x1355: 0x19f1, 0x1356: 0x19f9, 0x1357: 0x1a01, + 0x1358: 0x1a09, 0x1359: 0x1a11, 0x135a: 0x1a19, 0x135b: 0x1a21, 0x135c: 0x1a29, 0x135d: 0x1a31, + 0x135e: 0x1a3a, 0x135f: 0x1a42, 0x1360: 0x1a4a, 0x1361: 0x1a52, 0x1362: 0x1a5a, 0x1363: 0x1a62, + 0x1364: 0x1a69, 0x1365: 0x1a71, 0x1366: 0x1761, 0x1367: 0x1a79, 0x1368: 0x1741, 0x1369: 0x1769, + 0x136a: 0x1a81, 0x136b: 0x1a89, 0x136c: 0x1789, 0x136d: 0x1a91, 0x136e: 0x1791, 0x136f: 0x1799, + 0x1370: 0x1a99, 0x1371: 0x1aa1, 0x1372: 0x17b9, 0x1373: 0x1aa9, 0x1374: 0x17c1, 0x1375: 0x17c9, + 0x1376: 0x1ab1, 0x1377: 0x1ab9, 0x1378: 0x17d9, 0x1379: 0x1ac1, 0x137a: 0x17e1, 0x137b: 0x17e9, + 0x137c: 0x18d1, 0x137d: 0x18d9, 0x137e: 0x18f1, 0x137f: 0x18f9, // Block 0x4e, offset 0x1380 - 0x1380: 0x0818, 0x1381: 0x0818, 0x1382: 0x0040, 0x1383: 0x0040, 0x1384: 0x0040, 0x1385: 0x0040, - 0x1386: 0x0040, 0x1387: 0x0040, 0x1388: 0x0040, 0x1389: 0x0040, 0x138a: 0x0040, 0x138b: 0x0040, - 0x138c: 0x0040, 0x138d: 0x0040, 0x138e: 0x0040, 0x138f: 0x0040, 0x1390: 0x0040, 0x1391: 0x0040, - 0x1392: 0x0040, 0x1393: 0x7a41, 0x1394: 0x7a41, 0x1395: 0x7a41, 0x1396: 0x7a41, 0x1397: 0x7a59, - 0x1398: 0x7a59, 0x1399: 0x7a71, 0x139a: 0x7a71, 0x139b: 0x7a89, 0x139c: 0x7a89, 0x139d: 0x0479, - 0x139e: 0x7aa1, 0x139f: 0x7aa1, 0x13a0: 0x7ab9, 0x13a1: 0x7ab9, 0x13a2: 0x7ad1, 0x13a3: 0x7ad1, - 0x13a4: 0x7ae9, 0x13a5: 0x7ae9, 0x13a6: 0x7ae9, 0x13a7: 0x7ae9, 0x13a8: 0x7b01, 0x13a9: 0x7b01, - 0x13aa: 0x7b19, 0x13ab: 0x7b19, 0x13ac: 0x7b41, 0x13ad: 0x7b41, 0x13ae: 0x7b69, 0x13af: 0x7b69, - 0x13b0: 0x7b91, 0x13b1: 0x7b91, 0x13b2: 0x7bb9, 0x13b3: 0x7bb9, 0x13b4: 0x7be1, 0x13b5: 0x7be1, - 0x13b6: 0x7c09, 0x13b7: 0x7c09, 0x13b8: 0x7c09, 0x13b9: 0x7c31, 0x13ba: 0x7c31, 0x13bb: 0x7c31, - 0x13bc: 0x7c59, 0x13bd: 0x7c59, 0x13be: 0x7c59, 0x13bf: 0x7c59, + 0x1380: 0x1901, 0x1381: 0x1921, 0x1382: 0x1929, 0x1383: 0x1931, 0x1384: 0x1939, 0x1385: 0x1959, + 0x1386: 0x1961, 0x1387: 0x1969, 0x1388: 0x1ac9, 0x1389: 0x1989, 0x138a: 0x1ad1, 0x138b: 0x1ad9, + 0x138c: 0x19b9, 0x138d: 0x1ae1, 0x138e: 0x19c1, 0x138f: 0x19c9, 0x1390: 0x1a31, 0x1391: 0x1ae9, + 0x1392: 0x1af1, 0x1393: 0x1a09, 0x1394: 0x1af9, 0x1395: 0x1a11, 0x1396: 0x1a19, 0x1397: 0x1751, + 0x1398: 0x1759, 0x1399: 0x1b01, 0x139a: 0x1761, 0x139b: 0x1b09, 0x139c: 0x1771, 0x139d: 0x1779, + 0x139e: 0x1781, 0x139f: 0x1789, 0x13a0: 0x1b11, 0x13a1: 0x17a1, 0x13a2: 0x17a9, 0x13a3: 0x17b1, + 0x13a4: 0x17b9, 0x13a5: 0x1b19, 0x13a6: 0x17d9, 0x13a7: 0x17f1, 0x13a8: 0x17f9, 0x13a9: 0x1801, + 0x13aa: 0x1809, 0x13ab: 0x1811, 0x13ac: 0x1821, 0x13ad: 0x1829, 0x13ae: 0x1831, 0x13af: 0x1839, + 0x13b0: 0x1841, 0x13b1: 0x1849, 0x13b2: 0x1b21, 0x13b3: 0x1851, 0x13b4: 0x1859, 0x13b5: 0x1861, + 0x13b6: 0x1869, 0x13b7: 0x1871, 0x13b8: 0x1879, 0x13b9: 0x1889, 0x13ba: 0x1891, 0x13bb: 0x1899, + 0x13bc: 0x18a1, 0x13bd: 0x18a9, 0x13be: 0x18b1, 0x13bf: 0x18b9, // Block 0x4f, offset 0x13c0 - 0x13c0: 0x8649, 0x13c1: 0x8671, 0x13c2: 0x8699, 0x13c3: 0x86c1, 0x13c4: 0x86e9, 0x13c5: 0x8711, - 0x13c6: 0x8739, 0x13c7: 0x8761, 0x13c8: 0x8789, 0x13c9: 0x87b1, 0x13ca: 0x87d9, 0x13cb: 0x8801, - 0x13cc: 0x8829, 0x13cd: 0x8851, 0x13ce: 0x8879, 0x13cf: 0x88a1, 0x13d0: 0x88c9, 0x13d1: 0x88f1, - 0x13d2: 0x8919, 0x13d3: 0x8941, 0x13d4: 0x8969, 0x13d5: 0x8991, 0x13d6: 0x89b9, 0x13d7: 0x89e1, - 0x13d8: 0x8a09, 0x13d9: 0x8a31, 0x13da: 0x8a59, 0x13db: 0x8a81, 0x13dc: 0x8aa9, 0x13dd: 0x8ad1, - 0x13de: 0x8afa, 0x13df: 0x8b2a, 0x13e0: 0x8b5a, 0x13e1: 0x8b8a, 0x13e2: 0x8bba, 0x13e3: 0x8bea, - 0x13e4: 0x8c19, 0x13e5: 0x8c41, 0x13e6: 0x7cc1, 0x13e7: 0x8c69, 0x13e8: 0x7c31, 0x13e9: 0x7ce9, - 0x13ea: 0x8c91, 0x13eb: 0x8cb9, 0x13ec: 0x7d89, 0x13ed: 0x8ce1, 0x13ee: 0x7db1, 0x13ef: 0x7dd9, - 0x13f0: 0x8d09, 0x13f1: 0x8d31, 0x13f2: 0x7e79, 0x13f3: 0x8d59, 0x13f4: 0x7ea1, 0x13f5: 0x7ec9, - 0x13f6: 0x8d81, 0x13f7: 0x8da9, 0x13f8: 0x7f19, 0x13f9: 0x8dd1, 0x13fa: 0x7f41, 0x13fb: 0x7f69, - 0x13fc: 0x83f1, 0x13fd: 0x8419, 0x13fe: 0x8491, 0x13ff: 0x84b9, + 0x13c0: 0x18c1, 0x13c1: 0x18c9, 0x13c2: 0x18e1, 0x13c3: 0x18e9, 0x13c4: 0x1909, 0x13c5: 0x1911, + 0x13c6: 0x1919, 0x13c7: 0x1921, 0x13c8: 0x1929, 0x13c9: 0x1941, 0x13ca: 0x1949, 0x13cb: 0x1951, + 0x13cc: 0x1959, 0x13cd: 0x1b29, 0x13ce: 0x1971, 0x13cf: 0x1979, 0x13d0: 0x1981, 0x13d1: 0x1989, + 0x13d2: 0x19a1, 0x13d3: 0x19a9, 0x13d4: 0x19b1, 0x13d5: 0x19b9, 0x13d6: 0x1b31, 0x13d7: 0x19d1, + 0x13d8: 0x19d9, 0x13d9: 0x1b39, 0x13da: 0x19f1, 0x13db: 0x19f9, 0x13dc: 0x1a01, 0x13dd: 0x1a09, + 0x13de: 0x1b41, 0x13df: 0x1761, 0x13e0: 0x1b09, 0x13e1: 0x1789, 0x13e2: 0x1b11, 0x13e3: 0x17b9, + 0x13e4: 0x1b19, 0x13e5: 0x17d9, 0x13e6: 0x1b49, 0x13e7: 0x1841, 0x13e8: 0x1b51, 0x13e9: 0x1b59, + 0x13ea: 0x1b61, 0x13eb: 0x1921, 0x13ec: 0x1929, 0x13ed: 0x1959, 0x13ee: 0x19b9, 0x13ef: 0x1b31, + 0x13f0: 0x1a09, 0x13f1: 0x1b41, 0x13f2: 0x1b69, 0x13f3: 0x1b71, 0x13f4: 0x1b79, 0x13f5: 0x1b81, + 0x13f6: 0x1b89, 0x13f7: 0x1b91, 0x13f8: 0x1b99, 0x13f9: 0x1ba1, 0x13fa: 0x1ba9, 0x13fb: 0x1bb1, + 0x13fc: 0x1bb9, 0x13fd: 0x1bc1, 0x13fe: 0x1bc9, 0x13ff: 0x1bd1, // Block 0x50, offset 0x1400 - 0x1400: 0x84e1, 0x1401: 0x8581, 0x1402: 0x85a9, 0x1403: 0x85d1, 0x1404: 0x85f9, 0x1405: 0x8699, - 0x1406: 0x86c1, 0x1407: 0x86e9, 0x1408: 0x8df9, 0x1409: 0x8789, 0x140a: 0x8e21, 0x140b: 0x8e49, - 0x140c: 0x8879, 0x140d: 0x8e71, 0x140e: 0x88a1, 0x140f: 0x88c9, 0x1410: 0x8ad1, 0x1411: 0x8e99, - 0x1412: 0x8ec1, 0x1413: 0x8a09, 0x1414: 0x8ee9, 0x1415: 0x8a31, 0x1416: 0x8a59, 0x1417: 0x7c71, - 0x1418: 0x7c99, 0x1419: 0x8f11, 0x141a: 0x7cc1, 0x141b: 0x8f39, 0x141c: 0x7d11, 0x141d: 0x7d39, - 0x141e: 0x7d61, 0x141f: 0x7d89, 0x1420: 0x8f61, 0x1421: 0x7e01, 0x1422: 0x7e29, 0x1423: 0x7e51, - 0x1424: 0x7e79, 0x1425: 0x8f89, 0x1426: 0x7f19, 0x1427: 0x7f91, 0x1428: 0x7fb9, 0x1429: 0x7fe1, - 0x142a: 0x8009, 0x142b: 0x8031, 0x142c: 0x8081, 0x142d: 0x80a9, 0x142e: 0x80d1, 0x142f: 0x80f9, - 0x1430: 0x8121, 0x1431: 0x8149, 0x1432: 0x8fb1, 0x1433: 0x8171, 0x1434: 0x8199, 0x1435: 0x81c1, - 0x1436: 0x81e9, 0x1437: 0x8211, 0x1438: 0x8239, 0x1439: 0x8289, 0x143a: 0x82b1, 0x143b: 0x82d9, - 0x143c: 0x8301, 0x143d: 0x8329, 0x143e: 0x8351, 0x143f: 0x8379, + 0x1400: 0x1bd9, 0x1401: 0x1be1, 0x1402: 0x1be9, 0x1403: 0x1bf1, 0x1404: 0x1bf9, 0x1405: 0x1c01, + 0x1406: 0x1c09, 0x1407: 0x1c11, 0x1408: 0x1c19, 0x1409: 0x1c21, 0x140a: 0x1c29, 0x140b: 0x1c31, + 0x140c: 0x1b59, 0x140d: 0x1c39, 0x140e: 0x1c41, 0x140f: 0x1c49, 0x1410: 0x1c51, 0x1411: 0x1b81, + 0x1412: 0x1b89, 0x1413: 0x1b91, 0x1414: 0x1b99, 0x1415: 0x1ba1, 0x1416: 0x1ba9, 0x1417: 0x1bb1, + 0x1418: 0x1bb9, 0x1419: 0x1bc1, 0x141a: 0x1bc9, 0x141b: 0x1bd1, 0x141c: 0x1bd9, 0x141d: 0x1be1, + 0x141e: 0x1be9, 0x141f: 0x1bf1, 0x1420: 0x1bf9, 0x1421: 0x1c01, 0x1422: 0x1c09, 0x1423: 0x1c11, + 0x1424: 0x1c19, 0x1425: 0x1c21, 0x1426: 0x1c29, 0x1427: 0x1c31, 0x1428: 0x1b59, 0x1429: 0x1c39, + 0x142a: 0x1c41, 0x142b: 0x1c49, 0x142c: 0x1c51, 0x142d: 0x1c21, 0x142e: 0x1c29, 0x142f: 0x1c31, + 0x1430: 0x1b59, 0x1431: 0x1b51, 0x1432: 0x1b61, 0x1433: 0x1881, 0x1434: 0x1829, 0x1435: 0x1831, + 0x1436: 0x1839, 0x1437: 0x1c21, 0x1438: 0x1c29, 0x1439: 0x1c31, 0x143a: 0x1881, 0x143b: 0x1889, + 0x143c: 0x1c59, 0x143d: 0x1c59, 0x143e: 0x0018, 0x143f: 0x0018, // Block 0x51, offset 0x1440 - 0x1440: 0x83a1, 0x1441: 0x83c9, 0x1442: 0x8441, 0x1443: 0x8469, 0x1444: 0x8509, 0x1445: 0x8531, - 0x1446: 0x8559, 0x1447: 0x8581, 0x1448: 0x85a9, 0x1449: 0x8621, 0x144a: 0x8649, 0x144b: 0x8671, - 0x144c: 0x8699, 0x144d: 0x8fd9, 0x144e: 0x8711, 0x144f: 0x8739, 0x1450: 0x8761, 0x1451: 0x8789, - 0x1452: 0x8801, 0x1453: 0x8829, 0x1454: 0x8851, 0x1455: 0x8879, 0x1456: 0x9001, 0x1457: 0x88f1, - 0x1458: 0x8919, 0x1459: 0x9029, 0x145a: 0x8991, 0x145b: 0x89b9, 0x145c: 0x89e1, 0x145d: 0x8a09, - 0x145e: 0x9051, 0x145f: 0x7cc1, 0x1460: 0x8f39, 0x1461: 0x7d89, 0x1462: 0x8f61, 0x1463: 0x7e79, - 0x1464: 0x8f89, 0x1465: 0x7f19, 0x1466: 0x9079, 0x1467: 0x8121, 0x1468: 0x90a1, 0x1469: 0x90c9, - 0x146a: 0x90f1, 0x146b: 0x8581, 0x146c: 0x85a9, 0x146d: 0x8699, 0x146e: 0x8879, 0x146f: 0x9001, - 0x1470: 0x8a09, 0x1471: 0x9051, 0x1472: 0x9119, 0x1473: 0x9151, 0x1474: 0x9189, 0x1475: 0x91c1, - 0x1476: 0x91e9, 0x1477: 0x9211, 0x1478: 0x9239, 0x1479: 0x9261, 0x147a: 0x9289, 0x147b: 0x92b1, - 0x147c: 0x92d9, 0x147d: 0x9301, 0x147e: 0x9329, 0x147f: 0x9351, + 0x1440: 0x0040, 0x1441: 0x0040, 0x1442: 0x0040, 0x1443: 0x0040, 0x1444: 0x0040, 0x1445: 0x0040, + 0x1446: 0x0040, 0x1447: 0x0040, 0x1448: 0x0040, 0x1449: 0x0040, 0x144a: 0x0040, 0x144b: 0x0040, + 0x144c: 0x0040, 0x144d: 0x0040, 0x144e: 0x0040, 0x144f: 0x0040, 0x1450: 0x1c61, 0x1451: 0x1c69, + 0x1452: 0x1c69, 0x1453: 0x1c71, 0x1454: 0x1c79, 0x1455: 0x1c81, 0x1456: 0x1c89, 0x1457: 0x1c91, + 0x1458: 0x1c99, 0x1459: 0x1c99, 0x145a: 0x1ca1, 0x145b: 0x1ca9, 0x145c: 0x1cb1, 0x145d: 0x1cb9, + 0x145e: 0x1cc1, 0x145f: 0x1cc9, 0x1460: 0x1cc9, 0x1461: 0x1cd1, 0x1462: 0x1cd9, 0x1463: 0x1cd9, + 0x1464: 0x1ce1, 0x1465: 0x1ce1, 0x1466: 0x1ce9, 0x1467: 0x1cf1, 0x1468: 0x1cf1, 0x1469: 0x1cf9, + 0x146a: 0x1d01, 0x146b: 0x1d01, 0x146c: 0x1d09, 0x146d: 0x1d09, 0x146e: 0x1d11, 0x146f: 0x1d19, + 0x1470: 0x1d19, 0x1471: 0x1d21, 0x1472: 0x1d21, 0x1473: 0x1d29, 0x1474: 0x1d31, 0x1475: 0x1d39, + 0x1476: 0x1d41, 0x1477: 0x1d41, 0x1478: 0x1d49, 0x1479: 0x1d51, 0x147a: 0x1d59, 0x147b: 0x1d61, + 0x147c: 0x1d69, 0x147d: 0x1d69, 0x147e: 0x1d71, 0x147f: 0x1d79, // Block 0x52, offset 0x1480 - 0x1480: 0x9379, 0x1481: 0x93a1, 0x1482: 0x93c9, 0x1483: 0x93f1, 0x1484: 0x9419, 0x1485: 0x9441, - 0x1486: 0x9469, 0x1487: 0x9491, 0x1488: 0x94b9, 0x1489: 0x94e1, 0x148a: 0x9509, 0x148b: 0x9531, - 0x148c: 0x90c9, 0x148d: 0x9559, 0x148e: 0x9581, 0x148f: 0x95a9, 0x1490: 0x95d1, 0x1491: 0x91c1, - 0x1492: 0x91e9, 0x1493: 0x9211, 0x1494: 0x9239, 0x1495: 0x9261, 0x1496: 0x9289, 0x1497: 0x92b1, - 0x1498: 0x92d9, 0x1499: 0x9301, 0x149a: 0x9329, 0x149b: 0x9351, 0x149c: 0x9379, 0x149d: 0x93a1, - 0x149e: 0x93c9, 0x149f: 0x93f1, 0x14a0: 0x9419, 0x14a1: 0x9441, 0x14a2: 0x9469, 0x14a3: 0x9491, - 0x14a4: 0x94b9, 0x14a5: 0x94e1, 0x14a6: 0x9509, 0x14a7: 0x9531, 0x14a8: 0x90c9, 0x14a9: 0x9559, - 0x14aa: 0x9581, 0x14ab: 0x95a9, 0x14ac: 0x95d1, 0x14ad: 0x94e1, 0x14ae: 0x9509, 0x14af: 0x9531, - 0x14b0: 0x90c9, 0x14b1: 0x90a1, 0x14b2: 0x90f1, 0x14b3: 0x8261, 0x14b4: 0x80a9, 0x14b5: 0x80d1, - 0x14b6: 0x80f9, 0x14b7: 0x94e1, 0x14b8: 0x9509, 0x14b9: 0x9531, 0x14ba: 0x8261, 0x14bb: 0x8289, - 0x14bc: 0x95f9, 0x14bd: 0x95f9, 0x14be: 0x0018, 0x14bf: 0x0018, + 0x1480: 0x1f29, 0x1481: 0x1f31, 0x1482: 0x1f39, 0x1483: 0x1f11, 0x1484: 0x1d39, 0x1485: 0x1ce9, + 0x1486: 0x1f41, 0x1487: 0x1f49, 0x1488: 0x0040, 0x1489: 0x0040, 0x148a: 0x0040, 0x148b: 0x0040, + 0x148c: 0x0040, 0x148d: 0x0040, 0x148e: 0x0040, 0x148f: 0x0040, 0x1490: 0x0040, 0x1491: 0x0040, + 0x1492: 0x0040, 0x1493: 0x0040, 0x1494: 0x0040, 0x1495: 0x0040, 0x1496: 0x0040, 0x1497: 0x0040, + 0x1498: 0x0040, 0x1499: 0x0040, 0x149a: 0x0040, 0x149b: 0x0040, 0x149c: 0x0040, 0x149d: 0x0040, + 0x149e: 0x0040, 0x149f: 0x0040, 0x14a0: 0x0040, 0x14a1: 0x0040, 0x14a2: 0x0040, 0x14a3: 0x0040, + 0x14a4: 0x0040, 0x14a5: 0x0040, 0x14a6: 0x0040, 0x14a7: 0x0040, 0x14a8: 0x0040, 0x14a9: 0x0040, + 0x14aa: 0x0040, 0x14ab: 0x0040, 0x14ac: 0x0040, 0x14ad: 0x0040, 0x14ae: 0x0040, 0x14af: 0x0040, + 0x14b0: 0x1f51, 0x14b1: 0x1f59, 0x14b2: 0x1f61, 0x14b3: 0x1f69, 0x14b4: 0x1f71, 0x14b5: 0x1f79, + 0x14b6: 0x1f81, 0x14b7: 0x1f89, 0x14b8: 0x1f91, 0x14b9: 0x1f99, 0x14ba: 0x1fa2, 0x14bb: 0x1faa, + 0x14bc: 0x1fb1, 0x14bd: 0x0018, 0x14be: 0x0040, 0x14bf: 0x0040, // Block 0x53, offset 0x14c0 - 0x14c0: 0x0040, 0x14c1: 0x0040, 0x14c2: 0x0040, 0x14c3: 0x0040, 0x14c4: 0x0040, 0x14c5: 0x0040, - 0x14c6: 0x0040, 0x14c7: 0x0040, 0x14c8: 0x0040, 0x14c9: 0x0040, 0x14ca: 0x0040, 0x14cb: 0x0040, - 0x14cc: 0x0040, 0x14cd: 0x0040, 0x14ce: 0x0040, 0x14cf: 0x0040, 0x14d0: 0x9621, 0x14d1: 0x9659, - 0x14d2: 0x9659, 0x14d3: 0x9691, 0x14d4: 0x96c9, 0x14d5: 0x9701, 0x14d6: 0x9739, 0x14d7: 0x9771, - 0x14d8: 0x97a9, 0x14d9: 0x97a9, 0x14da: 0x97e1, 0x14db: 0x9819, 0x14dc: 0x9851, 0x14dd: 0x9889, - 0x14de: 0x98c1, 0x14df: 0x98f9, 0x14e0: 0x98f9, 0x14e1: 0x9931, 0x14e2: 0x9969, 0x14e3: 0x9969, - 0x14e4: 0x99a1, 0x14e5: 0x99a1, 0x14e6: 0x99d9, 0x14e7: 0x9a11, 0x14e8: 0x9a11, 0x14e9: 0x9a49, - 0x14ea: 0x9a81, 0x14eb: 0x9a81, 0x14ec: 0x9ab9, 0x14ed: 0x9ab9, 0x14ee: 0x9af1, 0x14ef: 0x9b29, - 0x14f0: 0x9b29, 0x14f1: 0x9b61, 0x14f2: 0x9b61, 0x14f3: 0x9b99, 0x14f4: 0x9bd1, 0x14f5: 0x9c09, - 0x14f6: 0x9c41, 0x14f7: 0x9c41, 0x14f8: 0x9c79, 0x14f9: 0x9cb1, 0x14fa: 0x9ce9, 0x14fb: 0x9d21, - 0x14fc: 0x9d59, 0x14fd: 0x9d59, 0x14fe: 0x9d91, 0x14ff: 0x9dc9, + 0x14c0: 0x33c0, 0x14c1: 0x33c0, 0x14c2: 0x33c0, 0x14c3: 0x33c0, 0x14c4: 0x33c0, 0x14c5: 0x33c0, + 0x14c6: 0x33c0, 0x14c7: 0x33c0, 0x14c8: 0x33c0, 0x14c9: 0x33c0, 0x14ca: 0x33c0, 0x14cb: 0x33c0, + 0x14cc: 0x33c0, 0x14cd: 0x33c0, 0x14ce: 0x33c0, 0x14cf: 0x33c0, 0x14d0: 0x1fba, 0x14d1: 0x7d8d, + 0x14d2: 0x0040, 0x14d3: 0x1fc2, 0x14d4: 0x0122, 0x14d5: 0x1fca, 0x14d6: 0x1fd2, 0x14d7: 0x7dad, + 0x14d8: 0x7dcd, 0x14d9: 0x0040, 0x14da: 0x0040, 0x14db: 0x0040, 0x14dc: 0x0040, 0x14dd: 0x0040, + 0x14de: 0x0040, 0x14df: 0x0040, 0x14e0: 0x3308, 0x14e1: 0x3308, 0x14e2: 0x3308, 0x14e3: 0x3308, + 0x14e4: 0x3308, 0x14e5: 0x3308, 0x14e6: 0x3308, 0x14e7: 0x3308, 0x14e8: 0x3308, 0x14e9: 0x3308, + 0x14ea: 0x3308, 0x14eb: 0x3308, 0x14ec: 0x3308, 0x14ed: 0x3308, 0x14ee: 0x3308, 0x14ef: 0x3308, + 0x14f0: 0x0040, 0x14f1: 0x7ded, 0x14f2: 0x7e0d, 0x14f3: 0x1fda, 0x14f4: 0x1fda, 0x14f5: 0x072a, + 0x14f6: 0x0732, 0x14f7: 0x1fe2, 0x14f8: 0x1fea, 0x14f9: 0x7e2d, 0x14fa: 0x7e4d, 0x14fb: 0x7e6d, + 0x14fc: 0x7e2d, 0x14fd: 0x7e8d, 0x14fe: 0x7ead, 0x14ff: 0x7e8d, // Block 0x54, offset 0x1500 - 0x1500: 0xa999, 0x1501: 0xa9d1, 0x1502: 0xaa09, 0x1503: 0xa8f1, 0x1504: 0x9c09, 0x1505: 0x99d9, - 0x1506: 0xaa41, 0x1507: 0xaa79, 0x1508: 0x0040, 0x1509: 0x0040, 0x150a: 0x0040, 0x150b: 0x0040, - 0x150c: 0x0040, 0x150d: 0x0040, 0x150e: 0x0040, 0x150f: 0x0040, 0x1510: 0x0040, 0x1511: 0x0040, - 0x1512: 0x0040, 0x1513: 0x0040, 0x1514: 0x0040, 0x1515: 0x0040, 0x1516: 0x0040, 0x1517: 0x0040, - 0x1518: 0x0040, 0x1519: 0x0040, 0x151a: 0x0040, 0x151b: 0x0040, 0x151c: 0x0040, 0x151d: 0x0040, - 0x151e: 0x0040, 0x151f: 0x0040, 0x1520: 0x0040, 0x1521: 0x0040, 0x1522: 0x0040, 0x1523: 0x0040, - 0x1524: 0x0040, 0x1525: 0x0040, 0x1526: 0x0040, 0x1527: 0x0040, 0x1528: 0x0040, 0x1529: 0x0040, - 0x152a: 0x0040, 0x152b: 0x0040, 0x152c: 0x0040, 0x152d: 0x0040, 0x152e: 0x0040, 0x152f: 0x0040, - 0x1530: 0xaab1, 0x1531: 0xaae9, 0x1532: 0xab21, 0x1533: 0xab69, 0x1534: 0xabb1, 0x1535: 0xabf9, - 0x1536: 0xac41, 0x1537: 0xac89, 0x1538: 0xacd1, 0x1539: 0xad19, 0x153a: 0xad52, 0x153b: 0xae62, - 0x153c: 0xaee1, 0x153d: 0x0018, 0x153e: 0x0040, 0x153f: 0x0040, + 0x1500: 0x7ecd, 0x1501: 0x7eed, 0x1502: 0x7f0d, 0x1503: 0x7eed, 0x1504: 0x7f2d, 0x1505: 0x0018, + 0x1506: 0x0018, 0x1507: 0x1ff2, 0x1508: 0x1ffa, 0x1509: 0x7f4e, 0x150a: 0x7f6e, 0x150b: 0x7f8e, + 0x150c: 0x7fae, 0x150d: 0x1fda, 0x150e: 0x1fda, 0x150f: 0x1fda, 0x1510: 0x1fba, 0x1511: 0x7fcd, + 0x1512: 0x0040, 0x1513: 0x0040, 0x1514: 0x0122, 0x1515: 0x1fc2, 0x1516: 0x1fd2, 0x1517: 0x1fca, + 0x1518: 0x7fed, 0x1519: 0x072a, 0x151a: 0x0732, 0x151b: 0x1fe2, 0x151c: 0x1fea, 0x151d: 0x7ecd, + 0x151e: 0x7f2d, 0x151f: 0x2002, 0x1520: 0x200a, 0x1521: 0x2012, 0x1522: 0x071a, 0x1523: 0x2019, + 0x1524: 0x2022, 0x1525: 0x202a, 0x1526: 0x0722, 0x1527: 0x0040, 0x1528: 0x2032, 0x1529: 0x203a, + 0x152a: 0x2042, 0x152b: 0x204a, 0x152c: 0x0040, 0x152d: 0x0040, 0x152e: 0x0040, 0x152f: 0x0040, + 0x1530: 0x800e, 0x1531: 0x2051, 0x1532: 0x802e, 0x1533: 0x0808, 0x1534: 0x804e, 0x1535: 0x0040, + 0x1536: 0x806e, 0x1537: 0x2059, 0x1538: 0x808e, 0x1539: 0x2061, 0x153a: 0x80ae, 0x153b: 0x2069, + 0x153c: 0x80ce, 0x153d: 0x2071, 0x153e: 0x80ee, 0x153f: 0x2079, // Block 0x55, offset 0x1540 - 0x1540: 0x33c0, 0x1541: 0x33c0, 0x1542: 0x33c0, 0x1543: 0x33c0, 0x1544: 0x33c0, 0x1545: 0x33c0, - 0x1546: 0x33c0, 0x1547: 0x33c0, 0x1548: 0x33c0, 0x1549: 0x33c0, 0x154a: 0x33c0, 0x154b: 0x33c0, - 0x154c: 0x33c0, 0x154d: 0x33c0, 0x154e: 0x33c0, 0x154f: 0x33c0, 0x1550: 0xaf2a, 0x1551: 0x7d8d, - 0x1552: 0x0040, 0x1553: 0xaf3a, 0x1554: 0x03c2, 0x1555: 0xaf4a, 0x1556: 0xaf5a, 0x1557: 0x7dad, - 0x1558: 0x7dcd, 0x1559: 0x0040, 0x155a: 0x0040, 0x155b: 0x0040, 0x155c: 0x0040, 0x155d: 0x0040, - 0x155e: 0x0040, 0x155f: 0x0040, 0x1560: 0x3308, 0x1561: 0x3308, 0x1562: 0x3308, 0x1563: 0x3308, - 0x1564: 0x3308, 0x1565: 0x3308, 0x1566: 0x3308, 0x1567: 0x3308, 0x1568: 0x3308, 0x1569: 0x3308, - 0x156a: 0x3308, 0x156b: 0x3308, 0x156c: 0x3308, 0x156d: 0x3308, 0x156e: 0x3308, 0x156f: 0x3308, - 0x1570: 0x0040, 0x1571: 0x7ded, 0x1572: 0x7e0d, 0x1573: 0xaf6a, 0x1574: 0xaf6a, 0x1575: 0x1fd2, - 0x1576: 0x1fe2, 0x1577: 0xaf7a, 0x1578: 0xaf8a, 0x1579: 0x7e2d, 0x157a: 0x7e4d, 0x157b: 0x7e6d, - 0x157c: 0x7e2d, 0x157d: 0x7e8d, 0x157e: 0x7ead, 0x157f: 0x7e8d, + 0x1540: 0x2081, 0x1541: 0x2089, 0x1542: 0x2089, 0x1543: 0x2091, 0x1544: 0x2091, 0x1545: 0x2099, + 0x1546: 0x2099, 0x1547: 0x20a1, 0x1548: 0x20a1, 0x1549: 0x20a9, 0x154a: 0x20a9, 0x154b: 0x20a9, + 0x154c: 0x20a9, 0x154d: 0x20b1, 0x154e: 0x20b1, 0x154f: 0x20b9, 0x1550: 0x20b9, 0x1551: 0x20b9, + 0x1552: 0x20b9, 0x1553: 0x20c1, 0x1554: 0x20c1, 0x1555: 0x20c9, 0x1556: 0x20c9, 0x1557: 0x20c9, + 0x1558: 0x20c9, 0x1559: 0x20d1, 0x155a: 0x20d1, 0x155b: 0x20d1, 0x155c: 0x20d1, 0x155d: 0x20d9, + 0x155e: 0x20d9, 0x155f: 0x20d9, 0x1560: 0x20d9, 0x1561: 0x20e1, 0x1562: 0x20e1, 0x1563: 0x20e1, + 0x1564: 0x20e1, 0x1565: 0x20e9, 0x1566: 0x20e9, 0x1567: 0x20e9, 0x1568: 0x20e9, 0x1569: 0x20f1, + 0x156a: 0x20f1, 0x156b: 0x20f9, 0x156c: 0x20f9, 0x156d: 0x2101, 0x156e: 0x2101, 0x156f: 0x2109, + 0x1570: 0x2109, 0x1571: 0x2111, 0x1572: 0x2111, 0x1573: 0x2111, 0x1574: 0x2111, 0x1575: 0x2119, + 0x1576: 0x2119, 0x1577: 0x2119, 0x1578: 0x2119, 0x1579: 0x2121, 0x157a: 0x2121, 0x157b: 0x2121, + 0x157c: 0x2121, 0x157d: 0x2129, 0x157e: 0x2129, 0x157f: 0x2129, // Block 0x56, offset 0x1580 - 0x1580: 0x7ecd, 0x1581: 0x7eed, 0x1582: 0x7f0d, 0x1583: 0x7eed, 0x1584: 0x7f2d, 0x1585: 0x0018, - 0x1586: 0x0018, 0x1587: 0xaf9a, 0x1588: 0xafaa, 0x1589: 0x7f4e, 0x158a: 0x7f6e, 0x158b: 0x7f8e, - 0x158c: 0x7fae, 0x158d: 0xaf6a, 0x158e: 0xaf6a, 0x158f: 0xaf6a, 0x1590: 0xaf2a, 0x1591: 0x7fcd, - 0x1592: 0x0040, 0x1593: 0x0040, 0x1594: 0x03c2, 0x1595: 0xaf3a, 0x1596: 0xaf5a, 0x1597: 0xaf4a, - 0x1598: 0x7fed, 0x1599: 0x1fd2, 0x159a: 0x1fe2, 0x159b: 0xaf7a, 0x159c: 0xaf8a, 0x159d: 0x7ecd, - 0x159e: 0x7f2d, 0x159f: 0xafba, 0x15a0: 0xafca, 0x15a1: 0xafda, 0x15a2: 0x1fb2, 0x15a3: 0xafe9, - 0x15a4: 0xaffa, 0x15a5: 0xb00a, 0x15a6: 0x1fc2, 0x15a7: 0x0040, 0x15a8: 0xb01a, 0x15a9: 0xb02a, - 0x15aa: 0xb03a, 0x15ab: 0xb04a, 0x15ac: 0x0040, 0x15ad: 0x0040, 0x15ae: 0x0040, 0x15af: 0x0040, - 0x15b0: 0x800e, 0x15b1: 0xb059, 0x15b2: 0x802e, 0x15b3: 0x0808, 0x15b4: 0x804e, 0x15b5: 0x0040, - 0x15b6: 0x806e, 0x15b7: 0xb081, 0x15b8: 0x808e, 0x15b9: 0xb0a9, 0x15ba: 0x80ae, 0x15bb: 0xb0d1, - 0x15bc: 0x80ce, 0x15bd: 0xb0f9, 0x15be: 0x80ee, 0x15bf: 0xb121, + 0x1580: 0x2129, 0x1581: 0x2131, 0x1582: 0x2131, 0x1583: 0x2131, 0x1584: 0x2131, 0x1585: 0x2139, + 0x1586: 0x2139, 0x1587: 0x2139, 0x1588: 0x2139, 0x1589: 0x2141, 0x158a: 0x2141, 0x158b: 0x2141, + 0x158c: 0x2141, 0x158d: 0x2149, 0x158e: 0x2149, 0x158f: 0x2149, 0x1590: 0x2149, 0x1591: 0x2151, + 0x1592: 0x2151, 0x1593: 0x2151, 0x1594: 0x2151, 0x1595: 0x2159, 0x1596: 0x2159, 0x1597: 0x2159, + 0x1598: 0x2159, 0x1599: 0x2161, 0x159a: 0x2161, 0x159b: 0x2161, 0x159c: 0x2161, 0x159d: 0x2169, + 0x159e: 0x2169, 0x159f: 0x2169, 0x15a0: 0x2169, 0x15a1: 0x2171, 0x15a2: 0x2171, 0x15a3: 0x2171, + 0x15a4: 0x2171, 0x15a5: 0x2179, 0x15a6: 0x2179, 0x15a7: 0x2179, 0x15a8: 0x2179, 0x15a9: 0x2181, + 0x15aa: 0x2181, 0x15ab: 0x2181, 0x15ac: 0x2181, 0x15ad: 0x2189, 0x15ae: 0x2189, 0x15af: 0x1701, + 0x15b0: 0x1701, 0x15b1: 0x2191, 0x15b2: 0x2191, 0x15b3: 0x2191, 0x15b4: 0x2191, 0x15b5: 0x2199, + 0x15b6: 0x2199, 0x15b7: 0x21a1, 0x15b8: 0x21a1, 0x15b9: 0x21a9, 0x15ba: 0x21a9, 0x15bb: 0x21b1, + 0x15bc: 0x21b1, 0x15bd: 0x0040, 0x15be: 0x0040, 0x15bf: 0x03c0, // Block 0x57, offset 0x15c0 - 0x15c0: 0xb149, 0x15c1: 0xb161, 0x15c2: 0xb161, 0x15c3: 0xb179, 0x15c4: 0xb179, 0x15c5: 0xb191, - 0x15c6: 0xb191, 0x15c7: 0xb1a9, 0x15c8: 0xb1a9, 0x15c9: 0xb1c1, 0x15ca: 0xb1c1, 0x15cb: 0xb1c1, - 0x15cc: 0xb1c1, 0x15cd: 0xb1d9, 0x15ce: 0xb1d9, 0x15cf: 0xb1f1, 0x15d0: 0xb1f1, 0x15d1: 0xb1f1, - 0x15d2: 0xb1f1, 0x15d3: 0xb209, 0x15d4: 0xb209, 0x15d5: 0xb221, 0x15d6: 0xb221, 0x15d7: 0xb221, - 0x15d8: 0xb221, 0x15d9: 0xb239, 0x15da: 0xb239, 0x15db: 0xb239, 0x15dc: 0xb239, 0x15dd: 0xb251, - 0x15de: 0xb251, 0x15df: 0xb251, 0x15e0: 0xb251, 0x15e1: 0xb269, 0x15e2: 0xb269, 0x15e3: 0xb269, - 0x15e4: 0xb269, 0x15e5: 0xb281, 0x15e6: 0xb281, 0x15e7: 0xb281, 0x15e8: 0xb281, 0x15e9: 0xb299, - 0x15ea: 0xb299, 0x15eb: 0xb2b1, 0x15ec: 0xb2b1, 0x15ed: 0xb2c9, 0x15ee: 0xb2c9, 0x15ef: 0xb2e1, - 0x15f0: 0xb2e1, 0x15f1: 0xb2f9, 0x15f2: 0xb2f9, 0x15f3: 0xb2f9, 0x15f4: 0xb2f9, 0x15f5: 0xb311, - 0x15f6: 0xb311, 0x15f7: 0xb311, 0x15f8: 0xb311, 0x15f9: 0xb329, 0x15fa: 0xb329, 0x15fb: 0xb329, - 0x15fc: 0xb329, 0x15fd: 0xb341, 0x15fe: 0xb341, 0x15ff: 0xb341, + 0x15c0: 0x0040, 0x15c1: 0x1fca, 0x15c2: 0x21ba, 0x15c3: 0x2002, 0x15c4: 0x203a, 0x15c5: 0x2042, + 0x15c6: 0x200a, 0x15c7: 0x21c2, 0x15c8: 0x072a, 0x15c9: 0x0732, 0x15ca: 0x2012, 0x15cb: 0x071a, + 0x15cc: 0x1fba, 0x15cd: 0x2019, 0x15ce: 0x0961, 0x15cf: 0x21ca, 0x15d0: 0x06e1, 0x15d1: 0x0049, + 0x15d2: 0x0029, 0x15d3: 0x0031, 0x15d4: 0x06e9, 0x15d5: 0x06f1, 0x15d6: 0x06f9, 0x15d7: 0x0701, + 0x15d8: 0x0709, 0x15d9: 0x0711, 0x15da: 0x1fc2, 0x15db: 0x0122, 0x15dc: 0x2022, 0x15dd: 0x0722, + 0x15de: 0x202a, 0x15df: 0x1fd2, 0x15e0: 0x204a, 0x15e1: 0x0019, 0x15e2: 0x02e9, 0x15e3: 0x03d9, + 0x15e4: 0x02f1, 0x15e5: 0x02f9, 0x15e6: 0x03f1, 0x15e7: 0x0309, 0x15e8: 0x00a9, 0x15e9: 0x0311, + 0x15ea: 0x00b1, 0x15eb: 0x0319, 0x15ec: 0x0101, 0x15ed: 0x0321, 0x15ee: 0x0329, 0x15ef: 0x0051, + 0x15f0: 0x0339, 0x15f1: 0x0751, 0x15f2: 0x00b9, 0x15f3: 0x0089, 0x15f4: 0x0341, 0x15f5: 0x0349, + 0x15f6: 0x0391, 0x15f7: 0x00c1, 0x15f8: 0x0109, 0x15f9: 0x00c9, 0x15fa: 0x04b1, 0x15fb: 0x1ff2, + 0x15fc: 0x2032, 0x15fd: 0x1ffa, 0x15fe: 0x21d2, 0x15ff: 0x1fda, // Block 0x58, offset 0x1600 - 0x1600: 0xb341, 0x1601: 0xb359, 0x1602: 0xb359, 0x1603: 0xb359, 0x1604: 0xb359, 0x1605: 0xb371, - 0x1606: 0xb371, 0x1607: 0xb371, 0x1608: 0xb371, 0x1609: 0xb389, 0x160a: 0xb389, 0x160b: 0xb389, - 0x160c: 0xb389, 0x160d: 0xb3a1, 0x160e: 0xb3a1, 0x160f: 0xb3a1, 0x1610: 0xb3a1, 0x1611: 0xb3b9, - 0x1612: 0xb3b9, 0x1613: 0xb3b9, 0x1614: 0xb3b9, 0x1615: 0xb3d1, 0x1616: 0xb3d1, 0x1617: 0xb3d1, - 0x1618: 0xb3d1, 0x1619: 0xb3e9, 0x161a: 0xb3e9, 0x161b: 0xb3e9, 0x161c: 0xb3e9, 0x161d: 0xb401, - 0x161e: 0xb401, 0x161f: 0xb401, 0x1620: 0xb401, 0x1621: 0xb419, 0x1622: 0xb419, 0x1623: 0xb419, - 0x1624: 0xb419, 0x1625: 0xb431, 0x1626: 0xb431, 0x1627: 0xb431, 0x1628: 0xb431, 0x1629: 0xb449, - 0x162a: 0xb449, 0x162b: 0xb449, 0x162c: 0xb449, 0x162d: 0xb461, 0x162e: 0xb461, 0x162f: 0x7b01, - 0x1630: 0x7b01, 0x1631: 0xb479, 0x1632: 0xb479, 0x1633: 0xb479, 0x1634: 0xb479, 0x1635: 0xb491, - 0x1636: 0xb491, 0x1637: 0xb4b9, 0x1638: 0xb4b9, 0x1639: 0xb4e1, 0x163a: 0xb4e1, 0x163b: 0xb509, - 0x163c: 0xb509, 0x163d: 0x0040, 0x163e: 0x0040, 0x163f: 0x03c0, + 0x1600: 0x0672, 0x1601: 0x0019, 0x1602: 0x02e9, 0x1603: 0x03d9, 0x1604: 0x02f1, 0x1605: 0x02f9, + 0x1606: 0x03f1, 0x1607: 0x0309, 0x1608: 0x00a9, 0x1609: 0x0311, 0x160a: 0x00b1, 0x160b: 0x0319, + 0x160c: 0x0101, 0x160d: 0x0321, 0x160e: 0x0329, 0x160f: 0x0051, 0x1610: 0x0339, 0x1611: 0x0751, + 0x1612: 0x00b9, 0x1613: 0x0089, 0x1614: 0x0341, 0x1615: 0x0349, 0x1616: 0x0391, 0x1617: 0x00c1, + 0x1618: 0x0109, 0x1619: 0x00c9, 0x161a: 0x04b1, 0x161b: 0x1fe2, 0x161c: 0x21da, 0x161d: 0x1fea, + 0x161e: 0x21e2, 0x161f: 0x810d, 0x1620: 0x812d, 0x1621: 0x0961, 0x1622: 0x814d, 0x1623: 0x814d, + 0x1624: 0x816d, 0x1625: 0x818d, 0x1626: 0x81ad, 0x1627: 0x81cd, 0x1628: 0x81ed, 0x1629: 0x820d, + 0x162a: 0x822d, 0x162b: 0x824d, 0x162c: 0x826d, 0x162d: 0x828d, 0x162e: 0x82ad, 0x162f: 0x82cd, + 0x1630: 0x82ed, 0x1631: 0x830d, 0x1632: 0x832d, 0x1633: 0x834d, 0x1634: 0x836d, 0x1635: 0x838d, + 0x1636: 0x83ad, 0x1637: 0x83cd, 0x1638: 0x83ed, 0x1639: 0x840d, 0x163a: 0x842d, 0x163b: 0x844d, + 0x163c: 0x81ed, 0x163d: 0x846d, 0x163e: 0x848d, 0x163f: 0x824d, // Block 0x59, offset 0x1640 - 0x1640: 0x0040, 0x1641: 0xaf4a, 0x1642: 0xb532, 0x1643: 0xafba, 0x1644: 0xb02a, 0x1645: 0xb03a, - 0x1646: 0xafca, 0x1647: 0xb542, 0x1648: 0x1fd2, 0x1649: 0x1fe2, 0x164a: 0xafda, 0x164b: 0x1fb2, - 0x164c: 0xaf2a, 0x164d: 0xafe9, 0x164e: 0x29d1, 0x164f: 0xb552, 0x1650: 0x1f41, 0x1651: 0x00c9, - 0x1652: 0x0069, 0x1653: 0x0079, 0x1654: 0x1f51, 0x1655: 0x1f61, 0x1656: 0x1f71, 0x1657: 0x1f81, - 0x1658: 0x1f91, 0x1659: 0x1fa1, 0x165a: 0xaf3a, 0x165b: 0x03c2, 0x165c: 0xaffa, 0x165d: 0x1fc2, - 0x165e: 0xb00a, 0x165f: 0xaf5a, 0x1660: 0xb04a, 0x1661: 0x0039, 0x1662: 0x0ee9, 0x1663: 0x1159, - 0x1664: 0x0ef9, 0x1665: 0x0f09, 0x1666: 0x1199, 0x1667: 0x0f31, 0x1668: 0x0249, 0x1669: 0x0f41, - 0x166a: 0x0259, 0x166b: 0x0f51, 0x166c: 0x0359, 0x166d: 0x0f61, 0x166e: 0x0f71, 0x166f: 0x00d9, - 0x1670: 0x0f99, 0x1671: 0x2039, 0x1672: 0x0269, 0x1673: 0x01d9, 0x1674: 0x0fa9, 0x1675: 0x0fb9, - 0x1676: 0x1089, 0x1677: 0x0279, 0x1678: 0x0369, 0x1679: 0x0289, 0x167a: 0x13d1, 0x167b: 0xaf9a, - 0x167c: 0xb01a, 0x167d: 0xafaa, 0x167e: 0xb562, 0x167f: 0xaf6a, + 0x1640: 0x84ad, 0x1641: 0x84cd, 0x1642: 0x84ed, 0x1643: 0x850d, 0x1644: 0x852d, 0x1645: 0x854d, + 0x1646: 0x856d, 0x1647: 0x858d, 0x1648: 0x850d, 0x1649: 0x85ad, 0x164a: 0x850d, 0x164b: 0x85cd, + 0x164c: 0x85cd, 0x164d: 0x85ed, 0x164e: 0x85ed, 0x164f: 0x860d, 0x1650: 0x854d, 0x1651: 0x862d, + 0x1652: 0x864d, 0x1653: 0x862d, 0x1654: 0x866d, 0x1655: 0x864d, 0x1656: 0x868d, 0x1657: 0x868d, + 0x1658: 0x86ad, 0x1659: 0x86ad, 0x165a: 0x86cd, 0x165b: 0x86cd, 0x165c: 0x864d, 0x165d: 0x814d, + 0x165e: 0x86ed, 0x165f: 0x870d, 0x1660: 0x0040, 0x1661: 0x872d, 0x1662: 0x874d, 0x1663: 0x876d, + 0x1664: 0x878d, 0x1665: 0x876d, 0x1666: 0x87ad, 0x1667: 0x87cd, 0x1668: 0x87ed, 0x1669: 0x87ed, + 0x166a: 0x880d, 0x166b: 0x880d, 0x166c: 0x882d, 0x166d: 0x882d, 0x166e: 0x880d, 0x166f: 0x880d, + 0x1670: 0x884d, 0x1671: 0x886d, 0x1672: 0x888d, 0x1673: 0x88ad, 0x1674: 0x88cd, 0x1675: 0x88ed, + 0x1676: 0x88ed, 0x1677: 0x88ed, 0x1678: 0x890d, 0x1679: 0x890d, 0x167a: 0x890d, 0x167b: 0x890d, + 0x167c: 0x87ed, 0x167d: 0x87ed, 0x167e: 0x87ed, 0x167f: 0x0040, // Block 0x5a, offset 0x1680 - 0x1680: 0x1caa, 0x1681: 0x0039, 0x1682: 0x0ee9, 0x1683: 0x1159, 0x1684: 0x0ef9, 0x1685: 0x0f09, - 0x1686: 0x1199, 0x1687: 0x0f31, 0x1688: 0x0249, 0x1689: 0x0f41, 0x168a: 0x0259, 0x168b: 0x0f51, - 0x168c: 0x0359, 0x168d: 0x0f61, 0x168e: 0x0f71, 0x168f: 0x00d9, 0x1690: 0x0f99, 0x1691: 0x2039, - 0x1692: 0x0269, 0x1693: 0x01d9, 0x1694: 0x0fa9, 0x1695: 0x0fb9, 0x1696: 0x1089, 0x1697: 0x0279, - 0x1698: 0x0369, 0x1699: 0x0289, 0x169a: 0x13d1, 0x169b: 0xaf7a, 0x169c: 0xb572, 0x169d: 0xaf8a, - 0x169e: 0xb582, 0x169f: 0x810d, 0x16a0: 0x812d, 0x16a1: 0x29d1, 0x16a2: 0x814d, 0x16a3: 0x814d, - 0x16a4: 0x816d, 0x16a5: 0x818d, 0x16a6: 0x81ad, 0x16a7: 0x81cd, 0x16a8: 0x81ed, 0x16a9: 0x820d, - 0x16aa: 0x822d, 0x16ab: 0x824d, 0x16ac: 0x826d, 0x16ad: 0x828d, 0x16ae: 0x82ad, 0x16af: 0x82cd, - 0x16b0: 0x82ed, 0x16b1: 0x830d, 0x16b2: 0x832d, 0x16b3: 0x834d, 0x16b4: 0x836d, 0x16b5: 0x838d, - 0x16b6: 0x83ad, 0x16b7: 0x83cd, 0x16b8: 0x83ed, 0x16b9: 0x840d, 0x16ba: 0x842d, 0x16bb: 0x844d, - 0x16bc: 0x81ed, 0x16bd: 0x846d, 0x16be: 0x848d, 0x16bf: 0x824d, + 0x1680: 0x0040, 0x1681: 0x0040, 0x1682: 0x874d, 0x1683: 0x872d, 0x1684: 0x892d, 0x1685: 0x872d, + 0x1686: 0x874d, 0x1687: 0x872d, 0x1688: 0x0040, 0x1689: 0x0040, 0x168a: 0x894d, 0x168b: 0x874d, + 0x168c: 0x896d, 0x168d: 0x892d, 0x168e: 0x896d, 0x168f: 0x874d, 0x1690: 0x0040, 0x1691: 0x0040, + 0x1692: 0x898d, 0x1693: 0x89ad, 0x1694: 0x88ad, 0x1695: 0x896d, 0x1696: 0x892d, 0x1697: 0x896d, + 0x1698: 0x0040, 0x1699: 0x0040, 0x169a: 0x89cd, 0x169b: 0x89ed, 0x169c: 0x89cd, 0x169d: 0x0040, + 0x169e: 0x0040, 0x169f: 0x0040, 0x16a0: 0x21e9, 0x16a1: 0x21f1, 0x16a2: 0x21f9, 0x16a3: 0x8a0e, + 0x16a4: 0x2201, 0x16a5: 0x2209, 0x16a6: 0x8a2d, 0x16a7: 0x0040, 0x16a8: 0x8a4d, 0x16a9: 0x8a6d, + 0x16aa: 0x8a8d, 0x16ab: 0x8a6d, 0x16ac: 0x8aad, 0x16ad: 0x8acd, 0x16ae: 0x8aed, 0x16af: 0x0040, + 0x16b0: 0x0040, 0x16b1: 0x0040, 0x16b2: 0x0040, 0x16b3: 0x0040, 0x16b4: 0x0040, 0x16b5: 0x0040, + 0x16b6: 0x0040, 0x16b7: 0x0040, 0x16b8: 0x0040, 0x16b9: 0x0340, 0x16ba: 0x0340, 0x16bb: 0x0340, + 0x16bc: 0x0040, 0x16bd: 0x0040, 0x16be: 0x0040, 0x16bf: 0x0040, // Block 0x5b, offset 0x16c0 - 0x16c0: 0x84ad, 0x16c1: 0x84cd, 0x16c2: 0x84ed, 0x16c3: 0x850d, 0x16c4: 0x852d, 0x16c5: 0x854d, - 0x16c6: 0x856d, 0x16c7: 0x858d, 0x16c8: 0x850d, 0x16c9: 0x85ad, 0x16ca: 0x850d, 0x16cb: 0x85cd, - 0x16cc: 0x85cd, 0x16cd: 0x85ed, 0x16ce: 0x85ed, 0x16cf: 0x860d, 0x16d0: 0x854d, 0x16d1: 0x862d, - 0x16d2: 0x864d, 0x16d3: 0x862d, 0x16d4: 0x866d, 0x16d5: 0x864d, 0x16d6: 0x868d, 0x16d7: 0x868d, - 0x16d8: 0x86ad, 0x16d9: 0x86ad, 0x16da: 0x86cd, 0x16db: 0x86cd, 0x16dc: 0x864d, 0x16dd: 0x814d, - 0x16de: 0x86ed, 0x16df: 0x870d, 0x16e0: 0x0040, 0x16e1: 0x872d, 0x16e2: 0x874d, 0x16e3: 0x876d, - 0x16e4: 0x878d, 0x16e5: 0x876d, 0x16e6: 0x87ad, 0x16e7: 0x87cd, 0x16e8: 0x87ed, 0x16e9: 0x87ed, - 0x16ea: 0x880d, 0x16eb: 0x880d, 0x16ec: 0x882d, 0x16ed: 0x882d, 0x16ee: 0x880d, 0x16ef: 0x880d, - 0x16f0: 0x884d, 0x16f1: 0x886d, 0x16f2: 0x888d, 0x16f3: 0x88ad, 0x16f4: 0x88cd, 0x16f5: 0x88ed, - 0x16f6: 0x88ed, 0x16f7: 0x88ed, 0x16f8: 0x890d, 0x16f9: 0x890d, 0x16fa: 0x890d, 0x16fb: 0x890d, - 0x16fc: 0x87ed, 0x16fd: 0x87ed, 0x16fe: 0x87ed, 0x16ff: 0x0040, + 0x16c0: 0x0a08, 0x16c1: 0x0a08, 0x16c2: 0x0a08, 0x16c3: 0x0a08, 0x16c4: 0x0a08, 0x16c5: 0x0c08, + 0x16c6: 0x0808, 0x16c7: 0x0c08, 0x16c8: 0x0818, 0x16c9: 0x0c08, 0x16ca: 0x0c08, 0x16cb: 0x0808, + 0x16cc: 0x0808, 0x16cd: 0x0908, 0x16ce: 0x0c08, 0x16cf: 0x0c08, 0x16d0: 0x0c08, 0x16d1: 0x0c08, + 0x16d2: 0x0c08, 0x16d3: 0x0a08, 0x16d4: 0x0a08, 0x16d5: 0x0a08, 0x16d6: 0x0a08, 0x16d7: 0x0908, + 0x16d8: 0x0a08, 0x16d9: 0x0a08, 0x16da: 0x0a08, 0x16db: 0x0a08, 0x16dc: 0x0a08, 0x16dd: 0x0c08, + 0x16de: 0x0a08, 0x16df: 0x0a08, 0x16e0: 0x0a08, 0x16e1: 0x0c08, 0x16e2: 0x0808, 0x16e3: 0x0808, + 0x16e4: 0x0c08, 0x16e5: 0x3308, 0x16e6: 0x3308, 0x16e7: 0x0040, 0x16e8: 0x0040, 0x16e9: 0x0040, + 0x16ea: 0x0040, 0x16eb: 0x0a18, 0x16ec: 0x0a18, 0x16ed: 0x0a18, 0x16ee: 0x0a18, 0x16ef: 0x0c18, + 0x16f0: 0x0818, 0x16f1: 0x0818, 0x16f2: 0x0818, 0x16f3: 0x0818, 0x16f4: 0x0818, 0x16f5: 0x0818, + 0x16f6: 0x0818, 0x16f7: 0x0040, 0x16f8: 0x0040, 0x16f9: 0x0040, 0x16fa: 0x0040, 0x16fb: 0x0040, + 0x16fc: 0x0040, 0x16fd: 0x0040, 0x16fe: 0x0040, 0x16ff: 0x0040, // Block 0x5c, offset 0x1700 - 0x1700: 0x0040, 0x1701: 0x0040, 0x1702: 0x874d, 0x1703: 0x872d, 0x1704: 0x892d, 0x1705: 0x872d, - 0x1706: 0x874d, 0x1707: 0x872d, 0x1708: 0x0040, 0x1709: 0x0040, 0x170a: 0x894d, 0x170b: 0x874d, - 0x170c: 0x896d, 0x170d: 0x892d, 0x170e: 0x896d, 0x170f: 0x874d, 0x1710: 0x0040, 0x1711: 0x0040, - 0x1712: 0x898d, 0x1713: 0x89ad, 0x1714: 0x88ad, 0x1715: 0x896d, 0x1716: 0x892d, 0x1717: 0x896d, - 0x1718: 0x0040, 0x1719: 0x0040, 0x171a: 0x89cd, 0x171b: 0x89ed, 0x171c: 0x89cd, 0x171d: 0x0040, - 0x171e: 0x0040, 0x171f: 0x0040, 0x1720: 0xb591, 0x1721: 0xb5a9, 0x1722: 0xb5c1, 0x1723: 0x8a0e, - 0x1724: 0xb5d9, 0x1725: 0xb5f1, 0x1726: 0x8a2d, 0x1727: 0x0040, 0x1728: 0x8a4d, 0x1729: 0x8a6d, - 0x172a: 0x8a8d, 0x172b: 0x8a6d, 0x172c: 0x8aad, 0x172d: 0x8acd, 0x172e: 0x8aed, 0x172f: 0x0040, + 0x1700: 0x0a08, 0x1701: 0x0c08, 0x1702: 0x0a08, 0x1703: 0x0c08, 0x1704: 0x0c08, 0x1705: 0x0c08, + 0x1706: 0x0a08, 0x1707: 0x0a08, 0x1708: 0x0a08, 0x1709: 0x0c08, 0x170a: 0x0a08, 0x170b: 0x0a08, + 0x170c: 0x0c08, 0x170d: 0x0a08, 0x170e: 0x0c08, 0x170f: 0x0c08, 0x1710: 0x0a08, 0x1711: 0x0c08, + 0x1712: 0x0040, 0x1713: 0x0040, 0x1714: 0x0040, 0x1715: 0x0040, 0x1716: 0x0040, 0x1717: 0x0040, + 0x1718: 0x0040, 0x1719: 0x0818, 0x171a: 0x0818, 0x171b: 0x0818, 0x171c: 0x0818, 0x171d: 0x0040, + 0x171e: 0x0040, 0x171f: 0x0040, 0x1720: 0x0040, 0x1721: 0x0040, 0x1722: 0x0040, 0x1723: 0x0040, + 0x1724: 0x0040, 0x1725: 0x0040, 0x1726: 0x0040, 0x1727: 0x0040, 0x1728: 0x0040, 0x1729: 0x0c18, + 0x172a: 0x0c18, 0x172b: 0x0c18, 0x172c: 0x0c18, 0x172d: 0x0a18, 0x172e: 0x0a18, 0x172f: 0x0818, 0x1730: 0x0040, 0x1731: 0x0040, 0x1732: 0x0040, 0x1733: 0x0040, 0x1734: 0x0040, 0x1735: 0x0040, - 0x1736: 0x0040, 0x1737: 0x0040, 0x1738: 0x0040, 0x1739: 0x0340, 0x173a: 0x0340, 0x173b: 0x0340, + 0x1736: 0x0040, 0x1737: 0x0040, 0x1738: 0x0040, 0x1739: 0x0040, 0x173a: 0x0040, 0x173b: 0x0040, 0x173c: 0x0040, 0x173d: 0x0040, 0x173e: 0x0040, 0x173f: 0x0040, // Block 0x5d, offset 0x1740 - 0x1740: 0x0a08, 0x1741: 0x0a08, 0x1742: 0x0a08, 0x1743: 0x0a08, 0x1744: 0x0a08, 0x1745: 0x0c08, - 0x1746: 0x0808, 0x1747: 0x0c08, 0x1748: 0x0818, 0x1749: 0x0c08, 0x174a: 0x0c08, 0x174b: 0x0808, - 0x174c: 0x0808, 0x174d: 0x0908, 0x174e: 0x0c08, 0x174f: 0x0c08, 0x1750: 0x0c08, 0x1751: 0x0c08, - 0x1752: 0x0c08, 0x1753: 0x0a08, 0x1754: 0x0a08, 0x1755: 0x0a08, 0x1756: 0x0a08, 0x1757: 0x0908, - 0x1758: 0x0a08, 0x1759: 0x0a08, 0x175a: 0x0a08, 0x175b: 0x0a08, 0x175c: 0x0a08, 0x175d: 0x0c08, - 0x175e: 0x0a08, 0x175f: 0x0a08, 0x1760: 0x0a08, 0x1761: 0x0c08, 0x1762: 0x0808, 0x1763: 0x0808, - 0x1764: 0x0c08, 0x1765: 0x3308, 0x1766: 0x3308, 0x1767: 0x0040, 0x1768: 0x0040, 0x1769: 0x0040, - 0x176a: 0x0040, 0x176b: 0x0a18, 0x176c: 0x0a18, 0x176d: 0x0a18, 0x176e: 0x0a18, 0x176f: 0x0c18, - 0x1770: 0x0818, 0x1771: 0x0818, 0x1772: 0x0818, 0x1773: 0x0818, 0x1774: 0x0818, 0x1775: 0x0818, - 0x1776: 0x0818, 0x1777: 0x0040, 0x1778: 0x0040, 0x1779: 0x0040, 0x177a: 0x0040, 0x177b: 0x0040, - 0x177c: 0x0040, 0x177d: 0x0040, 0x177e: 0x0040, 0x177f: 0x0040, + 0x1740: 0x3308, 0x1741: 0x3308, 0x1742: 0x3008, 0x1743: 0x3008, 0x1744: 0x0040, 0x1745: 0x0008, + 0x1746: 0x0008, 0x1747: 0x0008, 0x1748: 0x0008, 0x1749: 0x0008, 0x174a: 0x0008, 0x174b: 0x0008, + 0x174c: 0x0008, 0x174d: 0x0040, 0x174e: 0x0040, 0x174f: 0x0008, 0x1750: 0x0008, 0x1751: 0x0040, + 0x1752: 0x0040, 0x1753: 0x0008, 0x1754: 0x0008, 0x1755: 0x0008, 0x1756: 0x0008, 0x1757: 0x0008, + 0x1758: 0x0008, 0x1759: 0x0008, 0x175a: 0x0008, 0x175b: 0x0008, 0x175c: 0x0008, 0x175d: 0x0008, + 0x175e: 0x0008, 0x175f: 0x0008, 0x1760: 0x0008, 0x1761: 0x0008, 0x1762: 0x0008, 0x1763: 0x0008, + 0x1764: 0x0008, 0x1765: 0x0008, 0x1766: 0x0008, 0x1767: 0x0008, 0x1768: 0x0008, 0x1769: 0x0040, + 0x176a: 0x0008, 0x176b: 0x0008, 0x176c: 0x0008, 0x176d: 0x0008, 0x176e: 0x0008, 0x176f: 0x0008, + 0x1770: 0x0008, 0x1771: 0x0040, 0x1772: 0x0008, 0x1773: 0x0008, 0x1774: 0x0040, 0x1775: 0x0008, + 0x1776: 0x0008, 0x1777: 0x0008, 0x1778: 0x0008, 0x1779: 0x0008, 0x177a: 0x0040, 0x177b: 0x3308, + 0x177c: 0x3308, 0x177d: 0x0008, 0x177e: 0x3008, 0x177f: 0x3008, // Block 0x5e, offset 0x1780 - 0x1780: 0x0a08, 0x1781: 0x0c08, 0x1782: 0x0a08, 0x1783: 0x0c08, 0x1784: 0x0c08, 0x1785: 0x0c08, - 0x1786: 0x0a08, 0x1787: 0x0a08, 0x1788: 0x0a08, 0x1789: 0x0c08, 0x178a: 0x0a08, 0x178b: 0x0a08, - 0x178c: 0x0c08, 0x178d: 0x0a08, 0x178e: 0x0c08, 0x178f: 0x0c08, 0x1790: 0x0a08, 0x1791: 0x0c08, - 0x1792: 0x0040, 0x1793: 0x0040, 0x1794: 0x0040, 0x1795: 0x0040, 0x1796: 0x0040, 0x1797: 0x0040, - 0x1798: 0x0040, 0x1799: 0x0818, 0x179a: 0x0818, 0x179b: 0x0818, 0x179c: 0x0818, 0x179d: 0x0040, - 0x179e: 0x0040, 0x179f: 0x0040, 0x17a0: 0x0040, 0x17a1: 0x0040, 0x17a2: 0x0040, 0x17a3: 0x0040, - 0x17a4: 0x0040, 0x17a5: 0x0040, 0x17a6: 0x0040, 0x17a7: 0x0040, 0x17a8: 0x0040, 0x17a9: 0x0c18, - 0x17aa: 0x0c18, 0x17ab: 0x0c18, 0x17ac: 0x0c18, 0x17ad: 0x0a18, 0x17ae: 0x0a18, 0x17af: 0x0818, - 0x17b0: 0x0040, 0x17b1: 0x0040, 0x17b2: 0x0040, 0x17b3: 0x0040, 0x17b4: 0x0040, 0x17b5: 0x0040, + 0x1780: 0x3308, 0x1781: 0x3008, 0x1782: 0x3008, 0x1783: 0x3008, 0x1784: 0x3008, 0x1785: 0x0040, + 0x1786: 0x0040, 0x1787: 0x3008, 0x1788: 0x3008, 0x1789: 0x0040, 0x178a: 0x0040, 0x178b: 0x3008, + 0x178c: 0x3008, 0x178d: 0x3808, 0x178e: 0x0040, 0x178f: 0x0040, 0x1790: 0x0008, 0x1791: 0x0040, + 0x1792: 0x0040, 0x1793: 0x0040, 0x1794: 0x0040, 0x1795: 0x0040, 0x1796: 0x0040, 0x1797: 0x3008, + 0x1798: 0x0040, 0x1799: 0x0040, 0x179a: 0x0040, 0x179b: 0x0040, 0x179c: 0x0040, 0x179d: 0x0008, + 0x179e: 0x0008, 0x179f: 0x0008, 0x17a0: 0x0008, 0x17a1: 0x0008, 0x17a2: 0x3008, 0x17a3: 0x3008, + 0x17a4: 0x0040, 0x17a5: 0x0040, 0x17a6: 0x3308, 0x17a7: 0x3308, 0x17a8: 0x3308, 0x17a9: 0x3308, + 0x17aa: 0x3308, 0x17ab: 0x3308, 0x17ac: 0x3308, 0x17ad: 0x0040, 0x17ae: 0x0040, 0x17af: 0x0040, + 0x17b0: 0x3308, 0x17b1: 0x3308, 0x17b2: 0x3308, 0x17b3: 0x3308, 0x17b4: 0x3308, 0x17b5: 0x0040, 0x17b6: 0x0040, 0x17b7: 0x0040, 0x17b8: 0x0040, 0x17b9: 0x0040, 0x17ba: 0x0040, 0x17bb: 0x0040, 0x17bc: 0x0040, 0x17bd: 0x0040, 0x17be: 0x0040, 0x17bf: 0x0040, // Block 0x5f, offset 0x17c0 - 0x17c0: 0x3308, 0x17c1: 0x3308, 0x17c2: 0x3008, 0x17c3: 0x3008, 0x17c4: 0x0040, 0x17c5: 0x0008, - 0x17c6: 0x0008, 0x17c7: 0x0008, 0x17c8: 0x0008, 0x17c9: 0x0008, 0x17ca: 0x0008, 0x17cb: 0x0008, - 0x17cc: 0x0008, 0x17cd: 0x0040, 0x17ce: 0x0040, 0x17cf: 0x0008, 0x17d0: 0x0008, 0x17d1: 0x0040, - 0x17d2: 0x0040, 0x17d3: 0x0008, 0x17d4: 0x0008, 0x17d5: 0x0008, 0x17d6: 0x0008, 0x17d7: 0x0008, + 0x17c0: 0x0008, 0x17c1: 0x0008, 0x17c2: 0x0008, 0x17c3: 0x0008, 0x17c4: 0x0008, 0x17c5: 0x0008, + 0x17c6: 0x0008, 0x17c7: 0x0040, 0x17c8: 0x0040, 0x17c9: 0x0008, 0x17ca: 0x0040, 0x17cb: 0x0040, + 0x17cc: 0x0008, 0x17cd: 0x0008, 0x17ce: 0x0008, 0x17cf: 0x0008, 0x17d0: 0x0008, 0x17d1: 0x0008, + 0x17d2: 0x0008, 0x17d3: 0x0008, 0x17d4: 0x0040, 0x17d5: 0x0008, 0x17d6: 0x0008, 0x17d7: 0x0040, 0x17d8: 0x0008, 0x17d9: 0x0008, 0x17da: 0x0008, 0x17db: 0x0008, 0x17dc: 0x0008, 0x17dd: 0x0008, 0x17de: 0x0008, 0x17df: 0x0008, 0x17e0: 0x0008, 0x17e1: 0x0008, 0x17e2: 0x0008, 0x17e3: 0x0008, - 0x17e4: 0x0008, 0x17e5: 0x0008, 0x17e6: 0x0008, 0x17e7: 0x0008, 0x17e8: 0x0008, 0x17e9: 0x0040, + 0x17e4: 0x0008, 0x17e5: 0x0008, 0x17e6: 0x0008, 0x17e7: 0x0008, 0x17e8: 0x0008, 0x17e9: 0x0008, 0x17ea: 0x0008, 0x17eb: 0x0008, 0x17ec: 0x0008, 0x17ed: 0x0008, 0x17ee: 0x0008, 0x17ef: 0x0008, - 0x17f0: 0x0008, 0x17f1: 0x0040, 0x17f2: 0x0008, 0x17f3: 0x0008, 0x17f4: 0x0040, 0x17f5: 0x0008, - 0x17f6: 0x0008, 0x17f7: 0x0008, 0x17f8: 0x0008, 0x17f9: 0x0008, 0x17fa: 0x0040, 0x17fb: 0x3308, - 0x17fc: 0x3308, 0x17fd: 0x0008, 0x17fe: 0x3008, 0x17ff: 0x3008, + 0x17f0: 0x3008, 0x17f1: 0x3008, 0x17f2: 0x3008, 0x17f3: 0x3008, 0x17f4: 0x3008, 0x17f5: 0x3008, + 0x17f6: 0x0040, 0x17f7: 0x3008, 0x17f8: 0x3008, 0x17f9: 0x0040, 0x17fa: 0x0040, 0x17fb: 0x3308, + 0x17fc: 0x3308, 0x17fd: 0x3808, 0x17fe: 0x3b08, 0x17ff: 0x0008, // Block 0x60, offset 0x1800 - 0x1800: 0x3308, 0x1801: 0x3008, 0x1802: 0x3008, 0x1803: 0x3008, 0x1804: 0x3008, 0x1805: 0x0040, - 0x1806: 0x0040, 0x1807: 0x3008, 0x1808: 0x3008, 0x1809: 0x0040, 0x180a: 0x0040, 0x180b: 0x3008, - 0x180c: 0x3008, 0x180d: 0x3808, 0x180e: 0x0040, 0x180f: 0x0040, 0x1810: 0x0008, 0x1811: 0x0040, - 0x1812: 0x0040, 0x1813: 0x0040, 0x1814: 0x0040, 0x1815: 0x0040, 0x1816: 0x0040, 0x1817: 0x3008, - 0x1818: 0x0040, 0x1819: 0x0040, 0x181a: 0x0040, 0x181b: 0x0040, 0x181c: 0x0040, 0x181d: 0x0008, - 0x181e: 0x0008, 0x181f: 0x0008, 0x1820: 0x0008, 0x1821: 0x0008, 0x1822: 0x3008, 0x1823: 0x3008, - 0x1824: 0x0040, 0x1825: 0x0040, 0x1826: 0x3308, 0x1827: 0x3308, 0x1828: 0x3308, 0x1829: 0x3308, - 0x182a: 0x3308, 0x182b: 0x3308, 0x182c: 0x3308, 0x182d: 0x0040, 0x182e: 0x0040, 0x182f: 0x0040, - 0x1830: 0x3308, 0x1831: 0x3308, 0x1832: 0x3308, 0x1833: 0x3308, 0x1834: 0x3308, 0x1835: 0x0040, - 0x1836: 0x0040, 0x1837: 0x0040, 0x1838: 0x0040, 0x1839: 0x0040, 0x183a: 0x0040, 0x183b: 0x0040, - 0x183c: 0x0040, 0x183d: 0x0040, 0x183e: 0x0040, 0x183f: 0x0040, + 0x1800: 0x0019, 0x1801: 0x02e9, 0x1802: 0x03d9, 0x1803: 0x02f1, 0x1804: 0x02f9, 0x1805: 0x03f1, + 0x1806: 0x0309, 0x1807: 0x00a9, 0x1808: 0x0311, 0x1809: 0x00b1, 0x180a: 0x0319, 0x180b: 0x0101, + 0x180c: 0x0321, 0x180d: 0x0329, 0x180e: 0x0051, 0x180f: 0x0339, 0x1810: 0x0751, 0x1811: 0x00b9, + 0x1812: 0x0089, 0x1813: 0x0341, 0x1814: 0x0349, 0x1815: 0x0391, 0x1816: 0x00c1, 0x1817: 0x0109, + 0x1818: 0x00c9, 0x1819: 0x04b1, 0x181a: 0x0019, 0x181b: 0x02e9, 0x181c: 0x03d9, 0x181d: 0x02f1, + 0x181e: 0x02f9, 0x181f: 0x03f1, 0x1820: 0x0309, 0x1821: 0x00a9, 0x1822: 0x0311, 0x1823: 0x00b1, + 0x1824: 0x0319, 0x1825: 0x0101, 0x1826: 0x0321, 0x1827: 0x0329, 0x1828: 0x0051, 0x1829: 0x0339, + 0x182a: 0x0751, 0x182b: 0x00b9, 0x182c: 0x0089, 0x182d: 0x0341, 0x182e: 0x0349, 0x182f: 0x0391, + 0x1830: 0x00c1, 0x1831: 0x0109, 0x1832: 0x00c9, 0x1833: 0x04b1, 0x1834: 0x0019, 0x1835: 0x02e9, + 0x1836: 0x03d9, 0x1837: 0x02f1, 0x1838: 0x02f9, 0x1839: 0x03f1, 0x183a: 0x0309, 0x183b: 0x00a9, + 0x183c: 0x0311, 0x183d: 0x00b1, 0x183e: 0x0319, 0x183f: 0x0101, // Block 0x61, offset 0x1840 - 0x1840: 0x0008, 0x1841: 0x0008, 0x1842: 0x0008, 0x1843: 0x0008, 0x1844: 0x0008, 0x1845: 0x0008, - 0x1846: 0x0008, 0x1847: 0x0040, 0x1848: 0x0040, 0x1849: 0x0008, 0x184a: 0x0040, 0x184b: 0x0040, - 0x184c: 0x0008, 0x184d: 0x0008, 0x184e: 0x0008, 0x184f: 0x0008, 0x1850: 0x0008, 0x1851: 0x0008, - 0x1852: 0x0008, 0x1853: 0x0008, 0x1854: 0x0040, 0x1855: 0x0008, 0x1856: 0x0008, 0x1857: 0x0040, - 0x1858: 0x0008, 0x1859: 0x0008, 0x185a: 0x0008, 0x185b: 0x0008, 0x185c: 0x0008, 0x185d: 0x0008, - 0x185e: 0x0008, 0x185f: 0x0008, 0x1860: 0x0008, 0x1861: 0x0008, 0x1862: 0x0008, 0x1863: 0x0008, - 0x1864: 0x0008, 0x1865: 0x0008, 0x1866: 0x0008, 0x1867: 0x0008, 0x1868: 0x0008, 0x1869: 0x0008, - 0x186a: 0x0008, 0x186b: 0x0008, 0x186c: 0x0008, 0x186d: 0x0008, 0x186e: 0x0008, 0x186f: 0x0008, - 0x1870: 0x3008, 0x1871: 0x3008, 0x1872: 0x3008, 0x1873: 0x3008, 0x1874: 0x3008, 0x1875: 0x3008, - 0x1876: 0x0040, 0x1877: 0x3008, 0x1878: 0x3008, 0x1879: 0x0040, 0x187a: 0x0040, 0x187b: 0x3308, - 0x187c: 0x3308, 0x187d: 0x3808, 0x187e: 0x3b08, 0x187f: 0x0008, + 0x1840: 0x0321, 0x1841: 0x0329, 0x1842: 0x0051, 0x1843: 0x0339, 0x1844: 0x0751, 0x1845: 0x00b9, + 0x1846: 0x0089, 0x1847: 0x0341, 0x1848: 0x0349, 0x1849: 0x0391, 0x184a: 0x00c1, 0x184b: 0x0109, + 0x184c: 0x00c9, 0x184d: 0x04b1, 0x184e: 0x0019, 0x184f: 0x02e9, 0x1850: 0x03d9, 0x1851: 0x02f1, + 0x1852: 0x02f9, 0x1853: 0x03f1, 0x1854: 0x0309, 0x1855: 0x0040, 0x1856: 0x0311, 0x1857: 0x00b1, + 0x1858: 0x0319, 0x1859: 0x0101, 0x185a: 0x0321, 0x185b: 0x0329, 0x185c: 0x0051, 0x185d: 0x0339, + 0x185e: 0x0751, 0x185f: 0x00b9, 0x1860: 0x0089, 0x1861: 0x0341, 0x1862: 0x0349, 0x1863: 0x0391, + 0x1864: 0x00c1, 0x1865: 0x0109, 0x1866: 0x00c9, 0x1867: 0x04b1, 0x1868: 0x0019, 0x1869: 0x02e9, + 0x186a: 0x03d9, 0x186b: 0x02f1, 0x186c: 0x02f9, 0x186d: 0x03f1, 0x186e: 0x0309, 0x186f: 0x00a9, + 0x1870: 0x0311, 0x1871: 0x00b1, 0x1872: 0x0319, 0x1873: 0x0101, 0x1874: 0x0321, 0x1875: 0x0329, + 0x1876: 0x0051, 0x1877: 0x0339, 0x1878: 0x0751, 0x1879: 0x00b9, 0x187a: 0x0089, 0x187b: 0x0341, + 0x187c: 0x0349, 0x187d: 0x0391, 0x187e: 0x00c1, 0x187f: 0x0109, // Block 0x62, offset 0x1880 - 0x1880: 0x0039, 0x1881: 0x0ee9, 0x1882: 0x1159, 0x1883: 0x0ef9, 0x1884: 0x0f09, 0x1885: 0x1199, - 0x1886: 0x0f31, 0x1887: 0x0249, 0x1888: 0x0f41, 0x1889: 0x0259, 0x188a: 0x0f51, 0x188b: 0x0359, - 0x188c: 0x0f61, 0x188d: 0x0f71, 0x188e: 0x00d9, 0x188f: 0x0f99, 0x1890: 0x2039, 0x1891: 0x0269, - 0x1892: 0x01d9, 0x1893: 0x0fa9, 0x1894: 0x0fb9, 0x1895: 0x1089, 0x1896: 0x0279, 0x1897: 0x0369, - 0x1898: 0x0289, 0x1899: 0x13d1, 0x189a: 0x0039, 0x189b: 0x0ee9, 0x189c: 0x1159, 0x189d: 0x0ef9, - 0x189e: 0x0f09, 0x189f: 0x1199, 0x18a0: 0x0f31, 0x18a1: 0x0249, 0x18a2: 0x0f41, 0x18a3: 0x0259, - 0x18a4: 0x0f51, 0x18a5: 0x0359, 0x18a6: 0x0f61, 0x18a7: 0x0f71, 0x18a8: 0x00d9, 0x18a9: 0x0f99, - 0x18aa: 0x2039, 0x18ab: 0x0269, 0x18ac: 0x01d9, 0x18ad: 0x0fa9, 0x18ae: 0x0fb9, 0x18af: 0x1089, - 0x18b0: 0x0279, 0x18b1: 0x0369, 0x18b2: 0x0289, 0x18b3: 0x13d1, 0x18b4: 0x0039, 0x18b5: 0x0ee9, - 0x18b6: 0x1159, 0x18b7: 0x0ef9, 0x18b8: 0x0f09, 0x18b9: 0x1199, 0x18ba: 0x0f31, 0x18bb: 0x0249, - 0x18bc: 0x0f41, 0x18bd: 0x0259, 0x18be: 0x0f51, 0x18bf: 0x0359, + 0x1880: 0x00c9, 0x1881: 0x04b1, 0x1882: 0x0019, 0x1883: 0x02e9, 0x1884: 0x03d9, 0x1885: 0x02f1, + 0x1886: 0x02f9, 0x1887: 0x03f1, 0x1888: 0x0309, 0x1889: 0x00a9, 0x188a: 0x0311, 0x188b: 0x00b1, + 0x188c: 0x0319, 0x188d: 0x0101, 0x188e: 0x0321, 0x188f: 0x0329, 0x1890: 0x0051, 0x1891: 0x0339, + 0x1892: 0x0751, 0x1893: 0x00b9, 0x1894: 0x0089, 0x1895: 0x0341, 0x1896: 0x0349, 0x1897: 0x0391, + 0x1898: 0x00c1, 0x1899: 0x0109, 0x189a: 0x00c9, 0x189b: 0x04b1, 0x189c: 0x0019, 0x189d: 0x0040, + 0x189e: 0x03d9, 0x189f: 0x02f1, 0x18a0: 0x0040, 0x18a1: 0x0040, 0x18a2: 0x0309, 0x18a3: 0x0040, + 0x18a4: 0x0040, 0x18a5: 0x00b1, 0x18a6: 0x0319, 0x18a7: 0x0040, 0x18a8: 0x0040, 0x18a9: 0x0329, + 0x18aa: 0x0051, 0x18ab: 0x0339, 0x18ac: 0x0751, 0x18ad: 0x0040, 0x18ae: 0x0089, 0x18af: 0x0341, + 0x18b0: 0x0349, 0x18b1: 0x0391, 0x18b2: 0x00c1, 0x18b3: 0x0109, 0x18b4: 0x00c9, 0x18b5: 0x04b1, + 0x18b6: 0x0019, 0x18b7: 0x02e9, 0x18b8: 0x03d9, 0x18b9: 0x02f1, 0x18ba: 0x0040, 0x18bb: 0x03f1, + 0x18bc: 0x0040, 0x18bd: 0x00a9, 0x18be: 0x0311, 0x18bf: 0x00b1, // Block 0x63, offset 0x18c0 - 0x18c0: 0x0f61, 0x18c1: 0x0f71, 0x18c2: 0x00d9, 0x18c3: 0x0f99, 0x18c4: 0x2039, 0x18c5: 0x0269, - 0x18c6: 0x01d9, 0x18c7: 0x0fa9, 0x18c8: 0x0fb9, 0x18c9: 0x1089, 0x18ca: 0x0279, 0x18cb: 0x0369, - 0x18cc: 0x0289, 0x18cd: 0x13d1, 0x18ce: 0x0039, 0x18cf: 0x0ee9, 0x18d0: 0x1159, 0x18d1: 0x0ef9, - 0x18d2: 0x0f09, 0x18d3: 0x1199, 0x18d4: 0x0f31, 0x18d5: 0x0040, 0x18d6: 0x0f41, 0x18d7: 0x0259, - 0x18d8: 0x0f51, 0x18d9: 0x0359, 0x18da: 0x0f61, 0x18db: 0x0f71, 0x18dc: 0x00d9, 0x18dd: 0x0f99, - 0x18de: 0x2039, 0x18df: 0x0269, 0x18e0: 0x01d9, 0x18e1: 0x0fa9, 0x18e2: 0x0fb9, 0x18e3: 0x1089, - 0x18e4: 0x0279, 0x18e5: 0x0369, 0x18e6: 0x0289, 0x18e7: 0x13d1, 0x18e8: 0x0039, 0x18e9: 0x0ee9, - 0x18ea: 0x1159, 0x18eb: 0x0ef9, 0x18ec: 0x0f09, 0x18ed: 0x1199, 0x18ee: 0x0f31, 0x18ef: 0x0249, - 0x18f0: 0x0f41, 0x18f1: 0x0259, 0x18f2: 0x0f51, 0x18f3: 0x0359, 0x18f4: 0x0f61, 0x18f5: 0x0f71, - 0x18f6: 0x00d9, 0x18f7: 0x0f99, 0x18f8: 0x2039, 0x18f9: 0x0269, 0x18fa: 0x01d9, 0x18fb: 0x0fa9, - 0x18fc: 0x0fb9, 0x18fd: 0x1089, 0x18fe: 0x0279, 0x18ff: 0x0369, + 0x18c0: 0x0319, 0x18c1: 0x0101, 0x18c2: 0x0321, 0x18c3: 0x0329, 0x18c4: 0x0040, 0x18c5: 0x0339, + 0x18c6: 0x0751, 0x18c7: 0x00b9, 0x18c8: 0x0089, 0x18c9: 0x0341, 0x18ca: 0x0349, 0x18cb: 0x0391, + 0x18cc: 0x00c1, 0x18cd: 0x0109, 0x18ce: 0x00c9, 0x18cf: 0x04b1, 0x18d0: 0x0019, 0x18d1: 0x02e9, + 0x18d2: 0x03d9, 0x18d3: 0x02f1, 0x18d4: 0x02f9, 0x18d5: 0x03f1, 0x18d6: 0x0309, 0x18d7: 0x00a9, + 0x18d8: 0x0311, 0x18d9: 0x00b1, 0x18da: 0x0319, 0x18db: 0x0101, 0x18dc: 0x0321, 0x18dd: 0x0329, + 0x18de: 0x0051, 0x18df: 0x0339, 0x18e0: 0x0751, 0x18e1: 0x00b9, 0x18e2: 0x0089, 0x18e3: 0x0341, + 0x18e4: 0x0349, 0x18e5: 0x0391, 0x18e6: 0x00c1, 0x18e7: 0x0109, 0x18e8: 0x00c9, 0x18e9: 0x04b1, + 0x18ea: 0x0019, 0x18eb: 0x02e9, 0x18ec: 0x03d9, 0x18ed: 0x02f1, 0x18ee: 0x02f9, 0x18ef: 0x03f1, + 0x18f0: 0x0309, 0x18f1: 0x00a9, 0x18f2: 0x0311, 0x18f3: 0x00b1, 0x18f4: 0x0319, 0x18f5: 0x0101, + 0x18f6: 0x0321, 0x18f7: 0x0329, 0x18f8: 0x0051, 0x18f9: 0x0339, 0x18fa: 0x0751, 0x18fb: 0x00b9, + 0x18fc: 0x0089, 0x18fd: 0x0341, 0x18fe: 0x0349, 0x18ff: 0x0391, // Block 0x64, offset 0x1900 - 0x1900: 0x0289, 0x1901: 0x13d1, 0x1902: 0x0039, 0x1903: 0x0ee9, 0x1904: 0x1159, 0x1905: 0x0ef9, - 0x1906: 0x0f09, 0x1907: 0x1199, 0x1908: 0x0f31, 0x1909: 0x0249, 0x190a: 0x0f41, 0x190b: 0x0259, - 0x190c: 0x0f51, 0x190d: 0x0359, 0x190e: 0x0f61, 0x190f: 0x0f71, 0x1910: 0x00d9, 0x1911: 0x0f99, - 0x1912: 0x2039, 0x1913: 0x0269, 0x1914: 0x01d9, 0x1915: 0x0fa9, 0x1916: 0x0fb9, 0x1917: 0x1089, - 0x1918: 0x0279, 0x1919: 0x0369, 0x191a: 0x0289, 0x191b: 0x13d1, 0x191c: 0x0039, 0x191d: 0x0040, - 0x191e: 0x1159, 0x191f: 0x0ef9, 0x1920: 0x0040, 0x1921: 0x0040, 0x1922: 0x0f31, 0x1923: 0x0040, - 0x1924: 0x0040, 0x1925: 0x0259, 0x1926: 0x0f51, 0x1927: 0x0040, 0x1928: 0x0040, 0x1929: 0x0f71, - 0x192a: 0x00d9, 0x192b: 0x0f99, 0x192c: 0x2039, 0x192d: 0x0040, 0x192e: 0x01d9, 0x192f: 0x0fa9, - 0x1930: 0x0fb9, 0x1931: 0x1089, 0x1932: 0x0279, 0x1933: 0x0369, 0x1934: 0x0289, 0x1935: 0x13d1, - 0x1936: 0x0039, 0x1937: 0x0ee9, 0x1938: 0x1159, 0x1939: 0x0ef9, 0x193a: 0x0040, 0x193b: 0x1199, - 0x193c: 0x0040, 0x193d: 0x0249, 0x193e: 0x0f41, 0x193f: 0x0259, + 0x1900: 0x00c1, 0x1901: 0x0109, 0x1902: 0x00c9, 0x1903: 0x04b1, 0x1904: 0x0019, 0x1905: 0x02e9, + 0x1906: 0x0040, 0x1907: 0x02f1, 0x1908: 0x02f9, 0x1909: 0x03f1, 0x190a: 0x0309, 0x190b: 0x0040, + 0x190c: 0x0040, 0x190d: 0x00b1, 0x190e: 0x0319, 0x190f: 0x0101, 0x1910: 0x0321, 0x1911: 0x0329, + 0x1912: 0x0051, 0x1913: 0x0339, 0x1914: 0x0751, 0x1915: 0x0040, 0x1916: 0x0089, 0x1917: 0x0341, + 0x1918: 0x0349, 0x1919: 0x0391, 0x191a: 0x00c1, 0x191b: 0x0109, 0x191c: 0x00c9, 0x191d: 0x0040, + 0x191e: 0x0019, 0x191f: 0x02e9, 0x1920: 0x03d9, 0x1921: 0x02f1, 0x1922: 0x02f9, 0x1923: 0x03f1, + 0x1924: 0x0309, 0x1925: 0x00a9, 0x1926: 0x0311, 0x1927: 0x00b1, 0x1928: 0x0319, 0x1929: 0x0101, + 0x192a: 0x0321, 0x192b: 0x0329, 0x192c: 0x0051, 0x192d: 0x0339, 0x192e: 0x0751, 0x192f: 0x00b9, + 0x1930: 0x0089, 0x1931: 0x0341, 0x1932: 0x0349, 0x1933: 0x0391, 0x1934: 0x00c1, 0x1935: 0x0109, + 0x1936: 0x00c9, 0x1937: 0x04b1, 0x1938: 0x0019, 0x1939: 0x02e9, 0x193a: 0x0040, 0x193b: 0x02f1, + 0x193c: 0x02f9, 0x193d: 0x03f1, 0x193e: 0x0309, 0x193f: 0x0040, // Block 0x65, offset 0x1940 - 0x1940: 0x0f51, 0x1941: 0x0359, 0x1942: 0x0f61, 0x1943: 0x0f71, 0x1944: 0x0040, 0x1945: 0x0f99, - 0x1946: 0x2039, 0x1947: 0x0269, 0x1948: 0x01d9, 0x1949: 0x0fa9, 0x194a: 0x0fb9, 0x194b: 0x1089, - 0x194c: 0x0279, 0x194d: 0x0369, 0x194e: 0x0289, 0x194f: 0x13d1, 0x1950: 0x0039, 0x1951: 0x0ee9, - 0x1952: 0x1159, 0x1953: 0x0ef9, 0x1954: 0x0f09, 0x1955: 0x1199, 0x1956: 0x0f31, 0x1957: 0x0249, - 0x1958: 0x0f41, 0x1959: 0x0259, 0x195a: 0x0f51, 0x195b: 0x0359, 0x195c: 0x0f61, 0x195d: 0x0f71, - 0x195e: 0x00d9, 0x195f: 0x0f99, 0x1960: 0x2039, 0x1961: 0x0269, 0x1962: 0x01d9, 0x1963: 0x0fa9, - 0x1964: 0x0fb9, 0x1965: 0x1089, 0x1966: 0x0279, 0x1967: 0x0369, 0x1968: 0x0289, 0x1969: 0x13d1, - 0x196a: 0x0039, 0x196b: 0x0ee9, 0x196c: 0x1159, 0x196d: 0x0ef9, 0x196e: 0x0f09, 0x196f: 0x1199, - 0x1970: 0x0f31, 0x1971: 0x0249, 0x1972: 0x0f41, 0x1973: 0x0259, 0x1974: 0x0f51, 0x1975: 0x0359, - 0x1976: 0x0f61, 0x1977: 0x0f71, 0x1978: 0x00d9, 0x1979: 0x0f99, 0x197a: 0x2039, 0x197b: 0x0269, - 0x197c: 0x01d9, 0x197d: 0x0fa9, 0x197e: 0x0fb9, 0x197f: 0x1089, + 0x1940: 0x0311, 0x1941: 0x00b1, 0x1942: 0x0319, 0x1943: 0x0101, 0x1944: 0x0321, 0x1945: 0x0040, + 0x1946: 0x0051, 0x1947: 0x0040, 0x1948: 0x0040, 0x1949: 0x0040, 0x194a: 0x0089, 0x194b: 0x0341, + 0x194c: 0x0349, 0x194d: 0x0391, 0x194e: 0x00c1, 0x194f: 0x0109, 0x1950: 0x00c9, 0x1951: 0x0040, + 0x1952: 0x0019, 0x1953: 0x02e9, 0x1954: 0x03d9, 0x1955: 0x02f1, 0x1956: 0x02f9, 0x1957: 0x03f1, + 0x1958: 0x0309, 0x1959: 0x00a9, 0x195a: 0x0311, 0x195b: 0x00b1, 0x195c: 0x0319, 0x195d: 0x0101, + 0x195e: 0x0321, 0x195f: 0x0329, 0x1960: 0x0051, 0x1961: 0x0339, 0x1962: 0x0751, 0x1963: 0x00b9, + 0x1964: 0x0089, 0x1965: 0x0341, 0x1966: 0x0349, 0x1967: 0x0391, 0x1968: 0x00c1, 0x1969: 0x0109, + 0x196a: 0x00c9, 0x196b: 0x04b1, 0x196c: 0x0019, 0x196d: 0x02e9, 0x196e: 0x03d9, 0x196f: 0x02f1, + 0x1970: 0x02f9, 0x1971: 0x03f1, 0x1972: 0x0309, 0x1973: 0x00a9, 0x1974: 0x0311, 0x1975: 0x00b1, + 0x1976: 0x0319, 0x1977: 0x0101, 0x1978: 0x0321, 0x1979: 0x0329, 0x197a: 0x0051, 0x197b: 0x0339, + 0x197c: 0x0751, 0x197d: 0x00b9, 0x197e: 0x0089, 0x197f: 0x0341, // Block 0x66, offset 0x1980 - 0x1980: 0x0279, 0x1981: 0x0369, 0x1982: 0x0289, 0x1983: 0x13d1, 0x1984: 0x0039, 0x1985: 0x0ee9, - 0x1986: 0x0040, 0x1987: 0x0ef9, 0x1988: 0x0f09, 0x1989: 0x1199, 0x198a: 0x0f31, 0x198b: 0x0040, - 0x198c: 0x0040, 0x198d: 0x0259, 0x198e: 0x0f51, 0x198f: 0x0359, 0x1990: 0x0f61, 0x1991: 0x0f71, - 0x1992: 0x00d9, 0x1993: 0x0f99, 0x1994: 0x2039, 0x1995: 0x0040, 0x1996: 0x01d9, 0x1997: 0x0fa9, - 0x1998: 0x0fb9, 0x1999: 0x1089, 0x199a: 0x0279, 0x199b: 0x0369, 0x199c: 0x0289, 0x199d: 0x0040, - 0x199e: 0x0039, 0x199f: 0x0ee9, 0x19a0: 0x1159, 0x19a1: 0x0ef9, 0x19a2: 0x0f09, 0x19a3: 0x1199, - 0x19a4: 0x0f31, 0x19a5: 0x0249, 0x19a6: 0x0f41, 0x19a7: 0x0259, 0x19a8: 0x0f51, 0x19a9: 0x0359, - 0x19aa: 0x0f61, 0x19ab: 0x0f71, 0x19ac: 0x00d9, 0x19ad: 0x0f99, 0x19ae: 0x2039, 0x19af: 0x0269, - 0x19b0: 0x01d9, 0x19b1: 0x0fa9, 0x19b2: 0x0fb9, 0x19b3: 0x1089, 0x19b4: 0x0279, 0x19b5: 0x0369, - 0x19b6: 0x0289, 0x19b7: 0x13d1, 0x19b8: 0x0039, 0x19b9: 0x0ee9, 0x19ba: 0x0040, 0x19bb: 0x0ef9, - 0x19bc: 0x0f09, 0x19bd: 0x1199, 0x19be: 0x0f31, 0x19bf: 0x0040, + 0x1980: 0x0349, 0x1981: 0x0391, 0x1982: 0x00c1, 0x1983: 0x0109, 0x1984: 0x00c9, 0x1985: 0x04b1, + 0x1986: 0x0019, 0x1987: 0x02e9, 0x1988: 0x03d9, 0x1989: 0x02f1, 0x198a: 0x02f9, 0x198b: 0x03f1, + 0x198c: 0x0309, 0x198d: 0x00a9, 0x198e: 0x0311, 0x198f: 0x00b1, 0x1990: 0x0319, 0x1991: 0x0101, + 0x1992: 0x0321, 0x1993: 0x0329, 0x1994: 0x0051, 0x1995: 0x0339, 0x1996: 0x0751, 0x1997: 0x00b9, + 0x1998: 0x0089, 0x1999: 0x0341, 0x199a: 0x0349, 0x199b: 0x0391, 0x199c: 0x00c1, 0x199d: 0x0109, + 0x199e: 0x00c9, 0x199f: 0x04b1, 0x19a0: 0x0019, 0x19a1: 0x02e9, 0x19a2: 0x03d9, 0x19a3: 0x02f1, + 0x19a4: 0x02f9, 0x19a5: 0x03f1, 0x19a6: 0x0309, 0x19a7: 0x00a9, 0x19a8: 0x0311, 0x19a9: 0x00b1, + 0x19aa: 0x0319, 0x19ab: 0x0101, 0x19ac: 0x0321, 0x19ad: 0x0329, 0x19ae: 0x0051, 0x19af: 0x0339, + 0x19b0: 0x0751, 0x19b1: 0x00b9, 0x19b2: 0x0089, 0x19b3: 0x0341, 0x19b4: 0x0349, 0x19b5: 0x0391, + 0x19b6: 0x00c1, 0x19b7: 0x0109, 0x19b8: 0x00c9, 0x19b9: 0x04b1, 0x19ba: 0x0019, 0x19bb: 0x02e9, + 0x19bc: 0x03d9, 0x19bd: 0x02f1, 0x19be: 0x02f9, 0x19bf: 0x03f1, // Block 0x67, offset 0x19c0 - 0x19c0: 0x0f41, 0x19c1: 0x0259, 0x19c2: 0x0f51, 0x19c3: 0x0359, 0x19c4: 0x0f61, 0x19c5: 0x0040, - 0x19c6: 0x00d9, 0x19c7: 0x0040, 0x19c8: 0x0040, 0x19c9: 0x0040, 0x19ca: 0x01d9, 0x19cb: 0x0fa9, - 0x19cc: 0x0fb9, 0x19cd: 0x1089, 0x19ce: 0x0279, 0x19cf: 0x0369, 0x19d0: 0x0289, 0x19d1: 0x0040, - 0x19d2: 0x0039, 0x19d3: 0x0ee9, 0x19d4: 0x1159, 0x19d5: 0x0ef9, 0x19d6: 0x0f09, 0x19d7: 0x1199, - 0x19d8: 0x0f31, 0x19d9: 0x0249, 0x19da: 0x0f41, 0x19db: 0x0259, 0x19dc: 0x0f51, 0x19dd: 0x0359, - 0x19de: 0x0f61, 0x19df: 0x0f71, 0x19e0: 0x00d9, 0x19e1: 0x0f99, 0x19e2: 0x2039, 0x19e3: 0x0269, - 0x19e4: 0x01d9, 0x19e5: 0x0fa9, 0x19e6: 0x0fb9, 0x19e7: 0x1089, 0x19e8: 0x0279, 0x19e9: 0x0369, - 0x19ea: 0x0289, 0x19eb: 0x13d1, 0x19ec: 0x0039, 0x19ed: 0x0ee9, 0x19ee: 0x1159, 0x19ef: 0x0ef9, - 0x19f0: 0x0f09, 0x19f1: 0x1199, 0x19f2: 0x0f31, 0x19f3: 0x0249, 0x19f4: 0x0f41, 0x19f5: 0x0259, - 0x19f6: 0x0f51, 0x19f7: 0x0359, 0x19f8: 0x0f61, 0x19f9: 0x0f71, 0x19fa: 0x00d9, 0x19fb: 0x0f99, - 0x19fc: 0x2039, 0x19fd: 0x0269, 0x19fe: 0x01d9, 0x19ff: 0x0fa9, + 0x19c0: 0x0309, 0x19c1: 0x00a9, 0x19c2: 0x0311, 0x19c3: 0x00b1, 0x19c4: 0x0319, 0x19c5: 0x0101, + 0x19c6: 0x0321, 0x19c7: 0x0329, 0x19c8: 0x0051, 0x19c9: 0x0339, 0x19ca: 0x0751, 0x19cb: 0x00b9, + 0x19cc: 0x0089, 0x19cd: 0x0341, 0x19ce: 0x0349, 0x19cf: 0x0391, 0x19d0: 0x00c1, 0x19d1: 0x0109, + 0x19d2: 0x00c9, 0x19d3: 0x04b1, 0x19d4: 0x0019, 0x19d5: 0x02e9, 0x19d6: 0x03d9, 0x19d7: 0x02f1, + 0x19d8: 0x02f9, 0x19d9: 0x03f1, 0x19da: 0x0309, 0x19db: 0x00a9, 0x19dc: 0x0311, 0x19dd: 0x00b1, + 0x19de: 0x0319, 0x19df: 0x0101, 0x19e0: 0x0321, 0x19e1: 0x0329, 0x19e2: 0x0051, 0x19e3: 0x0339, + 0x19e4: 0x0751, 0x19e5: 0x00b9, 0x19e6: 0x0089, 0x19e7: 0x0341, 0x19e8: 0x0349, 0x19e9: 0x0391, + 0x19ea: 0x00c1, 0x19eb: 0x0109, 0x19ec: 0x00c9, 0x19ed: 0x04b1, 0x19ee: 0x0019, 0x19ef: 0x02e9, + 0x19f0: 0x03d9, 0x19f1: 0x02f1, 0x19f2: 0x02f9, 0x19f3: 0x03f1, 0x19f4: 0x0309, 0x19f5: 0x00a9, + 0x19f6: 0x0311, 0x19f7: 0x00b1, 0x19f8: 0x0319, 0x19f9: 0x0101, 0x19fa: 0x0321, 0x19fb: 0x0329, + 0x19fc: 0x0051, 0x19fd: 0x0339, 0x19fe: 0x0751, 0x19ff: 0x00b9, // Block 0x68, offset 0x1a00 - 0x1a00: 0x0fb9, 0x1a01: 0x1089, 0x1a02: 0x0279, 0x1a03: 0x0369, 0x1a04: 0x0289, 0x1a05: 0x13d1, - 0x1a06: 0x0039, 0x1a07: 0x0ee9, 0x1a08: 0x1159, 0x1a09: 0x0ef9, 0x1a0a: 0x0f09, 0x1a0b: 0x1199, - 0x1a0c: 0x0f31, 0x1a0d: 0x0249, 0x1a0e: 0x0f41, 0x1a0f: 0x0259, 0x1a10: 0x0f51, 0x1a11: 0x0359, - 0x1a12: 0x0f61, 0x1a13: 0x0f71, 0x1a14: 0x00d9, 0x1a15: 0x0f99, 0x1a16: 0x2039, 0x1a17: 0x0269, - 0x1a18: 0x01d9, 0x1a19: 0x0fa9, 0x1a1a: 0x0fb9, 0x1a1b: 0x1089, 0x1a1c: 0x0279, 0x1a1d: 0x0369, - 0x1a1e: 0x0289, 0x1a1f: 0x13d1, 0x1a20: 0x0039, 0x1a21: 0x0ee9, 0x1a22: 0x1159, 0x1a23: 0x0ef9, - 0x1a24: 0x0f09, 0x1a25: 0x1199, 0x1a26: 0x0f31, 0x1a27: 0x0249, 0x1a28: 0x0f41, 0x1a29: 0x0259, - 0x1a2a: 0x0f51, 0x1a2b: 0x0359, 0x1a2c: 0x0f61, 0x1a2d: 0x0f71, 0x1a2e: 0x00d9, 0x1a2f: 0x0f99, - 0x1a30: 0x2039, 0x1a31: 0x0269, 0x1a32: 0x01d9, 0x1a33: 0x0fa9, 0x1a34: 0x0fb9, 0x1a35: 0x1089, - 0x1a36: 0x0279, 0x1a37: 0x0369, 0x1a38: 0x0289, 0x1a39: 0x13d1, 0x1a3a: 0x0039, 0x1a3b: 0x0ee9, - 0x1a3c: 0x1159, 0x1a3d: 0x0ef9, 0x1a3e: 0x0f09, 0x1a3f: 0x1199, + 0x1a00: 0x0089, 0x1a01: 0x0341, 0x1a02: 0x0349, 0x1a03: 0x0391, 0x1a04: 0x00c1, 0x1a05: 0x0109, + 0x1a06: 0x00c9, 0x1a07: 0x04b1, 0x1a08: 0x0019, 0x1a09: 0x02e9, 0x1a0a: 0x03d9, 0x1a0b: 0x02f1, + 0x1a0c: 0x02f9, 0x1a0d: 0x03f1, 0x1a0e: 0x0309, 0x1a0f: 0x00a9, 0x1a10: 0x0311, 0x1a11: 0x00b1, + 0x1a12: 0x0319, 0x1a13: 0x0101, 0x1a14: 0x0321, 0x1a15: 0x0329, 0x1a16: 0x0051, 0x1a17: 0x0339, + 0x1a18: 0x0751, 0x1a19: 0x00b9, 0x1a1a: 0x0089, 0x1a1b: 0x0341, 0x1a1c: 0x0349, 0x1a1d: 0x0391, + 0x1a1e: 0x00c1, 0x1a1f: 0x0109, 0x1a20: 0x00c9, 0x1a21: 0x04b1, 0x1a22: 0x0019, 0x1a23: 0x02e9, + 0x1a24: 0x03d9, 0x1a25: 0x02f1, 0x1a26: 0x02f9, 0x1a27: 0x03f1, 0x1a28: 0x0309, 0x1a29: 0x00a9, + 0x1a2a: 0x0311, 0x1a2b: 0x00b1, 0x1a2c: 0x0319, 0x1a2d: 0x0101, 0x1a2e: 0x0321, 0x1a2f: 0x0329, + 0x1a30: 0x0051, 0x1a31: 0x0339, 0x1a32: 0x0751, 0x1a33: 0x00b9, 0x1a34: 0x0089, 0x1a35: 0x0341, + 0x1a36: 0x0349, 0x1a37: 0x0391, 0x1a38: 0x00c1, 0x1a39: 0x0109, 0x1a3a: 0x00c9, 0x1a3b: 0x04b1, + 0x1a3c: 0x0019, 0x1a3d: 0x02e9, 0x1a3e: 0x03d9, 0x1a3f: 0x02f1, // Block 0x69, offset 0x1a40 - 0x1a40: 0x0f31, 0x1a41: 0x0249, 0x1a42: 0x0f41, 0x1a43: 0x0259, 0x1a44: 0x0f51, 0x1a45: 0x0359, - 0x1a46: 0x0f61, 0x1a47: 0x0f71, 0x1a48: 0x00d9, 0x1a49: 0x0f99, 0x1a4a: 0x2039, 0x1a4b: 0x0269, - 0x1a4c: 0x01d9, 0x1a4d: 0x0fa9, 0x1a4e: 0x0fb9, 0x1a4f: 0x1089, 0x1a50: 0x0279, 0x1a51: 0x0369, - 0x1a52: 0x0289, 0x1a53: 0x13d1, 0x1a54: 0x0039, 0x1a55: 0x0ee9, 0x1a56: 0x1159, 0x1a57: 0x0ef9, - 0x1a58: 0x0f09, 0x1a59: 0x1199, 0x1a5a: 0x0f31, 0x1a5b: 0x0249, 0x1a5c: 0x0f41, 0x1a5d: 0x0259, - 0x1a5e: 0x0f51, 0x1a5f: 0x0359, 0x1a60: 0x0f61, 0x1a61: 0x0f71, 0x1a62: 0x00d9, 0x1a63: 0x0f99, - 0x1a64: 0x2039, 0x1a65: 0x0269, 0x1a66: 0x01d9, 0x1a67: 0x0fa9, 0x1a68: 0x0fb9, 0x1a69: 0x1089, - 0x1a6a: 0x0279, 0x1a6b: 0x0369, 0x1a6c: 0x0289, 0x1a6d: 0x13d1, 0x1a6e: 0x0039, 0x1a6f: 0x0ee9, - 0x1a70: 0x1159, 0x1a71: 0x0ef9, 0x1a72: 0x0f09, 0x1a73: 0x1199, 0x1a74: 0x0f31, 0x1a75: 0x0249, - 0x1a76: 0x0f41, 0x1a77: 0x0259, 0x1a78: 0x0f51, 0x1a79: 0x0359, 0x1a7a: 0x0f61, 0x1a7b: 0x0f71, - 0x1a7c: 0x00d9, 0x1a7d: 0x0f99, 0x1a7e: 0x2039, 0x1a7f: 0x0269, + 0x1a40: 0x02f9, 0x1a41: 0x03f1, 0x1a42: 0x0309, 0x1a43: 0x00a9, 0x1a44: 0x0311, 0x1a45: 0x00b1, + 0x1a46: 0x0319, 0x1a47: 0x0101, 0x1a48: 0x0321, 0x1a49: 0x0329, 0x1a4a: 0x0051, 0x1a4b: 0x0339, + 0x1a4c: 0x0751, 0x1a4d: 0x00b9, 0x1a4e: 0x0089, 0x1a4f: 0x0341, 0x1a50: 0x0349, 0x1a51: 0x0391, + 0x1a52: 0x00c1, 0x1a53: 0x0109, 0x1a54: 0x00c9, 0x1a55: 0x04b1, 0x1a56: 0x0019, 0x1a57: 0x02e9, + 0x1a58: 0x03d9, 0x1a59: 0x02f1, 0x1a5a: 0x02f9, 0x1a5b: 0x03f1, 0x1a5c: 0x0309, 0x1a5d: 0x00a9, + 0x1a5e: 0x0311, 0x1a5f: 0x00b1, 0x1a60: 0x0319, 0x1a61: 0x0101, 0x1a62: 0x0321, 0x1a63: 0x0329, + 0x1a64: 0x0051, 0x1a65: 0x0339, 0x1a66: 0x0751, 0x1a67: 0x00b9, 0x1a68: 0x0089, 0x1a69: 0x0341, + 0x1a6a: 0x0349, 0x1a6b: 0x0391, 0x1a6c: 0x00c1, 0x1a6d: 0x0109, 0x1a6e: 0x00c9, 0x1a6f: 0x04b1, + 0x1a70: 0x0019, 0x1a71: 0x02e9, 0x1a72: 0x03d9, 0x1a73: 0x02f1, 0x1a74: 0x02f9, 0x1a75: 0x03f1, + 0x1a76: 0x0309, 0x1a77: 0x00a9, 0x1a78: 0x0311, 0x1a79: 0x00b1, 0x1a7a: 0x0319, 0x1a7b: 0x0101, + 0x1a7c: 0x0321, 0x1a7d: 0x0329, 0x1a7e: 0x0051, 0x1a7f: 0x0339, // Block 0x6a, offset 0x1a80 - 0x1a80: 0x01d9, 0x1a81: 0x0fa9, 0x1a82: 0x0fb9, 0x1a83: 0x1089, 0x1a84: 0x0279, 0x1a85: 0x0369, - 0x1a86: 0x0289, 0x1a87: 0x13d1, 0x1a88: 0x0039, 0x1a89: 0x0ee9, 0x1a8a: 0x1159, 0x1a8b: 0x0ef9, - 0x1a8c: 0x0f09, 0x1a8d: 0x1199, 0x1a8e: 0x0f31, 0x1a8f: 0x0249, 0x1a90: 0x0f41, 0x1a91: 0x0259, - 0x1a92: 0x0f51, 0x1a93: 0x0359, 0x1a94: 0x0f61, 0x1a95: 0x0f71, 0x1a96: 0x00d9, 0x1a97: 0x0f99, - 0x1a98: 0x2039, 0x1a99: 0x0269, 0x1a9a: 0x01d9, 0x1a9b: 0x0fa9, 0x1a9c: 0x0fb9, 0x1a9d: 0x1089, - 0x1a9e: 0x0279, 0x1a9f: 0x0369, 0x1aa0: 0x0289, 0x1aa1: 0x13d1, 0x1aa2: 0x0039, 0x1aa3: 0x0ee9, - 0x1aa4: 0x1159, 0x1aa5: 0x0ef9, 0x1aa6: 0x0f09, 0x1aa7: 0x1199, 0x1aa8: 0x0f31, 0x1aa9: 0x0249, - 0x1aaa: 0x0f41, 0x1aab: 0x0259, 0x1aac: 0x0f51, 0x1aad: 0x0359, 0x1aae: 0x0f61, 0x1aaf: 0x0f71, - 0x1ab0: 0x00d9, 0x1ab1: 0x0f99, 0x1ab2: 0x2039, 0x1ab3: 0x0269, 0x1ab4: 0x01d9, 0x1ab5: 0x0fa9, - 0x1ab6: 0x0fb9, 0x1ab7: 0x1089, 0x1ab8: 0x0279, 0x1ab9: 0x0369, 0x1aba: 0x0289, 0x1abb: 0x13d1, - 0x1abc: 0x0039, 0x1abd: 0x0ee9, 0x1abe: 0x1159, 0x1abf: 0x0ef9, + 0x1a80: 0x0751, 0x1a81: 0x00b9, 0x1a82: 0x0089, 0x1a83: 0x0341, 0x1a84: 0x0349, 0x1a85: 0x0391, + 0x1a86: 0x00c1, 0x1a87: 0x0109, 0x1a88: 0x00c9, 0x1a89: 0x04b1, 0x1a8a: 0x0019, 0x1a8b: 0x02e9, + 0x1a8c: 0x03d9, 0x1a8d: 0x02f1, 0x1a8e: 0x02f9, 0x1a8f: 0x03f1, 0x1a90: 0x0309, 0x1a91: 0x00a9, + 0x1a92: 0x0311, 0x1a93: 0x00b1, 0x1a94: 0x0319, 0x1a95: 0x0101, 0x1a96: 0x0321, 0x1a97: 0x0329, + 0x1a98: 0x0051, 0x1a99: 0x0339, 0x1a9a: 0x0751, 0x1a9b: 0x00b9, 0x1a9c: 0x0089, 0x1a9d: 0x0341, + 0x1a9e: 0x0349, 0x1a9f: 0x0391, 0x1aa0: 0x00c1, 0x1aa1: 0x0109, 0x1aa2: 0x00c9, 0x1aa3: 0x04b1, + 0x1aa4: 0x2279, 0x1aa5: 0x2281, 0x1aa6: 0x0040, 0x1aa7: 0x0040, 0x1aa8: 0x2289, 0x1aa9: 0x0399, + 0x1aaa: 0x03a1, 0x1aab: 0x03a9, 0x1aac: 0x2291, 0x1aad: 0x2299, 0x1aae: 0x22a1, 0x1aaf: 0x04d1, + 0x1ab0: 0x05f9, 0x1ab1: 0x22a9, 0x1ab2: 0x22b1, 0x1ab3: 0x22b9, 0x1ab4: 0x22c1, 0x1ab5: 0x22c9, + 0x1ab6: 0x22d1, 0x1ab7: 0x0799, 0x1ab8: 0x03c1, 0x1ab9: 0x04d1, 0x1aba: 0x22d9, 0x1abb: 0x22e1, + 0x1abc: 0x22e9, 0x1abd: 0x03b1, 0x1abe: 0x03b9, 0x1abf: 0x22f1, // Block 0x6b, offset 0x1ac0 - 0x1ac0: 0x0f09, 0x1ac1: 0x1199, 0x1ac2: 0x0f31, 0x1ac3: 0x0249, 0x1ac4: 0x0f41, 0x1ac5: 0x0259, - 0x1ac6: 0x0f51, 0x1ac7: 0x0359, 0x1ac8: 0x0f61, 0x1ac9: 0x0f71, 0x1aca: 0x00d9, 0x1acb: 0x0f99, - 0x1acc: 0x2039, 0x1acd: 0x0269, 0x1ace: 0x01d9, 0x1acf: 0x0fa9, 0x1ad0: 0x0fb9, 0x1ad1: 0x1089, - 0x1ad2: 0x0279, 0x1ad3: 0x0369, 0x1ad4: 0x0289, 0x1ad5: 0x13d1, 0x1ad6: 0x0039, 0x1ad7: 0x0ee9, - 0x1ad8: 0x1159, 0x1ad9: 0x0ef9, 0x1ada: 0x0f09, 0x1adb: 0x1199, 0x1adc: 0x0f31, 0x1add: 0x0249, - 0x1ade: 0x0f41, 0x1adf: 0x0259, 0x1ae0: 0x0f51, 0x1ae1: 0x0359, 0x1ae2: 0x0f61, 0x1ae3: 0x0f71, - 0x1ae4: 0x00d9, 0x1ae5: 0x0f99, 0x1ae6: 0x2039, 0x1ae7: 0x0269, 0x1ae8: 0x01d9, 0x1ae9: 0x0fa9, - 0x1aea: 0x0fb9, 0x1aeb: 0x1089, 0x1aec: 0x0279, 0x1aed: 0x0369, 0x1aee: 0x0289, 0x1aef: 0x13d1, - 0x1af0: 0x0039, 0x1af1: 0x0ee9, 0x1af2: 0x1159, 0x1af3: 0x0ef9, 0x1af4: 0x0f09, 0x1af5: 0x1199, - 0x1af6: 0x0f31, 0x1af7: 0x0249, 0x1af8: 0x0f41, 0x1af9: 0x0259, 0x1afa: 0x0f51, 0x1afb: 0x0359, - 0x1afc: 0x0f61, 0x1afd: 0x0f71, 0x1afe: 0x00d9, 0x1aff: 0x0f99, + 0x1ac0: 0x0769, 0x1ac1: 0x22f9, 0x1ac2: 0x2289, 0x1ac3: 0x0399, 0x1ac4: 0x03a1, 0x1ac5: 0x03a9, + 0x1ac6: 0x2291, 0x1ac7: 0x2299, 0x1ac8: 0x22a1, 0x1ac9: 0x04d1, 0x1aca: 0x05f9, 0x1acb: 0x22a9, + 0x1acc: 0x22b1, 0x1acd: 0x22b9, 0x1ace: 0x22c1, 0x1acf: 0x22c9, 0x1ad0: 0x22d1, 0x1ad1: 0x0799, + 0x1ad2: 0x03c1, 0x1ad3: 0x22d9, 0x1ad4: 0x22d9, 0x1ad5: 0x22e1, 0x1ad6: 0x22e9, 0x1ad7: 0x03b1, + 0x1ad8: 0x03b9, 0x1ad9: 0x22f1, 0x1ada: 0x0769, 0x1adb: 0x2301, 0x1adc: 0x2291, 0x1add: 0x04d1, + 0x1ade: 0x22a9, 0x1adf: 0x03b1, 0x1ae0: 0x03c1, 0x1ae1: 0x0799, 0x1ae2: 0x2289, 0x1ae3: 0x0399, + 0x1ae4: 0x03a1, 0x1ae5: 0x03a9, 0x1ae6: 0x2291, 0x1ae7: 0x2299, 0x1ae8: 0x22a1, 0x1ae9: 0x04d1, + 0x1aea: 0x05f9, 0x1aeb: 0x22a9, 0x1aec: 0x22b1, 0x1aed: 0x22b9, 0x1aee: 0x22c1, 0x1aef: 0x22c9, + 0x1af0: 0x22d1, 0x1af1: 0x0799, 0x1af2: 0x03c1, 0x1af3: 0x04d1, 0x1af4: 0x22d9, 0x1af5: 0x22e1, + 0x1af6: 0x22e9, 0x1af7: 0x03b1, 0x1af8: 0x03b9, 0x1af9: 0x22f1, 0x1afa: 0x0769, 0x1afb: 0x22f9, + 0x1afc: 0x2289, 0x1afd: 0x0399, 0x1afe: 0x03a1, 0x1aff: 0x03a9, // Block 0x6c, offset 0x1b00 - 0x1b00: 0x2039, 0x1b01: 0x0269, 0x1b02: 0x01d9, 0x1b03: 0x0fa9, 0x1b04: 0x0fb9, 0x1b05: 0x1089, - 0x1b06: 0x0279, 0x1b07: 0x0369, 0x1b08: 0x0289, 0x1b09: 0x13d1, 0x1b0a: 0x0039, 0x1b0b: 0x0ee9, - 0x1b0c: 0x1159, 0x1b0d: 0x0ef9, 0x1b0e: 0x0f09, 0x1b0f: 0x1199, 0x1b10: 0x0f31, 0x1b11: 0x0249, - 0x1b12: 0x0f41, 0x1b13: 0x0259, 0x1b14: 0x0f51, 0x1b15: 0x0359, 0x1b16: 0x0f61, 0x1b17: 0x0f71, - 0x1b18: 0x00d9, 0x1b19: 0x0f99, 0x1b1a: 0x2039, 0x1b1b: 0x0269, 0x1b1c: 0x01d9, 0x1b1d: 0x0fa9, - 0x1b1e: 0x0fb9, 0x1b1f: 0x1089, 0x1b20: 0x0279, 0x1b21: 0x0369, 0x1b22: 0x0289, 0x1b23: 0x13d1, - 0x1b24: 0xbad1, 0x1b25: 0xbae9, 0x1b26: 0x0040, 0x1b27: 0x0040, 0x1b28: 0xbb01, 0x1b29: 0x1099, - 0x1b2a: 0x10b1, 0x1b2b: 0x10c9, 0x1b2c: 0xbb19, 0x1b2d: 0xbb31, 0x1b2e: 0xbb49, 0x1b2f: 0x1429, - 0x1b30: 0x1a31, 0x1b31: 0xbb61, 0x1b32: 0xbb79, 0x1b33: 0xbb91, 0x1b34: 0xbba9, 0x1b35: 0xbbc1, - 0x1b36: 0xbbd9, 0x1b37: 0x2109, 0x1b38: 0x1111, 0x1b39: 0x1429, 0x1b3a: 0xbbf1, 0x1b3b: 0xbc09, - 0x1b3c: 0xbc21, 0x1b3d: 0x10e1, 0x1b3e: 0x10f9, 0x1b3f: 0xbc39, + 0x1b00: 0x2291, 0x1b01: 0x2299, 0x1b02: 0x22a1, 0x1b03: 0x04d1, 0x1b04: 0x05f9, 0x1b05: 0x22a9, + 0x1b06: 0x22b1, 0x1b07: 0x22b9, 0x1b08: 0x22c1, 0x1b09: 0x22c9, 0x1b0a: 0x22d1, 0x1b0b: 0x0799, + 0x1b0c: 0x03c1, 0x1b0d: 0x22d9, 0x1b0e: 0x22d9, 0x1b0f: 0x22e1, 0x1b10: 0x22e9, 0x1b11: 0x03b1, + 0x1b12: 0x03b9, 0x1b13: 0x22f1, 0x1b14: 0x0769, 0x1b15: 0x2301, 0x1b16: 0x2291, 0x1b17: 0x04d1, + 0x1b18: 0x22a9, 0x1b19: 0x03b1, 0x1b1a: 0x03c1, 0x1b1b: 0x0799, 0x1b1c: 0x2289, 0x1b1d: 0x0399, + 0x1b1e: 0x03a1, 0x1b1f: 0x03a9, 0x1b20: 0x2291, 0x1b21: 0x2299, 0x1b22: 0x22a1, 0x1b23: 0x04d1, + 0x1b24: 0x05f9, 0x1b25: 0x22a9, 0x1b26: 0x22b1, 0x1b27: 0x22b9, 0x1b28: 0x22c1, 0x1b29: 0x22c9, + 0x1b2a: 0x22d1, 0x1b2b: 0x0799, 0x1b2c: 0x03c1, 0x1b2d: 0x04d1, 0x1b2e: 0x22d9, 0x1b2f: 0x22e1, + 0x1b30: 0x22e9, 0x1b31: 0x03b1, 0x1b32: 0x03b9, 0x1b33: 0x22f1, 0x1b34: 0x0769, 0x1b35: 0x22f9, + 0x1b36: 0x2289, 0x1b37: 0x0399, 0x1b38: 0x03a1, 0x1b39: 0x03a9, 0x1b3a: 0x2291, 0x1b3b: 0x2299, + 0x1b3c: 0x22a1, 0x1b3d: 0x04d1, 0x1b3e: 0x05f9, 0x1b3f: 0x22a9, // Block 0x6d, offset 0x1b40 - 0x1b40: 0x2079, 0x1b41: 0xbc51, 0x1b42: 0xbb01, 0x1b43: 0x1099, 0x1b44: 0x10b1, 0x1b45: 0x10c9, - 0x1b46: 0xbb19, 0x1b47: 0xbb31, 0x1b48: 0xbb49, 0x1b49: 0x1429, 0x1b4a: 0x1a31, 0x1b4b: 0xbb61, - 0x1b4c: 0xbb79, 0x1b4d: 0xbb91, 0x1b4e: 0xbba9, 0x1b4f: 0xbbc1, 0x1b50: 0xbbd9, 0x1b51: 0x2109, - 0x1b52: 0x1111, 0x1b53: 0xbbf1, 0x1b54: 0xbbf1, 0x1b55: 0xbc09, 0x1b56: 0xbc21, 0x1b57: 0x10e1, - 0x1b58: 0x10f9, 0x1b59: 0xbc39, 0x1b5a: 0x2079, 0x1b5b: 0xbc71, 0x1b5c: 0xbb19, 0x1b5d: 0x1429, - 0x1b5e: 0xbb61, 0x1b5f: 0x10e1, 0x1b60: 0x1111, 0x1b61: 0x2109, 0x1b62: 0xbb01, 0x1b63: 0x1099, - 0x1b64: 0x10b1, 0x1b65: 0x10c9, 0x1b66: 0xbb19, 0x1b67: 0xbb31, 0x1b68: 0xbb49, 0x1b69: 0x1429, - 0x1b6a: 0x1a31, 0x1b6b: 0xbb61, 0x1b6c: 0xbb79, 0x1b6d: 0xbb91, 0x1b6e: 0xbba9, 0x1b6f: 0xbbc1, - 0x1b70: 0xbbd9, 0x1b71: 0x2109, 0x1b72: 0x1111, 0x1b73: 0x1429, 0x1b74: 0xbbf1, 0x1b75: 0xbc09, - 0x1b76: 0xbc21, 0x1b77: 0x10e1, 0x1b78: 0x10f9, 0x1b79: 0xbc39, 0x1b7a: 0x2079, 0x1b7b: 0xbc51, - 0x1b7c: 0xbb01, 0x1b7d: 0x1099, 0x1b7e: 0x10b1, 0x1b7f: 0x10c9, + 0x1b40: 0x22b1, 0x1b41: 0x22b9, 0x1b42: 0x22c1, 0x1b43: 0x22c9, 0x1b44: 0x22d1, 0x1b45: 0x0799, + 0x1b46: 0x03c1, 0x1b47: 0x22d9, 0x1b48: 0x22d9, 0x1b49: 0x22e1, 0x1b4a: 0x22e9, 0x1b4b: 0x03b1, + 0x1b4c: 0x03b9, 0x1b4d: 0x22f1, 0x1b4e: 0x0769, 0x1b4f: 0x2301, 0x1b50: 0x2291, 0x1b51: 0x04d1, + 0x1b52: 0x22a9, 0x1b53: 0x03b1, 0x1b54: 0x03c1, 0x1b55: 0x0799, 0x1b56: 0x2289, 0x1b57: 0x0399, + 0x1b58: 0x03a1, 0x1b59: 0x03a9, 0x1b5a: 0x2291, 0x1b5b: 0x2299, 0x1b5c: 0x22a1, 0x1b5d: 0x04d1, + 0x1b5e: 0x05f9, 0x1b5f: 0x22a9, 0x1b60: 0x22b1, 0x1b61: 0x22b9, 0x1b62: 0x22c1, 0x1b63: 0x22c9, + 0x1b64: 0x22d1, 0x1b65: 0x0799, 0x1b66: 0x03c1, 0x1b67: 0x04d1, 0x1b68: 0x22d9, 0x1b69: 0x22e1, + 0x1b6a: 0x22e9, 0x1b6b: 0x03b1, 0x1b6c: 0x03b9, 0x1b6d: 0x22f1, 0x1b6e: 0x0769, 0x1b6f: 0x22f9, + 0x1b70: 0x2289, 0x1b71: 0x0399, 0x1b72: 0x03a1, 0x1b73: 0x03a9, 0x1b74: 0x2291, 0x1b75: 0x2299, + 0x1b76: 0x22a1, 0x1b77: 0x04d1, 0x1b78: 0x05f9, 0x1b79: 0x22a9, 0x1b7a: 0x22b1, 0x1b7b: 0x22b9, + 0x1b7c: 0x22c1, 0x1b7d: 0x22c9, 0x1b7e: 0x22d1, 0x1b7f: 0x0799, // Block 0x6e, offset 0x1b80 - 0x1b80: 0xbb19, 0x1b81: 0xbb31, 0x1b82: 0xbb49, 0x1b83: 0x1429, 0x1b84: 0x1a31, 0x1b85: 0xbb61, - 0x1b86: 0xbb79, 0x1b87: 0xbb91, 0x1b88: 0xbba9, 0x1b89: 0xbbc1, 0x1b8a: 0xbbd9, 0x1b8b: 0x2109, - 0x1b8c: 0x1111, 0x1b8d: 0xbbf1, 0x1b8e: 0xbbf1, 0x1b8f: 0xbc09, 0x1b90: 0xbc21, 0x1b91: 0x10e1, - 0x1b92: 0x10f9, 0x1b93: 0xbc39, 0x1b94: 0x2079, 0x1b95: 0xbc71, 0x1b96: 0xbb19, 0x1b97: 0x1429, - 0x1b98: 0xbb61, 0x1b99: 0x10e1, 0x1b9a: 0x1111, 0x1b9b: 0x2109, 0x1b9c: 0xbb01, 0x1b9d: 0x1099, - 0x1b9e: 0x10b1, 0x1b9f: 0x10c9, 0x1ba0: 0xbb19, 0x1ba1: 0xbb31, 0x1ba2: 0xbb49, 0x1ba3: 0x1429, - 0x1ba4: 0x1a31, 0x1ba5: 0xbb61, 0x1ba6: 0xbb79, 0x1ba7: 0xbb91, 0x1ba8: 0xbba9, 0x1ba9: 0xbbc1, - 0x1baa: 0xbbd9, 0x1bab: 0x2109, 0x1bac: 0x1111, 0x1bad: 0x1429, 0x1bae: 0xbbf1, 0x1baf: 0xbc09, - 0x1bb0: 0xbc21, 0x1bb1: 0x10e1, 0x1bb2: 0x10f9, 0x1bb3: 0xbc39, 0x1bb4: 0x2079, 0x1bb5: 0xbc51, - 0x1bb6: 0xbb01, 0x1bb7: 0x1099, 0x1bb8: 0x10b1, 0x1bb9: 0x10c9, 0x1bba: 0xbb19, 0x1bbb: 0xbb31, - 0x1bbc: 0xbb49, 0x1bbd: 0x1429, 0x1bbe: 0x1a31, 0x1bbf: 0xbb61, + 0x1b80: 0x03c1, 0x1b81: 0x22d9, 0x1b82: 0x22d9, 0x1b83: 0x22e1, 0x1b84: 0x22e9, 0x1b85: 0x03b1, + 0x1b86: 0x03b9, 0x1b87: 0x22f1, 0x1b88: 0x0769, 0x1b89: 0x2301, 0x1b8a: 0x2291, 0x1b8b: 0x04d1, + 0x1b8c: 0x22a9, 0x1b8d: 0x03b1, 0x1b8e: 0x03c1, 0x1b8f: 0x0799, 0x1b90: 0x2289, 0x1b91: 0x0399, + 0x1b92: 0x03a1, 0x1b93: 0x03a9, 0x1b94: 0x2291, 0x1b95: 0x2299, 0x1b96: 0x22a1, 0x1b97: 0x04d1, + 0x1b98: 0x05f9, 0x1b99: 0x22a9, 0x1b9a: 0x22b1, 0x1b9b: 0x22b9, 0x1b9c: 0x22c1, 0x1b9d: 0x22c9, + 0x1b9e: 0x22d1, 0x1b9f: 0x0799, 0x1ba0: 0x03c1, 0x1ba1: 0x04d1, 0x1ba2: 0x22d9, 0x1ba3: 0x22e1, + 0x1ba4: 0x22e9, 0x1ba5: 0x03b1, 0x1ba6: 0x03b9, 0x1ba7: 0x22f1, 0x1ba8: 0x0769, 0x1ba9: 0x22f9, + 0x1baa: 0x2289, 0x1bab: 0x0399, 0x1bac: 0x03a1, 0x1bad: 0x03a9, 0x1bae: 0x2291, 0x1baf: 0x2299, + 0x1bb0: 0x22a1, 0x1bb1: 0x04d1, 0x1bb2: 0x05f9, 0x1bb3: 0x22a9, 0x1bb4: 0x22b1, 0x1bb5: 0x22b9, + 0x1bb6: 0x22c1, 0x1bb7: 0x22c9, 0x1bb8: 0x22d1, 0x1bb9: 0x0799, 0x1bba: 0x03c1, 0x1bbb: 0x22d9, + 0x1bbc: 0x22d9, 0x1bbd: 0x22e1, 0x1bbe: 0x22e9, 0x1bbf: 0x03b1, // Block 0x6f, offset 0x1bc0 - 0x1bc0: 0xbb79, 0x1bc1: 0xbb91, 0x1bc2: 0xbba9, 0x1bc3: 0xbbc1, 0x1bc4: 0xbbd9, 0x1bc5: 0x2109, - 0x1bc6: 0x1111, 0x1bc7: 0xbbf1, 0x1bc8: 0xbbf1, 0x1bc9: 0xbc09, 0x1bca: 0xbc21, 0x1bcb: 0x10e1, - 0x1bcc: 0x10f9, 0x1bcd: 0xbc39, 0x1bce: 0x2079, 0x1bcf: 0xbc71, 0x1bd0: 0xbb19, 0x1bd1: 0x1429, - 0x1bd2: 0xbb61, 0x1bd3: 0x10e1, 0x1bd4: 0x1111, 0x1bd5: 0x2109, 0x1bd6: 0xbb01, 0x1bd7: 0x1099, - 0x1bd8: 0x10b1, 0x1bd9: 0x10c9, 0x1bda: 0xbb19, 0x1bdb: 0xbb31, 0x1bdc: 0xbb49, 0x1bdd: 0x1429, - 0x1bde: 0x1a31, 0x1bdf: 0xbb61, 0x1be0: 0xbb79, 0x1be1: 0xbb91, 0x1be2: 0xbba9, 0x1be3: 0xbbc1, - 0x1be4: 0xbbd9, 0x1be5: 0x2109, 0x1be6: 0x1111, 0x1be7: 0x1429, 0x1be8: 0xbbf1, 0x1be9: 0xbc09, - 0x1bea: 0xbc21, 0x1beb: 0x10e1, 0x1bec: 0x10f9, 0x1bed: 0xbc39, 0x1bee: 0x2079, 0x1bef: 0xbc51, - 0x1bf0: 0xbb01, 0x1bf1: 0x1099, 0x1bf2: 0x10b1, 0x1bf3: 0x10c9, 0x1bf4: 0xbb19, 0x1bf5: 0xbb31, - 0x1bf6: 0xbb49, 0x1bf7: 0x1429, 0x1bf8: 0x1a31, 0x1bf9: 0xbb61, 0x1bfa: 0xbb79, 0x1bfb: 0xbb91, - 0x1bfc: 0xbba9, 0x1bfd: 0xbbc1, 0x1bfe: 0xbbd9, 0x1bff: 0x2109, + 0x1bc0: 0x03b9, 0x1bc1: 0x22f1, 0x1bc2: 0x0769, 0x1bc3: 0x2301, 0x1bc4: 0x2291, 0x1bc5: 0x04d1, + 0x1bc6: 0x22a9, 0x1bc7: 0x03b1, 0x1bc8: 0x03c1, 0x1bc9: 0x0799, 0x1bca: 0x2309, 0x1bcb: 0x2309, + 0x1bcc: 0x0040, 0x1bcd: 0x0040, 0x1bce: 0x06e1, 0x1bcf: 0x0049, 0x1bd0: 0x0029, 0x1bd1: 0x0031, + 0x1bd2: 0x06e9, 0x1bd3: 0x06f1, 0x1bd4: 0x06f9, 0x1bd5: 0x0701, 0x1bd6: 0x0709, 0x1bd7: 0x0711, + 0x1bd8: 0x06e1, 0x1bd9: 0x0049, 0x1bda: 0x0029, 0x1bdb: 0x0031, 0x1bdc: 0x06e9, 0x1bdd: 0x06f1, + 0x1bde: 0x06f9, 0x1bdf: 0x0701, 0x1be0: 0x0709, 0x1be1: 0x0711, 0x1be2: 0x06e1, 0x1be3: 0x0049, + 0x1be4: 0x0029, 0x1be5: 0x0031, 0x1be6: 0x06e9, 0x1be7: 0x06f1, 0x1be8: 0x06f9, 0x1be9: 0x0701, + 0x1bea: 0x0709, 0x1beb: 0x0711, 0x1bec: 0x06e1, 0x1bed: 0x0049, 0x1bee: 0x0029, 0x1bef: 0x0031, + 0x1bf0: 0x06e9, 0x1bf1: 0x06f1, 0x1bf2: 0x06f9, 0x1bf3: 0x0701, 0x1bf4: 0x0709, 0x1bf5: 0x0711, + 0x1bf6: 0x06e1, 0x1bf7: 0x0049, 0x1bf8: 0x0029, 0x1bf9: 0x0031, 0x1bfa: 0x06e9, 0x1bfb: 0x06f1, + 0x1bfc: 0x06f9, 0x1bfd: 0x0701, 0x1bfe: 0x0709, 0x1bff: 0x0711, // Block 0x70, offset 0x1c00 - 0x1c00: 0x1111, 0x1c01: 0xbbf1, 0x1c02: 0xbbf1, 0x1c03: 0xbc09, 0x1c04: 0xbc21, 0x1c05: 0x10e1, - 0x1c06: 0x10f9, 0x1c07: 0xbc39, 0x1c08: 0x2079, 0x1c09: 0xbc71, 0x1c0a: 0xbb19, 0x1c0b: 0x1429, - 0x1c0c: 0xbb61, 0x1c0d: 0x10e1, 0x1c0e: 0x1111, 0x1c0f: 0x2109, 0x1c10: 0xbb01, 0x1c11: 0x1099, - 0x1c12: 0x10b1, 0x1c13: 0x10c9, 0x1c14: 0xbb19, 0x1c15: 0xbb31, 0x1c16: 0xbb49, 0x1c17: 0x1429, - 0x1c18: 0x1a31, 0x1c19: 0xbb61, 0x1c1a: 0xbb79, 0x1c1b: 0xbb91, 0x1c1c: 0xbba9, 0x1c1d: 0xbbc1, - 0x1c1e: 0xbbd9, 0x1c1f: 0x2109, 0x1c20: 0x1111, 0x1c21: 0x1429, 0x1c22: 0xbbf1, 0x1c23: 0xbc09, - 0x1c24: 0xbc21, 0x1c25: 0x10e1, 0x1c26: 0x10f9, 0x1c27: 0xbc39, 0x1c28: 0x2079, 0x1c29: 0xbc51, - 0x1c2a: 0xbb01, 0x1c2b: 0x1099, 0x1c2c: 0x10b1, 0x1c2d: 0x10c9, 0x1c2e: 0xbb19, 0x1c2f: 0xbb31, - 0x1c30: 0xbb49, 0x1c31: 0x1429, 0x1c32: 0x1a31, 0x1c33: 0xbb61, 0x1c34: 0xbb79, 0x1c35: 0xbb91, - 0x1c36: 0xbba9, 0x1c37: 0xbbc1, 0x1c38: 0xbbd9, 0x1c39: 0x2109, 0x1c3a: 0x1111, 0x1c3b: 0xbbf1, - 0x1c3c: 0xbbf1, 0x1c3d: 0xbc09, 0x1c3e: 0xbc21, 0x1c3f: 0x10e1, + 0x1c00: 0xe115, 0x1c01: 0xe115, 0x1c02: 0xe135, 0x1c03: 0xe135, 0x1c04: 0xe115, 0x1c05: 0xe115, + 0x1c06: 0xe175, 0x1c07: 0xe175, 0x1c08: 0xe115, 0x1c09: 0xe115, 0x1c0a: 0xe135, 0x1c0b: 0xe135, + 0x1c0c: 0xe115, 0x1c0d: 0xe115, 0x1c0e: 0xe1f5, 0x1c0f: 0xe1f5, 0x1c10: 0xe115, 0x1c11: 0xe115, + 0x1c12: 0xe135, 0x1c13: 0xe135, 0x1c14: 0xe115, 0x1c15: 0xe115, 0x1c16: 0xe175, 0x1c17: 0xe175, + 0x1c18: 0xe115, 0x1c19: 0xe115, 0x1c1a: 0xe135, 0x1c1b: 0xe135, 0x1c1c: 0xe115, 0x1c1d: 0xe115, + 0x1c1e: 0x8b3d, 0x1c1f: 0x8b3d, 0x1c20: 0x04b5, 0x1c21: 0x04b5, 0x1c22: 0x0a08, 0x1c23: 0x0a08, + 0x1c24: 0x0a08, 0x1c25: 0x0a08, 0x1c26: 0x0a08, 0x1c27: 0x0a08, 0x1c28: 0x0a08, 0x1c29: 0x0a08, + 0x1c2a: 0x0a08, 0x1c2b: 0x0a08, 0x1c2c: 0x0a08, 0x1c2d: 0x0a08, 0x1c2e: 0x0a08, 0x1c2f: 0x0a08, + 0x1c30: 0x0a08, 0x1c31: 0x0a08, 0x1c32: 0x0a08, 0x1c33: 0x0a08, 0x1c34: 0x0a08, 0x1c35: 0x0a08, + 0x1c36: 0x0a08, 0x1c37: 0x0a08, 0x1c38: 0x0a08, 0x1c39: 0x0a08, 0x1c3a: 0x0a08, 0x1c3b: 0x0a08, + 0x1c3c: 0x0a08, 0x1c3d: 0x0a08, 0x1c3e: 0x0a08, 0x1c3f: 0x0a08, // Block 0x71, offset 0x1c40 - 0x1c40: 0x10f9, 0x1c41: 0xbc39, 0x1c42: 0x2079, 0x1c43: 0xbc71, 0x1c44: 0xbb19, 0x1c45: 0x1429, - 0x1c46: 0xbb61, 0x1c47: 0x10e1, 0x1c48: 0x1111, 0x1c49: 0x2109, 0x1c4a: 0xbc91, 0x1c4b: 0xbc91, - 0x1c4c: 0x0040, 0x1c4d: 0x0040, 0x1c4e: 0x1f41, 0x1c4f: 0x00c9, 0x1c50: 0x0069, 0x1c51: 0x0079, - 0x1c52: 0x1f51, 0x1c53: 0x1f61, 0x1c54: 0x1f71, 0x1c55: 0x1f81, 0x1c56: 0x1f91, 0x1c57: 0x1fa1, - 0x1c58: 0x1f41, 0x1c59: 0x00c9, 0x1c5a: 0x0069, 0x1c5b: 0x0079, 0x1c5c: 0x1f51, 0x1c5d: 0x1f61, - 0x1c5e: 0x1f71, 0x1c5f: 0x1f81, 0x1c60: 0x1f91, 0x1c61: 0x1fa1, 0x1c62: 0x1f41, 0x1c63: 0x00c9, - 0x1c64: 0x0069, 0x1c65: 0x0079, 0x1c66: 0x1f51, 0x1c67: 0x1f61, 0x1c68: 0x1f71, 0x1c69: 0x1f81, - 0x1c6a: 0x1f91, 0x1c6b: 0x1fa1, 0x1c6c: 0x1f41, 0x1c6d: 0x00c9, 0x1c6e: 0x0069, 0x1c6f: 0x0079, - 0x1c70: 0x1f51, 0x1c71: 0x1f61, 0x1c72: 0x1f71, 0x1c73: 0x1f81, 0x1c74: 0x1f91, 0x1c75: 0x1fa1, - 0x1c76: 0x1f41, 0x1c77: 0x00c9, 0x1c78: 0x0069, 0x1c79: 0x0079, 0x1c7a: 0x1f51, 0x1c7b: 0x1f61, - 0x1c7c: 0x1f71, 0x1c7d: 0x1f81, 0x1c7e: 0x1f91, 0x1c7f: 0x1fa1, + 0x1c40: 0x20b1, 0x1c41: 0x20b9, 0x1c42: 0x20d9, 0x1c43: 0x20f1, 0x1c44: 0x0040, 0x1c45: 0x2189, + 0x1c46: 0x2109, 0x1c47: 0x20e1, 0x1c48: 0x2131, 0x1c49: 0x2191, 0x1c4a: 0x2161, 0x1c4b: 0x2169, + 0x1c4c: 0x2171, 0x1c4d: 0x2179, 0x1c4e: 0x2111, 0x1c4f: 0x2141, 0x1c50: 0x2151, 0x1c51: 0x2121, + 0x1c52: 0x2159, 0x1c53: 0x2101, 0x1c54: 0x2119, 0x1c55: 0x20c9, 0x1c56: 0x20d1, 0x1c57: 0x20e9, + 0x1c58: 0x20f9, 0x1c59: 0x2129, 0x1c5a: 0x2139, 0x1c5b: 0x2149, 0x1c5c: 0x2311, 0x1c5d: 0x1689, + 0x1c5e: 0x2319, 0x1c5f: 0x2321, 0x1c60: 0x0040, 0x1c61: 0x20b9, 0x1c62: 0x20d9, 0x1c63: 0x0040, + 0x1c64: 0x2181, 0x1c65: 0x0040, 0x1c66: 0x0040, 0x1c67: 0x20e1, 0x1c68: 0x0040, 0x1c69: 0x2191, + 0x1c6a: 0x2161, 0x1c6b: 0x2169, 0x1c6c: 0x2171, 0x1c6d: 0x2179, 0x1c6e: 0x2111, 0x1c6f: 0x2141, + 0x1c70: 0x2151, 0x1c71: 0x2121, 0x1c72: 0x2159, 0x1c73: 0x0040, 0x1c74: 0x2119, 0x1c75: 0x20c9, + 0x1c76: 0x20d1, 0x1c77: 0x20e9, 0x1c78: 0x0040, 0x1c79: 0x2129, 0x1c7a: 0x0040, 0x1c7b: 0x2149, + 0x1c7c: 0x0040, 0x1c7d: 0x0040, 0x1c7e: 0x0040, 0x1c7f: 0x0040, // Block 0x72, offset 0x1c80 - 0x1c80: 0xe115, 0x1c81: 0xe115, 0x1c82: 0xe135, 0x1c83: 0xe135, 0x1c84: 0xe115, 0x1c85: 0xe115, - 0x1c86: 0xe175, 0x1c87: 0xe175, 0x1c88: 0xe115, 0x1c89: 0xe115, 0x1c8a: 0xe135, 0x1c8b: 0xe135, - 0x1c8c: 0xe115, 0x1c8d: 0xe115, 0x1c8e: 0xe1f5, 0x1c8f: 0xe1f5, 0x1c90: 0xe115, 0x1c91: 0xe115, - 0x1c92: 0xe135, 0x1c93: 0xe135, 0x1c94: 0xe115, 0x1c95: 0xe115, 0x1c96: 0xe175, 0x1c97: 0xe175, - 0x1c98: 0xe115, 0x1c99: 0xe115, 0x1c9a: 0xe135, 0x1c9b: 0xe135, 0x1c9c: 0xe115, 0x1c9d: 0xe115, - 0x1c9e: 0x8b3d, 0x1c9f: 0x8b3d, 0x1ca0: 0x04b5, 0x1ca1: 0x04b5, 0x1ca2: 0x0a08, 0x1ca3: 0x0a08, - 0x1ca4: 0x0a08, 0x1ca5: 0x0a08, 0x1ca6: 0x0a08, 0x1ca7: 0x0a08, 0x1ca8: 0x0a08, 0x1ca9: 0x0a08, - 0x1caa: 0x0a08, 0x1cab: 0x0a08, 0x1cac: 0x0a08, 0x1cad: 0x0a08, 0x1cae: 0x0a08, 0x1caf: 0x0a08, - 0x1cb0: 0x0a08, 0x1cb1: 0x0a08, 0x1cb2: 0x0a08, 0x1cb3: 0x0a08, 0x1cb4: 0x0a08, 0x1cb5: 0x0a08, - 0x1cb6: 0x0a08, 0x1cb7: 0x0a08, 0x1cb8: 0x0a08, 0x1cb9: 0x0a08, 0x1cba: 0x0a08, 0x1cbb: 0x0a08, - 0x1cbc: 0x0a08, 0x1cbd: 0x0a08, 0x1cbe: 0x0a08, 0x1cbf: 0x0a08, + 0x1c80: 0x0040, 0x1c81: 0x0040, 0x1c82: 0x20d9, 0x1c83: 0x0040, 0x1c84: 0x0040, 0x1c85: 0x0040, + 0x1c86: 0x0040, 0x1c87: 0x20e1, 0x1c88: 0x0040, 0x1c89: 0x2191, 0x1c8a: 0x0040, 0x1c8b: 0x2169, + 0x1c8c: 0x0040, 0x1c8d: 0x2179, 0x1c8e: 0x2111, 0x1c8f: 0x2141, 0x1c90: 0x0040, 0x1c91: 0x2121, + 0x1c92: 0x2159, 0x1c93: 0x0040, 0x1c94: 0x2119, 0x1c95: 0x0040, 0x1c96: 0x0040, 0x1c97: 0x20e9, + 0x1c98: 0x0040, 0x1c99: 0x2129, 0x1c9a: 0x0040, 0x1c9b: 0x2149, 0x1c9c: 0x0040, 0x1c9d: 0x1689, + 0x1c9e: 0x0040, 0x1c9f: 0x2321, 0x1ca0: 0x0040, 0x1ca1: 0x20b9, 0x1ca2: 0x20d9, 0x1ca3: 0x0040, + 0x1ca4: 0x2181, 0x1ca5: 0x0040, 0x1ca6: 0x0040, 0x1ca7: 0x20e1, 0x1ca8: 0x2131, 0x1ca9: 0x2191, + 0x1caa: 0x2161, 0x1cab: 0x0040, 0x1cac: 0x2171, 0x1cad: 0x2179, 0x1cae: 0x2111, 0x1caf: 0x2141, + 0x1cb0: 0x2151, 0x1cb1: 0x2121, 0x1cb2: 0x2159, 0x1cb3: 0x0040, 0x1cb4: 0x2119, 0x1cb5: 0x20c9, + 0x1cb6: 0x20d1, 0x1cb7: 0x20e9, 0x1cb8: 0x0040, 0x1cb9: 0x2129, 0x1cba: 0x2139, 0x1cbb: 0x2149, + 0x1cbc: 0x2311, 0x1cbd: 0x0040, 0x1cbe: 0x2319, 0x1cbf: 0x0040, // Block 0x73, offset 0x1cc0 - 0x1cc0: 0xb1d9, 0x1cc1: 0xb1f1, 0x1cc2: 0xb251, 0x1cc3: 0xb299, 0x1cc4: 0x0040, 0x1cc5: 0xb461, - 0x1cc6: 0xb2e1, 0x1cc7: 0xb269, 0x1cc8: 0xb359, 0x1cc9: 0xb479, 0x1cca: 0xb3e9, 0x1ccb: 0xb401, - 0x1ccc: 0xb419, 0x1ccd: 0xb431, 0x1cce: 0xb2f9, 0x1ccf: 0xb389, 0x1cd0: 0xb3b9, 0x1cd1: 0xb329, - 0x1cd2: 0xb3d1, 0x1cd3: 0xb2c9, 0x1cd4: 0xb311, 0x1cd5: 0xb221, 0x1cd6: 0xb239, 0x1cd7: 0xb281, - 0x1cd8: 0xb2b1, 0x1cd9: 0xb341, 0x1cda: 0xb371, 0x1cdb: 0xb3a1, 0x1cdc: 0xbca9, 0x1cdd: 0x7999, - 0x1cde: 0xbcc1, 0x1cdf: 0xbcd9, 0x1ce0: 0x0040, 0x1ce1: 0xb1f1, 0x1ce2: 0xb251, 0x1ce3: 0x0040, - 0x1ce4: 0xb449, 0x1ce5: 0x0040, 0x1ce6: 0x0040, 0x1ce7: 0xb269, 0x1ce8: 0x0040, 0x1ce9: 0xb479, - 0x1cea: 0xb3e9, 0x1ceb: 0xb401, 0x1cec: 0xb419, 0x1ced: 0xb431, 0x1cee: 0xb2f9, 0x1cef: 0xb389, - 0x1cf0: 0xb3b9, 0x1cf1: 0xb329, 0x1cf2: 0xb3d1, 0x1cf3: 0x0040, 0x1cf4: 0xb311, 0x1cf5: 0xb221, - 0x1cf6: 0xb239, 0x1cf7: 0xb281, 0x1cf8: 0x0040, 0x1cf9: 0xb341, 0x1cfa: 0x0040, 0x1cfb: 0xb3a1, + 0x1cc0: 0x20b1, 0x1cc1: 0x20b9, 0x1cc2: 0x20d9, 0x1cc3: 0x20f1, 0x1cc4: 0x2181, 0x1cc5: 0x2189, + 0x1cc6: 0x2109, 0x1cc7: 0x20e1, 0x1cc8: 0x2131, 0x1cc9: 0x2191, 0x1cca: 0x0040, 0x1ccb: 0x2169, + 0x1ccc: 0x2171, 0x1ccd: 0x2179, 0x1cce: 0x2111, 0x1ccf: 0x2141, 0x1cd0: 0x2151, 0x1cd1: 0x2121, + 0x1cd2: 0x2159, 0x1cd3: 0x2101, 0x1cd4: 0x2119, 0x1cd5: 0x20c9, 0x1cd6: 0x20d1, 0x1cd7: 0x20e9, + 0x1cd8: 0x20f9, 0x1cd9: 0x2129, 0x1cda: 0x2139, 0x1cdb: 0x2149, 0x1cdc: 0x0040, 0x1cdd: 0x0040, + 0x1cde: 0x0040, 0x1cdf: 0x0040, 0x1ce0: 0x0040, 0x1ce1: 0x20b9, 0x1ce2: 0x20d9, 0x1ce3: 0x20f1, + 0x1ce4: 0x0040, 0x1ce5: 0x2189, 0x1ce6: 0x2109, 0x1ce7: 0x20e1, 0x1ce8: 0x2131, 0x1ce9: 0x2191, + 0x1cea: 0x0040, 0x1ceb: 0x2169, 0x1cec: 0x2171, 0x1ced: 0x2179, 0x1cee: 0x2111, 0x1cef: 0x2141, + 0x1cf0: 0x2151, 0x1cf1: 0x2121, 0x1cf2: 0x2159, 0x1cf3: 0x2101, 0x1cf4: 0x2119, 0x1cf5: 0x20c9, + 0x1cf6: 0x20d1, 0x1cf7: 0x20e9, 0x1cf8: 0x20f9, 0x1cf9: 0x2129, 0x1cfa: 0x2139, 0x1cfb: 0x2149, 0x1cfc: 0x0040, 0x1cfd: 0x0040, 0x1cfe: 0x0040, 0x1cff: 0x0040, // Block 0x74, offset 0x1d00 - 0x1d00: 0x0040, 0x1d01: 0x0040, 0x1d02: 0xb251, 0x1d03: 0x0040, 0x1d04: 0x0040, 0x1d05: 0x0040, - 0x1d06: 0x0040, 0x1d07: 0xb269, 0x1d08: 0x0040, 0x1d09: 0xb479, 0x1d0a: 0x0040, 0x1d0b: 0xb401, - 0x1d0c: 0x0040, 0x1d0d: 0xb431, 0x1d0e: 0xb2f9, 0x1d0f: 0xb389, 0x1d10: 0x0040, 0x1d11: 0xb329, - 0x1d12: 0xb3d1, 0x1d13: 0x0040, 0x1d14: 0xb311, 0x1d15: 0x0040, 0x1d16: 0x0040, 0x1d17: 0xb281, - 0x1d18: 0x0040, 0x1d19: 0xb341, 0x1d1a: 0x0040, 0x1d1b: 0xb3a1, 0x1d1c: 0x0040, 0x1d1d: 0x7999, - 0x1d1e: 0x0040, 0x1d1f: 0xbcd9, 0x1d20: 0x0040, 0x1d21: 0xb1f1, 0x1d22: 0xb251, 0x1d23: 0x0040, - 0x1d24: 0xb449, 0x1d25: 0x0040, 0x1d26: 0x0040, 0x1d27: 0xb269, 0x1d28: 0xb359, 0x1d29: 0xb479, - 0x1d2a: 0xb3e9, 0x1d2b: 0x0040, 0x1d2c: 0xb419, 0x1d2d: 0xb431, 0x1d2e: 0xb2f9, 0x1d2f: 0xb389, - 0x1d30: 0xb3b9, 0x1d31: 0xb329, 0x1d32: 0xb3d1, 0x1d33: 0x0040, 0x1d34: 0xb311, 0x1d35: 0xb221, - 0x1d36: 0xb239, 0x1d37: 0xb281, 0x1d38: 0x0040, 0x1d39: 0xb341, 0x1d3a: 0xb371, 0x1d3b: 0xb3a1, - 0x1d3c: 0xbca9, 0x1d3d: 0x0040, 0x1d3e: 0xbcc1, 0x1d3f: 0x0040, + 0x1d00: 0x0040, 0x1d01: 0x232a, 0x1d02: 0x2332, 0x1d03: 0x233a, 0x1d04: 0x2342, 0x1d05: 0x234a, + 0x1d06: 0x2352, 0x1d07: 0x235a, 0x1d08: 0x2362, 0x1d09: 0x236a, 0x1d0a: 0x2372, 0x1d0b: 0x0018, + 0x1d0c: 0x0018, 0x1d0d: 0x0018, 0x1d0e: 0x0018, 0x1d0f: 0x0018, 0x1d10: 0x237a, 0x1d11: 0x2382, + 0x1d12: 0x238a, 0x1d13: 0x2392, 0x1d14: 0x239a, 0x1d15: 0x23a2, 0x1d16: 0x23aa, 0x1d17: 0x23b2, + 0x1d18: 0x23ba, 0x1d19: 0x23c2, 0x1d1a: 0x23ca, 0x1d1b: 0x23d2, 0x1d1c: 0x23da, 0x1d1d: 0x23e2, + 0x1d1e: 0x23ea, 0x1d1f: 0x23f2, 0x1d20: 0x23fa, 0x1d21: 0x2402, 0x1d22: 0x240a, 0x1d23: 0x2412, + 0x1d24: 0x241a, 0x1d25: 0x2422, 0x1d26: 0x242a, 0x1d27: 0x2432, 0x1d28: 0x243a, 0x1d29: 0x2442, + 0x1d2a: 0x2449, 0x1d2b: 0x03d9, 0x1d2c: 0x00b9, 0x1d2d: 0x1239, 0x1d2e: 0x2451, 0x1d2f: 0x0018, + 0x1d30: 0x0019, 0x1d31: 0x02e9, 0x1d32: 0x03d9, 0x1d33: 0x02f1, 0x1d34: 0x02f9, 0x1d35: 0x03f1, + 0x1d36: 0x0309, 0x1d37: 0x00a9, 0x1d38: 0x0311, 0x1d39: 0x00b1, 0x1d3a: 0x0319, 0x1d3b: 0x0101, + 0x1d3c: 0x0321, 0x1d3d: 0x0329, 0x1d3e: 0x0051, 0x1d3f: 0x0339, // Block 0x75, offset 0x1d40 - 0x1d40: 0xb1d9, 0x1d41: 0xb1f1, 0x1d42: 0xb251, 0x1d43: 0xb299, 0x1d44: 0xb449, 0x1d45: 0xb461, - 0x1d46: 0xb2e1, 0x1d47: 0xb269, 0x1d48: 0xb359, 0x1d49: 0xb479, 0x1d4a: 0x0040, 0x1d4b: 0xb401, - 0x1d4c: 0xb419, 0x1d4d: 0xb431, 0x1d4e: 0xb2f9, 0x1d4f: 0xb389, 0x1d50: 0xb3b9, 0x1d51: 0xb329, - 0x1d52: 0xb3d1, 0x1d53: 0xb2c9, 0x1d54: 0xb311, 0x1d55: 0xb221, 0x1d56: 0xb239, 0x1d57: 0xb281, - 0x1d58: 0xb2b1, 0x1d59: 0xb341, 0x1d5a: 0xb371, 0x1d5b: 0xb3a1, 0x1d5c: 0x0040, 0x1d5d: 0x0040, - 0x1d5e: 0x0040, 0x1d5f: 0x0040, 0x1d60: 0x0040, 0x1d61: 0xb1f1, 0x1d62: 0xb251, 0x1d63: 0xb299, - 0x1d64: 0x0040, 0x1d65: 0xb461, 0x1d66: 0xb2e1, 0x1d67: 0xb269, 0x1d68: 0xb359, 0x1d69: 0xb479, - 0x1d6a: 0x0040, 0x1d6b: 0xb401, 0x1d6c: 0xb419, 0x1d6d: 0xb431, 0x1d6e: 0xb2f9, 0x1d6f: 0xb389, - 0x1d70: 0xb3b9, 0x1d71: 0xb329, 0x1d72: 0xb3d1, 0x1d73: 0xb2c9, 0x1d74: 0xb311, 0x1d75: 0xb221, - 0x1d76: 0xb239, 0x1d77: 0xb281, 0x1d78: 0xb2b1, 0x1d79: 0xb341, 0x1d7a: 0xb371, 0x1d7b: 0xb3a1, - 0x1d7c: 0x0040, 0x1d7d: 0x0040, 0x1d7e: 0x0040, 0x1d7f: 0x0040, + 0x1d40: 0x0751, 0x1d41: 0x00b9, 0x1d42: 0x0089, 0x1d43: 0x0341, 0x1d44: 0x0349, 0x1d45: 0x0391, + 0x1d46: 0x00c1, 0x1d47: 0x0109, 0x1d48: 0x00c9, 0x1d49: 0x04b1, 0x1d4a: 0x2459, 0x1d4b: 0x11f9, + 0x1d4c: 0x2461, 0x1d4d: 0x04d9, 0x1d4e: 0x2469, 0x1d4f: 0x2471, 0x1d50: 0x0018, 0x1d51: 0x0018, + 0x1d52: 0x0018, 0x1d53: 0x0018, 0x1d54: 0x0018, 0x1d55: 0x0018, 0x1d56: 0x0018, 0x1d57: 0x0018, + 0x1d58: 0x0018, 0x1d59: 0x0018, 0x1d5a: 0x0018, 0x1d5b: 0x0018, 0x1d5c: 0x0018, 0x1d5d: 0x0018, + 0x1d5e: 0x0018, 0x1d5f: 0x0018, 0x1d60: 0x0018, 0x1d61: 0x0018, 0x1d62: 0x0018, 0x1d63: 0x0018, + 0x1d64: 0x0018, 0x1d65: 0x0018, 0x1d66: 0x0018, 0x1d67: 0x0018, 0x1d68: 0x0018, 0x1d69: 0x0018, + 0x1d6a: 0x2479, 0x1d6b: 0x2481, 0x1d6c: 0x2489, 0x1d6d: 0x0018, 0x1d6e: 0x0018, 0x1d6f: 0x0018, + 0x1d70: 0x0018, 0x1d71: 0x0018, 0x1d72: 0x0018, 0x1d73: 0x0018, 0x1d74: 0x0018, 0x1d75: 0x0018, + 0x1d76: 0x0018, 0x1d77: 0x0018, 0x1d78: 0x0018, 0x1d79: 0x0018, 0x1d7a: 0x0018, 0x1d7b: 0x0018, + 0x1d7c: 0x0018, 0x1d7d: 0x0018, 0x1d7e: 0x0018, 0x1d7f: 0x0018, // Block 0x76, offset 0x1d80 - 0x1d80: 0x0040, 0x1d81: 0xbcf2, 0x1d82: 0xbd0a, 0x1d83: 0xbd22, 0x1d84: 0xbd3a, 0x1d85: 0xbd52, - 0x1d86: 0xbd6a, 0x1d87: 0xbd82, 0x1d88: 0xbd9a, 0x1d89: 0xbdb2, 0x1d8a: 0xbdca, 0x1d8b: 0x0018, - 0x1d8c: 0x0018, 0x1d8d: 0x0018, 0x1d8e: 0x0018, 0x1d8f: 0x0018, 0x1d90: 0xbde2, 0x1d91: 0xbe02, - 0x1d92: 0xbe22, 0x1d93: 0xbe42, 0x1d94: 0xbe62, 0x1d95: 0xbe82, 0x1d96: 0xbea2, 0x1d97: 0xbec2, - 0x1d98: 0xbee2, 0x1d99: 0xbf02, 0x1d9a: 0xbf22, 0x1d9b: 0xbf42, 0x1d9c: 0xbf62, 0x1d9d: 0xbf82, - 0x1d9e: 0xbfa2, 0x1d9f: 0xbfc2, 0x1da0: 0xbfe2, 0x1da1: 0xc002, 0x1da2: 0xc022, 0x1da3: 0xc042, - 0x1da4: 0xc062, 0x1da5: 0xc082, 0x1da6: 0xc0a2, 0x1da7: 0xc0c2, 0x1da8: 0xc0e2, 0x1da9: 0xc102, - 0x1daa: 0xc121, 0x1dab: 0x1159, 0x1dac: 0x0269, 0x1dad: 0x66a9, 0x1dae: 0xc161, 0x1daf: 0x0018, - 0x1db0: 0x0039, 0x1db1: 0x0ee9, 0x1db2: 0x1159, 0x1db3: 0x0ef9, 0x1db4: 0x0f09, 0x1db5: 0x1199, - 0x1db6: 0x0f31, 0x1db7: 0x0249, 0x1db8: 0x0f41, 0x1db9: 0x0259, 0x1dba: 0x0f51, 0x1dbb: 0x0359, - 0x1dbc: 0x0f61, 0x1dbd: 0x0f71, 0x1dbe: 0x00d9, 0x1dbf: 0x0f99, + 0x1d80: 0x2499, 0x1d81: 0x24a1, 0x1d82: 0x24a9, 0x1d83: 0x0040, 0x1d84: 0x0040, 0x1d85: 0x0040, + 0x1d86: 0x0040, 0x1d87: 0x0040, 0x1d88: 0x0040, 0x1d89: 0x0040, 0x1d8a: 0x0040, 0x1d8b: 0x0040, + 0x1d8c: 0x0040, 0x1d8d: 0x0040, 0x1d8e: 0x0040, 0x1d8f: 0x0040, 0x1d90: 0x24b1, 0x1d91: 0x24b9, + 0x1d92: 0x24c1, 0x1d93: 0x24c9, 0x1d94: 0x24d1, 0x1d95: 0x24d9, 0x1d96: 0x24e1, 0x1d97: 0x24e9, + 0x1d98: 0x24f1, 0x1d99: 0x24f9, 0x1d9a: 0x2501, 0x1d9b: 0x2509, 0x1d9c: 0x2511, 0x1d9d: 0x2519, + 0x1d9e: 0x2521, 0x1d9f: 0x2529, 0x1da0: 0x2531, 0x1da1: 0x2539, 0x1da2: 0x2541, 0x1da3: 0x2549, + 0x1da4: 0x2551, 0x1da5: 0x2559, 0x1da6: 0x2561, 0x1da7: 0x2569, 0x1da8: 0x2571, 0x1da9: 0x2579, + 0x1daa: 0x2581, 0x1dab: 0x2589, 0x1dac: 0x2591, 0x1dad: 0x2599, 0x1dae: 0x25a1, 0x1daf: 0x25a9, + 0x1db0: 0x25b1, 0x1db1: 0x25b9, 0x1db2: 0x25c1, 0x1db3: 0x25c9, 0x1db4: 0x25d1, 0x1db5: 0x25d9, + 0x1db6: 0x25e1, 0x1db7: 0x25e9, 0x1db8: 0x25f1, 0x1db9: 0x25f9, 0x1dba: 0x2601, 0x1dbb: 0x2609, + 0x1dbc: 0x0040, 0x1dbd: 0x0040, 0x1dbe: 0x0040, 0x1dbf: 0x0040, // Block 0x77, offset 0x1dc0 - 0x1dc0: 0x2039, 0x1dc1: 0x0269, 0x1dc2: 0x01d9, 0x1dc3: 0x0fa9, 0x1dc4: 0x0fb9, 0x1dc5: 0x1089, - 0x1dc6: 0x0279, 0x1dc7: 0x0369, 0x1dc8: 0x0289, 0x1dc9: 0x13d1, 0x1dca: 0xc179, 0x1dcb: 0x65e9, - 0x1dcc: 0xc191, 0x1dcd: 0x1441, 0x1dce: 0xc1a9, 0x1dcf: 0xc1c9, 0x1dd0: 0x0018, 0x1dd1: 0x0018, - 0x1dd2: 0x0018, 0x1dd3: 0x0018, 0x1dd4: 0x0018, 0x1dd5: 0x0018, 0x1dd6: 0x0018, 0x1dd7: 0x0018, - 0x1dd8: 0x0018, 0x1dd9: 0x0018, 0x1dda: 0x0018, 0x1ddb: 0x0018, 0x1ddc: 0x0018, 0x1ddd: 0x0018, - 0x1dde: 0x0018, 0x1ddf: 0x0018, 0x1de0: 0x0018, 0x1de1: 0x0018, 0x1de2: 0x0018, 0x1de3: 0x0018, - 0x1de4: 0x0018, 0x1de5: 0x0018, 0x1de6: 0x0018, 0x1de7: 0x0018, 0x1de8: 0x0018, 0x1de9: 0x0018, - 0x1dea: 0xc1e1, 0x1deb: 0xc1f9, 0x1dec: 0xc211, 0x1ded: 0x0018, 0x1dee: 0x0018, 0x1def: 0x0018, - 0x1df0: 0x0018, 0x1df1: 0x0018, 0x1df2: 0x0018, 0x1df3: 0x0018, 0x1df4: 0x0018, 0x1df5: 0x0018, - 0x1df6: 0x0018, 0x1df7: 0x0018, 0x1df8: 0x0018, 0x1df9: 0x0018, 0x1dfa: 0x0018, 0x1dfb: 0x0018, - 0x1dfc: 0x0018, 0x1dfd: 0x0018, 0x1dfe: 0x0018, 0x1dff: 0x0018, + 0x1dc0: 0x2669, 0x1dc1: 0x2671, 0x1dc2: 0x2679, 0x1dc3: 0x8b55, 0x1dc4: 0x2681, 0x1dc5: 0x2689, + 0x1dc6: 0x2691, 0x1dc7: 0x2699, 0x1dc8: 0x26a1, 0x1dc9: 0x26a9, 0x1dca: 0x26b1, 0x1dcb: 0x26b9, + 0x1dcc: 0x26c1, 0x1dcd: 0x8b75, 0x1dce: 0x26c9, 0x1dcf: 0x26d1, 0x1dd0: 0x26d9, 0x1dd1: 0x26e1, + 0x1dd2: 0x8b95, 0x1dd3: 0x26e9, 0x1dd4: 0x26f1, 0x1dd5: 0x2521, 0x1dd6: 0x8bb5, 0x1dd7: 0x26f9, + 0x1dd8: 0x2701, 0x1dd9: 0x2709, 0x1dda: 0x2711, 0x1ddb: 0x2719, 0x1ddc: 0x8bd5, 0x1ddd: 0x2721, + 0x1dde: 0x2729, 0x1ddf: 0x2731, 0x1de0: 0x2739, 0x1de1: 0x2741, 0x1de2: 0x25f9, 0x1de3: 0x2749, + 0x1de4: 0x2751, 0x1de5: 0x2759, 0x1de6: 0x2761, 0x1de7: 0x2769, 0x1de8: 0x2771, 0x1de9: 0x2779, + 0x1dea: 0x2781, 0x1deb: 0x2789, 0x1dec: 0x2791, 0x1ded: 0x2799, 0x1dee: 0x27a1, 0x1def: 0x27a9, + 0x1df0: 0x27b1, 0x1df1: 0x27b9, 0x1df2: 0x27b9, 0x1df3: 0x27b9, 0x1df4: 0x8bf5, 0x1df5: 0x27c1, + 0x1df6: 0x27c9, 0x1df7: 0x27d1, 0x1df8: 0x8c15, 0x1df9: 0x27d9, 0x1dfa: 0x27e1, 0x1dfb: 0x27e9, + 0x1dfc: 0x27f1, 0x1dfd: 0x27f9, 0x1dfe: 0x2801, 0x1dff: 0x2809, // Block 0x78, offset 0x1e00 - 0x1e00: 0xc241, 0x1e01: 0xc279, 0x1e02: 0xc2b1, 0x1e03: 0x0040, 0x1e04: 0x0040, 0x1e05: 0x0040, - 0x1e06: 0x0040, 0x1e07: 0x0040, 0x1e08: 0x0040, 0x1e09: 0x0040, 0x1e0a: 0x0040, 0x1e0b: 0x0040, - 0x1e0c: 0x0040, 0x1e0d: 0x0040, 0x1e0e: 0x0040, 0x1e0f: 0x0040, 0x1e10: 0xc2d1, 0x1e11: 0xc2f1, - 0x1e12: 0xc311, 0x1e13: 0xc331, 0x1e14: 0xc351, 0x1e15: 0xc371, 0x1e16: 0xc391, 0x1e17: 0xc3b1, - 0x1e18: 0xc3d1, 0x1e19: 0xc3f1, 0x1e1a: 0xc411, 0x1e1b: 0xc431, 0x1e1c: 0xc451, 0x1e1d: 0xc471, - 0x1e1e: 0xc491, 0x1e1f: 0xc4b1, 0x1e20: 0xc4d1, 0x1e21: 0xc4f1, 0x1e22: 0xc511, 0x1e23: 0xc531, - 0x1e24: 0xc551, 0x1e25: 0xc571, 0x1e26: 0xc591, 0x1e27: 0xc5b1, 0x1e28: 0xc5d1, 0x1e29: 0xc5f1, - 0x1e2a: 0xc611, 0x1e2b: 0xc631, 0x1e2c: 0xc651, 0x1e2d: 0xc671, 0x1e2e: 0xc691, 0x1e2f: 0xc6b1, - 0x1e30: 0xc6d1, 0x1e31: 0xc6f1, 0x1e32: 0xc711, 0x1e33: 0xc731, 0x1e34: 0xc751, 0x1e35: 0xc771, - 0x1e36: 0xc791, 0x1e37: 0xc7b1, 0x1e38: 0xc7d1, 0x1e39: 0xc7f1, 0x1e3a: 0xc811, 0x1e3b: 0xc831, - 0x1e3c: 0x0040, 0x1e3d: 0x0040, 0x1e3e: 0x0040, 0x1e3f: 0x0040, + 0x1e00: 0x2811, 0x1e01: 0x2819, 0x1e02: 0x2821, 0x1e03: 0x2829, 0x1e04: 0x2831, 0x1e05: 0x2839, + 0x1e06: 0x2839, 0x1e07: 0x2841, 0x1e08: 0x2849, 0x1e09: 0x2851, 0x1e0a: 0x2859, 0x1e0b: 0x2861, + 0x1e0c: 0x2869, 0x1e0d: 0x2871, 0x1e0e: 0x2879, 0x1e0f: 0x2881, 0x1e10: 0x2889, 0x1e11: 0x2891, + 0x1e12: 0x2899, 0x1e13: 0x28a1, 0x1e14: 0x28a9, 0x1e15: 0x28b1, 0x1e16: 0x28b9, 0x1e17: 0x28c1, + 0x1e18: 0x28c9, 0x1e19: 0x8c35, 0x1e1a: 0x28d1, 0x1e1b: 0x28d9, 0x1e1c: 0x28e1, 0x1e1d: 0x24d9, + 0x1e1e: 0x28e9, 0x1e1f: 0x28f1, 0x1e20: 0x8c55, 0x1e21: 0x8c75, 0x1e22: 0x28f9, 0x1e23: 0x2901, + 0x1e24: 0x2909, 0x1e25: 0x2911, 0x1e26: 0x2919, 0x1e27: 0x2921, 0x1e28: 0x2040, 0x1e29: 0x2929, + 0x1e2a: 0x2931, 0x1e2b: 0x2931, 0x1e2c: 0x8c95, 0x1e2d: 0x2939, 0x1e2e: 0x2941, 0x1e2f: 0x2949, + 0x1e30: 0x2951, 0x1e31: 0x8cb5, 0x1e32: 0x2959, 0x1e33: 0x2961, 0x1e34: 0x2040, 0x1e35: 0x2969, + 0x1e36: 0x2971, 0x1e37: 0x2979, 0x1e38: 0x2981, 0x1e39: 0x2989, 0x1e3a: 0x2991, 0x1e3b: 0x8cd5, + 0x1e3c: 0x2999, 0x1e3d: 0x8cf5, 0x1e3e: 0x29a1, 0x1e3f: 0x29a9, // Block 0x79, offset 0x1e40 - 0x1e40: 0xcb61, 0x1e41: 0xcb81, 0x1e42: 0xcba1, 0x1e43: 0x8b55, 0x1e44: 0xcbc1, 0x1e45: 0xcbe1, - 0x1e46: 0xcc01, 0x1e47: 0xcc21, 0x1e48: 0xcc41, 0x1e49: 0xcc61, 0x1e4a: 0xcc81, 0x1e4b: 0xcca1, - 0x1e4c: 0xccc1, 0x1e4d: 0x8b75, 0x1e4e: 0xcce1, 0x1e4f: 0xcd01, 0x1e50: 0xcd21, 0x1e51: 0xcd41, - 0x1e52: 0x8b95, 0x1e53: 0xcd61, 0x1e54: 0xcd81, 0x1e55: 0xc491, 0x1e56: 0x8bb5, 0x1e57: 0xcda1, - 0x1e58: 0xcdc1, 0x1e59: 0xcde1, 0x1e5a: 0xce01, 0x1e5b: 0xce21, 0x1e5c: 0x8bd5, 0x1e5d: 0xce41, - 0x1e5e: 0xce61, 0x1e5f: 0xce81, 0x1e60: 0xcea1, 0x1e61: 0xcec1, 0x1e62: 0xc7f1, 0x1e63: 0xcee1, - 0x1e64: 0xcf01, 0x1e65: 0xcf21, 0x1e66: 0xcf41, 0x1e67: 0xcf61, 0x1e68: 0xcf81, 0x1e69: 0xcfa1, - 0x1e6a: 0xcfc1, 0x1e6b: 0xcfe1, 0x1e6c: 0xd001, 0x1e6d: 0xd021, 0x1e6e: 0xd041, 0x1e6f: 0xd061, - 0x1e70: 0xd081, 0x1e71: 0xd0a1, 0x1e72: 0xd0a1, 0x1e73: 0xd0a1, 0x1e74: 0x8bf5, 0x1e75: 0xd0c1, - 0x1e76: 0xd0e1, 0x1e77: 0xd101, 0x1e78: 0x8c15, 0x1e79: 0xd121, 0x1e7a: 0xd141, 0x1e7b: 0xd161, - 0x1e7c: 0xd181, 0x1e7d: 0xd1a1, 0x1e7e: 0xd1c1, 0x1e7f: 0xd1e1, + 0x1e40: 0x29b1, 0x1e41: 0x29b9, 0x1e42: 0x29c1, 0x1e43: 0x29c9, 0x1e44: 0x29d1, 0x1e45: 0x29d9, + 0x1e46: 0x29e1, 0x1e47: 0x29e9, 0x1e48: 0x29f1, 0x1e49: 0x8d15, 0x1e4a: 0x29f9, 0x1e4b: 0x2a01, + 0x1e4c: 0x2a09, 0x1e4d: 0x2a11, 0x1e4e: 0x2a19, 0x1e4f: 0x8d35, 0x1e50: 0x2a21, 0x1e51: 0x8d55, + 0x1e52: 0x8d75, 0x1e53: 0x2a29, 0x1e54: 0x2a31, 0x1e55: 0x2a31, 0x1e56: 0x2a39, 0x1e57: 0x8d95, + 0x1e58: 0x8db5, 0x1e59: 0x2a41, 0x1e5a: 0x2a49, 0x1e5b: 0x2a51, 0x1e5c: 0x2a59, 0x1e5d: 0x2a61, + 0x1e5e: 0x2a69, 0x1e5f: 0x2a71, 0x1e60: 0x2a79, 0x1e61: 0x2a81, 0x1e62: 0x2a89, 0x1e63: 0x2a91, + 0x1e64: 0x8dd5, 0x1e65: 0x2a99, 0x1e66: 0x2aa1, 0x1e67: 0x2aa9, 0x1e68: 0x2ab1, 0x1e69: 0x2aa9, + 0x1e6a: 0x2ab9, 0x1e6b: 0x2ac1, 0x1e6c: 0x2ac9, 0x1e6d: 0x2ad1, 0x1e6e: 0x2ad9, 0x1e6f: 0x2ae1, + 0x1e70: 0x2ae9, 0x1e71: 0x2af1, 0x1e72: 0x2af9, 0x1e73: 0x2b01, 0x1e74: 0x2b09, 0x1e75: 0x2b11, + 0x1e76: 0x2b19, 0x1e77: 0x2b21, 0x1e78: 0x8df5, 0x1e79: 0x2b29, 0x1e7a: 0x2b31, 0x1e7b: 0x2b39, + 0x1e7c: 0x2b41, 0x1e7d: 0x2b49, 0x1e7e: 0x8e15, 0x1e7f: 0x2b51, // Block 0x7a, offset 0x1e80 - 0x1e80: 0xd201, 0x1e81: 0xd221, 0x1e82: 0xd241, 0x1e83: 0xd261, 0x1e84: 0xd281, 0x1e85: 0xd2a1, - 0x1e86: 0xd2a1, 0x1e87: 0xd2c1, 0x1e88: 0xd2e1, 0x1e89: 0xd301, 0x1e8a: 0xd321, 0x1e8b: 0xd341, - 0x1e8c: 0xd361, 0x1e8d: 0xd381, 0x1e8e: 0xd3a1, 0x1e8f: 0xd3c1, 0x1e90: 0xd3e1, 0x1e91: 0xd401, - 0x1e92: 0xd421, 0x1e93: 0xd441, 0x1e94: 0xd461, 0x1e95: 0xd481, 0x1e96: 0xd4a1, 0x1e97: 0xd4c1, - 0x1e98: 0xd4e1, 0x1e99: 0x8c35, 0x1e9a: 0xd501, 0x1e9b: 0xd521, 0x1e9c: 0xd541, 0x1e9d: 0xc371, - 0x1e9e: 0xd561, 0x1e9f: 0xd581, 0x1ea0: 0x8c55, 0x1ea1: 0x8c75, 0x1ea2: 0xd5a1, 0x1ea3: 0xd5c1, - 0x1ea4: 0xd5e1, 0x1ea5: 0xd601, 0x1ea6: 0xd621, 0x1ea7: 0xd641, 0x1ea8: 0x2040, 0x1ea9: 0xd661, - 0x1eaa: 0xd681, 0x1eab: 0xd681, 0x1eac: 0x8c95, 0x1ead: 0xd6a1, 0x1eae: 0xd6c1, 0x1eaf: 0xd6e1, - 0x1eb0: 0xd701, 0x1eb1: 0x8cb5, 0x1eb2: 0xd721, 0x1eb3: 0xd741, 0x1eb4: 0x2040, 0x1eb5: 0xd761, - 0x1eb6: 0xd781, 0x1eb7: 0xd7a1, 0x1eb8: 0xd7c1, 0x1eb9: 0xd7e1, 0x1eba: 0xd801, 0x1ebb: 0x8cd5, - 0x1ebc: 0xd821, 0x1ebd: 0x8cf5, 0x1ebe: 0xd841, 0x1ebf: 0xd861, + 0x1e80: 0x2b59, 0x1e81: 0x2b61, 0x1e82: 0x2b69, 0x1e83: 0x2b71, 0x1e84: 0x2b79, 0x1e85: 0x2b81, + 0x1e86: 0x2b89, 0x1e87: 0x2b91, 0x1e88: 0x2b99, 0x1e89: 0x2ba1, 0x1e8a: 0x8e35, 0x1e8b: 0x2ba9, + 0x1e8c: 0x2bb1, 0x1e8d: 0x2bb9, 0x1e8e: 0x2bc1, 0x1e8f: 0x2bc9, 0x1e90: 0x2bd1, 0x1e91: 0x2bd9, + 0x1e92: 0x2be1, 0x1e93: 0x2be9, 0x1e94: 0x2bf1, 0x1e95: 0x2bf9, 0x1e96: 0x2c01, 0x1e97: 0x2c09, + 0x1e98: 0x2c11, 0x1e99: 0x2c19, 0x1e9a: 0x2c21, 0x1e9b: 0x2c29, 0x1e9c: 0x2c31, 0x1e9d: 0x8e55, + 0x1e9e: 0x2c39, 0x1e9f: 0x2c41, 0x1ea0: 0x2c49, 0x1ea1: 0x2c51, 0x1ea2: 0x2c59, 0x1ea3: 0x8e75, + 0x1ea4: 0x2c61, 0x1ea5: 0x2c69, 0x1ea6: 0x2c71, 0x1ea7: 0x2c79, 0x1ea8: 0x2c81, 0x1ea9: 0x2c89, + 0x1eaa: 0x2c91, 0x1eab: 0x2c99, 0x1eac: 0x7f0d, 0x1ead: 0x2ca1, 0x1eae: 0x2ca9, 0x1eaf: 0x2cb1, + 0x1eb0: 0x8e95, 0x1eb1: 0x2cb9, 0x1eb2: 0x2cc1, 0x1eb3: 0x2cc9, 0x1eb4: 0x2cd1, 0x1eb5: 0x2cd9, + 0x1eb6: 0x2ce1, 0x1eb7: 0x8eb5, 0x1eb8: 0x8ed5, 0x1eb9: 0x8ef5, 0x1eba: 0x2ce9, 0x1ebb: 0x8f15, + 0x1ebc: 0x2cf1, 0x1ebd: 0x2cf9, 0x1ebe: 0x2d01, 0x1ebf: 0x2d09, // Block 0x7b, offset 0x1ec0 - 0x1ec0: 0xd881, 0x1ec1: 0xd8a1, 0x1ec2: 0xd8c1, 0x1ec3: 0xd8e1, 0x1ec4: 0xd901, 0x1ec5: 0xd921, - 0x1ec6: 0xd941, 0x1ec7: 0xd961, 0x1ec8: 0xd981, 0x1ec9: 0x8d15, 0x1eca: 0xd9a1, 0x1ecb: 0xd9c1, - 0x1ecc: 0xd9e1, 0x1ecd: 0xda01, 0x1ece: 0xda21, 0x1ecf: 0x8d35, 0x1ed0: 0xda41, 0x1ed1: 0x8d55, - 0x1ed2: 0x8d75, 0x1ed3: 0xda61, 0x1ed4: 0xda81, 0x1ed5: 0xda81, 0x1ed6: 0xdaa1, 0x1ed7: 0x8d95, - 0x1ed8: 0x8db5, 0x1ed9: 0xdac1, 0x1eda: 0xdae1, 0x1edb: 0xdb01, 0x1edc: 0xdb21, 0x1edd: 0xdb41, - 0x1ede: 0xdb61, 0x1edf: 0xdb81, 0x1ee0: 0xdba1, 0x1ee1: 0xdbc1, 0x1ee2: 0xdbe1, 0x1ee3: 0xdc01, - 0x1ee4: 0x8dd5, 0x1ee5: 0xdc21, 0x1ee6: 0xdc41, 0x1ee7: 0xdc61, 0x1ee8: 0xdc81, 0x1ee9: 0xdc61, - 0x1eea: 0xdca1, 0x1eeb: 0xdcc1, 0x1eec: 0xdce1, 0x1eed: 0xdd01, 0x1eee: 0xdd21, 0x1eef: 0xdd41, - 0x1ef0: 0xdd61, 0x1ef1: 0xdd81, 0x1ef2: 0xdda1, 0x1ef3: 0xddc1, 0x1ef4: 0xdde1, 0x1ef5: 0xde01, - 0x1ef6: 0xde21, 0x1ef7: 0xde41, 0x1ef8: 0x8df5, 0x1ef9: 0xde61, 0x1efa: 0xde81, 0x1efb: 0xdea1, - 0x1efc: 0xdec1, 0x1efd: 0xdee1, 0x1efe: 0x8e15, 0x1eff: 0xdf01, + 0x1ec0: 0x2d11, 0x1ec1: 0x2d19, 0x1ec2: 0x2d21, 0x1ec3: 0x2d29, 0x1ec4: 0x2d31, 0x1ec5: 0x2d39, + 0x1ec6: 0x8f35, 0x1ec7: 0x2d41, 0x1ec8: 0x2d49, 0x1ec9: 0x2d51, 0x1eca: 0x2d59, 0x1ecb: 0x2d61, + 0x1ecc: 0x2d69, 0x1ecd: 0x8f55, 0x1ece: 0x2d71, 0x1ecf: 0x2d79, 0x1ed0: 0x8f75, 0x1ed1: 0x8f95, + 0x1ed2: 0x2d81, 0x1ed3: 0x2d89, 0x1ed4: 0x2d91, 0x1ed5: 0x2d99, 0x1ed6: 0x2da1, 0x1ed7: 0x2da9, + 0x1ed8: 0x2db1, 0x1ed9: 0x2db9, 0x1eda: 0x2dc1, 0x1edb: 0x8fb5, 0x1edc: 0x2dc9, 0x1edd: 0x8fd5, + 0x1ede: 0x2dd1, 0x1edf: 0x2040, 0x1ee0: 0x2dd9, 0x1ee1: 0x2de1, 0x1ee2: 0x2de9, 0x1ee3: 0x8ff5, + 0x1ee4: 0x2df1, 0x1ee5: 0x2df9, 0x1ee6: 0x9015, 0x1ee7: 0x9035, 0x1ee8: 0x2e01, 0x1ee9: 0x2e09, + 0x1eea: 0x2e11, 0x1eeb: 0x2e19, 0x1eec: 0x2e21, 0x1eed: 0x2e21, 0x1eee: 0x2e29, 0x1eef: 0x2e31, + 0x1ef0: 0x2e39, 0x1ef1: 0x2e41, 0x1ef2: 0x2e49, 0x1ef3: 0x2e51, 0x1ef4: 0x2e59, 0x1ef5: 0x9055, + 0x1ef6: 0x2e61, 0x1ef7: 0x9075, 0x1ef8: 0x2e69, 0x1ef9: 0x9095, 0x1efa: 0x2e71, 0x1efb: 0x90b5, + 0x1efc: 0x90d5, 0x1efd: 0x90f5, 0x1efe: 0x2e79, 0x1eff: 0x2e81, // Block 0x7c, offset 0x1f00 - 0x1f00: 0xe601, 0x1f01: 0xe621, 0x1f02: 0xe641, 0x1f03: 0xe661, 0x1f04: 0xe681, 0x1f05: 0xe6a1, - 0x1f06: 0x8f35, 0x1f07: 0xe6c1, 0x1f08: 0xe6e1, 0x1f09: 0xe701, 0x1f0a: 0xe721, 0x1f0b: 0xe741, - 0x1f0c: 0xe761, 0x1f0d: 0x8f55, 0x1f0e: 0xe781, 0x1f0f: 0xe7a1, 0x1f10: 0x8f75, 0x1f11: 0x8f95, - 0x1f12: 0xe7c1, 0x1f13: 0xe7e1, 0x1f14: 0xe801, 0x1f15: 0xe821, 0x1f16: 0xe841, 0x1f17: 0xe861, - 0x1f18: 0xe881, 0x1f19: 0xe8a1, 0x1f1a: 0xe8c1, 0x1f1b: 0x8fb5, 0x1f1c: 0xe8e1, 0x1f1d: 0x8fd5, - 0x1f1e: 0xe901, 0x1f1f: 0x2040, 0x1f20: 0xe921, 0x1f21: 0xe941, 0x1f22: 0xe961, 0x1f23: 0x8ff5, - 0x1f24: 0xe981, 0x1f25: 0xe9a1, 0x1f26: 0x9015, 0x1f27: 0x9035, 0x1f28: 0xe9c1, 0x1f29: 0xe9e1, - 0x1f2a: 0xea01, 0x1f2b: 0xea21, 0x1f2c: 0xea41, 0x1f2d: 0xea41, 0x1f2e: 0xea61, 0x1f2f: 0xea81, - 0x1f30: 0xeaa1, 0x1f31: 0xeac1, 0x1f32: 0xeae1, 0x1f33: 0xeb01, 0x1f34: 0xeb21, 0x1f35: 0x9055, - 0x1f36: 0xeb41, 0x1f37: 0x9075, 0x1f38: 0xeb61, 0x1f39: 0x9095, 0x1f3a: 0xeb81, 0x1f3b: 0x90b5, - 0x1f3c: 0x90d5, 0x1f3d: 0x90f5, 0x1f3e: 0xeba1, 0x1f3f: 0xebc1, + 0x1f00: 0x2e89, 0x1f01: 0x9115, 0x1f02: 0x9135, 0x1f03: 0x9155, 0x1f04: 0x9175, 0x1f05: 0x2e91, + 0x1f06: 0x2e99, 0x1f07: 0x2e99, 0x1f08: 0x2ea1, 0x1f09: 0x2ea9, 0x1f0a: 0x2eb1, 0x1f0b: 0x2eb9, + 0x1f0c: 0x2ec1, 0x1f0d: 0x9195, 0x1f0e: 0x2ec9, 0x1f0f: 0x2ed1, 0x1f10: 0x2ed9, 0x1f11: 0x2ee1, + 0x1f12: 0x91b5, 0x1f13: 0x2ee9, 0x1f14: 0x91d5, 0x1f15: 0x91f5, 0x1f16: 0x2ef1, 0x1f17: 0x2ef9, + 0x1f18: 0x2f01, 0x1f19: 0x2f09, 0x1f1a: 0x2f11, 0x1f1b: 0x2f19, 0x1f1c: 0x9215, 0x1f1d: 0x9235, + 0x1f1e: 0x9255, 0x1f1f: 0x2040, 0x1f20: 0x2f21, 0x1f21: 0x9275, 0x1f22: 0x2f29, 0x1f23: 0x2f31, + 0x1f24: 0x2f39, 0x1f25: 0x9295, 0x1f26: 0x2f41, 0x1f27: 0x2f49, 0x1f28: 0x2f51, 0x1f29: 0x2f59, + 0x1f2a: 0x2f61, 0x1f2b: 0x92b5, 0x1f2c: 0x2f69, 0x1f2d: 0x2f71, 0x1f2e: 0x2f79, 0x1f2f: 0x2f81, + 0x1f30: 0x2f89, 0x1f31: 0x2f91, 0x1f32: 0x92d5, 0x1f33: 0x92f5, 0x1f34: 0x2f99, 0x1f35: 0x9315, + 0x1f36: 0x2fa1, 0x1f37: 0x9335, 0x1f38: 0x2fa9, 0x1f39: 0x2fb1, 0x1f3a: 0x2fb9, 0x1f3b: 0x9355, + 0x1f3c: 0x9375, 0x1f3d: 0x2fc1, 0x1f3e: 0x9395, 0x1f3f: 0x2fc9, // Block 0x7d, offset 0x1f40 - 0x1f40: 0xebe1, 0x1f41: 0x9115, 0x1f42: 0x9135, 0x1f43: 0x9155, 0x1f44: 0x9175, 0x1f45: 0xec01, - 0x1f46: 0xec21, 0x1f47: 0xec21, 0x1f48: 0xec41, 0x1f49: 0xec61, 0x1f4a: 0xec81, 0x1f4b: 0xeca1, - 0x1f4c: 0xecc1, 0x1f4d: 0x9195, 0x1f4e: 0xece1, 0x1f4f: 0xed01, 0x1f50: 0xed21, 0x1f51: 0xed41, - 0x1f52: 0x91b5, 0x1f53: 0xed61, 0x1f54: 0x91d5, 0x1f55: 0x91f5, 0x1f56: 0xed81, 0x1f57: 0xeda1, - 0x1f58: 0xedc1, 0x1f59: 0xede1, 0x1f5a: 0xee01, 0x1f5b: 0xee21, 0x1f5c: 0x9215, 0x1f5d: 0x9235, - 0x1f5e: 0x9255, 0x1f5f: 0x2040, 0x1f60: 0xee41, 0x1f61: 0x9275, 0x1f62: 0xee61, 0x1f63: 0xee81, - 0x1f64: 0xeea1, 0x1f65: 0x9295, 0x1f66: 0xeec1, 0x1f67: 0xeee1, 0x1f68: 0xef01, 0x1f69: 0xef21, - 0x1f6a: 0xef41, 0x1f6b: 0x92b5, 0x1f6c: 0xef61, 0x1f6d: 0xef81, 0x1f6e: 0xefa1, 0x1f6f: 0xefc1, - 0x1f70: 0xefe1, 0x1f71: 0xf001, 0x1f72: 0x92d5, 0x1f73: 0x92f5, 0x1f74: 0xf021, 0x1f75: 0x9315, - 0x1f76: 0xf041, 0x1f77: 0x9335, 0x1f78: 0xf061, 0x1f79: 0xf081, 0x1f7a: 0xf0a1, 0x1f7b: 0x9355, - 0x1f7c: 0x9375, 0x1f7d: 0xf0c1, 0x1f7e: 0x9395, 0x1f7f: 0xf0e1, + 0x1f40: 0x93b5, 0x1f41: 0x2fd1, 0x1f42: 0x2fd9, 0x1f43: 0x2fe1, 0x1f44: 0x2fe9, 0x1f45: 0x2ff1, + 0x1f46: 0x2ff9, 0x1f47: 0x93d5, 0x1f48: 0x93f5, 0x1f49: 0x9415, 0x1f4a: 0x9435, 0x1f4b: 0x2a29, + 0x1f4c: 0x3001, 0x1f4d: 0x3009, 0x1f4e: 0x3011, 0x1f4f: 0x3019, 0x1f50: 0x3021, 0x1f51: 0x3029, + 0x1f52: 0x3031, 0x1f53: 0x3039, 0x1f54: 0x3041, 0x1f55: 0x3049, 0x1f56: 0x3051, 0x1f57: 0x9455, + 0x1f58: 0x3059, 0x1f59: 0x3061, 0x1f5a: 0x3069, 0x1f5b: 0x3071, 0x1f5c: 0x3079, 0x1f5d: 0x3081, + 0x1f5e: 0x3089, 0x1f5f: 0x3091, 0x1f60: 0x3099, 0x1f61: 0x30a1, 0x1f62: 0x30a9, 0x1f63: 0x30b1, + 0x1f64: 0x9475, 0x1f65: 0x9495, 0x1f66: 0x94b5, 0x1f67: 0x30b9, 0x1f68: 0x30c1, 0x1f69: 0x30c9, + 0x1f6a: 0x30d1, 0x1f6b: 0x94d5, 0x1f6c: 0x30d9, 0x1f6d: 0x94f5, 0x1f6e: 0x30e1, 0x1f6f: 0x30e9, + 0x1f70: 0x9515, 0x1f71: 0x9535, 0x1f72: 0x30f1, 0x1f73: 0x30f9, 0x1f74: 0x3101, 0x1f75: 0x3109, + 0x1f76: 0x3111, 0x1f77: 0x3119, 0x1f78: 0x3121, 0x1f79: 0x3129, 0x1f7a: 0x3131, 0x1f7b: 0x3139, + 0x1f7c: 0x3141, 0x1f7d: 0x3149, 0x1f7e: 0x3151, 0x1f7f: 0x2040, // Block 0x7e, offset 0x1f80 - 0x1f80: 0xf721, 0x1f81: 0xf741, 0x1f82: 0xf761, 0x1f83: 0xf781, 0x1f84: 0xf7a1, 0x1f85: 0x9555, - 0x1f86: 0xf7c1, 0x1f87: 0xf7e1, 0x1f88: 0xf801, 0x1f89: 0xf821, 0x1f8a: 0xf841, 0x1f8b: 0x9575, - 0x1f8c: 0x9595, 0x1f8d: 0xf861, 0x1f8e: 0xf881, 0x1f8f: 0xf8a1, 0x1f90: 0xf8c1, 0x1f91: 0xf8e1, - 0x1f92: 0xf901, 0x1f93: 0x95b5, 0x1f94: 0xf921, 0x1f95: 0xf941, 0x1f96: 0xf961, 0x1f97: 0xf981, - 0x1f98: 0x95d5, 0x1f99: 0x95f5, 0x1f9a: 0xf9a1, 0x1f9b: 0xf9c1, 0x1f9c: 0xf9e1, 0x1f9d: 0x9615, - 0x1f9e: 0xfa01, 0x1f9f: 0xfa21, 0x1fa0: 0x684d, 0x1fa1: 0x9635, 0x1fa2: 0xfa41, 0x1fa3: 0xfa61, - 0x1fa4: 0xfa81, 0x1fa5: 0x9655, 0x1fa6: 0xfaa1, 0x1fa7: 0xfac1, 0x1fa8: 0xfae1, 0x1fa9: 0xfb01, - 0x1faa: 0xfb21, 0x1fab: 0xfb41, 0x1fac: 0xfb61, 0x1fad: 0x9675, 0x1fae: 0xfb81, 0x1faf: 0xfba1, - 0x1fb0: 0xfbc1, 0x1fb1: 0x9695, 0x1fb2: 0xfbe1, 0x1fb3: 0xfc01, 0x1fb4: 0xfc21, 0x1fb5: 0xfc41, - 0x1fb6: 0x7b6d, 0x1fb7: 0x96b5, 0x1fb8: 0xfc61, 0x1fb9: 0xfc81, 0x1fba: 0xfca1, 0x1fbb: 0x96d5, - 0x1fbc: 0xfcc1, 0x1fbd: 0x96f5, 0x1fbe: 0xfce1, 0x1fbf: 0xfce1, + 0x1f80: 0x3159, 0x1f81: 0x3161, 0x1f82: 0x3169, 0x1f83: 0x3171, 0x1f84: 0x3179, 0x1f85: 0x9555, + 0x1f86: 0x3181, 0x1f87: 0x3189, 0x1f88: 0x3191, 0x1f89: 0x3199, 0x1f8a: 0x31a1, 0x1f8b: 0x9575, + 0x1f8c: 0x9595, 0x1f8d: 0x31a9, 0x1f8e: 0x31b1, 0x1f8f: 0x31b9, 0x1f90: 0x31c1, 0x1f91: 0x31c9, + 0x1f92: 0x31d1, 0x1f93: 0x95b5, 0x1f94: 0x31d9, 0x1f95: 0x31e1, 0x1f96: 0x31e9, 0x1f97: 0x31f1, + 0x1f98: 0x95d5, 0x1f99: 0x95f5, 0x1f9a: 0x31f9, 0x1f9b: 0x3201, 0x1f9c: 0x3209, 0x1f9d: 0x9615, + 0x1f9e: 0x3211, 0x1f9f: 0x3219, 0x1fa0: 0x684d, 0x1fa1: 0x9635, 0x1fa2: 0x3221, 0x1fa3: 0x3229, + 0x1fa4: 0x3231, 0x1fa5: 0x9655, 0x1fa6: 0x3239, 0x1fa7: 0x3241, 0x1fa8: 0x3249, 0x1fa9: 0x3251, + 0x1faa: 0x3259, 0x1fab: 0x3261, 0x1fac: 0x3269, 0x1fad: 0x9675, 0x1fae: 0x3271, 0x1faf: 0x3279, + 0x1fb0: 0x3281, 0x1fb1: 0x9695, 0x1fb2: 0x3289, 0x1fb3: 0x3291, 0x1fb4: 0x3299, 0x1fb5: 0x32a1, + 0x1fb6: 0x7b6d, 0x1fb7: 0x96b5, 0x1fb8: 0x32a9, 0x1fb9: 0x32b1, 0x1fba: 0x32b9, 0x1fbb: 0x96d5, + 0x1fbc: 0x32c1, 0x1fbd: 0x96f5, 0x1fbe: 0x32c9, 0x1fbf: 0x32c9, // Block 0x7f, offset 0x1fc0 - 0x1fc0: 0xfd01, 0x1fc1: 0x9715, 0x1fc2: 0xfd21, 0x1fc3: 0xfd41, 0x1fc4: 0xfd61, 0x1fc5: 0xfd81, - 0x1fc6: 0xfda1, 0x1fc7: 0xfdc1, 0x1fc8: 0xfde1, 0x1fc9: 0x9735, 0x1fca: 0xfe01, 0x1fcb: 0xfe21, - 0x1fcc: 0xfe41, 0x1fcd: 0xfe61, 0x1fce: 0xfe81, 0x1fcf: 0xfea1, 0x1fd0: 0x9755, 0x1fd1: 0xfec1, - 0x1fd2: 0x9775, 0x1fd3: 0x9795, 0x1fd4: 0x97b5, 0x1fd5: 0xfee1, 0x1fd6: 0xff01, 0x1fd7: 0xff21, - 0x1fd8: 0xff41, 0x1fd9: 0xff61, 0x1fda: 0xff81, 0x1fdb: 0xffa1, 0x1fdc: 0xffc1, 0x1fdd: 0x97d5, + 0x1fc0: 0x32d1, 0x1fc1: 0x9715, 0x1fc2: 0x32d9, 0x1fc3: 0x32e1, 0x1fc4: 0x32e9, 0x1fc5: 0x32f1, + 0x1fc6: 0x32f9, 0x1fc7: 0x3301, 0x1fc8: 0x3309, 0x1fc9: 0x9735, 0x1fca: 0x3311, 0x1fcb: 0x3319, + 0x1fcc: 0x3321, 0x1fcd: 0x3329, 0x1fce: 0x3331, 0x1fcf: 0x3339, 0x1fd0: 0x9755, 0x1fd1: 0x3341, + 0x1fd2: 0x9775, 0x1fd3: 0x9795, 0x1fd4: 0x97b5, 0x1fd5: 0x3349, 0x1fd6: 0x3351, 0x1fd7: 0x3359, + 0x1fd8: 0x3361, 0x1fd9: 0x3369, 0x1fda: 0x3371, 0x1fdb: 0x3379, 0x1fdc: 0x3381, 0x1fdd: 0x97d5, 0x1fde: 0x0040, 0x1fdf: 0x0040, 0x1fe0: 0x0040, 0x1fe1: 0x0040, 0x1fe2: 0x0040, 0x1fe3: 0x0040, 0x1fe4: 0x0040, 0x1fe5: 0x0040, 0x1fe6: 0x0040, 0x1fe7: 0x0040, 0x1fe8: 0x0040, 0x1fe9: 0x0040, 0x1fea: 0x0040, 0x1feb: 0x0040, 0x1fec: 0x0040, 0x1fed: 0x0040, 0x1fee: 0x0040, 0x1fef: 0x0040, @@ -2134,7 +2277,7 @@ var idnaIndex = [2368]uint16{ 0x1b8: 0xd6, 0x1b9: 0xd7, 0x1ba: 0xd8, 0x1bb: 0xd9, 0x1bc: 0xda, 0x1bd: 0xdb, 0x1be: 0xdc, 0x1bf: 0x37, // Block 0x7, offset 0x1c0 0x1c0: 0x38, 0x1c1: 0xdd, 0x1c2: 0xde, 0x1c3: 0xdf, 0x1c4: 0xe0, 0x1c5: 0x39, 0x1c6: 0x3a, 0x1c7: 0xe1, - 0x1c8: 0xe2, 0x1c9: 0x3b, 0x1ca: 0x3c, 0x1cb: 0x3d, 0x1cc: 0x3e, 0x1cd: 0x3f, 0x1ce: 0x40, 0x1cf: 0x41, + 0x1c8: 0xe2, 0x1c9: 0x3b, 0x1ca: 0x3c, 0x1cb: 0x3d, 0x1cc: 0xe3, 0x1cd: 0xe4, 0x1ce: 0x3e, 0x1cf: 0x3f, 0x1d0: 0xa0, 0x1d1: 0xa0, 0x1d2: 0xa0, 0x1d3: 0xa0, 0x1d4: 0xa0, 0x1d5: 0xa0, 0x1d6: 0xa0, 0x1d7: 0xa0, 0x1d8: 0xa0, 0x1d9: 0xa0, 0x1da: 0xa0, 0x1db: 0xa0, 0x1dc: 0xa0, 0x1dd: 0xa0, 0x1de: 0xa0, 0x1df: 0xa0, 0x1e0: 0xa0, 0x1e1: 0xa0, 0x1e2: 0xa0, 0x1e3: 0xa0, 0x1e4: 0xa0, 0x1e5: 0xa0, 0x1e6: 0xa0, 0x1e7: 0xa0, @@ -2167,143 +2310,143 @@ var idnaIndex = [2368]uint16{ 0x2a0: 0xa0, 0x2a1: 0xa0, 0x2a2: 0xa0, 0x2a3: 0xa0, 0x2a4: 0xa0, 0x2a5: 0xa0, 0x2a6: 0xa0, 0x2a7: 0xa0, 0x2a8: 0xa0, 0x2a9: 0xa0, 0x2aa: 0xa0, 0x2ab: 0xa0, 0x2ac: 0xa0, 0x2ad: 0xa0, 0x2ae: 0xa0, 0x2af: 0xa0, 0x2b0: 0xa0, 0x2b1: 0xa0, 0x2b2: 0xa0, 0x2b3: 0xa0, 0x2b4: 0xa0, 0x2b5: 0xa0, 0x2b6: 0xa0, 0x2b7: 0xa0, - 0x2b8: 0xa0, 0x2b9: 0xa0, 0x2ba: 0xa0, 0x2bb: 0xa0, 0x2bc: 0xa0, 0x2bd: 0xa0, 0x2be: 0xa0, 0x2bf: 0xe3, + 0x2b8: 0xa0, 0x2b9: 0xa0, 0x2ba: 0xa0, 0x2bb: 0xa0, 0x2bc: 0xa0, 0x2bd: 0xa0, 0x2be: 0xa0, 0x2bf: 0xe5, // Block 0xb, offset 0x2c0 0x2c0: 0xa0, 0x2c1: 0xa0, 0x2c2: 0xa0, 0x2c3: 0xa0, 0x2c4: 0xa0, 0x2c5: 0xa0, 0x2c6: 0xa0, 0x2c7: 0xa0, 0x2c8: 0xa0, 0x2c9: 0xa0, 0x2ca: 0xa0, 0x2cb: 0xa0, 0x2cc: 0xa0, 0x2cd: 0xa0, 0x2ce: 0xa0, 0x2cf: 0xa0, - 0x2d0: 0xa0, 0x2d1: 0xa0, 0x2d2: 0xe4, 0x2d3: 0xe5, 0x2d4: 0xa0, 0x2d5: 0xa0, 0x2d6: 0xa0, 0x2d7: 0xa0, - 0x2d8: 0xe6, 0x2d9: 0x42, 0x2da: 0x43, 0x2db: 0xe7, 0x2dc: 0x44, 0x2dd: 0x45, 0x2de: 0x46, 0x2df: 0xe8, - 0x2e0: 0xe9, 0x2e1: 0xea, 0x2e2: 0xeb, 0x2e3: 0xec, 0x2e4: 0xed, 0x2e5: 0xee, 0x2e6: 0xef, 0x2e7: 0xf0, - 0x2e8: 0xf1, 0x2e9: 0xf2, 0x2ea: 0xf3, 0x2eb: 0xf4, 0x2ec: 0xf5, 0x2ed: 0xf6, 0x2ee: 0xf7, 0x2ef: 0xf8, + 0x2d0: 0xa0, 0x2d1: 0xa0, 0x2d2: 0xe6, 0x2d3: 0xe7, 0x2d4: 0xa0, 0x2d5: 0xa0, 0x2d6: 0xa0, 0x2d7: 0xa0, + 0x2d8: 0xe8, 0x2d9: 0x40, 0x2da: 0x41, 0x2db: 0xe9, 0x2dc: 0x42, 0x2dd: 0x43, 0x2de: 0x44, 0x2df: 0xea, + 0x2e0: 0xeb, 0x2e1: 0xec, 0x2e2: 0xed, 0x2e3: 0xee, 0x2e4: 0xef, 0x2e5: 0xf0, 0x2e6: 0xf1, 0x2e7: 0xf2, + 0x2e8: 0xf3, 0x2e9: 0xf4, 0x2ea: 0xf5, 0x2eb: 0xf6, 0x2ec: 0xf7, 0x2ed: 0xf8, 0x2ee: 0xf9, 0x2ef: 0xfa, 0x2f0: 0xa0, 0x2f1: 0xa0, 0x2f2: 0xa0, 0x2f3: 0xa0, 0x2f4: 0xa0, 0x2f5: 0xa0, 0x2f6: 0xa0, 0x2f7: 0xa0, 0x2f8: 0xa0, 0x2f9: 0xa0, 0x2fa: 0xa0, 0x2fb: 0xa0, 0x2fc: 0xa0, 0x2fd: 0xa0, 0x2fe: 0xa0, 0x2ff: 0xa0, // Block 0xc, offset 0x300 0x300: 0xa0, 0x301: 0xa0, 0x302: 0xa0, 0x303: 0xa0, 0x304: 0xa0, 0x305: 0xa0, 0x306: 0xa0, 0x307: 0xa0, 0x308: 0xa0, 0x309: 0xa0, 0x30a: 0xa0, 0x30b: 0xa0, 0x30c: 0xa0, 0x30d: 0xa0, 0x30e: 0xa0, 0x30f: 0xa0, 0x310: 0xa0, 0x311: 0xa0, 0x312: 0xa0, 0x313: 0xa0, 0x314: 0xa0, 0x315: 0xa0, 0x316: 0xa0, 0x317: 0xa0, - 0x318: 0xa0, 0x319: 0xa0, 0x31a: 0xa0, 0x31b: 0xa0, 0x31c: 0xa0, 0x31d: 0xa0, 0x31e: 0xf9, 0x31f: 0xfa, + 0x318: 0xa0, 0x319: 0xa0, 0x31a: 0xa0, 0x31b: 0xa0, 0x31c: 0xa0, 0x31d: 0xa0, 0x31e: 0xfb, 0x31f: 0xfc, // Block 0xd, offset 0x340 - 0x340: 0xfb, 0x341: 0xfb, 0x342: 0xfb, 0x343: 0xfb, 0x344: 0xfb, 0x345: 0xfb, 0x346: 0xfb, 0x347: 0xfb, - 0x348: 0xfb, 0x349: 0xfb, 0x34a: 0xfb, 0x34b: 0xfb, 0x34c: 0xfb, 0x34d: 0xfb, 0x34e: 0xfb, 0x34f: 0xfb, - 0x350: 0xfb, 0x351: 0xfb, 0x352: 0xfb, 0x353: 0xfb, 0x354: 0xfb, 0x355: 0xfb, 0x356: 0xfb, 0x357: 0xfb, - 0x358: 0xfb, 0x359: 0xfb, 0x35a: 0xfb, 0x35b: 0xfb, 0x35c: 0xfb, 0x35d: 0xfb, 0x35e: 0xfb, 0x35f: 0xfb, - 0x360: 0xfb, 0x361: 0xfb, 0x362: 0xfb, 0x363: 0xfb, 0x364: 0xfb, 0x365: 0xfb, 0x366: 0xfb, 0x367: 0xfb, - 0x368: 0xfb, 0x369: 0xfb, 0x36a: 0xfb, 0x36b: 0xfb, 0x36c: 0xfb, 0x36d: 0xfb, 0x36e: 0xfb, 0x36f: 0xfb, - 0x370: 0xfb, 0x371: 0xfb, 0x372: 0xfb, 0x373: 0xfb, 0x374: 0xfb, 0x375: 0xfb, 0x376: 0xfb, 0x377: 0xfb, - 0x378: 0xfb, 0x379: 0xfb, 0x37a: 0xfb, 0x37b: 0xfb, 0x37c: 0xfb, 0x37d: 0xfb, 0x37e: 0xfb, 0x37f: 0xfb, + 0x340: 0xfd, 0x341: 0xfd, 0x342: 0xfd, 0x343: 0xfd, 0x344: 0xfd, 0x345: 0xfd, 0x346: 0xfd, 0x347: 0xfd, + 0x348: 0xfd, 0x349: 0xfd, 0x34a: 0xfd, 0x34b: 0xfd, 0x34c: 0xfd, 0x34d: 0xfd, 0x34e: 0xfd, 0x34f: 0xfd, + 0x350: 0xfd, 0x351: 0xfd, 0x352: 0xfd, 0x353: 0xfd, 0x354: 0xfd, 0x355: 0xfd, 0x356: 0xfd, 0x357: 0xfd, + 0x358: 0xfd, 0x359: 0xfd, 0x35a: 0xfd, 0x35b: 0xfd, 0x35c: 0xfd, 0x35d: 0xfd, 0x35e: 0xfd, 0x35f: 0xfd, + 0x360: 0xfd, 0x361: 0xfd, 0x362: 0xfd, 0x363: 0xfd, 0x364: 0xfd, 0x365: 0xfd, 0x366: 0xfd, 0x367: 0xfd, + 0x368: 0xfd, 0x369: 0xfd, 0x36a: 0xfd, 0x36b: 0xfd, 0x36c: 0xfd, 0x36d: 0xfd, 0x36e: 0xfd, 0x36f: 0xfd, + 0x370: 0xfd, 0x371: 0xfd, 0x372: 0xfd, 0x373: 0xfd, 0x374: 0xfd, 0x375: 0xfd, 0x376: 0xfd, 0x377: 0xfd, + 0x378: 0xfd, 0x379: 0xfd, 0x37a: 0xfd, 0x37b: 0xfd, 0x37c: 0xfd, 0x37d: 0xfd, 0x37e: 0xfd, 0x37f: 0xfd, // Block 0xe, offset 0x380 - 0x380: 0xfb, 0x381: 0xfb, 0x382: 0xfb, 0x383: 0xfb, 0x384: 0xfb, 0x385: 0xfb, 0x386: 0xfb, 0x387: 0xfb, - 0x388: 0xfb, 0x389: 0xfb, 0x38a: 0xfb, 0x38b: 0xfb, 0x38c: 0xfb, 0x38d: 0xfb, 0x38e: 0xfb, 0x38f: 0xfb, - 0x390: 0xfb, 0x391: 0xfb, 0x392: 0xfb, 0x393: 0xfb, 0x394: 0xfb, 0x395: 0xfb, 0x396: 0xfb, 0x397: 0xfb, - 0x398: 0xfb, 0x399: 0xfb, 0x39a: 0xfb, 0x39b: 0xfb, 0x39c: 0xfb, 0x39d: 0xfb, 0x39e: 0xfb, 0x39f: 0xfb, - 0x3a0: 0xfb, 0x3a1: 0xfb, 0x3a2: 0xfb, 0x3a3: 0xfb, 0x3a4: 0xfc, 0x3a5: 0xfd, 0x3a6: 0xfe, 0x3a7: 0xff, - 0x3a8: 0x47, 0x3a9: 0x100, 0x3aa: 0x101, 0x3ab: 0x48, 0x3ac: 0x49, 0x3ad: 0x4a, 0x3ae: 0x4b, 0x3af: 0x4c, - 0x3b0: 0x102, 0x3b1: 0x4d, 0x3b2: 0x4e, 0x3b3: 0x4f, 0x3b4: 0x50, 0x3b5: 0x51, 0x3b6: 0x103, 0x3b7: 0x52, - 0x3b8: 0x53, 0x3b9: 0x54, 0x3ba: 0x55, 0x3bb: 0x56, 0x3bc: 0x57, 0x3bd: 0x58, 0x3be: 0x59, 0x3bf: 0x5a, + 0x380: 0xfd, 0x381: 0xfd, 0x382: 0xfd, 0x383: 0xfd, 0x384: 0xfd, 0x385: 0xfd, 0x386: 0xfd, 0x387: 0xfd, + 0x388: 0xfd, 0x389: 0xfd, 0x38a: 0xfd, 0x38b: 0xfd, 0x38c: 0xfd, 0x38d: 0xfd, 0x38e: 0xfd, 0x38f: 0xfd, + 0x390: 0xfd, 0x391: 0xfd, 0x392: 0xfd, 0x393: 0xfd, 0x394: 0xfd, 0x395: 0xfd, 0x396: 0xfd, 0x397: 0xfd, + 0x398: 0xfd, 0x399: 0xfd, 0x39a: 0xfd, 0x39b: 0xfd, 0x39c: 0xfd, 0x39d: 0xfd, 0x39e: 0xfd, 0x39f: 0xfd, + 0x3a0: 0xfd, 0x3a1: 0xfd, 0x3a2: 0xfd, 0x3a3: 0xfd, 0x3a4: 0xfe, 0x3a5: 0xff, 0x3a6: 0x100, 0x3a7: 0x101, + 0x3a8: 0x45, 0x3a9: 0x102, 0x3aa: 0x103, 0x3ab: 0x46, 0x3ac: 0x47, 0x3ad: 0x48, 0x3ae: 0x49, 0x3af: 0x4a, + 0x3b0: 0x104, 0x3b1: 0x4b, 0x3b2: 0x4c, 0x3b3: 0x4d, 0x3b4: 0x4e, 0x3b5: 0x4f, 0x3b6: 0x105, 0x3b7: 0x50, + 0x3b8: 0x51, 0x3b9: 0x52, 0x3ba: 0x53, 0x3bb: 0x54, 0x3bc: 0x55, 0x3bd: 0x56, 0x3be: 0x57, 0x3bf: 0x58, // Block 0xf, offset 0x3c0 - 0x3c0: 0x104, 0x3c1: 0x105, 0x3c2: 0xa0, 0x3c3: 0x106, 0x3c4: 0x107, 0x3c5: 0x9c, 0x3c6: 0x108, 0x3c7: 0x109, - 0x3c8: 0xfb, 0x3c9: 0xfb, 0x3ca: 0x10a, 0x3cb: 0x10b, 0x3cc: 0x10c, 0x3cd: 0x10d, 0x3ce: 0x10e, 0x3cf: 0x10f, - 0x3d0: 0x110, 0x3d1: 0xa0, 0x3d2: 0x111, 0x3d3: 0x112, 0x3d4: 0x113, 0x3d5: 0x114, 0x3d6: 0xfb, 0x3d7: 0xfb, - 0x3d8: 0xa0, 0x3d9: 0xa0, 0x3da: 0xa0, 0x3db: 0xa0, 0x3dc: 0x115, 0x3dd: 0x116, 0x3de: 0xfb, 0x3df: 0xfb, - 0x3e0: 0x117, 0x3e1: 0x118, 0x3e2: 0x119, 0x3e3: 0x11a, 0x3e4: 0x11b, 0x3e5: 0xfb, 0x3e6: 0x11c, 0x3e7: 0x11d, - 0x3e8: 0x11e, 0x3e9: 0x11f, 0x3ea: 0x120, 0x3eb: 0x5b, 0x3ec: 0x121, 0x3ed: 0x122, 0x3ee: 0x5c, 0x3ef: 0xfb, - 0x3f0: 0x123, 0x3f1: 0x124, 0x3f2: 0x125, 0x3f3: 0x126, 0x3f4: 0x127, 0x3f5: 0xfb, 0x3f6: 0xfb, 0x3f7: 0xfb, - 0x3f8: 0xfb, 0x3f9: 0x128, 0x3fa: 0x129, 0x3fb: 0xfb, 0x3fc: 0x12a, 0x3fd: 0x12b, 0x3fe: 0x12c, 0x3ff: 0x12d, + 0x3c0: 0x106, 0x3c1: 0x107, 0x3c2: 0xa0, 0x3c3: 0x108, 0x3c4: 0x109, 0x3c5: 0x9c, 0x3c6: 0x10a, 0x3c7: 0x10b, + 0x3c8: 0xfd, 0x3c9: 0xfd, 0x3ca: 0x10c, 0x3cb: 0x10d, 0x3cc: 0x10e, 0x3cd: 0x10f, 0x3ce: 0x110, 0x3cf: 0x111, + 0x3d0: 0x112, 0x3d1: 0xa0, 0x3d2: 0x113, 0x3d3: 0x114, 0x3d4: 0x115, 0x3d5: 0x116, 0x3d6: 0xfd, 0x3d7: 0xfd, + 0x3d8: 0xa0, 0x3d9: 0xa0, 0x3da: 0xa0, 0x3db: 0xa0, 0x3dc: 0x117, 0x3dd: 0x118, 0x3de: 0xfd, 0x3df: 0xfd, + 0x3e0: 0x119, 0x3e1: 0x11a, 0x3e2: 0x11b, 0x3e3: 0x11c, 0x3e4: 0x11d, 0x3e5: 0xfd, 0x3e6: 0x11e, 0x3e7: 0x11f, + 0x3e8: 0x120, 0x3e9: 0x121, 0x3ea: 0x122, 0x3eb: 0x59, 0x3ec: 0x123, 0x3ed: 0x124, 0x3ee: 0x5a, 0x3ef: 0xfd, + 0x3f0: 0x125, 0x3f1: 0x126, 0x3f2: 0x127, 0x3f3: 0x128, 0x3f4: 0x129, 0x3f5: 0xfd, 0x3f6: 0xfd, 0x3f7: 0xfd, + 0x3f8: 0xfd, 0x3f9: 0x12a, 0x3fa: 0x12b, 0x3fb: 0xfd, 0x3fc: 0x12c, 0x3fd: 0x12d, 0x3fe: 0x12e, 0x3ff: 0x12f, // Block 0x10, offset 0x400 - 0x400: 0x12e, 0x401: 0x12f, 0x402: 0x130, 0x403: 0x131, 0x404: 0x132, 0x405: 0x133, 0x406: 0x134, 0x407: 0x135, - 0x408: 0x136, 0x409: 0xfb, 0x40a: 0x137, 0x40b: 0x138, 0x40c: 0x5d, 0x40d: 0x5e, 0x40e: 0xfb, 0x40f: 0xfb, - 0x410: 0x139, 0x411: 0x13a, 0x412: 0x13b, 0x413: 0x13c, 0x414: 0xfb, 0x415: 0xfb, 0x416: 0x13d, 0x417: 0x13e, - 0x418: 0x13f, 0x419: 0x140, 0x41a: 0x141, 0x41b: 0x142, 0x41c: 0x143, 0x41d: 0xfb, 0x41e: 0xfb, 0x41f: 0xfb, - 0x420: 0x144, 0x421: 0xfb, 0x422: 0x145, 0x423: 0x146, 0x424: 0x5f, 0x425: 0x147, 0x426: 0x148, 0x427: 0x149, - 0x428: 0x14a, 0x429: 0x14b, 0x42a: 0x14c, 0x42b: 0x14d, 0x42c: 0xfb, 0x42d: 0xfb, 0x42e: 0xfb, 0x42f: 0xfb, - 0x430: 0x14e, 0x431: 0x14f, 0x432: 0x150, 0x433: 0xfb, 0x434: 0x151, 0x435: 0x152, 0x436: 0x153, 0x437: 0xfb, - 0x438: 0xfb, 0x439: 0xfb, 0x43a: 0xfb, 0x43b: 0x154, 0x43c: 0xfb, 0x43d: 0xfb, 0x43e: 0x155, 0x43f: 0x156, + 0x400: 0x130, 0x401: 0x131, 0x402: 0x132, 0x403: 0x133, 0x404: 0x134, 0x405: 0x135, 0x406: 0x136, 0x407: 0x137, + 0x408: 0x138, 0x409: 0xfd, 0x40a: 0x139, 0x40b: 0x13a, 0x40c: 0x5b, 0x40d: 0x5c, 0x40e: 0xfd, 0x40f: 0xfd, + 0x410: 0x13b, 0x411: 0x13c, 0x412: 0x13d, 0x413: 0x13e, 0x414: 0xfd, 0x415: 0xfd, 0x416: 0x13f, 0x417: 0x140, + 0x418: 0x141, 0x419: 0x142, 0x41a: 0x143, 0x41b: 0x144, 0x41c: 0x145, 0x41d: 0xfd, 0x41e: 0xfd, 0x41f: 0xfd, + 0x420: 0x146, 0x421: 0xfd, 0x422: 0x147, 0x423: 0x148, 0x424: 0x5d, 0x425: 0x149, 0x426: 0x14a, 0x427: 0x14b, + 0x428: 0x14c, 0x429: 0x14d, 0x42a: 0x14e, 0x42b: 0x14f, 0x42c: 0xfd, 0x42d: 0xfd, 0x42e: 0xfd, 0x42f: 0xfd, + 0x430: 0x150, 0x431: 0x151, 0x432: 0x152, 0x433: 0xfd, 0x434: 0x153, 0x435: 0x154, 0x436: 0x155, 0x437: 0xfd, + 0x438: 0xfd, 0x439: 0xfd, 0x43a: 0xfd, 0x43b: 0x156, 0x43c: 0xfd, 0x43d: 0xfd, 0x43e: 0x157, 0x43f: 0x158, // Block 0x11, offset 0x440 0x440: 0xa0, 0x441: 0xa0, 0x442: 0xa0, 0x443: 0xa0, 0x444: 0xa0, 0x445: 0xa0, 0x446: 0xa0, 0x447: 0xa0, - 0x448: 0xa0, 0x449: 0xa0, 0x44a: 0xa0, 0x44b: 0xa0, 0x44c: 0xa0, 0x44d: 0xa0, 0x44e: 0x157, 0x44f: 0xfb, - 0x450: 0x9c, 0x451: 0x158, 0x452: 0xa0, 0x453: 0xa0, 0x454: 0xa0, 0x455: 0x159, 0x456: 0xfb, 0x457: 0xfb, - 0x458: 0xfb, 0x459: 0xfb, 0x45a: 0xfb, 0x45b: 0xfb, 0x45c: 0xfb, 0x45d: 0xfb, 0x45e: 0xfb, 0x45f: 0xfb, - 0x460: 0xfb, 0x461: 0xfb, 0x462: 0xfb, 0x463: 0xfb, 0x464: 0xfb, 0x465: 0xfb, 0x466: 0xfb, 0x467: 0xfb, - 0x468: 0xfb, 0x469: 0xfb, 0x46a: 0xfb, 0x46b: 0xfb, 0x46c: 0xfb, 0x46d: 0xfb, 0x46e: 0xfb, 0x46f: 0xfb, - 0x470: 0xfb, 0x471: 0xfb, 0x472: 0xfb, 0x473: 0xfb, 0x474: 0xfb, 0x475: 0xfb, 0x476: 0xfb, 0x477: 0xfb, - 0x478: 0xfb, 0x479: 0xfb, 0x47a: 0xfb, 0x47b: 0xfb, 0x47c: 0xfb, 0x47d: 0xfb, 0x47e: 0xfb, 0x47f: 0xfb, + 0x448: 0xa0, 0x449: 0xa0, 0x44a: 0xa0, 0x44b: 0xa0, 0x44c: 0xa0, 0x44d: 0xa0, 0x44e: 0x159, 0x44f: 0xfd, + 0x450: 0x9c, 0x451: 0x15a, 0x452: 0xa0, 0x453: 0xa0, 0x454: 0xa0, 0x455: 0x15b, 0x456: 0xfd, 0x457: 0xfd, + 0x458: 0xfd, 0x459: 0xfd, 0x45a: 0xfd, 0x45b: 0xfd, 0x45c: 0xfd, 0x45d: 0xfd, 0x45e: 0xfd, 0x45f: 0xfd, + 0x460: 0xfd, 0x461: 0xfd, 0x462: 0xfd, 0x463: 0xfd, 0x464: 0xfd, 0x465: 0xfd, 0x466: 0xfd, 0x467: 0xfd, + 0x468: 0xfd, 0x469: 0xfd, 0x46a: 0xfd, 0x46b: 0xfd, 0x46c: 0xfd, 0x46d: 0xfd, 0x46e: 0xfd, 0x46f: 0xfd, + 0x470: 0xfd, 0x471: 0xfd, 0x472: 0xfd, 0x473: 0xfd, 0x474: 0xfd, 0x475: 0xfd, 0x476: 0xfd, 0x477: 0xfd, + 0x478: 0xfd, 0x479: 0xfd, 0x47a: 0xfd, 0x47b: 0xfd, 0x47c: 0xfd, 0x47d: 0xfd, 0x47e: 0xfd, 0x47f: 0xfd, // Block 0x12, offset 0x480 0x480: 0xa0, 0x481: 0xa0, 0x482: 0xa0, 0x483: 0xa0, 0x484: 0xa0, 0x485: 0xa0, 0x486: 0xa0, 0x487: 0xa0, 0x488: 0xa0, 0x489: 0xa0, 0x48a: 0xa0, 0x48b: 0xa0, 0x48c: 0xa0, 0x48d: 0xa0, 0x48e: 0xa0, 0x48f: 0xa0, - 0x490: 0x15a, 0x491: 0xfb, 0x492: 0xfb, 0x493: 0xfb, 0x494: 0xfb, 0x495: 0xfb, 0x496: 0xfb, 0x497: 0xfb, - 0x498: 0xfb, 0x499: 0xfb, 0x49a: 0xfb, 0x49b: 0xfb, 0x49c: 0xfb, 0x49d: 0xfb, 0x49e: 0xfb, 0x49f: 0xfb, - 0x4a0: 0xfb, 0x4a1: 0xfb, 0x4a2: 0xfb, 0x4a3: 0xfb, 0x4a4: 0xfb, 0x4a5: 0xfb, 0x4a6: 0xfb, 0x4a7: 0xfb, - 0x4a8: 0xfb, 0x4a9: 0xfb, 0x4aa: 0xfb, 0x4ab: 0xfb, 0x4ac: 0xfb, 0x4ad: 0xfb, 0x4ae: 0xfb, 0x4af: 0xfb, - 0x4b0: 0xfb, 0x4b1: 0xfb, 0x4b2: 0xfb, 0x4b3: 0xfb, 0x4b4: 0xfb, 0x4b5: 0xfb, 0x4b6: 0xfb, 0x4b7: 0xfb, - 0x4b8: 0xfb, 0x4b9: 0xfb, 0x4ba: 0xfb, 0x4bb: 0xfb, 0x4bc: 0xfb, 0x4bd: 0xfb, 0x4be: 0xfb, 0x4bf: 0xfb, + 0x490: 0x15c, 0x491: 0xfd, 0x492: 0xfd, 0x493: 0xfd, 0x494: 0xfd, 0x495: 0xfd, 0x496: 0xfd, 0x497: 0xfd, + 0x498: 0xfd, 0x499: 0xfd, 0x49a: 0xfd, 0x49b: 0xfd, 0x49c: 0xfd, 0x49d: 0xfd, 0x49e: 0xfd, 0x49f: 0xfd, + 0x4a0: 0xfd, 0x4a1: 0xfd, 0x4a2: 0xfd, 0x4a3: 0xfd, 0x4a4: 0xfd, 0x4a5: 0xfd, 0x4a6: 0xfd, 0x4a7: 0xfd, + 0x4a8: 0xfd, 0x4a9: 0xfd, 0x4aa: 0xfd, 0x4ab: 0xfd, 0x4ac: 0xfd, 0x4ad: 0xfd, 0x4ae: 0xfd, 0x4af: 0xfd, + 0x4b0: 0xfd, 0x4b1: 0xfd, 0x4b2: 0xfd, 0x4b3: 0xfd, 0x4b4: 0xfd, 0x4b5: 0xfd, 0x4b6: 0xfd, 0x4b7: 0xfd, + 0x4b8: 0xfd, 0x4b9: 0xfd, 0x4ba: 0xfd, 0x4bb: 0xfd, 0x4bc: 0xfd, 0x4bd: 0xfd, 0x4be: 0xfd, 0x4bf: 0xfd, // Block 0x13, offset 0x4c0 - 0x4c0: 0xfb, 0x4c1: 0xfb, 0x4c2: 0xfb, 0x4c3: 0xfb, 0x4c4: 0xfb, 0x4c5: 0xfb, 0x4c6: 0xfb, 0x4c7: 0xfb, - 0x4c8: 0xfb, 0x4c9: 0xfb, 0x4ca: 0xfb, 0x4cb: 0xfb, 0x4cc: 0xfb, 0x4cd: 0xfb, 0x4ce: 0xfb, 0x4cf: 0xfb, + 0x4c0: 0xfd, 0x4c1: 0xfd, 0x4c2: 0xfd, 0x4c3: 0xfd, 0x4c4: 0xfd, 0x4c5: 0xfd, 0x4c6: 0xfd, 0x4c7: 0xfd, + 0x4c8: 0xfd, 0x4c9: 0xfd, 0x4ca: 0xfd, 0x4cb: 0xfd, 0x4cc: 0xfd, 0x4cd: 0xfd, 0x4ce: 0xfd, 0x4cf: 0xfd, 0x4d0: 0xa0, 0x4d1: 0xa0, 0x4d2: 0xa0, 0x4d3: 0xa0, 0x4d4: 0xa0, 0x4d5: 0xa0, 0x4d6: 0xa0, 0x4d7: 0xa0, - 0x4d8: 0xa0, 0x4d9: 0x15b, 0x4da: 0xfb, 0x4db: 0xfb, 0x4dc: 0xfb, 0x4dd: 0xfb, 0x4de: 0xfb, 0x4df: 0xfb, - 0x4e0: 0xfb, 0x4e1: 0xfb, 0x4e2: 0xfb, 0x4e3: 0xfb, 0x4e4: 0xfb, 0x4e5: 0xfb, 0x4e6: 0xfb, 0x4e7: 0xfb, - 0x4e8: 0xfb, 0x4e9: 0xfb, 0x4ea: 0xfb, 0x4eb: 0xfb, 0x4ec: 0xfb, 0x4ed: 0xfb, 0x4ee: 0xfb, 0x4ef: 0xfb, - 0x4f0: 0xfb, 0x4f1: 0xfb, 0x4f2: 0xfb, 0x4f3: 0xfb, 0x4f4: 0xfb, 0x4f5: 0xfb, 0x4f6: 0xfb, 0x4f7: 0xfb, - 0x4f8: 0xfb, 0x4f9: 0xfb, 0x4fa: 0xfb, 0x4fb: 0xfb, 0x4fc: 0xfb, 0x4fd: 0xfb, 0x4fe: 0xfb, 0x4ff: 0xfb, + 0x4d8: 0xa0, 0x4d9: 0x15d, 0x4da: 0xfd, 0x4db: 0xfd, 0x4dc: 0xfd, 0x4dd: 0xfd, 0x4de: 0xfd, 0x4df: 0xfd, + 0x4e0: 0xfd, 0x4e1: 0xfd, 0x4e2: 0xfd, 0x4e3: 0xfd, 0x4e4: 0xfd, 0x4e5: 0xfd, 0x4e6: 0xfd, 0x4e7: 0xfd, + 0x4e8: 0xfd, 0x4e9: 0xfd, 0x4ea: 0xfd, 0x4eb: 0xfd, 0x4ec: 0xfd, 0x4ed: 0xfd, 0x4ee: 0xfd, 0x4ef: 0xfd, + 0x4f0: 0xfd, 0x4f1: 0xfd, 0x4f2: 0xfd, 0x4f3: 0xfd, 0x4f4: 0xfd, 0x4f5: 0xfd, 0x4f6: 0xfd, 0x4f7: 0xfd, + 0x4f8: 0xfd, 0x4f9: 0xfd, 0x4fa: 0xfd, 0x4fb: 0xfd, 0x4fc: 0xfd, 0x4fd: 0xfd, 0x4fe: 0xfd, 0x4ff: 0xfd, // Block 0x14, offset 0x500 - 0x500: 0xfb, 0x501: 0xfb, 0x502: 0xfb, 0x503: 0xfb, 0x504: 0xfb, 0x505: 0xfb, 0x506: 0xfb, 0x507: 0xfb, - 0x508: 0xfb, 0x509: 0xfb, 0x50a: 0xfb, 0x50b: 0xfb, 0x50c: 0xfb, 0x50d: 0xfb, 0x50e: 0xfb, 0x50f: 0xfb, - 0x510: 0xfb, 0x511: 0xfb, 0x512: 0xfb, 0x513: 0xfb, 0x514: 0xfb, 0x515: 0xfb, 0x516: 0xfb, 0x517: 0xfb, - 0x518: 0xfb, 0x519: 0xfb, 0x51a: 0xfb, 0x51b: 0xfb, 0x51c: 0xfb, 0x51d: 0xfb, 0x51e: 0xfb, 0x51f: 0xfb, + 0x500: 0xfd, 0x501: 0xfd, 0x502: 0xfd, 0x503: 0xfd, 0x504: 0xfd, 0x505: 0xfd, 0x506: 0xfd, 0x507: 0xfd, + 0x508: 0xfd, 0x509: 0xfd, 0x50a: 0xfd, 0x50b: 0xfd, 0x50c: 0xfd, 0x50d: 0xfd, 0x50e: 0xfd, 0x50f: 0xfd, + 0x510: 0xfd, 0x511: 0xfd, 0x512: 0xfd, 0x513: 0xfd, 0x514: 0xfd, 0x515: 0xfd, 0x516: 0xfd, 0x517: 0xfd, + 0x518: 0xfd, 0x519: 0xfd, 0x51a: 0xfd, 0x51b: 0xfd, 0x51c: 0xfd, 0x51d: 0xfd, 0x51e: 0xfd, 0x51f: 0xfd, 0x520: 0xa0, 0x521: 0xa0, 0x522: 0xa0, 0x523: 0xa0, 0x524: 0xa0, 0x525: 0xa0, 0x526: 0xa0, 0x527: 0xa0, - 0x528: 0x14d, 0x529: 0x15c, 0x52a: 0xfb, 0x52b: 0x15d, 0x52c: 0x15e, 0x52d: 0x15f, 0x52e: 0x160, 0x52f: 0xfb, - 0x530: 0xfb, 0x531: 0xfb, 0x532: 0xfb, 0x533: 0xfb, 0x534: 0xfb, 0x535: 0xfb, 0x536: 0xfb, 0x537: 0xfb, - 0x538: 0xfb, 0x539: 0x161, 0x53a: 0x162, 0x53b: 0xfb, 0x53c: 0xa0, 0x53d: 0x163, 0x53e: 0x164, 0x53f: 0x165, + 0x528: 0x14f, 0x529: 0x15e, 0x52a: 0xfd, 0x52b: 0x15f, 0x52c: 0x160, 0x52d: 0x161, 0x52e: 0x162, 0x52f: 0xfd, + 0x530: 0xfd, 0x531: 0xfd, 0x532: 0xfd, 0x533: 0xfd, 0x534: 0xfd, 0x535: 0xfd, 0x536: 0xfd, 0x537: 0xfd, + 0x538: 0xfd, 0x539: 0x163, 0x53a: 0x164, 0x53b: 0xfd, 0x53c: 0xa0, 0x53d: 0x165, 0x53e: 0x166, 0x53f: 0x167, // Block 0x15, offset 0x540 0x540: 0xa0, 0x541: 0xa0, 0x542: 0xa0, 0x543: 0xa0, 0x544: 0xa0, 0x545: 0xa0, 0x546: 0xa0, 0x547: 0xa0, 0x548: 0xa0, 0x549: 0xa0, 0x54a: 0xa0, 0x54b: 0xa0, 0x54c: 0xa0, 0x54d: 0xa0, 0x54e: 0xa0, 0x54f: 0xa0, 0x550: 0xa0, 0x551: 0xa0, 0x552: 0xa0, 0x553: 0xa0, 0x554: 0xa0, 0x555: 0xa0, 0x556: 0xa0, 0x557: 0xa0, - 0x558: 0xa0, 0x559: 0xa0, 0x55a: 0xa0, 0x55b: 0xa0, 0x55c: 0xa0, 0x55d: 0xa0, 0x55e: 0xa0, 0x55f: 0x166, + 0x558: 0xa0, 0x559: 0xa0, 0x55a: 0xa0, 0x55b: 0xa0, 0x55c: 0xa0, 0x55d: 0xa0, 0x55e: 0xa0, 0x55f: 0x168, 0x560: 0xa0, 0x561: 0xa0, 0x562: 0xa0, 0x563: 0xa0, 0x564: 0xa0, 0x565: 0xa0, 0x566: 0xa0, 0x567: 0xa0, 0x568: 0xa0, 0x569: 0xa0, 0x56a: 0xa0, 0x56b: 0xa0, 0x56c: 0xa0, 0x56d: 0xa0, 0x56e: 0xa0, 0x56f: 0xa0, - 0x570: 0xa0, 0x571: 0xa0, 0x572: 0xa0, 0x573: 0x167, 0x574: 0x168, 0x575: 0xfb, 0x576: 0xfb, 0x577: 0xfb, - 0x578: 0xfb, 0x579: 0xfb, 0x57a: 0xfb, 0x57b: 0xfb, 0x57c: 0xfb, 0x57d: 0xfb, 0x57e: 0xfb, 0x57f: 0xfb, + 0x570: 0xa0, 0x571: 0xa0, 0x572: 0xa0, 0x573: 0x169, 0x574: 0x16a, 0x575: 0xfd, 0x576: 0xfd, 0x577: 0xfd, + 0x578: 0xfd, 0x579: 0xfd, 0x57a: 0xfd, 0x57b: 0xfd, 0x57c: 0xfd, 0x57d: 0xfd, 0x57e: 0xfd, 0x57f: 0xfd, // Block 0x16, offset 0x580 - 0x580: 0xa0, 0x581: 0xa0, 0x582: 0xa0, 0x583: 0xa0, 0x584: 0x169, 0x585: 0x16a, 0x586: 0xa0, 0x587: 0xa0, - 0x588: 0xa0, 0x589: 0xa0, 0x58a: 0xa0, 0x58b: 0x16b, 0x58c: 0xfb, 0x58d: 0xfb, 0x58e: 0xfb, 0x58f: 0xfb, - 0x590: 0xfb, 0x591: 0xfb, 0x592: 0xfb, 0x593: 0xfb, 0x594: 0xfb, 0x595: 0xfb, 0x596: 0xfb, 0x597: 0xfb, - 0x598: 0xfb, 0x599: 0xfb, 0x59a: 0xfb, 0x59b: 0xfb, 0x59c: 0xfb, 0x59d: 0xfb, 0x59e: 0xfb, 0x59f: 0xfb, - 0x5a0: 0xfb, 0x5a1: 0xfb, 0x5a2: 0xfb, 0x5a3: 0xfb, 0x5a4: 0xfb, 0x5a5: 0xfb, 0x5a6: 0xfb, 0x5a7: 0xfb, - 0x5a8: 0xfb, 0x5a9: 0xfb, 0x5aa: 0xfb, 0x5ab: 0xfb, 0x5ac: 0xfb, 0x5ad: 0xfb, 0x5ae: 0xfb, 0x5af: 0xfb, - 0x5b0: 0xa0, 0x5b1: 0x16c, 0x5b2: 0x16d, 0x5b3: 0xfb, 0x5b4: 0xfb, 0x5b5: 0xfb, 0x5b6: 0xfb, 0x5b7: 0xfb, - 0x5b8: 0xfb, 0x5b9: 0xfb, 0x5ba: 0xfb, 0x5bb: 0xfb, 0x5bc: 0xfb, 0x5bd: 0xfb, 0x5be: 0xfb, 0x5bf: 0xfb, + 0x580: 0xa0, 0x581: 0xa0, 0x582: 0xa0, 0x583: 0xa0, 0x584: 0x16b, 0x585: 0x16c, 0x586: 0xa0, 0x587: 0xa0, + 0x588: 0xa0, 0x589: 0xa0, 0x58a: 0xa0, 0x58b: 0x16d, 0x58c: 0xfd, 0x58d: 0xfd, 0x58e: 0xfd, 0x58f: 0xfd, + 0x590: 0xfd, 0x591: 0xfd, 0x592: 0xfd, 0x593: 0xfd, 0x594: 0xfd, 0x595: 0xfd, 0x596: 0xfd, 0x597: 0xfd, + 0x598: 0xfd, 0x599: 0xfd, 0x59a: 0xfd, 0x59b: 0xfd, 0x59c: 0xfd, 0x59d: 0xfd, 0x59e: 0xfd, 0x59f: 0xfd, + 0x5a0: 0xfd, 0x5a1: 0xfd, 0x5a2: 0xfd, 0x5a3: 0xfd, 0x5a4: 0xfd, 0x5a5: 0xfd, 0x5a6: 0xfd, 0x5a7: 0xfd, + 0x5a8: 0xfd, 0x5a9: 0xfd, 0x5aa: 0xfd, 0x5ab: 0xfd, 0x5ac: 0xfd, 0x5ad: 0xfd, 0x5ae: 0xfd, 0x5af: 0xfd, + 0x5b0: 0xa0, 0x5b1: 0x16e, 0x5b2: 0x16f, 0x5b3: 0xfd, 0x5b4: 0xfd, 0x5b5: 0xfd, 0x5b6: 0xfd, 0x5b7: 0xfd, + 0x5b8: 0xfd, 0x5b9: 0xfd, 0x5ba: 0xfd, 0x5bb: 0xfd, 0x5bc: 0xfd, 0x5bd: 0xfd, 0x5be: 0xfd, 0x5bf: 0xfd, // Block 0x17, offset 0x5c0 - 0x5c0: 0x9c, 0x5c1: 0x9c, 0x5c2: 0x9c, 0x5c3: 0x16e, 0x5c4: 0x16f, 0x5c5: 0x170, 0x5c6: 0x171, 0x5c7: 0x172, - 0x5c8: 0x9c, 0x5c9: 0x173, 0x5ca: 0xfb, 0x5cb: 0x174, 0x5cc: 0x9c, 0x5cd: 0x175, 0x5ce: 0xfb, 0x5cf: 0xfb, - 0x5d0: 0x60, 0x5d1: 0x61, 0x5d2: 0x62, 0x5d3: 0x63, 0x5d4: 0x64, 0x5d5: 0x65, 0x5d6: 0x66, 0x5d7: 0x67, - 0x5d8: 0x68, 0x5d9: 0x69, 0x5da: 0x6a, 0x5db: 0x6b, 0x5dc: 0x6c, 0x5dd: 0x6d, 0x5de: 0x6e, 0x5df: 0x6f, + 0x5c0: 0x9c, 0x5c1: 0x9c, 0x5c2: 0x9c, 0x5c3: 0x170, 0x5c4: 0x171, 0x5c5: 0x172, 0x5c6: 0x173, 0x5c7: 0x174, + 0x5c8: 0x9c, 0x5c9: 0x175, 0x5ca: 0xfd, 0x5cb: 0x176, 0x5cc: 0x9c, 0x5cd: 0x177, 0x5ce: 0xfd, 0x5cf: 0xfd, + 0x5d0: 0x5e, 0x5d1: 0x5f, 0x5d2: 0x60, 0x5d3: 0x61, 0x5d4: 0x62, 0x5d5: 0x63, 0x5d6: 0x64, 0x5d7: 0x65, + 0x5d8: 0x66, 0x5d9: 0x67, 0x5da: 0x68, 0x5db: 0x69, 0x5dc: 0x6a, 0x5dd: 0x6b, 0x5de: 0x6c, 0x5df: 0x6d, 0x5e0: 0x9c, 0x5e1: 0x9c, 0x5e2: 0x9c, 0x5e3: 0x9c, 0x5e4: 0x9c, 0x5e5: 0x9c, 0x5e6: 0x9c, 0x5e7: 0x9c, - 0x5e8: 0x176, 0x5e9: 0x177, 0x5ea: 0x178, 0x5eb: 0xfb, 0x5ec: 0xfb, 0x5ed: 0xfb, 0x5ee: 0xfb, 0x5ef: 0xfb, - 0x5f0: 0xfb, 0x5f1: 0xfb, 0x5f2: 0xfb, 0x5f3: 0xfb, 0x5f4: 0xfb, 0x5f5: 0xfb, 0x5f6: 0xfb, 0x5f7: 0xfb, - 0x5f8: 0xfb, 0x5f9: 0xfb, 0x5fa: 0xfb, 0x5fb: 0xfb, 0x5fc: 0xfb, 0x5fd: 0xfb, 0x5fe: 0xfb, 0x5ff: 0xfb, + 0x5e8: 0x178, 0x5e9: 0x179, 0x5ea: 0x17a, 0x5eb: 0xfd, 0x5ec: 0xfd, 0x5ed: 0xfd, 0x5ee: 0xfd, 0x5ef: 0xfd, + 0x5f0: 0xfd, 0x5f1: 0xfd, 0x5f2: 0xfd, 0x5f3: 0xfd, 0x5f4: 0xfd, 0x5f5: 0xfd, 0x5f6: 0xfd, 0x5f7: 0xfd, + 0x5f8: 0xfd, 0x5f9: 0xfd, 0x5fa: 0xfd, 0x5fb: 0xfd, 0x5fc: 0xfd, 0x5fd: 0xfd, 0x5fe: 0xfd, 0x5ff: 0xfd, // Block 0x18, offset 0x600 - 0x600: 0x179, 0x601: 0xfb, 0x602: 0xfb, 0x603: 0xfb, 0x604: 0x17a, 0x605: 0x17b, 0x606: 0xfb, 0x607: 0xfb, - 0x608: 0xfb, 0x609: 0xfb, 0x60a: 0xfb, 0x60b: 0x17c, 0x60c: 0xfb, 0x60d: 0xfb, 0x60e: 0xfb, 0x60f: 0xfb, - 0x610: 0xfb, 0x611: 0xfb, 0x612: 0xfb, 0x613: 0xfb, 0x614: 0xfb, 0x615: 0xfb, 0x616: 0xfb, 0x617: 0xfb, - 0x618: 0xfb, 0x619: 0xfb, 0x61a: 0xfb, 0x61b: 0xfb, 0x61c: 0xfb, 0x61d: 0xfb, 0x61e: 0xfb, 0x61f: 0xfb, - 0x620: 0x123, 0x621: 0x123, 0x622: 0x123, 0x623: 0x17d, 0x624: 0x70, 0x625: 0x17e, 0x626: 0xfb, 0x627: 0xfb, - 0x628: 0xfb, 0x629: 0xfb, 0x62a: 0xfb, 0x62b: 0xfb, 0x62c: 0xfb, 0x62d: 0xfb, 0x62e: 0xfb, 0x62f: 0xfb, - 0x630: 0xfb, 0x631: 0x17f, 0x632: 0x180, 0x633: 0xfb, 0x634: 0x181, 0x635: 0xfb, 0x636: 0xfb, 0x637: 0xfb, - 0x638: 0x71, 0x639: 0x72, 0x63a: 0x73, 0x63b: 0x182, 0x63c: 0xfb, 0x63d: 0xfb, 0x63e: 0xfb, 0x63f: 0xfb, + 0x600: 0x17b, 0x601: 0xfd, 0x602: 0xfd, 0x603: 0xfd, 0x604: 0x17c, 0x605: 0x17d, 0x606: 0xfd, 0x607: 0xfd, + 0x608: 0xfd, 0x609: 0xfd, 0x60a: 0xfd, 0x60b: 0x17e, 0x60c: 0xfd, 0x60d: 0xfd, 0x60e: 0xfd, 0x60f: 0xfd, + 0x610: 0xfd, 0x611: 0xfd, 0x612: 0xfd, 0x613: 0xfd, 0x614: 0xfd, 0x615: 0xfd, 0x616: 0xfd, 0x617: 0xfd, + 0x618: 0xfd, 0x619: 0xfd, 0x61a: 0xfd, 0x61b: 0xfd, 0x61c: 0xfd, 0x61d: 0xfd, 0x61e: 0xfd, 0x61f: 0xfd, + 0x620: 0x125, 0x621: 0x125, 0x622: 0x125, 0x623: 0x17f, 0x624: 0x6e, 0x625: 0x180, 0x626: 0xfd, 0x627: 0xfd, + 0x628: 0xfd, 0x629: 0xfd, 0x62a: 0xfd, 0x62b: 0xfd, 0x62c: 0xfd, 0x62d: 0xfd, 0x62e: 0xfd, 0x62f: 0xfd, + 0x630: 0xfd, 0x631: 0x181, 0x632: 0x182, 0x633: 0xfd, 0x634: 0x183, 0x635: 0xfd, 0x636: 0xfd, 0x637: 0xfd, + 0x638: 0x6f, 0x639: 0x70, 0x63a: 0x71, 0x63b: 0x184, 0x63c: 0xfd, 0x63d: 0xfd, 0x63e: 0xfd, 0x63f: 0xfd, // Block 0x19, offset 0x640 - 0x640: 0x183, 0x641: 0x9c, 0x642: 0x184, 0x643: 0x185, 0x644: 0x74, 0x645: 0x75, 0x646: 0x186, 0x647: 0x187, - 0x648: 0x76, 0x649: 0x188, 0x64a: 0xfb, 0x64b: 0xfb, 0x64c: 0x9c, 0x64d: 0x9c, 0x64e: 0x9c, 0x64f: 0x9c, + 0x640: 0x185, 0x641: 0x9c, 0x642: 0x186, 0x643: 0x187, 0x644: 0x72, 0x645: 0x73, 0x646: 0x188, 0x647: 0x189, + 0x648: 0x74, 0x649: 0x18a, 0x64a: 0xfd, 0x64b: 0xfd, 0x64c: 0x9c, 0x64d: 0x9c, 0x64e: 0x9c, 0x64f: 0x9c, 0x650: 0x9c, 0x651: 0x9c, 0x652: 0x9c, 0x653: 0x9c, 0x654: 0x9c, 0x655: 0x9c, 0x656: 0x9c, 0x657: 0x9c, - 0x658: 0x9c, 0x659: 0x9c, 0x65a: 0x9c, 0x65b: 0x189, 0x65c: 0x9c, 0x65d: 0x18a, 0x65e: 0x9c, 0x65f: 0x18b, - 0x660: 0x18c, 0x661: 0x18d, 0x662: 0x18e, 0x663: 0xfb, 0x664: 0x9c, 0x665: 0x18f, 0x666: 0x9c, 0x667: 0x190, - 0x668: 0x9c, 0x669: 0x191, 0x66a: 0x192, 0x66b: 0x193, 0x66c: 0x9c, 0x66d: 0x9c, 0x66e: 0x194, 0x66f: 0x195, - 0x670: 0xfb, 0x671: 0xfb, 0x672: 0xfb, 0x673: 0xfb, 0x674: 0xfb, 0x675: 0xfb, 0x676: 0xfb, 0x677: 0xfb, - 0x678: 0xfb, 0x679: 0xfb, 0x67a: 0xfb, 0x67b: 0xfb, 0x67c: 0xfb, 0x67d: 0xfb, 0x67e: 0xfb, 0x67f: 0xfb, + 0x658: 0x9c, 0x659: 0x9c, 0x65a: 0x9c, 0x65b: 0x18b, 0x65c: 0x9c, 0x65d: 0x18c, 0x65e: 0x9c, 0x65f: 0x18d, + 0x660: 0x18e, 0x661: 0x18f, 0x662: 0x190, 0x663: 0xfd, 0x664: 0x9c, 0x665: 0x191, 0x666: 0x9c, 0x667: 0x192, + 0x668: 0x9c, 0x669: 0x193, 0x66a: 0x194, 0x66b: 0x195, 0x66c: 0x9c, 0x66d: 0x9c, 0x66e: 0x196, 0x66f: 0x197, + 0x670: 0xfd, 0x671: 0xfd, 0x672: 0xfd, 0x673: 0xfd, 0x674: 0xfd, 0x675: 0xfd, 0x676: 0xfd, 0x677: 0xfd, + 0x678: 0xfd, 0x679: 0xfd, 0x67a: 0xfd, 0x67b: 0xfd, 0x67c: 0xfd, 0x67d: 0xfd, 0x67e: 0xfd, 0x67f: 0xfd, // Block 0x1a, offset 0x680 0x680: 0xa0, 0x681: 0xa0, 0x682: 0xa0, 0x683: 0xa0, 0x684: 0xa0, 0x685: 0xa0, 0x686: 0xa0, 0x687: 0xa0, 0x688: 0xa0, 0x689: 0xa0, 0x68a: 0xa0, 0x68b: 0xa0, 0x68c: 0xa0, 0x68d: 0xa0, 0x68e: 0xa0, 0x68f: 0xa0, 0x690: 0xa0, 0x691: 0xa0, 0x692: 0xa0, 0x693: 0xa0, 0x694: 0xa0, 0x695: 0xa0, 0x696: 0xa0, 0x697: 0xa0, - 0x698: 0xa0, 0x699: 0xa0, 0x69a: 0xa0, 0x69b: 0x196, 0x69c: 0xa0, 0x69d: 0xa0, 0x69e: 0xa0, 0x69f: 0xa0, + 0x698: 0xa0, 0x699: 0xa0, 0x69a: 0xa0, 0x69b: 0x198, 0x69c: 0xa0, 0x69d: 0xa0, 0x69e: 0xa0, 0x69f: 0xa0, 0x6a0: 0xa0, 0x6a1: 0xa0, 0x6a2: 0xa0, 0x6a3: 0xa0, 0x6a4: 0xa0, 0x6a5: 0xa0, 0x6a6: 0xa0, 0x6a7: 0xa0, 0x6a8: 0xa0, 0x6a9: 0xa0, 0x6aa: 0xa0, 0x6ab: 0xa0, 0x6ac: 0xa0, 0x6ad: 0xa0, 0x6ae: 0xa0, 0x6af: 0xa0, 0x6b0: 0xa0, 0x6b1: 0xa0, 0x6b2: 0xa0, 0x6b3: 0xa0, 0x6b4: 0xa0, 0x6b5: 0xa0, 0x6b6: 0xa0, 0x6b7: 0xa0, @@ -2312,8 +2455,8 @@ var idnaIndex = [2368]uint16{ 0x6c0: 0xa0, 0x6c1: 0xa0, 0x6c2: 0xa0, 0x6c3: 0xa0, 0x6c4: 0xa0, 0x6c5: 0xa0, 0x6c6: 0xa0, 0x6c7: 0xa0, 0x6c8: 0xa0, 0x6c9: 0xa0, 0x6ca: 0xa0, 0x6cb: 0xa0, 0x6cc: 0xa0, 0x6cd: 0xa0, 0x6ce: 0xa0, 0x6cf: 0xa0, 0x6d0: 0xa0, 0x6d1: 0xa0, 0x6d2: 0xa0, 0x6d3: 0xa0, 0x6d4: 0xa0, 0x6d5: 0xa0, 0x6d6: 0xa0, 0x6d7: 0xa0, - 0x6d8: 0xa0, 0x6d9: 0xa0, 0x6da: 0xa0, 0x6db: 0xa0, 0x6dc: 0x197, 0x6dd: 0xa0, 0x6de: 0xa0, 0x6df: 0xa0, - 0x6e0: 0x198, 0x6e1: 0xa0, 0x6e2: 0xa0, 0x6e3: 0xa0, 0x6e4: 0xa0, 0x6e5: 0xa0, 0x6e6: 0xa0, 0x6e7: 0xa0, + 0x6d8: 0xa0, 0x6d9: 0xa0, 0x6da: 0xa0, 0x6db: 0xa0, 0x6dc: 0x199, 0x6dd: 0xa0, 0x6de: 0xa0, 0x6df: 0xa0, + 0x6e0: 0x19a, 0x6e1: 0xa0, 0x6e2: 0xa0, 0x6e3: 0xa0, 0x6e4: 0xa0, 0x6e5: 0xa0, 0x6e6: 0xa0, 0x6e7: 0xa0, 0x6e8: 0xa0, 0x6e9: 0xa0, 0x6ea: 0xa0, 0x6eb: 0xa0, 0x6ec: 0xa0, 0x6ed: 0xa0, 0x6ee: 0xa0, 0x6ef: 0xa0, 0x6f0: 0xa0, 0x6f1: 0xa0, 0x6f2: 0xa0, 0x6f3: 0xa0, 0x6f4: 0xa0, 0x6f5: 0xa0, 0x6f6: 0xa0, 0x6f7: 0xa0, 0x6f8: 0xa0, 0x6f9: 0xa0, 0x6fa: 0xa0, 0x6fb: 0xa0, 0x6fc: 0xa0, 0x6fd: 0xa0, 0x6fe: 0xa0, 0x6ff: 0xa0, @@ -2325,34 +2468,34 @@ var idnaIndex = [2368]uint16{ 0x720: 0xa0, 0x721: 0xa0, 0x722: 0xa0, 0x723: 0xa0, 0x724: 0xa0, 0x725: 0xa0, 0x726: 0xa0, 0x727: 0xa0, 0x728: 0xa0, 0x729: 0xa0, 0x72a: 0xa0, 0x72b: 0xa0, 0x72c: 0xa0, 0x72d: 0xa0, 0x72e: 0xa0, 0x72f: 0xa0, 0x730: 0xa0, 0x731: 0xa0, 0x732: 0xa0, 0x733: 0xa0, 0x734: 0xa0, 0x735: 0xa0, 0x736: 0xa0, 0x737: 0xa0, - 0x738: 0xa0, 0x739: 0xa0, 0x73a: 0x199, 0x73b: 0xa0, 0x73c: 0xa0, 0x73d: 0xa0, 0x73e: 0xa0, 0x73f: 0xa0, + 0x738: 0xa0, 0x739: 0xa0, 0x73a: 0x19b, 0x73b: 0xa0, 0x73c: 0xa0, 0x73d: 0xa0, 0x73e: 0xa0, 0x73f: 0xa0, // Block 0x1d, offset 0x740 0x740: 0xa0, 0x741: 0xa0, 0x742: 0xa0, 0x743: 0xa0, 0x744: 0xa0, 0x745: 0xa0, 0x746: 0xa0, 0x747: 0xa0, 0x748: 0xa0, 0x749: 0xa0, 0x74a: 0xa0, 0x74b: 0xa0, 0x74c: 0xa0, 0x74d: 0xa0, 0x74e: 0xa0, 0x74f: 0xa0, 0x750: 0xa0, 0x751: 0xa0, 0x752: 0xa0, 0x753: 0xa0, 0x754: 0xa0, 0x755: 0xa0, 0x756: 0xa0, 0x757: 0xa0, 0x758: 0xa0, 0x759: 0xa0, 0x75a: 0xa0, 0x75b: 0xa0, 0x75c: 0xa0, 0x75d: 0xa0, 0x75e: 0xa0, 0x75f: 0xa0, 0x760: 0xa0, 0x761: 0xa0, 0x762: 0xa0, 0x763: 0xa0, 0x764: 0xa0, 0x765: 0xa0, 0x766: 0xa0, 0x767: 0xa0, - 0x768: 0xa0, 0x769: 0xa0, 0x76a: 0xa0, 0x76b: 0xa0, 0x76c: 0xa0, 0x76d: 0xa0, 0x76e: 0xa0, 0x76f: 0x19a, - 0x770: 0xfb, 0x771: 0xfb, 0x772: 0xfb, 0x773: 0xfb, 0x774: 0xfb, 0x775: 0xfb, 0x776: 0xfb, 0x777: 0xfb, - 0x778: 0xfb, 0x779: 0xfb, 0x77a: 0xfb, 0x77b: 0xfb, 0x77c: 0xfb, 0x77d: 0xfb, 0x77e: 0xfb, 0x77f: 0xfb, + 0x768: 0xa0, 0x769: 0xa0, 0x76a: 0xa0, 0x76b: 0xa0, 0x76c: 0xa0, 0x76d: 0xa0, 0x76e: 0xa0, 0x76f: 0x19c, + 0x770: 0xfd, 0x771: 0xfd, 0x772: 0xfd, 0x773: 0xfd, 0x774: 0xfd, 0x775: 0xfd, 0x776: 0xfd, 0x777: 0xfd, + 0x778: 0xfd, 0x779: 0xfd, 0x77a: 0xfd, 0x77b: 0xfd, 0x77c: 0xfd, 0x77d: 0xfd, 0x77e: 0xfd, 0x77f: 0xfd, // Block 0x1e, offset 0x780 - 0x780: 0xfb, 0x781: 0xfb, 0x782: 0xfb, 0x783: 0xfb, 0x784: 0xfb, 0x785: 0xfb, 0x786: 0xfb, 0x787: 0xfb, - 0x788: 0xfb, 0x789: 0xfb, 0x78a: 0xfb, 0x78b: 0xfb, 0x78c: 0xfb, 0x78d: 0xfb, 0x78e: 0xfb, 0x78f: 0xfb, - 0x790: 0xfb, 0x791: 0xfb, 0x792: 0xfb, 0x793: 0xfb, 0x794: 0xfb, 0x795: 0xfb, 0x796: 0xfb, 0x797: 0xfb, - 0x798: 0xfb, 0x799: 0xfb, 0x79a: 0xfb, 0x79b: 0xfb, 0x79c: 0xfb, 0x79d: 0xfb, 0x79e: 0xfb, 0x79f: 0xfb, - 0x7a0: 0x77, 0x7a1: 0x78, 0x7a2: 0x79, 0x7a3: 0x19b, 0x7a4: 0x7a, 0x7a5: 0x7b, 0x7a6: 0x19c, 0x7a7: 0x7c, - 0x7a8: 0x7d, 0x7a9: 0xfb, 0x7aa: 0xfb, 0x7ab: 0xfb, 0x7ac: 0xfb, 0x7ad: 0xfb, 0x7ae: 0xfb, 0x7af: 0xfb, - 0x7b0: 0xfb, 0x7b1: 0xfb, 0x7b2: 0xfb, 0x7b3: 0xfb, 0x7b4: 0xfb, 0x7b5: 0xfb, 0x7b6: 0xfb, 0x7b7: 0xfb, - 0x7b8: 0xfb, 0x7b9: 0xfb, 0x7ba: 0xfb, 0x7bb: 0xfb, 0x7bc: 0xfb, 0x7bd: 0xfb, 0x7be: 0xfb, 0x7bf: 0xfb, + 0x780: 0xfd, 0x781: 0xfd, 0x782: 0xfd, 0x783: 0xfd, 0x784: 0xfd, 0x785: 0xfd, 0x786: 0xfd, 0x787: 0xfd, + 0x788: 0xfd, 0x789: 0xfd, 0x78a: 0xfd, 0x78b: 0xfd, 0x78c: 0xfd, 0x78d: 0xfd, 0x78e: 0xfd, 0x78f: 0xfd, + 0x790: 0xfd, 0x791: 0xfd, 0x792: 0xfd, 0x793: 0xfd, 0x794: 0xfd, 0x795: 0xfd, 0x796: 0xfd, 0x797: 0xfd, + 0x798: 0xfd, 0x799: 0xfd, 0x79a: 0xfd, 0x79b: 0xfd, 0x79c: 0xfd, 0x79d: 0xfd, 0x79e: 0xfd, 0x79f: 0xfd, + 0x7a0: 0x75, 0x7a1: 0x76, 0x7a2: 0x77, 0x7a3: 0x78, 0x7a4: 0x79, 0x7a5: 0x7a, 0x7a6: 0x7b, 0x7a7: 0x7c, + 0x7a8: 0x7d, 0x7a9: 0xfd, 0x7aa: 0xfd, 0x7ab: 0xfd, 0x7ac: 0xfd, 0x7ad: 0xfd, 0x7ae: 0xfd, 0x7af: 0xfd, + 0x7b0: 0xfd, 0x7b1: 0xfd, 0x7b2: 0xfd, 0x7b3: 0xfd, 0x7b4: 0xfd, 0x7b5: 0xfd, 0x7b6: 0xfd, 0x7b7: 0xfd, + 0x7b8: 0xfd, 0x7b9: 0xfd, 0x7ba: 0xfd, 0x7bb: 0xfd, 0x7bc: 0xfd, 0x7bd: 0xfd, 0x7be: 0xfd, 0x7bf: 0xfd, // Block 0x1f, offset 0x7c0 0x7c0: 0xa0, 0x7c1: 0xa0, 0x7c2: 0xa0, 0x7c3: 0xa0, 0x7c4: 0xa0, 0x7c5: 0xa0, 0x7c6: 0xa0, 0x7c7: 0xa0, - 0x7c8: 0xa0, 0x7c9: 0xa0, 0x7ca: 0xa0, 0x7cb: 0xa0, 0x7cc: 0xa0, 0x7cd: 0x19d, 0x7ce: 0xfb, 0x7cf: 0xfb, - 0x7d0: 0xfb, 0x7d1: 0xfb, 0x7d2: 0xfb, 0x7d3: 0xfb, 0x7d4: 0xfb, 0x7d5: 0xfb, 0x7d6: 0xfb, 0x7d7: 0xfb, - 0x7d8: 0xfb, 0x7d9: 0xfb, 0x7da: 0xfb, 0x7db: 0xfb, 0x7dc: 0xfb, 0x7dd: 0xfb, 0x7de: 0xfb, 0x7df: 0xfb, - 0x7e0: 0xfb, 0x7e1: 0xfb, 0x7e2: 0xfb, 0x7e3: 0xfb, 0x7e4: 0xfb, 0x7e5: 0xfb, 0x7e6: 0xfb, 0x7e7: 0xfb, - 0x7e8: 0xfb, 0x7e9: 0xfb, 0x7ea: 0xfb, 0x7eb: 0xfb, 0x7ec: 0xfb, 0x7ed: 0xfb, 0x7ee: 0xfb, 0x7ef: 0xfb, - 0x7f0: 0xfb, 0x7f1: 0xfb, 0x7f2: 0xfb, 0x7f3: 0xfb, 0x7f4: 0xfb, 0x7f5: 0xfb, 0x7f6: 0xfb, 0x7f7: 0xfb, - 0x7f8: 0xfb, 0x7f9: 0xfb, 0x7fa: 0xfb, 0x7fb: 0xfb, 0x7fc: 0xfb, 0x7fd: 0xfb, 0x7fe: 0xfb, 0x7ff: 0xfb, + 0x7c8: 0xa0, 0x7c9: 0xa0, 0x7ca: 0xa0, 0x7cb: 0xa0, 0x7cc: 0xa0, 0x7cd: 0x19d, 0x7ce: 0xfd, 0x7cf: 0xfd, + 0x7d0: 0xfd, 0x7d1: 0xfd, 0x7d2: 0xfd, 0x7d3: 0xfd, 0x7d4: 0xfd, 0x7d5: 0xfd, 0x7d6: 0xfd, 0x7d7: 0xfd, + 0x7d8: 0xfd, 0x7d9: 0xfd, 0x7da: 0xfd, 0x7db: 0xfd, 0x7dc: 0xfd, 0x7dd: 0xfd, 0x7de: 0xfd, 0x7df: 0xfd, + 0x7e0: 0xfd, 0x7e1: 0xfd, 0x7e2: 0xfd, 0x7e3: 0xfd, 0x7e4: 0xfd, 0x7e5: 0xfd, 0x7e6: 0xfd, 0x7e7: 0xfd, + 0x7e8: 0xfd, 0x7e9: 0xfd, 0x7ea: 0xfd, 0x7eb: 0xfd, 0x7ec: 0xfd, 0x7ed: 0xfd, 0x7ee: 0xfd, 0x7ef: 0xfd, + 0x7f0: 0xfd, 0x7f1: 0xfd, 0x7f2: 0xfd, 0x7f3: 0xfd, 0x7f4: 0xfd, 0x7f5: 0xfd, 0x7f6: 0xfd, 0x7f7: 0xfd, + 0x7f8: 0xfd, 0x7f9: 0xfd, 0x7fa: 0xfd, 0x7fb: 0xfd, 0x7fc: 0xfd, 0x7fd: 0xfd, 0x7fe: 0xfd, 0x7ff: 0xfd, // Block 0x20, offset 0x800 0x810: 0x0d, 0x811: 0x0e, 0x812: 0x0f, 0x813: 0x10, 0x814: 0x11, 0x815: 0x0b, 0x816: 0x12, 0x817: 0x07, 0x818: 0x13, 0x819: 0x0b, 0x81a: 0x0b, 0x81b: 0x14, 0x81c: 0x0b, 0x81d: 0x15, 0x81e: 0x16, 0x81f: 0x17, @@ -2370,14 +2513,14 @@ var idnaIndex = [2368]uint16{ 0x870: 0x0b, 0x871: 0x0b, 0x872: 0x0b, 0x873: 0x0b, 0x874: 0x0b, 0x875: 0x0b, 0x876: 0x0b, 0x877: 0x0b, 0x878: 0x0b, 0x879: 0x0b, 0x87a: 0x0b, 0x87b: 0x0b, 0x87c: 0x0b, 0x87d: 0x0b, 0x87e: 0x0b, 0x87f: 0x0b, // Block 0x22, offset 0x880 - 0x880: 0x19e, 0x881: 0x19f, 0x882: 0xfb, 0x883: 0xfb, 0x884: 0x1a0, 0x885: 0x1a0, 0x886: 0x1a0, 0x887: 0x1a1, - 0x888: 0xfb, 0x889: 0xfb, 0x88a: 0xfb, 0x88b: 0xfb, 0x88c: 0xfb, 0x88d: 0xfb, 0x88e: 0xfb, 0x88f: 0xfb, - 0x890: 0xfb, 0x891: 0xfb, 0x892: 0xfb, 0x893: 0xfb, 0x894: 0xfb, 0x895: 0xfb, 0x896: 0xfb, 0x897: 0xfb, - 0x898: 0xfb, 0x899: 0xfb, 0x89a: 0xfb, 0x89b: 0xfb, 0x89c: 0xfb, 0x89d: 0xfb, 0x89e: 0xfb, 0x89f: 0xfb, - 0x8a0: 0xfb, 0x8a1: 0xfb, 0x8a2: 0xfb, 0x8a3: 0xfb, 0x8a4: 0xfb, 0x8a5: 0xfb, 0x8a6: 0xfb, 0x8a7: 0xfb, - 0x8a8: 0xfb, 0x8a9: 0xfb, 0x8aa: 0xfb, 0x8ab: 0xfb, 0x8ac: 0xfb, 0x8ad: 0xfb, 0x8ae: 0xfb, 0x8af: 0xfb, - 0x8b0: 0xfb, 0x8b1: 0xfb, 0x8b2: 0xfb, 0x8b3: 0xfb, 0x8b4: 0xfb, 0x8b5: 0xfb, 0x8b6: 0xfb, 0x8b7: 0xfb, - 0x8b8: 0xfb, 0x8b9: 0xfb, 0x8ba: 0xfb, 0x8bb: 0xfb, 0x8bc: 0xfb, 0x8bd: 0xfb, 0x8be: 0xfb, 0x8bf: 0xfb, + 0x880: 0x19e, 0x881: 0x19f, 0x882: 0xfd, 0x883: 0xfd, 0x884: 0x1a0, 0x885: 0x1a0, 0x886: 0x1a0, 0x887: 0x1a1, + 0x888: 0xfd, 0x889: 0xfd, 0x88a: 0xfd, 0x88b: 0xfd, 0x88c: 0xfd, 0x88d: 0xfd, 0x88e: 0xfd, 0x88f: 0xfd, + 0x890: 0xfd, 0x891: 0xfd, 0x892: 0xfd, 0x893: 0xfd, 0x894: 0xfd, 0x895: 0xfd, 0x896: 0xfd, 0x897: 0xfd, + 0x898: 0xfd, 0x899: 0xfd, 0x89a: 0xfd, 0x89b: 0xfd, 0x89c: 0xfd, 0x89d: 0xfd, 0x89e: 0xfd, 0x89f: 0xfd, + 0x8a0: 0xfd, 0x8a1: 0xfd, 0x8a2: 0xfd, 0x8a3: 0xfd, 0x8a4: 0xfd, 0x8a5: 0xfd, 0x8a6: 0xfd, 0x8a7: 0xfd, + 0x8a8: 0xfd, 0x8a9: 0xfd, 0x8aa: 0xfd, 0x8ab: 0xfd, 0x8ac: 0xfd, 0x8ad: 0xfd, 0x8ae: 0xfd, 0x8af: 0xfd, + 0x8b0: 0xfd, 0x8b1: 0xfd, 0x8b2: 0xfd, 0x8b3: 0xfd, 0x8b4: 0xfd, 0x8b5: 0xfd, 0x8b6: 0xfd, 0x8b7: 0xfd, + 0x8b8: 0xfd, 0x8b9: 0xfd, 0x8ba: 0xfd, 0x8bb: 0xfd, 0x8bc: 0xfd, 0x8bd: 0xfd, 0x8be: 0xfd, 0x8bf: 0xfd, // Block 0x23, offset 0x8c0 0x8c0: 0x0b, 0x8c1: 0x0b, 0x8c2: 0x0b, 0x8c3: 0x0b, 0x8c4: 0x0b, 0x8c5: 0x0b, 0x8c6: 0x0b, 0x8c7: 0x0b, 0x8c8: 0x0b, 0x8c9: 0x0b, 0x8ca: 0x0b, 0x8cb: 0x0b, 0x8cc: 0x0b, 0x8cd: 0x0b, 0x8ce: 0x0b, 0x8cf: 0x0b, @@ -2393,10 +2536,10 @@ var idnaIndex = [2368]uint16{ } // idnaSparseOffset: 292 entries, 584 bytes -var idnaSparseOffset = []uint16{0x0, 0x8, 0x19, 0x25, 0x27, 0x2c, 0x33, 0x3e, 0x4a, 0x4e, 0x5d, 0x62, 0x6c, 0x78, 0x85, 0x8b, 0x94, 0xa4, 0xb2, 0xbd, 0xca, 0xdb, 0xe5, 0xec, 0xf9, 0x10a, 0x111, 0x11c, 0x12b, 0x139, 0x143, 0x145, 0x14a, 0x14d, 0x150, 0x152, 0x15e, 0x169, 0x171, 0x177, 0x17d, 0x182, 0x187, 0x18a, 0x18e, 0x194, 0x199, 0x1a5, 0x1af, 0x1b5, 0x1c6, 0x1d0, 0x1d3, 0x1db, 0x1de, 0x1eb, 0x1f3, 0x1f7, 0x1fe, 0x206, 0x216, 0x222, 0x225, 0x22f, 0x23b, 0x247, 0x253, 0x25b, 0x260, 0x26d, 0x27e, 0x282, 0x28d, 0x291, 0x29a, 0x2a2, 0x2a8, 0x2ad, 0x2b0, 0x2b4, 0x2ba, 0x2be, 0x2c2, 0x2c6, 0x2cc, 0x2d4, 0x2db, 0x2e6, 0x2f0, 0x2f4, 0x2f7, 0x2fd, 0x301, 0x303, 0x306, 0x308, 0x30b, 0x315, 0x318, 0x327, 0x32b, 0x330, 0x333, 0x337, 0x33c, 0x341, 0x347, 0x358, 0x368, 0x36e, 0x372, 0x381, 0x386, 0x38e, 0x398, 0x3a3, 0x3ab, 0x3bc, 0x3c5, 0x3d5, 0x3e2, 0x3ee, 0x3f3, 0x400, 0x404, 0x409, 0x40b, 0x40d, 0x411, 0x413, 0x417, 0x420, 0x426, 0x42a, 0x43a, 0x444, 0x449, 0x44c, 0x452, 0x459, 0x45e, 0x462, 0x468, 0x46d, 0x476, 0x47b, 0x481, 0x488, 0x48f, 0x496, 0x49a, 0x49f, 0x4a2, 0x4a7, 0x4b3, 0x4b9, 0x4be, 0x4c5, 0x4cd, 0x4d2, 0x4d6, 0x4e6, 0x4ed, 0x4f1, 0x4f5, 0x4fc, 0x4fe, 0x501, 0x504, 0x508, 0x511, 0x515, 0x51d, 0x525, 0x52d, 0x539, 0x545, 0x54b, 0x554, 0x560, 0x567, 0x570, 0x57b, 0x582, 0x591, 0x59e, 0x5ab, 0x5b4, 0x5b8, 0x5c7, 0x5cf, 0x5da, 0x5e3, 0x5e9, 0x5f1, 0x5fa, 0x605, 0x608, 0x614, 0x61d, 0x620, 0x625, 0x62e, 0x633, 0x640, 0x64b, 0x654, 0x65e, 0x661, 0x66b, 0x674, 0x680, 0x68d, 0x69a, 0x6a8, 0x6af, 0x6b3, 0x6b7, 0x6ba, 0x6bf, 0x6c2, 0x6c7, 0x6ca, 0x6d1, 0x6d8, 0x6dc, 0x6e7, 0x6ea, 0x6ed, 0x6f0, 0x6f6, 0x6fc, 0x705, 0x708, 0x70b, 0x70e, 0x711, 0x718, 0x71b, 0x720, 0x72a, 0x72d, 0x731, 0x740, 0x74c, 0x750, 0x755, 0x759, 0x75e, 0x762, 0x767, 0x770, 0x77b, 0x781, 0x787, 0x78d, 0x793, 0x79c, 0x79f, 0x7a2, 0x7a6, 0x7aa, 0x7ae, 0x7b4, 0x7ba, 0x7bf, 0x7c2, 0x7d2, 0x7d9, 0x7dc, 0x7e1, 0x7e5, 0x7eb, 0x7f2, 0x7f6, 0x7fa, 0x803, 0x80a, 0x80f, 0x813, 0x821, 0x824, 0x827, 0x82b, 0x82f, 0x832, 0x842, 0x853, 0x856, 0x85b, 0x85d, 0x85f} +var idnaSparseOffset = []uint16{0x0, 0x8, 0x19, 0x25, 0x27, 0x2c, 0x33, 0x3e, 0x4a, 0x4e, 0x5d, 0x62, 0x6c, 0x78, 0x85, 0x8b, 0x94, 0xa4, 0xb2, 0xbd, 0xca, 0xdb, 0xe5, 0xec, 0xf9, 0x10a, 0x111, 0x11c, 0x12b, 0x139, 0x143, 0x145, 0x14a, 0x14d, 0x150, 0x152, 0x15e, 0x169, 0x171, 0x177, 0x17d, 0x182, 0x187, 0x18a, 0x18e, 0x194, 0x199, 0x1a5, 0x1af, 0x1b5, 0x1c6, 0x1d0, 0x1d3, 0x1db, 0x1de, 0x1eb, 0x1f3, 0x1f7, 0x1fe, 0x206, 0x216, 0x222, 0x225, 0x22f, 0x23b, 0x247, 0x253, 0x25b, 0x260, 0x26d, 0x27e, 0x282, 0x28d, 0x291, 0x29a, 0x2a2, 0x2a8, 0x2ad, 0x2b0, 0x2b4, 0x2ba, 0x2be, 0x2c2, 0x2c6, 0x2cc, 0x2d4, 0x2db, 0x2e6, 0x2f0, 0x2f4, 0x2f7, 0x2fd, 0x301, 0x303, 0x306, 0x308, 0x30b, 0x315, 0x318, 0x327, 0x32b, 0x32f, 0x331, 0x33a, 0x33d, 0x341, 0x346, 0x34b, 0x351, 0x362, 0x372, 0x378, 0x37c, 0x38b, 0x390, 0x398, 0x3a2, 0x3ad, 0x3b5, 0x3c6, 0x3cf, 0x3df, 0x3ec, 0x3f8, 0x3fd, 0x40a, 0x40e, 0x413, 0x415, 0x417, 0x41b, 0x41d, 0x421, 0x42a, 0x430, 0x434, 0x444, 0x44e, 0x453, 0x456, 0x45c, 0x463, 0x468, 0x46c, 0x472, 0x477, 0x480, 0x485, 0x48b, 0x492, 0x499, 0x4a0, 0x4a4, 0x4a9, 0x4ac, 0x4b1, 0x4bd, 0x4c3, 0x4c8, 0x4cf, 0x4d7, 0x4dc, 0x4e0, 0x4f0, 0x4f7, 0x4fb, 0x4ff, 0x506, 0x508, 0x50b, 0x50e, 0x512, 0x51b, 0x51f, 0x527, 0x52f, 0x537, 0x543, 0x54f, 0x555, 0x55e, 0x56a, 0x571, 0x57a, 0x585, 0x58c, 0x59b, 0x5a8, 0x5b5, 0x5be, 0x5c2, 0x5d1, 0x5d9, 0x5e4, 0x5ed, 0x5f3, 0x5fb, 0x604, 0x60f, 0x612, 0x61e, 0x627, 0x62a, 0x62f, 0x638, 0x63d, 0x64a, 0x655, 0x65e, 0x668, 0x66b, 0x675, 0x67e, 0x68a, 0x697, 0x6a4, 0x6b2, 0x6b9, 0x6bd, 0x6c1, 0x6c4, 0x6c9, 0x6cc, 0x6d1, 0x6d4, 0x6db, 0x6e2, 0x6e6, 0x6f1, 0x6f4, 0x6f7, 0x6fa, 0x700, 0x706, 0x70f, 0x712, 0x715, 0x718, 0x71b, 0x722, 0x725, 0x72a, 0x734, 0x737, 0x73b, 0x74a, 0x756, 0x75a, 0x75f, 0x763, 0x768, 0x76c, 0x771, 0x77a, 0x785, 0x78b, 0x791, 0x797, 0x79d, 0x7a6, 0x7a9, 0x7ac, 0x7b0, 0x7b4, 0x7b8, 0x7be, 0x7c4, 0x7c9, 0x7cc, 0x7dc, 0x7e3, 0x7e6, 0x7eb, 0x7ef, 0x7f5, 0x7fc, 0x800, 0x804, 0x80d, 0x814, 0x819, 0x81d, 0x82b, 0x82e, 0x831, 0x835, 0x839, 0x83c, 0x83f, 0x844, 0x846, 0x848} -// idnaSparseValues: 2146 entries, 8584 bytes -var idnaSparseValues = [2146]valueRange{ +// idnaSparseValues: 2123 entries, 8492 bytes +var idnaSparseValues = [2123]valueRange{ // Block 0x0, offset 0x0 {value: 0x0000, lo: 0x07}, {value: 0xe105, lo: 0x80, hi: 0x96}, @@ -2427,15 +2570,15 @@ var idnaSparseValues = [2146]valueRange{ // Block 0x2, offset 0x19 {value: 0x0000, lo: 0x0b}, {value: 0x0008, lo: 0x80, hi: 0xaf}, - {value: 0x0249, lo: 0xb0, hi: 0xb0}, + {value: 0x00a9, lo: 0xb0, hi: 0xb0}, {value: 0x037d, lo: 0xb1, hi: 0xb1}, - {value: 0x0259, lo: 0xb2, hi: 0xb2}, - {value: 0x0269, lo: 0xb3, hi: 0xb3}, + {value: 0x00b1, lo: 0xb2, hi: 0xb2}, + {value: 0x00b9, lo: 0xb3, hi: 0xb3}, {value: 0x034d, lo: 0xb4, hi: 0xb4}, {value: 0x0395, lo: 0xb5, hi: 0xb5}, {value: 0xe1bd, lo: 0xb6, hi: 0xb6}, - {value: 0x0279, lo: 0xb7, hi: 0xb7}, - {value: 0x0289, lo: 0xb8, hi: 0xb8}, + {value: 0x00c1, lo: 0xb7, hi: 0xb7}, + {value: 0x00c9, lo: 0xb8, hi: 0xb8}, {value: 0x0008, lo: 0xb9, hi: 0xbf}, // Block 0x3, offset 0x25 {value: 0x0000, lo: 0x01}, @@ -2457,7 +2600,7 @@ var idnaSparseValues = [2146]valueRange{ // Block 0x6, offset 0x33 {value: 0x0000, lo: 0x0a}, {value: 0x0008, lo: 0x80, hi: 0x86}, - {value: 0x0401, lo: 0x87, hi: 0x87}, + {value: 0x0131, lo: 0x87, hi: 0x87}, {value: 0x0008, lo: 0x88, hi: 0x88}, {value: 0x0018, lo: 0x89, hi: 0x8a}, {value: 0x0040, lo: 0x8b, hi: 0x8c}, @@ -2643,7 +2786,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0x81, hi: 0xb0}, {value: 0x3308, lo: 0xb1, hi: 0xb1}, {value: 0x0008, lo: 0xb2, hi: 0xb2}, - {value: 0x08f1, lo: 0xb3, hi: 0xb3}, + {value: 0x01f1, lo: 0xb3, hi: 0xb3}, {value: 0x3308, lo: 0xb4, hi: 0xb9}, {value: 0x3b08, lo: 0xba, hi: 0xba}, {value: 0x0040, lo: 0xbb, hi: 0xbe}, @@ -2666,8 +2809,8 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x8e, hi: 0x8f}, {value: 0x0008, lo: 0x90, hi: 0x99}, {value: 0x0040, lo: 0x9a, hi: 0x9b}, - {value: 0x0961, lo: 0x9c, hi: 0x9c}, - {value: 0x0999, lo: 0x9d, hi: 0x9d}, + {value: 0x0201, lo: 0x9c, hi: 0x9c}, + {value: 0x0209, lo: 0x9d, hi: 0x9d}, {value: 0x0008, lo: 0x9e, hi: 0x9f}, {value: 0x0040, lo: 0xa0, hi: 0xbf}, // Block 0x18, offset 0xf9 @@ -3075,13 +3218,13 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0018, lo: 0xbe, hi: 0xbf}, // Block 0x44, offset 0x260 {value: 0x0000, lo: 0x0c}, - {value: 0x0e29, lo: 0x80, hi: 0x80}, - {value: 0x0e41, lo: 0x81, hi: 0x81}, - {value: 0x0e59, lo: 0x82, hi: 0x82}, - {value: 0x0e71, lo: 0x83, hi: 0x83}, - {value: 0x0e89, lo: 0x84, hi: 0x85}, - {value: 0x0ea1, lo: 0x86, hi: 0x86}, - {value: 0x0eb9, lo: 0x87, hi: 0x87}, + {value: 0x02a9, lo: 0x80, hi: 0x80}, + {value: 0x02b1, lo: 0x81, hi: 0x81}, + {value: 0x02b9, lo: 0x82, hi: 0x82}, + {value: 0x02c1, lo: 0x83, hi: 0x83}, + {value: 0x02c9, lo: 0x84, hi: 0x85}, + {value: 0x02d1, lo: 0x86, hi: 0x86}, + {value: 0x02d9, lo: 0x87, hi: 0x87}, {value: 0x057d, lo: 0x88, hi: 0x88}, {value: 0x0040, lo: 0x89, hi: 0x8f}, {value: 0x059d, lo: 0x90, hi: 0xba}, @@ -3133,18 +3276,18 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x83, hi: 0x83}, {value: 0x0008, lo: 0x84, hi: 0x84}, {value: 0x0018, lo: 0x85, hi: 0x88}, - {value: 0x24c1, lo: 0x89, hi: 0x89}, + {value: 0x0851, lo: 0x89, hi: 0x89}, {value: 0x0018, lo: 0x8a, hi: 0x8b}, {value: 0x0040, lo: 0x8c, hi: 0x8f}, {value: 0x0018, lo: 0x90, hi: 0xbf}, // Block 0x4a, offset 0x29a {value: 0x0000, lo: 0x07}, {value: 0x0018, lo: 0x80, hi: 0xab}, - {value: 0x24f1, lo: 0xac, hi: 0xac}, - {value: 0x2529, lo: 0xad, hi: 0xad}, + {value: 0x0859, lo: 0xac, hi: 0xac}, + {value: 0x0861, lo: 0xad, hi: 0xad}, {value: 0x0018, lo: 0xae, hi: 0xae}, - {value: 0x2579, lo: 0xaf, hi: 0xaf}, - {value: 0x25b1, lo: 0xb0, hi: 0xb0}, + {value: 0x0869, lo: 0xaf, hi: 0xaf}, + {value: 0x0871, lo: 0xb0, hi: 0xb0}, {value: 0x0018, lo: 0xb1, hi: 0xbf}, // Block 0x4b, offset 0x2a2 {value: 0x0000, lo: 0x05}, @@ -3166,19 +3309,19 @@ var idnaSparseValues = [2146]valueRange{ // Block 0x4e, offset 0x2b0 {value: 0x0000, lo: 0x03}, {value: 0x0018, lo: 0x80, hi: 0x8b}, - {value: 0x28c1, lo: 0x8c, hi: 0x8c}, + {value: 0x0929, lo: 0x8c, hi: 0x8c}, {value: 0x0018, lo: 0x8d, hi: 0xbf}, // Block 0x4f, offset 0x2b4 {value: 0x0000, lo: 0x05}, {value: 0x0018, lo: 0x80, hi: 0xb3}, {value: 0x0e7e, lo: 0xb4, hi: 0xb4}, - {value: 0x292a, lo: 0xb5, hi: 0xb5}, + {value: 0x0932, lo: 0xb5, hi: 0xb5}, {value: 0x0e9e, lo: 0xb6, hi: 0xb6}, {value: 0x0018, lo: 0xb7, hi: 0xbf}, // Block 0x50, offset 0x2ba {value: 0x0000, lo: 0x03}, {value: 0x0018, lo: 0x80, hi: 0x9b}, - {value: 0x2941, lo: 0x9c, hi: 0x9c}, + {value: 0x0939, lo: 0x9c, hi: 0x9c}, {value: 0x0018, lo: 0x9d, hi: 0xbf}, // Block 0x51, offset 0x2be {value: 0x0000, lo: 0x03}, @@ -3277,16 +3420,16 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0x80, hi: 0x96}, {value: 0x0040, lo: 0x97, hi: 0x98}, {value: 0x3308, lo: 0x99, hi: 0x9a}, - {value: 0x29e2, lo: 0x9b, hi: 0x9b}, - {value: 0x2a0a, lo: 0x9c, hi: 0x9c}, + {value: 0x096a, lo: 0x9b, hi: 0x9b}, + {value: 0x0972, lo: 0x9c, hi: 0x9c}, {value: 0x0008, lo: 0x9d, hi: 0x9e}, - {value: 0x2a31, lo: 0x9f, hi: 0x9f}, + {value: 0x0979, lo: 0x9f, hi: 0x9f}, {value: 0x0018, lo: 0xa0, hi: 0xa0}, {value: 0x0008, lo: 0xa1, hi: 0xbf}, // Block 0x61, offset 0x315 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0xbe}, - {value: 0x2a69, lo: 0xbf, hi: 0xbf}, + {value: 0x0981, lo: 0xbf, hi: 0xbf}, // Block 0x62, offset 0x318 {value: 0x0000, lo: 0x0e}, {value: 0x0040, lo: 0x80, hi: 0x84}, @@ -3309,46 +3452,58 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xa4, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xbf}, // Block 0x64, offset 0x32b - {value: 0x0030, lo: 0x04}, - {value: 0x2aa2, lo: 0x80, hi: 0x9d}, - {value: 0x305a, lo: 0x9e, hi: 0x9e}, + {value: 0x0008, lo: 0x03}, + {value: 0x098a, lo: 0x80, hi: 0x9e}, {value: 0x0040, lo: 0x9f, hi: 0x9f}, - {value: 0x30a2, lo: 0xa0, hi: 0xbf}, - // Block 0x65, offset 0x330 + {value: 0x0a82, lo: 0xa0, hi: 0xbf}, + // Block 0x65, offset 0x32f + {value: 0x0008, lo: 0x01}, + {value: 0x0d19, lo: 0x80, hi: 0xbf}, + // Block 0x66, offset 0x331 + {value: 0x0008, lo: 0x08}, + {value: 0x0f19, lo: 0x80, hi: 0xb0}, + {value: 0x4045, lo: 0xb1, hi: 0xb1}, + {value: 0x10a1, lo: 0xb2, hi: 0xb3}, + {value: 0x4065, lo: 0xb4, hi: 0xb4}, + {value: 0x10b1, lo: 0xb5, hi: 0xb7}, + {value: 0x4085, lo: 0xb8, hi: 0xb8}, + {value: 0x4085, lo: 0xb9, hi: 0xb9}, + {value: 0x10c9, lo: 0xba, hi: 0xbf}, + // Block 0x67, offset 0x33a {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0xbc}, {value: 0x0040, lo: 0xbd, hi: 0xbf}, - // Block 0x66, offset 0x333 + // Block 0x68, offset 0x33d {value: 0x0000, lo: 0x03}, {value: 0x0008, lo: 0x80, hi: 0x8c}, {value: 0x0040, lo: 0x8d, hi: 0x8f}, {value: 0x0018, lo: 0x90, hi: 0xbf}, - // Block 0x67, offset 0x337 + // Block 0x69, offset 0x341 {value: 0x0000, lo: 0x04}, {value: 0x0018, lo: 0x80, hi: 0x86}, {value: 0x0040, lo: 0x87, hi: 0x8f}, {value: 0x0008, lo: 0x90, hi: 0xbd}, {value: 0x0018, lo: 0xbe, hi: 0xbf}, - // Block 0x68, offset 0x33c + // Block 0x6a, offset 0x346 {value: 0x0000, lo: 0x04}, {value: 0x0008, lo: 0x80, hi: 0x8c}, {value: 0x0018, lo: 0x8d, hi: 0x8f}, {value: 0x0008, lo: 0x90, hi: 0xab}, {value: 0x0040, lo: 0xac, hi: 0xbf}, - // Block 0x69, offset 0x341 + // Block 0x6b, offset 0x34b {value: 0x0000, lo: 0x05}, {value: 0x0008, lo: 0x80, hi: 0xa5}, {value: 0x0018, lo: 0xa6, hi: 0xaf}, {value: 0x3308, lo: 0xb0, hi: 0xb1}, {value: 0x0018, lo: 0xb2, hi: 0xb7}, {value: 0x0040, lo: 0xb8, hi: 0xbf}, - // Block 0x6a, offset 0x347 + // Block 0x6c, offset 0x351 {value: 0x0000, lo: 0x10}, {value: 0x0040, lo: 0x80, hi: 0x81}, {value: 0xe00d, lo: 0x82, hi: 0x82}, {value: 0x0008, lo: 0x83, hi: 0x83}, {value: 0x03f5, lo: 0x84, hi: 0x84}, - {value: 0x1329, lo: 0x85, hi: 0x85}, + {value: 0x0479, lo: 0x85, hi: 0x85}, {value: 0x447d, lo: 0x86, hi: 0x86}, {value: 0xe07d, lo: 0x87, hi: 0x87}, {value: 0x0008, lo: 0x88, hi: 0x88}, @@ -3357,10 +3512,10 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x8b, hi: 0xb4}, {value: 0xe01d, lo: 0xb5, hi: 0xb5}, {value: 0x0008, lo: 0xb6, hi: 0xb7}, - {value: 0x2009, lo: 0xb8, hi: 0xb8}, - {value: 0x6ec1, lo: 0xb9, hi: 0xb9}, + {value: 0x0741, lo: 0xb8, hi: 0xb8}, + {value: 0x13f1, lo: 0xb9, hi: 0xb9}, {value: 0x0008, lo: 0xba, hi: 0xbf}, - // Block 0x6b, offset 0x358 + // Block 0x6d, offset 0x362 {value: 0x0000, lo: 0x0f}, {value: 0x0008, lo: 0x80, hi: 0x81}, {value: 0x3308, lo: 0x82, hi: 0x82}, @@ -3377,19 +3532,19 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xad, hi: 0xaf}, {value: 0x0018, lo: 0xb0, hi: 0xb9}, {value: 0x0040, lo: 0xba, hi: 0xbf}, - // Block 0x6c, offset 0x368 + // Block 0x6e, offset 0x372 {value: 0x0000, lo: 0x05}, {value: 0x0208, lo: 0x80, hi: 0xb1}, {value: 0x0108, lo: 0xb2, hi: 0xb2}, {value: 0x0008, lo: 0xb3, hi: 0xb3}, {value: 0x0018, lo: 0xb4, hi: 0xb7}, {value: 0x0040, lo: 0xb8, hi: 0xbf}, - // Block 0x6d, offset 0x36e + // Block 0x6f, offset 0x378 {value: 0x0000, lo: 0x03}, {value: 0x3008, lo: 0x80, hi: 0x81}, {value: 0x0008, lo: 0x82, hi: 0xb3}, {value: 0x3008, lo: 0xb4, hi: 0xbf}, - // Block 0x6e, offset 0x372 + // Block 0x70, offset 0x37c {value: 0x0000, lo: 0x0e}, {value: 0x3008, lo: 0x80, hi: 0x83}, {value: 0x3b08, lo: 0x84, hi: 0x84}, @@ -3405,13 +3560,13 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0018, lo: 0xbc, hi: 0xbc}, {value: 0x0008, lo: 0xbd, hi: 0xbe}, {value: 0x3308, lo: 0xbf, hi: 0xbf}, - // Block 0x6f, offset 0x381 + // Block 0x71, offset 0x38b {value: 0x0000, lo: 0x04}, {value: 0x0008, lo: 0x80, hi: 0xa5}, {value: 0x3308, lo: 0xa6, hi: 0xad}, {value: 0x0018, lo: 0xae, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xbf}, - // Block 0x70, offset 0x386 + // Block 0x72, offset 0x390 {value: 0x0000, lo: 0x07}, {value: 0x0008, lo: 0x80, hi: 0x86}, {value: 0x3308, lo: 0x87, hi: 0x91}, @@ -3420,7 +3575,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x94, hi: 0x9e}, {value: 0x0018, lo: 0x9f, hi: 0xbc}, {value: 0x0040, lo: 0xbd, hi: 0xbf}, - // Block 0x71, offset 0x38e + // Block 0x73, offset 0x398 {value: 0x0000, lo: 0x09}, {value: 0x3308, lo: 0x80, hi: 0x82}, {value: 0x3008, lo: 0x83, hi: 0x83}, @@ -3431,7 +3586,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3008, lo: 0xba, hi: 0xbb}, {value: 0x3308, lo: 0xbc, hi: 0xbd}, {value: 0x3008, lo: 0xbe, hi: 0xbf}, - // Block 0x72, offset 0x398 + // Block 0x74, offset 0x3a2 {value: 0x0000, lo: 0x0a}, {value: 0x3808, lo: 0x80, hi: 0x80}, {value: 0x0018, lo: 0x81, hi: 0x8d}, @@ -3443,7 +3598,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xa5, hi: 0xa5}, {value: 0x0008, lo: 0xa6, hi: 0xbe}, {value: 0x0040, lo: 0xbf, hi: 0xbf}, - // Block 0x73, offset 0x3a3 + // Block 0x75, offset 0x3ad {value: 0x0000, lo: 0x07}, {value: 0x0008, lo: 0x80, hi: 0xa8}, {value: 0x3308, lo: 0xa9, hi: 0xae}, @@ -3452,7 +3607,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3008, lo: 0xb3, hi: 0xb4}, {value: 0x3308, lo: 0xb5, hi: 0xb6}, {value: 0x0040, lo: 0xb7, hi: 0xbf}, - // Block 0x74, offset 0x3ab + // Block 0x76, offset 0x3b5 {value: 0x0000, lo: 0x10}, {value: 0x0008, lo: 0x80, hi: 0x82}, {value: 0x3308, lo: 0x83, hi: 0x83}, @@ -3470,7 +3625,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xbc, hi: 0xbc}, {value: 0x3008, lo: 0xbd, hi: 0xbd}, {value: 0x0008, lo: 0xbe, hi: 0xbf}, - // Block 0x75, offset 0x3bc + // Block 0x77, offset 0x3c6 {value: 0x0000, lo: 0x08}, {value: 0x0008, lo: 0x80, hi: 0xaf}, {value: 0x3308, lo: 0xb0, hi: 0xb0}, @@ -3480,7 +3635,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xb7, hi: 0xb8}, {value: 0x0008, lo: 0xb9, hi: 0xbd}, {value: 0x3308, lo: 0xbe, hi: 0xbf}, - // Block 0x76, offset 0x3c5 + // Block 0x78, offset 0x3cf {value: 0x0000, lo: 0x0f}, {value: 0x0008, lo: 0x80, hi: 0x80}, {value: 0x3308, lo: 0x81, hi: 0x81}, @@ -3497,7 +3652,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3008, lo: 0xb5, hi: 0xb5}, {value: 0x3b08, lo: 0xb6, hi: 0xb6}, {value: 0x0040, lo: 0xb7, hi: 0xbf}, - // Block 0x77, offset 0x3d5 + // Block 0x79, offset 0x3df {value: 0x0000, lo: 0x0c}, {value: 0x0040, lo: 0x80, hi: 0x80}, {value: 0x0008, lo: 0x81, hi: 0x86}, @@ -3511,26 +3666,26 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0xa8, hi: 0xae}, {value: 0x0040, lo: 0xaf, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xbf}, - // Block 0x78, offset 0x3e2 + // Block 0x7a, offset 0x3ec {value: 0x0000, lo: 0x0b}, {value: 0x0008, lo: 0x80, hi: 0x9a}, {value: 0x0018, lo: 0x9b, hi: 0x9b}, {value: 0x449d, lo: 0x9c, hi: 0x9c}, {value: 0x44b5, lo: 0x9d, hi: 0x9d}, - {value: 0x2971, lo: 0x9e, hi: 0x9e}, + {value: 0x0941, lo: 0x9e, hi: 0x9e}, {value: 0xe06d, lo: 0x9f, hi: 0x9f}, {value: 0x0008, lo: 0xa0, hi: 0xa8}, - {value: 0x6ed9, lo: 0xa9, hi: 0xa9}, + {value: 0x13f9, lo: 0xa9, hi: 0xa9}, {value: 0x0018, lo: 0xaa, hi: 0xab}, {value: 0x0040, lo: 0xac, hi: 0xaf}, {value: 0x44cd, lo: 0xb0, hi: 0xbf}, - // Block 0x79, offset 0x3ee + // Block 0x7b, offset 0x3f8 {value: 0x0000, lo: 0x04}, {value: 0x44ed, lo: 0x80, hi: 0x8f}, {value: 0x450d, lo: 0x90, hi: 0x9f}, {value: 0x452d, lo: 0xa0, hi: 0xaf}, {value: 0x450d, lo: 0xb0, hi: 0xbf}, - // Block 0x7a, offset 0x3f3 + // Block 0x7c, offset 0x3fd {value: 0x0000, lo: 0x0c}, {value: 0x0008, lo: 0x80, hi: 0xa2}, {value: 0x3008, lo: 0xa3, hi: 0xa4}, @@ -3544,76 +3699,76 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xae, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xb9}, {value: 0x0040, lo: 0xba, hi: 0xbf}, - // Block 0x7b, offset 0x400 + // Block 0x7d, offset 0x40a {value: 0x0000, lo: 0x03}, {value: 0x0008, lo: 0x80, hi: 0xa3}, {value: 0x0040, lo: 0xa4, hi: 0xaf}, {value: 0x0018, lo: 0xb0, hi: 0xbf}, - // Block 0x7c, offset 0x404 + // Block 0x7e, offset 0x40e {value: 0x0000, lo: 0x04}, {value: 0x0018, lo: 0x80, hi: 0x86}, {value: 0x0040, lo: 0x87, hi: 0x8a}, {value: 0x0018, lo: 0x8b, hi: 0xbb}, {value: 0x0040, lo: 0xbc, hi: 0xbf}, - // Block 0x7d, offset 0x409 + // Block 0x7f, offset 0x413 {value: 0x0000, lo: 0x01}, {value: 0x0040, lo: 0x80, hi: 0xbf}, - // Block 0x7e, offset 0x40b + // Block 0x80, offset 0x415 {value: 0x0020, lo: 0x01}, {value: 0x454d, lo: 0x80, hi: 0xbf}, - // Block 0x7f, offset 0x40d + // Block 0x81, offset 0x417 {value: 0x0020, lo: 0x03}, {value: 0x4d4d, lo: 0x80, hi: 0x94}, {value: 0x4b0d, lo: 0x95, hi: 0x95}, {value: 0x4fed, lo: 0x96, hi: 0xbf}, - // Block 0x80, offset 0x411 + // Block 0x82, offset 0x41b {value: 0x0020, lo: 0x01}, {value: 0x552d, lo: 0x80, hi: 0xbf}, - // Block 0x81, offset 0x413 + // Block 0x83, offset 0x41d {value: 0x0020, lo: 0x03}, {value: 0x5d2d, lo: 0x80, hi: 0x84}, {value: 0x568d, lo: 0x85, hi: 0x85}, {value: 0x5dcd, lo: 0x86, hi: 0xbf}, - // Block 0x82, offset 0x417 + // Block 0x84, offset 0x421 {value: 0x0020, lo: 0x08}, {value: 0x6b8d, lo: 0x80, hi: 0x8f}, {value: 0x6d4d, lo: 0x90, hi: 0x90}, {value: 0x6d8d, lo: 0x91, hi: 0xab}, - {value: 0x6ef1, lo: 0xac, hi: 0xac}, + {value: 0x1401, lo: 0xac, hi: 0xac}, {value: 0x70ed, lo: 0xad, hi: 0xad}, {value: 0x0040, lo: 0xae, hi: 0xae}, {value: 0x0040, lo: 0xaf, hi: 0xaf}, {value: 0x710d, lo: 0xb0, hi: 0xbf}, - // Block 0x83, offset 0x420 + // Block 0x85, offset 0x42a {value: 0x0020, lo: 0x05}, {value: 0x730d, lo: 0x80, hi: 0xad}, {value: 0x656d, lo: 0xae, hi: 0xae}, {value: 0x78cd, lo: 0xaf, hi: 0xb5}, {value: 0x6f8d, lo: 0xb6, hi: 0xb6}, {value: 0x79ad, lo: 0xb7, hi: 0xbf}, - // Block 0x84, offset 0x426 - {value: 0x0028, lo: 0x03}, - {value: 0x7c71, lo: 0x80, hi: 0x82}, - {value: 0x7c31, lo: 0x83, hi: 0x83}, - {value: 0x7ce9, lo: 0x84, hi: 0xbf}, - // Block 0x85, offset 0x42a - {value: 0x0038, lo: 0x0f}, - {value: 0x9e01, lo: 0x80, hi: 0x83}, - {value: 0x9ea9, lo: 0x84, hi: 0x85}, - {value: 0x9ee1, lo: 0x86, hi: 0x87}, - {value: 0x9f19, lo: 0x88, hi: 0x8f}, + // Block 0x86, offset 0x430 + {value: 0x0008, lo: 0x03}, + {value: 0x1751, lo: 0x80, hi: 0x82}, + {value: 0x1741, lo: 0x83, hi: 0x83}, + {value: 0x1769, lo: 0x84, hi: 0xbf}, + // Block 0x87, offset 0x434 + {value: 0x0008, lo: 0x0f}, + {value: 0x1d81, lo: 0x80, hi: 0x83}, + {value: 0x1d99, lo: 0x84, hi: 0x85}, + {value: 0x1da1, lo: 0x86, hi: 0x87}, + {value: 0x1da9, lo: 0x88, hi: 0x8f}, {value: 0x0040, lo: 0x90, hi: 0x90}, {value: 0x0040, lo: 0x91, hi: 0x91}, - {value: 0xa0d9, lo: 0x92, hi: 0x97}, - {value: 0xa1f1, lo: 0x98, hi: 0x9c}, - {value: 0xa2d1, lo: 0x9d, hi: 0xb3}, - {value: 0x9d91, lo: 0xb4, hi: 0xb4}, - {value: 0x9e01, lo: 0xb5, hi: 0xb5}, - {value: 0xa7d9, lo: 0xb6, hi: 0xbb}, - {value: 0xa8b9, lo: 0xbc, hi: 0xbc}, - {value: 0xa849, lo: 0xbd, hi: 0xbd}, - {value: 0xa929, lo: 0xbe, hi: 0xbf}, - // Block 0x86, offset 0x43a + {value: 0x1de9, lo: 0x92, hi: 0x97}, + {value: 0x1e11, lo: 0x98, hi: 0x9c}, + {value: 0x1e31, lo: 0x9d, hi: 0xb3}, + {value: 0x1d71, lo: 0xb4, hi: 0xb4}, + {value: 0x1d81, lo: 0xb5, hi: 0xb5}, + {value: 0x1ee9, lo: 0xb6, hi: 0xbb}, + {value: 0x1f09, lo: 0xbc, hi: 0xbc}, + {value: 0x1ef9, lo: 0xbd, hi: 0xbd}, + {value: 0x1f19, lo: 0xbe, hi: 0xbf}, + // Block 0x88, offset 0x444 {value: 0x0000, lo: 0x09}, {value: 0x0008, lo: 0x80, hi: 0x8b}, {value: 0x0040, lo: 0x8c, hi: 0x8c}, @@ -3624,24 +3779,24 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0xbc, hi: 0xbd}, {value: 0x0040, lo: 0xbe, hi: 0xbe}, {value: 0x0008, lo: 0xbf, hi: 0xbf}, - // Block 0x87, offset 0x444 + // Block 0x89, offset 0x44e {value: 0x0000, lo: 0x04}, {value: 0x0008, lo: 0x80, hi: 0x8d}, {value: 0x0040, lo: 0x8e, hi: 0x8f}, {value: 0x0008, lo: 0x90, hi: 0x9d}, {value: 0x0040, lo: 0x9e, hi: 0xbf}, - // Block 0x88, offset 0x449 + // Block 0x8a, offset 0x453 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0xba}, {value: 0x0040, lo: 0xbb, hi: 0xbf}, - // Block 0x89, offset 0x44c + // Block 0x8b, offset 0x456 {value: 0x0000, lo: 0x05}, {value: 0x0018, lo: 0x80, hi: 0x82}, {value: 0x0040, lo: 0x83, hi: 0x86}, {value: 0x0018, lo: 0x87, hi: 0xb3}, {value: 0x0040, lo: 0xb4, hi: 0xb6}, {value: 0x0018, lo: 0xb7, hi: 0xbf}, - // Block 0x8a, offset 0x452 + // Block 0x8c, offset 0x45c {value: 0x0000, lo: 0x06}, {value: 0x0018, lo: 0x80, hi: 0x8e}, {value: 0x0040, lo: 0x8f, hi: 0x8f}, @@ -3649,31 +3804,31 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x9d, hi: 0x9f}, {value: 0x0018, lo: 0xa0, hi: 0xa0}, {value: 0x0040, lo: 0xa1, hi: 0xbf}, - // Block 0x8b, offset 0x459 + // Block 0x8d, offset 0x463 {value: 0x0000, lo: 0x04}, {value: 0x0040, lo: 0x80, hi: 0x8f}, {value: 0x0018, lo: 0x90, hi: 0xbc}, {value: 0x3308, lo: 0xbd, hi: 0xbd}, {value: 0x0040, lo: 0xbe, hi: 0xbf}, - // Block 0x8c, offset 0x45e + // Block 0x8e, offset 0x468 {value: 0x0000, lo: 0x03}, {value: 0x0008, lo: 0x80, hi: 0x9c}, {value: 0x0040, lo: 0x9d, hi: 0x9f}, {value: 0x0008, lo: 0xa0, hi: 0xbf}, - // Block 0x8d, offset 0x462 + // Block 0x8f, offset 0x46c {value: 0x0000, lo: 0x05}, {value: 0x0008, lo: 0x80, hi: 0x90}, {value: 0x0040, lo: 0x91, hi: 0x9f}, {value: 0x3308, lo: 0xa0, hi: 0xa0}, {value: 0x0018, lo: 0xa1, hi: 0xbb}, {value: 0x0040, lo: 0xbc, hi: 0xbf}, - // Block 0x8e, offset 0x468 + // Block 0x90, offset 0x472 {value: 0x0000, lo: 0x04}, {value: 0x0008, lo: 0x80, hi: 0x9f}, {value: 0x0018, lo: 0xa0, hi: 0xa3}, {value: 0x0040, lo: 0xa4, hi: 0xac}, {value: 0x0008, lo: 0xad, hi: 0xbf}, - // Block 0x8f, offset 0x46d + // Block 0x91, offset 0x477 {value: 0x0000, lo: 0x08}, {value: 0x0008, lo: 0x80, hi: 0x80}, {value: 0x0018, lo: 0x81, hi: 0x81}, @@ -3683,20 +3838,20 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0x90, hi: 0xb5}, {value: 0x3308, lo: 0xb6, hi: 0xba}, {value: 0x0040, lo: 0xbb, hi: 0xbf}, - // Block 0x90, offset 0x476 + // Block 0x92, offset 0x480 {value: 0x0000, lo: 0x04}, {value: 0x0008, lo: 0x80, hi: 0x9d}, {value: 0x0040, lo: 0x9e, hi: 0x9e}, {value: 0x0018, lo: 0x9f, hi: 0x9f}, {value: 0x0008, lo: 0xa0, hi: 0xbf}, - // Block 0x91, offset 0x47b + // Block 0x93, offset 0x485 {value: 0x0000, lo: 0x05}, {value: 0x0008, lo: 0x80, hi: 0x83}, {value: 0x0040, lo: 0x84, hi: 0x87}, {value: 0x0008, lo: 0x88, hi: 0x8f}, {value: 0x0018, lo: 0x90, hi: 0x95}, {value: 0x0040, lo: 0x96, hi: 0xbf}, - // Block 0x92, offset 0x481 + // Block 0x94, offset 0x48b {value: 0x0000, lo: 0x06}, {value: 0xe145, lo: 0x80, hi: 0x87}, {value: 0xe1c5, lo: 0x88, hi: 0x8f}, @@ -3704,7 +3859,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x8b0d, lo: 0x98, hi: 0x9f}, {value: 0x8b25, lo: 0xa0, hi: 0xa7}, {value: 0x0008, lo: 0xa8, hi: 0xbf}, - // Block 0x93, offset 0x488 + // Block 0x95, offset 0x492 {value: 0x0000, lo: 0x06}, {value: 0x0008, lo: 0x80, hi: 0x9d}, {value: 0x0040, lo: 0x9e, hi: 0x9f}, @@ -3712,7 +3867,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xaa, hi: 0xaf}, {value: 0x8b25, lo: 0xb0, hi: 0xb7}, {value: 0x8b0d, lo: 0xb8, hi: 0xbf}, - // Block 0x94, offset 0x48f + // Block 0x96, offset 0x499 {value: 0x0000, lo: 0x06}, {value: 0xe145, lo: 0x80, hi: 0x87}, {value: 0xe1c5, lo: 0x88, hi: 0x8f}, @@ -3720,28 +3875,28 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x94, hi: 0x97}, {value: 0x0008, lo: 0x98, hi: 0xbb}, {value: 0x0040, lo: 0xbc, hi: 0xbf}, - // Block 0x95, offset 0x496 + // Block 0x97, offset 0x4a0 {value: 0x0000, lo: 0x03}, {value: 0x0008, lo: 0x80, hi: 0xa7}, {value: 0x0040, lo: 0xa8, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xbf}, - // Block 0x96, offset 0x49a + // Block 0x98, offset 0x4a4 {value: 0x0000, lo: 0x04}, {value: 0x0008, lo: 0x80, hi: 0xa3}, {value: 0x0040, lo: 0xa4, hi: 0xae}, {value: 0x0018, lo: 0xaf, hi: 0xaf}, {value: 0x0040, lo: 0xb0, hi: 0xbf}, - // Block 0x97, offset 0x49f + // Block 0x99, offset 0x4a9 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0xb6}, {value: 0x0040, lo: 0xb7, hi: 0xbf}, - // Block 0x98, offset 0x4a2 + // Block 0x9a, offset 0x4ac {value: 0x0000, lo: 0x04}, {value: 0x0008, lo: 0x80, hi: 0x95}, {value: 0x0040, lo: 0x96, hi: 0x9f}, {value: 0x0008, lo: 0xa0, hi: 0xa7}, {value: 0x0040, lo: 0xa8, hi: 0xbf}, - // Block 0x99, offset 0x4a7 + // Block 0x9b, offset 0x4b1 {value: 0x0000, lo: 0x0b}, {value: 0x0808, lo: 0x80, hi: 0x85}, {value: 0x0040, lo: 0x86, hi: 0x87}, @@ -3754,20 +3909,20 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0808, lo: 0xbc, hi: 0xbc}, {value: 0x0040, lo: 0xbd, hi: 0xbe}, {value: 0x0808, lo: 0xbf, hi: 0xbf}, - // Block 0x9a, offset 0x4b3 + // Block 0x9c, offset 0x4bd {value: 0x0000, lo: 0x05}, {value: 0x0808, lo: 0x80, hi: 0x95}, {value: 0x0040, lo: 0x96, hi: 0x96}, {value: 0x0818, lo: 0x97, hi: 0x9f}, {value: 0x0808, lo: 0xa0, hi: 0xb6}, {value: 0x0818, lo: 0xb7, hi: 0xbf}, - // Block 0x9b, offset 0x4b9 + // Block 0x9d, offset 0x4c3 {value: 0x0000, lo: 0x04}, {value: 0x0808, lo: 0x80, hi: 0x9e}, {value: 0x0040, lo: 0x9f, hi: 0xa6}, {value: 0x0818, lo: 0xa7, hi: 0xaf}, {value: 0x0040, lo: 0xb0, hi: 0xbf}, - // Block 0x9c, offset 0x4be + // Block 0x9e, offset 0x4c8 {value: 0x0000, lo: 0x06}, {value: 0x0040, lo: 0x80, hi: 0x9f}, {value: 0x0808, lo: 0xa0, hi: 0xb2}, @@ -3775,7 +3930,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0808, lo: 0xb4, hi: 0xb5}, {value: 0x0040, lo: 0xb6, hi: 0xba}, {value: 0x0818, lo: 0xbb, hi: 0xbf}, - // Block 0x9d, offset 0x4c5 + // Block 0x9f, offset 0x4cf {value: 0x0000, lo: 0x07}, {value: 0x0808, lo: 0x80, hi: 0x95}, {value: 0x0818, lo: 0x96, hi: 0x9b}, @@ -3784,18 +3939,18 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0808, lo: 0xa0, hi: 0xb9}, {value: 0x0040, lo: 0xba, hi: 0xbe}, {value: 0x0818, lo: 0xbf, hi: 0xbf}, - // Block 0x9e, offset 0x4cd + // Block 0xa0, offset 0x4d7 {value: 0x0000, lo: 0x04}, {value: 0x0808, lo: 0x80, hi: 0xb7}, {value: 0x0040, lo: 0xb8, hi: 0xbb}, {value: 0x0818, lo: 0xbc, hi: 0xbd}, {value: 0x0808, lo: 0xbe, hi: 0xbf}, - // Block 0x9f, offset 0x4d2 + // Block 0xa1, offset 0x4dc {value: 0x0000, lo: 0x03}, {value: 0x0818, lo: 0x80, hi: 0x8f}, {value: 0x0040, lo: 0x90, hi: 0x91}, {value: 0x0818, lo: 0x92, hi: 0xbf}, - // Block 0xa0, offset 0x4d6 + // Block 0xa2, offset 0x4e0 {value: 0x0000, lo: 0x0f}, {value: 0x0808, lo: 0x80, hi: 0x80}, {value: 0x3308, lo: 0x81, hi: 0x83}, @@ -3812,7 +3967,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xb8, hi: 0xba}, {value: 0x0040, lo: 0xbb, hi: 0xbe}, {value: 0x3b08, lo: 0xbf, hi: 0xbf}, - // Block 0xa1, offset 0x4e6 + // Block 0xa3, offset 0x4f0 {value: 0x0000, lo: 0x06}, {value: 0x0818, lo: 0x80, hi: 0x88}, {value: 0x0040, lo: 0x89, hi: 0x8f}, @@ -3820,17 +3975,17 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x99, hi: 0x9f}, {value: 0x0808, lo: 0xa0, hi: 0xbc}, {value: 0x0818, lo: 0xbd, hi: 0xbf}, - // Block 0xa2, offset 0x4ed + // Block 0xa4, offset 0x4f7 {value: 0x0000, lo: 0x03}, {value: 0x0808, lo: 0x80, hi: 0x9c}, {value: 0x0818, lo: 0x9d, hi: 0x9f}, {value: 0x0040, lo: 0xa0, hi: 0xbf}, - // Block 0xa3, offset 0x4f1 + // Block 0xa5, offset 0x4fb {value: 0x0000, lo: 0x03}, {value: 0x0808, lo: 0x80, hi: 0xb5}, {value: 0x0040, lo: 0xb6, hi: 0xb8}, {value: 0x0018, lo: 0xb9, hi: 0xbf}, - // Block 0xa4, offset 0x4f5 + // Block 0xa6, offset 0x4ff {value: 0x0000, lo: 0x06}, {value: 0x0808, lo: 0x80, hi: 0x95}, {value: 0x0040, lo: 0x96, hi: 0x97}, @@ -3838,23 +3993,23 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0808, lo: 0xa0, hi: 0xb2}, {value: 0x0040, lo: 0xb3, hi: 0xb7}, {value: 0x0818, lo: 0xb8, hi: 0xbf}, - // Block 0xa5, offset 0x4fc + // Block 0xa7, offset 0x506 {value: 0x0000, lo: 0x01}, {value: 0x0808, lo: 0x80, hi: 0xbf}, - // Block 0xa6, offset 0x4fe + // Block 0xa8, offset 0x508 {value: 0x0000, lo: 0x02}, {value: 0x0808, lo: 0x80, hi: 0x88}, {value: 0x0040, lo: 0x89, hi: 0xbf}, - // Block 0xa7, offset 0x501 + // Block 0xa9, offset 0x50b {value: 0x0000, lo: 0x02}, {value: 0x03dd, lo: 0x80, hi: 0xb2}, {value: 0x0040, lo: 0xb3, hi: 0xbf}, - // Block 0xa8, offset 0x504 + // Block 0xaa, offset 0x50e {value: 0x0000, lo: 0x03}, {value: 0x0808, lo: 0x80, hi: 0xb2}, {value: 0x0040, lo: 0xb3, hi: 0xb9}, {value: 0x0818, lo: 0xba, hi: 0xbf}, - // Block 0xa9, offset 0x508 + // Block 0xab, offset 0x512 {value: 0x0000, lo: 0x08}, {value: 0x0908, lo: 0x80, hi: 0x80}, {value: 0x0a08, lo: 0x81, hi: 0xa1}, @@ -3864,12 +4019,12 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xa8, hi: 0xaf}, {value: 0x0808, lo: 0xb0, hi: 0xb9}, {value: 0x0040, lo: 0xba, hi: 0xbf}, - // Block 0xaa, offset 0x511 + // Block 0xac, offset 0x51b {value: 0x0000, lo: 0x03}, {value: 0x0040, lo: 0x80, hi: 0x9f}, {value: 0x0818, lo: 0xa0, hi: 0xbe}, {value: 0x0040, lo: 0xbf, hi: 0xbf}, - // Block 0xab, offset 0x515 + // Block 0xad, offset 0x51f {value: 0x0000, lo: 0x07}, {value: 0x0808, lo: 0x80, hi: 0xa9}, {value: 0x0040, lo: 0xaa, hi: 0xaa}, @@ -3878,7 +4033,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xae, hi: 0xaf}, {value: 0x0808, lo: 0xb0, hi: 0xb1}, {value: 0x0040, lo: 0xb2, hi: 0xbf}, - // Block 0xac, offset 0x51d + // Block 0xae, offset 0x527 {value: 0x0000, lo: 0x07}, {value: 0x0808, lo: 0x80, hi: 0x9c}, {value: 0x0818, lo: 0x9d, hi: 0xa6}, @@ -3887,7 +4042,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0a08, lo: 0xb0, hi: 0xb2}, {value: 0x0c08, lo: 0xb3, hi: 0xb3}, {value: 0x0a08, lo: 0xb4, hi: 0xbf}, - // Block 0xad, offset 0x525 + // Block 0xaf, offset 0x52f {value: 0x0000, lo: 0x07}, {value: 0x0a08, lo: 0x80, hi: 0x84}, {value: 0x0808, lo: 0x85, hi: 0x85}, @@ -3896,7 +4051,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0c18, lo: 0x94, hi: 0x94}, {value: 0x0818, lo: 0x95, hi: 0x99}, {value: 0x0040, lo: 0x9a, hi: 0xbf}, - // Block 0xae, offset 0x52d + // Block 0xb0, offset 0x537 {value: 0x0000, lo: 0x0b}, {value: 0x0040, lo: 0x80, hi: 0xaf}, {value: 0x0a08, lo: 0xb0, hi: 0xb0}, @@ -3909,7 +4064,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0a08, lo: 0xbb, hi: 0xbc}, {value: 0x0c08, lo: 0xbd, hi: 0xbd}, {value: 0x0a08, lo: 0xbe, hi: 0xbf}, - // Block 0xaf, offset 0x539 + // Block 0xb1, offset 0x543 {value: 0x0000, lo: 0x0b}, {value: 0x0808, lo: 0x80, hi: 0x80}, {value: 0x0a08, lo: 0x81, hi: 0x81}, @@ -3922,14 +4077,14 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x8c, hi: 0x9f}, {value: 0x0808, lo: 0xa0, hi: 0xb6}, {value: 0x0040, lo: 0xb7, hi: 0xbf}, - // Block 0xb0, offset 0x545 + // Block 0xb2, offset 0x54f {value: 0x0000, lo: 0x05}, {value: 0x3008, lo: 0x80, hi: 0x80}, {value: 0x3308, lo: 0x81, hi: 0x81}, {value: 0x3008, lo: 0x82, hi: 0x82}, {value: 0x0008, lo: 0x83, hi: 0xb7}, {value: 0x3308, lo: 0xb8, hi: 0xbf}, - // Block 0xb1, offset 0x54b + // Block 0xb3, offset 0x555 {value: 0x0000, lo: 0x08}, {value: 0x3308, lo: 0x80, hi: 0x85}, {value: 0x3b08, lo: 0x86, hi: 0x86}, @@ -3939,7 +4094,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0xa6, hi: 0xaf}, {value: 0x0040, lo: 0xb0, hi: 0xbe}, {value: 0x3b08, lo: 0xbf, hi: 0xbf}, - // Block 0xb2, offset 0x554 + // Block 0xb4, offset 0x55e {value: 0x0000, lo: 0x0b}, {value: 0x3308, lo: 0x80, hi: 0x81}, {value: 0x3008, lo: 0x82, hi: 0x82}, @@ -3952,7 +4107,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0018, lo: 0xbb, hi: 0xbc}, {value: 0x0040, lo: 0xbd, hi: 0xbd}, {value: 0x0018, lo: 0xbe, hi: 0xbf}, - // Block 0xb3, offset 0x560 + // Block 0xb5, offset 0x56a {value: 0x0000, lo: 0x06}, {value: 0x0018, lo: 0x80, hi: 0x81}, {value: 0x0040, lo: 0x82, hi: 0x8f}, @@ -3960,7 +4115,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xa9, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xb9}, {value: 0x0040, lo: 0xba, hi: 0xbf}, - // Block 0xb4, offset 0x567 + // Block 0xb6, offset 0x571 {value: 0x0000, lo: 0x08}, {value: 0x3308, lo: 0x80, hi: 0x82}, {value: 0x0008, lo: 0x83, hi: 0xa6}, @@ -3970,7 +4125,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3b08, lo: 0xb3, hi: 0xb4}, {value: 0x0040, lo: 0xb5, hi: 0xb5}, {value: 0x0008, lo: 0xb6, hi: 0xbf}, - // Block 0xb5, offset 0x570 + // Block 0xb7, offset 0x57a {value: 0x0000, lo: 0x0a}, {value: 0x0018, lo: 0x80, hi: 0x83}, {value: 0x0008, lo: 0x84, hi: 0x84}, @@ -3982,7 +4137,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0018, lo: 0xb4, hi: 0xb5}, {value: 0x0008, lo: 0xb6, hi: 0xb6}, {value: 0x0040, lo: 0xb7, hi: 0xbf}, - // Block 0xb6, offset 0x57b + // Block 0xb8, offset 0x585 {value: 0x0000, lo: 0x06}, {value: 0x3308, lo: 0x80, hi: 0x81}, {value: 0x3008, lo: 0x82, hi: 0x82}, @@ -3990,7 +4145,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3008, lo: 0xb3, hi: 0xb5}, {value: 0x3308, lo: 0xb6, hi: 0xbe}, {value: 0x3008, lo: 0xbf, hi: 0xbf}, - // Block 0xb7, offset 0x582 + // Block 0xb9, offset 0x58c {value: 0x0000, lo: 0x0e}, {value: 0x3808, lo: 0x80, hi: 0x80}, {value: 0x0008, lo: 0x81, hi: 0x84}, @@ -4006,7 +4161,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xa0, hi: 0xa0}, {value: 0x0018, lo: 0xa1, hi: 0xb4}, {value: 0x0040, lo: 0xb5, hi: 0xbf}, - // Block 0xb8, offset 0x591 + // Block 0xba, offset 0x59b {value: 0x0000, lo: 0x0c}, {value: 0x0008, lo: 0x80, hi: 0x91}, {value: 0x0040, lo: 0x92, hi: 0x92}, @@ -4020,7 +4175,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0018, lo: 0xb8, hi: 0xbd}, {value: 0x3308, lo: 0xbe, hi: 0xbe}, {value: 0x0040, lo: 0xbf, hi: 0xbf}, - // Block 0xb9, offset 0x59e + // Block 0xbb, offset 0x5a8 {value: 0x0000, lo: 0x0c}, {value: 0x0008, lo: 0x80, hi: 0x86}, {value: 0x0040, lo: 0x87, hi: 0x87}, @@ -4034,7 +4189,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0018, lo: 0xa9, hi: 0xa9}, {value: 0x0040, lo: 0xaa, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xbf}, - // Block 0xba, offset 0x5ab + // Block 0xbc, offset 0x5b5 {value: 0x0000, lo: 0x08}, {value: 0x0008, lo: 0x80, hi: 0x9e}, {value: 0x3308, lo: 0x9f, hi: 0x9f}, @@ -4044,12 +4199,12 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xab, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xb9}, {value: 0x0040, lo: 0xba, hi: 0xbf}, - // Block 0xbb, offset 0x5b4 + // Block 0xbd, offset 0x5be {value: 0x0000, lo: 0x03}, {value: 0x0008, lo: 0x80, hi: 0xb4}, {value: 0x3008, lo: 0xb5, hi: 0xb7}, {value: 0x3308, lo: 0xb8, hi: 0xbf}, - // Block 0xbc, offset 0x5b8 + // Block 0xbe, offset 0x5c2 {value: 0x0000, lo: 0x0e}, {value: 0x3008, lo: 0x80, hi: 0x81}, {value: 0x3b08, lo: 0x82, hi: 0x82}, @@ -4065,7 +4220,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0x9e, hi: 0x9e}, {value: 0x0008, lo: 0x9f, hi: 0xa1}, {value: 0x0040, lo: 0xa2, hi: 0xbf}, - // Block 0xbd, offset 0x5c7 + // Block 0xbf, offset 0x5d1 {value: 0x0000, lo: 0x07}, {value: 0x0008, lo: 0x80, hi: 0xaf}, {value: 0x3008, lo: 0xb0, hi: 0xb2}, @@ -4074,7 +4229,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xba, hi: 0xba}, {value: 0x3008, lo: 0xbb, hi: 0xbe}, {value: 0x3308, lo: 0xbf, hi: 0xbf}, - // Block 0xbe, offset 0x5cf + // Block 0xc0, offset 0x5d9 {value: 0x0000, lo: 0x0a}, {value: 0x3308, lo: 0x80, hi: 0x80}, {value: 0x3008, lo: 0x81, hi: 0x81}, @@ -4086,7 +4241,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x88, hi: 0x8f}, {value: 0x0008, lo: 0x90, hi: 0x99}, {value: 0x0040, lo: 0x9a, hi: 0xbf}, - // Block 0xbf, offset 0x5da + // Block 0xc1, offset 0x5e4 {value: 0x0000, lo: 0x08}, {value: 0x0008, lo: 0x80, hi: 0xae}, {value: 0x3008, lo: 0xaf, hi: 0xb1}, @@ -4096,14 +4251,14 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xbc, hi: 0xbd}, {value: 0x3008, lo: 0xbe, hi: 0xbe}, {value: 0x3b08, lo: 0xbf, hi: 0xbf}, - // Block 0xc0, offset 0x5e3 + // Block 0xc2, offset 0x5ed {value: 0x0000, lo: 0x05}, {value: 0x3308, lo: 0x80, hi: 0x80}, {value: 0x0018, lo: 0x81, hi: 0x97}, {value: 0x0008, lo: 0x98, hi: 0x9b}, {value: 0x3308, lo: 0x9c, hi: 0x9d}, {value: 0x0040, lo: 0x9e, hi: 0xbf}, - // Block 0xc1, offset 0x5e9 + // Block 0xc3, offset 0x5f3 {value: 0x0000, lo: 0x07}, {value: 0x0008, lo: 0x80, hi: 0xaf}, {value: 0x3008, lo: 0xb0, hi: 0xb2}, @@ -4112,7 +4267,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xbd, hi: 0xbd}, {value: 0x3008, lo: 0xbe, hi: 0xbe}, {value: 0x3b08, lo: 0xbf, hi: 0xbf}, - // Block 0xc2, offset 0x5f1 + // Block 0xc4, offset 0x5fb {value: 0x0000, lo: 0x08}, {value: 0x3308, lo: 0x80, hi: 0x80}, {value: 0x0018, lo: 0x81, hi: 0x83}, @@ -4122,7 +4277,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x9a, hi: 0x9f}, {value: 0x0018, lo: 0xa0, hi: 0xac}, {value: 0x0040, lo: 0xad, hi: 0xbf}, - // Block 0xc3, offset 0x5fa + // Block 0xc5, offset 0x604 {value: 0x0000, lo: 0x0a}, {value: 0x0008, lo: 0x80, hi: 0xaa}, {value: 0x3308, lo: 0xab, hi: 0xab}, @@ -4134,11 +4289,11 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xb7, hi: 0xb7}, {value: 0x0008, lo: 0xb8, hi: 0xb8}, {value: 0x0040, lo: 0xb9, hi: 0xbf}, - // Block 0xc4, offset 0x605 + // Block 0xc6, offset 0x60f {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0x89}, {value: 0x0040, lo: 0x8a, hi: 0xbf}, - // Block 0xc5, offset 0x608 + // Block 0xc7, offset 0x612 {value: 0x0000, lo: 0x0b}, {value: 0x0008, lo: 0x80, hi: 0x9a}, {value: 0x0040, lo: 0x9b, hi: 0x9c}, @@ -4151,7 +4306,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xac, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xb9}, {value: 0x0018, lo: 0xba, hi: 0xbf}, - // Block 0xc6, offset 0x614 + // Block 0xc8, offset 0x61e {value: 0x0000, lo: 0x08}, {value: 0x0008, lo: 0x80, hi: 0xab}, {value: 0x3008, lo: 0xac, hi: 0xae}, @@ -4161,17 +4316,17 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xba, hi: 0xba}, {value: 0x0018, lo: 0xbb, hi: 0xbb}, {value: 0x0040, lo: 0xbc, hi: 0xbf}, - // Block 0xc7, offset 0x61d + // Block 0xc9, offset 0x627 {value: 0x0000, lo: 0x02}, {value: 0x0040, lo: 0x80, hi: 0x9f}, {value: 0x049d, lo: 0xa0, hi: 0xbf}, - // Block 0xc8, offset 0x620 + // Block 0xca, offset 0x62a {value: 0x0000, lo: 0x04}, {value: 0x0008, lo: 0x80, hi: 0xa9}, {value: 0x0018, lo: 0xaa, hi: 0xb2}, {value: 0x0040, lo: 0xb3, hi: 0xbe}, {value: 0x0008, lo: 0xbf, hi: 0xbf}, - // Block 0xc9, offset 0x625 + // Block 0xcb, offset 0x62f {value: 0x0000, lo: 0x08}, {value: 0x3008, lo: 0x80, hi: 0x80}, {value: 0x0008, lo: 0x81, hi: 0x81}, @@ -4181,13 +4336,13 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x87, hi: 0x8f}, {value: 0x0008, lo: 0x90, hi: 0x99}, {value: 0x0040, lo: 0x9a, hi: 0xbf}, - // Block 0xca, offset 0x62e + // Block 0xcc, offset 0x638 {value: 0x0000, lo: 0x04}, {value: 0x0040, lo: 0x80, hi: 0x9f}, {value: 0x0008, lo: 0xa0, hi: 0xa7}, {value: 0x0040, lo: 0xa8, hi: 0xa9}, {value: 0x0008, lo: 0xaa, hi: 0xbf}, - // Block 0xcb, offset 0x633 + // Block 0xcd, offset 0x63d {value: 0x0000, lo: 0x0c}, {value: 0x0008, lo: 0x80, hi: 0x90}, {value: 0x3008, lo: 0x91, hi: 0x93}, @@ -4201,7 +4356,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0xa3, hi: 0xa3}, {value: 0x3008, lo: 0xa4, hi: 0xa4}, {value: 0x0040, lo: 0xa5, hi: 0xbf}, - // Block 0xcc, offset 0x640 + // Block 0xce, offset 0x64a {value: 0x0000, lo: 0x0a}, {value: 0x0008, lo: 0x80, hi: 0x80}, {value: 0x3308, lo: 0x81, hi: 0x8a}, @@ -4213,7 +4368,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0xba, hi: 0xba}, {value: 0x3308, lo: 0xbb, hi: 0xbe}, {value: 0x0018, lo: 0xbf, hi: 0xbf}, - // Block 0xcd, offset 0x64b + // Block 0xcf, offset 0x655 {value: 0x0000, lo: 0x08}, {value: 0x0018, lo: 0x80, hi: 0x86}, {value: 0x3b08, lo: 0x87, hi: 0x87}, @@ -4223,7 +4378,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3008, lo: 0x97, hi: 0x98}, {value: 0x3308, lo: 0x99, hi: 0x9b}, {value: 0x0008, lo: 0x9c, hi: 0xbf}, - // Block 0xce, offset 0x654 + // Block 0xd0, offset 0x65e {value: 0x0000, lo: 0x09}, {value: 0x0008, lo: 0x80, hi: 0x89}, {value: 0x3308, lo: 0x8a, hi: 0x96}, @@ -4234,11 +4389,11 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0x9d, hi: 0x9d}, {value: 0x0018, lo: 0x9e, hi: 0xa2}, {value: 0x0040, lo: 0xa3, hi: 0xbf}, - // Block 0xcf, offset 0x65e + // Block 0xd1, offset 0x668 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0xb8}, {value: 0x0040, lo: 0xb9, hi: 0xbf}, - // Block 0xd0, offset 0x661 + // Block 0xd2, offset 0x66b {value: 0x0000, lo: 0x09}, {value: 0x0008, lo: 0x80, hi: 0x88}, {value: 0x0040, lo: 0x89, hi: 0x89}, @@ -4249,7 +4404,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xb8, hi: 0xbd}, {value: 0x3008, lo: 0xbe, hi: 0xbe}, {value: 0x3b08, lo: 0xbf, hi: 0xbf}, - // Block 0xd1, offset 0x66b + // Block 0xd3, offset 0x675 {value: 0x0000, lo: 0x08}, {value: 0x0008, lo: 0x80, hi: 0x80}, {value: 0x0018, lo: 0x81, hi: 0x85}, @@ -4259,7 +4414,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xad, hi: 0xaf}, {value: 0x0018, lo: 0xb0, hi: 0xb1}, {value: 0x0008, lo: 0xb2, hi: 0xbf}, - // Block 0xd2, offset 0x674 + // Block 0xd4, offset 0x67e {value: 0x0000, lo: 0x0b}, {value: 0x0008, lo: 0x80, hi: 0x8f}, {value: 0x0040, lo: 0x90, hi: 0x91}, @@ -4272,7 +4427,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3008, lo: 0xb4, hi: 0xb4}, {value: 0x3308, lo: 0xb5, hi: 0xb6}, {value: 0x0040, lo: 0xb7, hi: 0xbf}, - // Block 0xd3, offset 0x680 + // Block 0xd5, offset 0x68a {value: 0x0000, lo: 0x0c}, {value: 0x0008, lo: 0x80, hi: 0x86}, {value: 0x0040, lo: 0x87, hi: 0x87}, @@ -4286,7 +4441,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xbc, hi: 0xbd}, {value: 0x0040, lo: 0xbe, hi: 0xbe}, {value: 0x3308, lo: 0xbf, hi: 0xbf}, - // Block 0xd4, offset 0x68d + // Block 0xd6, offset 0x697 {value: 0x0000, lo: 0x0c}, {value: 0x3308, lo: 0x80, hi: 0x83}, {value: 0x3b08, lo: 0x84, hi: 0x85}, @@ -4300,7 +4455,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0xa7, hi: 0xa8}, {value: 0x0040, lo: 0xa9, hi: 0xa9}, {value: 0x0008, lo: 0xaa, hi: 0xbf}, - // Block 0xd5, offset 0x69a + // Block 0xd7, offset 0x6a4 {value: 0x0000, lo: 0x0d}, {value: 0x0008, lo: 0x80, hi: 0x89}, {value: 0x3008, lo: 0x8a, hi: 0x8e}, @@ -4315,7 +4470,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x99, hi: 0x9f}, {value: 0x0008, lo: 0xa0, hi: 0xa9}, {value: 0x0040, lo: 0xaa, hi: 0xbf}, - // Block 0xd6, offset 0x6a8 + // Block 0xd8, offset 0x6b2 {value: 0x0000, lo: 0x06}, {value: 0x0040, lo: 0x80, hi: 0x9f}, {value: 0x0008, lo: 0xa0, hi: 0xb2}, @@ -4323,41 +4478,41 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3008, lo: 0xb5, hi: 0xb6}, {value: 0x0018, lo: 0xb7, hi: 0xb8}, {value: 0x0040, lo: 0xb9, hi: 0xbf}, - // Block 0xd7, offset 0x6af + // Block 0xd9, offset 0x6b9 {value: 0x0000, lo: 0x03}, {value: 0x0040, lo: 0x80, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xb0}, {value: 0x0040, lo: 0xb1, hi: 0xbf}, - // Block 0xd8, offset 0x6b3 + // Block 0xda, offset 0x6bd {value: 0x0000, lo: 0x03}, {value: 0x0018, lo: 0x80, hi: 0xb1}, {value: 0x0040, lo: 0xb2, hi: 0xbe}, {value: 0x0018, lo: 0xbf, hi: 0xbf}, - // Block 0xd9, offset 0x6b7 + // Block 0xdb, offset 0x6c1 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0x99}, {value: 0x0040, lo: 0x9a, hi: 0xbf}, - // Block 0xda, offset 0x6ba + // Block 0xdc, offset 0x6c4 {value: 0x0000, lo: 0x04}, {value: 0x0018, lo: 0x80, hi: 0xae}, {value: 0x0040, lo: 0xaf, hi: 0xaf}, {value: 0x0018, lo: 0xb0, hi: 0xb4}, {value: 0x0040, lo: 0xb5, hi: 0xbf}, - // Block 0xdb, offset 0x6bf + // Block 0xdd, offset 0x6c9 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0x83}, {value: 0x0040, lo: 0x84, hi: 0xbf}, - // Block 0xdc, offset 0x6c2 + // Block 0xde, offset 0x6cc {value: 0x0000, lo: 0x04}, {value: 0x0008, lo: 0x80, hi: 0xae}, {value: 0x0040, lo: 0xaf, hi: 0xaf}, {value: 0x0340, lo: 0xb0, hi: 0xb8}, {value: 0x0040, lo: 0xb9, hi: 0xbf}, - // Block 0xdd, offset 0x6c7 + // Block 0xdf, offset 0x6d1 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0x86}, {value: 0x0040, lo: 0x87, hi: 0xbf}, - // Block 0xde, offset 0x6ca + // Block 0xe0, offset 0x6d4 {value: 0x0000, lo: 0x06}, {value: 0x0008, lo: 0x80, hi: 0x9e}, {value: 0x0040, lo: 0x9f, hi: 0x9f}, @@ -4365,7 +4520,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xaa, hi: 0xad}, {value: 0x0018, lo: 0xae, hi: 0xaf}, {value: 0x0040, lo: 0xb0, hi: 0xbf}, - // Block 0xdf, offset 0x6d1 + // Block 0xe1, offset 0x6db {value: 0x0000, lo: 0x06}, {value: 0x0040, lo: 0x80, hi: 0x8f}, {value: 0x0008, lo: 0x90, hi: 0xad}, @@ -4373,12 +4528,12 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x3308, lo: 0xb0, hi: 0xb4}, {value: 0x0018, lo: 0xb5, hi: 0xb5}, {value: 0x0040, lo: 0xb6, hi: 0xbf}, - // Block 0xe0, offset 0x6d8 + // Block 0xe2, offset 0x6e2 {value: 0x0000, lo: 0x03}, {value: 0x0008, lo: 0x80, hi: 0xaf}, {value: 0x3308, lo: 0xb0, hi: 0xb6}, {value: 0x0018, lo: 0xb7, hi: 0xbf}, - // Block 0xe1, offset 0x6dc + // Block 0xe3, offset 0x6e6 {value: 0x0000, lo: 0x0a}, {value: 0x0008, lo: 0x80, hi: 0x83}, {value: 0x0018, lo: 0x84, hi: 0x85}, @@ -4390,33 +4545,33 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0xa3, hi: 0xb7}, {value: 0x0040, lo: 0xb8, hi: 0xbc}, {value: 0x0008, lo: 0xbd, hi: 0xbf}, - // Block 0xe2, offset 0x6e7 + // Block 0xe4, offset 0x6f1 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0x8f}, {value: 0x0040, lo: 0x90, hi: 0xbf}, - // Block 0xe3, offset 0x6ea + // Block 0xe5, offset 0x6f4 {value: 0x0000, lo: 0x02}, {value: 0xe105, lo: 0x80, hi: 0x9f}, {value: 0x0008, lo: 0xa0, hi: 0xbf}, - // Block 0xe4, offset 0x6ed + // Block 0xe6, offset 0x6f7 {value: 0x0000, lo: 0x02}, {value: 0x0018, lo: 0x80, hi: 0x9a}, {value: 0x0040, lo: 0x9b, hi: 0xbf}, - // Block 0xe5, offset 0x6f0 + // Block 0xe7, offset 0x6fa {value: 0x0000, lo: 0x05}, {value: 0x0008, lo: 0x80, hi: 0x8a}, {value: 0x0040, lo: 0x8b, hi: 0x8e}, {value: 0x3308, lo: 0x8f, hi: 0x8f}, {value: 0x0008, lo: 0x90, hi: 0x90}, {value: 0x3008, lo: 0x91, hi: 0xbf}, - // Block 0xe6, offset 0x6f6 + // Block 0xe8, offset 0x700 {value: 0x0000, lo: 0x05}, {value: 0x3008, lo: 0x80, hi: 0x87}, {value: 0x0040, lo: 0x88, hi: 0x8e}, {value: 0x3308, lo: 0x8f, hi: 0x92}, {value: 0x0008, lo: 0x93, hi: 0x9f}, {value: 0x0040, lo: 0xa0, hi: 0xbf}, - // Block 0xe7, offset 0x6fc + // Block 0xe9, offset 0x706 {value: 0x0000, lo: 0x08}, {value: 0x0040, lo: 0x80, hi: 0x9f}, {value: 0x0008, lo: 0xa0, hi: 0xa1}, @@ -4426,23 +4581,23 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xa5, hi: 0xaf}, {value: 0x3008, lo: 0xb0, hi: 0xb1}, {value: 0x0040, lo: 0xb2, hi: 0xbf}, - // Block 0xe8, offset 0x705 + // Block 0xea, offset 0x70f {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0xb7}, {value: 0x0040, lo: 0xb8, hi: 0xbf}, - // Block 0xe9, offset 0x708 + // Block 0xeb, offset 0x712 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0x95}, {value: 0x0040, lo: 0x96, hi: 0xbf}, - // Block 0xea, offset 0x70b + // Block 0xec, offset 0x715 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0x88}, {value: 0x0040, lo: 0x89, hi: 0xbf}, - // Block 0xeb, offset 0x70e + // Block 0xed, offset 0x718 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0x9e}, {value: 0x0040, lo: 0x9f, hi: 0xbf}, - // Block 0xec, offset 0x711 + // Block 0xee, offset 0x71b {value: 0x0000, lo: 0x06}, {value: 0x0040, lo: 0x80, hi: 0x8f}, {value: 0x0008, lo: 0x90, hi: 0x92}, @@ -4450,17 +4605,17 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0008, lo: 0xa4, hi: 0xa7}, {value: 0x0040, lo: 0xa8, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xbf}, - // Block 0xed, offset 0x718 + // Block 0xef, offset 0x722 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0xbb}, {value: 0x0040, lo: 0xbc, hi: 0xbf}, - // Block 0xee, offset 0x71b + // Block 0xf0, offset 0x725 {value: 0x0000, lo: 0x04}, {value: 0x0008, lo: 0x80, hi: 0xaa}, {value: 0x0040, lo: 0xab, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xbc}, {value: 0x0040, lo: 0xbd, hi: 0xbf}, - // Block 0xef, offset 0x720 + // Block 0xf1, offset 0x72a {value: 0x0000, lo: 0x09}, {value: 0x0008, lo: 0x80, hi: 0x88}, {value: 0x0040, lo: 0x89, hi: 0x8f}, @@ -4471,32 +4626,32 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0018, lo: 0x9f, hi: 0x9f}, {value: 0x03c0, lo: 0xa0, hi: 0xa3}, {value: 0x0040, lo: 0xa4, hi: 0xbf}, - // Block 0xf0, offset 0x72a + // Block 0xf2, offset 0x734 {value: 0x0000, lo: 0x02}, {value: 0x0018, lo: 0x80, hi: 0xb5}, {value: 0x0040, lo: 0xb6, hi: 0xbf}, - // Block 0xf1, offset 0x72d + // Block 0xf3, offset 0x737 {value: 0x0000, lo: 0x03}, {value: 0x0018, lo: 0x80, hi: 0xa6}, {value: 0x0040, lo: 0xa7, hi: 0xa8}, {value: 0x0018, lo: 0xa9, hi: 0xbf}, - // Block 0xf2, offset 0x731 + // Block 0xf4, offset 0x73b {value: 0x0000, lo: 0x0e}, {value: 0x0018, lo: 0x80, hi: 0x9d}, - {value: 0xb609, lo: 0x9e, hi: 0x9e}, - {value: 0xb651, lo: 0x9f, hi: 0x9f}, - {value: 0xb699, lo: 0xa0, hi: 0xa0}, - {value: 0xb701, lo: 0xa1, hi: 0xa1}, - {value: 0xb769, lo: 0xa2, hi: 0xa2}, - {value: 0xb7d1, lo: 0xa3, hi: 0xa3}, - {value: 0xb839, lo: 0xa4, hi: 0xa4}, + {value: 0x2211, lo: 0x9e, hi: 0x9e}, + {value: 0x2219, lo: 0x9f, hi: 0x9f}, + {value: 0x2221, lo: 0xa0, hi: 0xa0}, + {value: 0x2229, lo: 0xa1, hi: 0xa1}, + {value: 0x2231, lo: 0xa2, hi: 0xa2}, + {value: 0x2239, lo: 0xa3, hi: 0xa3}, + {value: 0x2241, lo: 0xa4, hi: 0xa4}, {value: 0x3018, lo: 0xa5, hi: 0xa6}, {value: 0x3318, lo: 0xa7, hi: 0xa9}, {value: 0x0018, lo: 0xaa, hi: 0xac}, {value: 0x3018, lo: 0xad, hi: 0xb2}, {value: 0x0340, lo: 0xb3, hi: 0xba}, {value: 0x3318, lo: 0xbb, hi: 0xbf}, - // Block 0xf3, offset 0x740 + // Block 0xf5, offset 0x74a {value: 0x0000, lo: 0x0b}, {value: 0x3318, lo: 0x80, hi: 0x82}, {value: 0x0018, lo: 0x83, hi: 0x84}, @@ -4504,45 +4659,45 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0018, lo: 0x8c, hi: 0xa9}, {value: 0x3318, lo: 0xaa, hi: 0xad}, {value: 0x0018, lo: 0xae, hi: 0xba}, - {value: 0xb8a1, lo: 0xbb, hi: 0xbb}, - {value: 0xb8e9, lo: 0xbc, hi: 0xbc}, - {value: 0xb931, lo: 0xbd, hi: 0xbd}, - {value: 0xb999, lo: 0xbe, hi: 0xbe}, - {value: 0xba01, lo: 0xbf, hi: 0xbf}, - // Block 0xf4, offset 0x74c + {value: 0x2249, lo: 0xbb, hi: 0xbb}, + {value: 0x2251, lo: 0xbc, hi: 0xbc}, + {value: 0x2259, lo: 0xbd, hi: 0xbd}, + {value: 0x2261, lo: 0xbe, hi: 0xbe}, + {value: 0x2269, lo: 0xbf, hi: 0xbf}, + // Block 0xf6, offset 0x756 {value: 0x0000, lo: 0x03}, - {value: 0xba69, lo: 0x80, hi: 0x80}, + {value: 0x2271, lo: 0x80, hi: 0x80}, {value: 0x0018, lo: 0x81, hi: 0xa8}, {value: 0x0040, lo: 0xa9, hi: 0xbf}, - // Block 0xf5, offset 0x750 + // Block 0xf7, offset 0x75a {value: 0x0000, lo: 0x04}, {value: 0x0018, lo: 0x80, hi: 0x81}, {value: 0x3318, lo: 0x82, hi: 0x84}, {value: 0x0018, lo: 0x85, hi: 0x85}, {value: 0x0040, lo: 0x86, hi: 0xbf}, - // Block 0xf6, offset 0x755 + // Block 0xf8, offset 0x75f {value: 0x0000, lo: 0x03}, {value: 0x0040, lo: 0x80, hi: 0x9f}, {value: 0x0018, lo: 0xa0, hi: 0xb3}, {value: 0x0040, lo: 0xb4, hi: 0xbf}, - // Block 0xf7, offset 0x759 + // Block 0xf9, offset 0x763 {value: 0x0000, lo: 0x04}, {value: 0x0018, lo: 0x80, hi: 0x96}, {value: 0x0040, lo: 0x97, hi: 0x9f}, {value: 0x0018, lo: 0xa0, hi: 0xb8}, {value: 0x0040, lo: 0xb9, hi: 0xbf}, - // Block 0xf8, offset 0x75e + // Block 0xfa, offset 0x768 {value: 0x0000, lo: 0x03}, {value: 0x3308, lo: 0x80, hi: 0xb6}, {value: 0x0018, lo: 0xb7, hi: 0xba}, {value: 0x3308, lo: 0xbb, hi: 0xbf}, - // Block 0xf9, offset 0x762 + // Block 0xfb, offset 0x76c {value: 0x0000, lo: 0x04}, {value: 0x3308, lo: 0x80, hi: 0xac}, {value: 0x0018, lo: 0xad, hi: 0xb4}, {value: 0x3308, lo: 0xb5, hi: 0xb5}, {value: 0x0018, lo: 0xb6, hi: 0xbf}, - // Block 0xfa, offset 0x767 + // Block 0xfc, offset 0x771 {value: 0x0000, lo: 0x08}, {value: 0x0018, lo: 0x80, hi: 0x83}, {value: 0x3308, lo: 0x84, hi: 0x84}, @@ -4552,7 +4707,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xa0, hi: 0xa0}, {value: 0x3308, lo: 0xa1, hi: 0xaf}, {value: 0x0040, lo: 0xb0, hi: 0xbf}, - // Block 0xfb, offset 0x770 + // Block 0xfd, offset 0x77a {value: 0x0000, lo: 0x0a}, {value: 0x3308, lo: 0x80, hi: 0x86}, {value: 0x0040, lo: 0x87, hi: 0x87}, @@ -4564,35 +4719,35 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xa5, hi: 0xa5}, {value: 0x3308, lo: 0xa6, hi: 0xaa}, {value: 0x0040, lo: 0xab, hi: 0xbf}, - // Block 0xfc, offset 0x77b + // Block 0xfe, offset 0x785 {value: 0x0000, lo: 0x05}, {value: 0x0008, lo: 0x80, hi: 0xac}, {value: 0x0040, lo: 0xad, hi: 0xaf}, {value: 0x3308, lo: 0xb0, hi: 0xb6}, {value: 0x0008, lo: 0xb7, hi: 0xbd}, {value: 0x0040, lo: 0xbe, hi: 0xbf}, - // Block 0xfd, offset 0x781 + // Block 0xff, offset 0x78b {value: 0x0000, lo: 0x05}, {value: 0x0008, lo: 0x80, hi: 0x89}, {value: 0x0040, lo: 0x8a, hi: 0x8d}, {value: 0x0008, lo: 0x8e, hi: 0x8e}, {value: 0x0018, lo: 0x8f, hi: 0x8f}, {value: 0x0040, lo: 0x90, hi: 0xbf}, - // Block 0xfe, offset 0x787 + // Block 0x100, offset 0x791 {value: 0x0000, lo: 0x05}, {value: 0x0008, lo: 0x80, hi: 0xab}, {value: 0x3308, lo: 0xac, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xb9}, {value: 0x0040, lo: 0xba, hi: 0xbe}, {value: 0x0018, lo: 0xbf, hi: 0xbf}, - // Block 0xff, offset 0x78d + // Block 0x101, offset 0x797 {value: 0x0000, lo: 0x05}, {value: 0x0808, lo: 0x80, hi: 0x84}, {value: 0x0040, lo: 0x85, hi: 0x86}, {value: 0x0818, lo: 0x87, hi: 0x8f}, {value: 0x3308, lo: 0x90, hi: 0x96}, {value: 0x0040, lo: 0x97, hi: 0xbf}, - // Block 0x100, offset 0x793 + // Block 0x102, offset 0x79d {value: 0x0000, lo: 0x08}, {value: 0x0a08, lo: 0x80, hi: 0x83}, {value: 0x3308, lo: 0x84, hi: 0x8a}, @@ -4602,71 +4757,71 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0x9a, hi: 0x9d}, {value: 0x0818, lo: 0x9e, hi: 0x9f}, {value: 0x0040, lo: 0xa0, hi: 0xbf}, - // Block 0x101, offset 0x79c + // Block 0x103, offset 0x7a6 {value: 0x0000, lo: 0x02}, {value: 0x0040, lo: 0x80, hi: 0xb0}, {value: 0x0818, lo: 0xb1, hi: 0xbf}, - // Block 0x102, offset 0x79f + // Block 0x104, offset 0x7a9 {value: 0x0000, lo: 0x02}, {value: 0x0818, lo: 0x80, hi: 0xb4}, {value: 0x0040, lo: 0xb5, hi: 0xbf}, - // Block 0x103, offset 0x7a2 + // Block 0x105, offset 0x7ac {value: 0x0000, lo: 0x03}, {value: 0x0040, lo: 0x80, hi: 0x80}, {value: 0x0818, lo: 0x81, hi: 0xbd}, {value: 0x0040, lo: 0xbe, hi: 0xbf}, - // Block 0x104, offset 0x7a6 + // Block 0x106, offset 0x7b0 {value: 0x0000, lo: 0x03}, {value: 0x0040, lo: 0x80, hi: 0xaf}, {value: 0x0018, lo: 0xb0, hi: 0xb1}, {value: 0x0040, lo: 0xb2, hi: 0xbf}, - // Block 0x105, offset 0x7aa + // Block 0x107, offset 0x7b4 {value: 0x0000, lo: 0x03}, {value: 0x0018, lo: 0x80, hi: 0xab}, {value: 0x0040, lo: 0xac, hi: 0xaf}, {value: 0x0018, lo: 0xb0, hi: 0xbf}, - // Block 0x106, offset 0x7ae + // Block 0x108, offset 0x7b8 {value: 0x0000, lo: 0x05}, {value: 0x0018, lo: 0x80, hi: 0x93}, {value: 0x0040, lo: 0x94, hi: 0x9f}, {value: 0x0018, lo: 0xa0, hi: 0xae}, {value: 0x0040, lo: 0xaf, hi: 0xb0}, {value: 0x0018, lo: 0xb1, hi: 0xbf}, - // Block 0x107, offset 0x7b4 + // Block 0x109, offset 0x7be {value: 0x0000, lo: 0x05}, {value: 0x0040, lo: 0x80, hi: 0x80}, {value: 0x0018, lo: 0x81, hi: 0x8f}, {value: 0x0040, lo: 0x90, hi: 0x90}, {value: 0x0018, lo: 0x91, hi: 0xb5}, {value: 0x0040, lo: 0xb6, hi: 0xbf}, - // Block 0x108, offset 0x7ba + // Block 0x10a, offset 0x7c4 {value: 0x0000, lo: 0x04}, {value: 0x0018, lo: 0x80, hi: 0x8f}, - {value: 0xc229, lo: 0x90, hi: 0x90}, + {value: 0x2491, lo: 0x90, hi: 0x90}, {value: 0x0018, lo: 0x91, hi: 0xad}, {value: 0x0040, lo: 0xae, hi: 0xbf}, - // Block 0x109, offset 0x7bf + // Block 0x10b, offset 0x7c9 {value: 0x0000, lo: 0x02}, {value: 0x0040, lo: 0x80, hi: 0xa5}, {value: 0x0018, lo: 0xa6, hi: 0xbf}, - // Block 0x10a, offset 0x7c2 + // Block 0x10c, offset 0x7cc {value: 0x0000, lo: 0x0f}, - {value: 0xc851, lo: 0x80, hi: 0x80}, - {value: 0xc8a1, lo: 0x81, hi: 0x81}, - {value: 0xc8f1, lo: 0x82, hi: 0x82}, - {value: 0xc941, lo: 0x83, hi: 0x83}, - {value: 0xc991, lo: 0x84, hi: 0x84}, - {value: 0xc9e1, lo: 0x85, hi: 0x85}, - {value: 0xca31, lo: 0x86, hi: 0x86}, - {value: 0xca81, lo: 0x87, hi: 0x87}, - {value: 0xcad1, lo: 0x88, hi: 0x88}, + {value: 0x2611, lo: 0x80, hi: 0x80}, + {value: 0x2619, lo: 0x81, hi: 0x81}, + {value: 0x2621, lo: 0x82, hi: 0x82}, + {value: 0x2629, lo: 0x83, hi: 0x83}, + {value: 0x2631, lo: 0x84, hi: 0x84}, + {value: 0x2639, lo: 0x85, hi: 0x85}, + {value: 0x2641, lo: 0x86, hi: 0x86}, + {value: 0x2649, lo: 0x87, hi: 0x87}, + {value: 0x2651, lo: 0x88, hi: 0x88}, {value: 0x0040, lo: 0x89, hi: 0x8f}, - {value: 0xcb21, lo: 0x90, hi: 0x90}, - {value: 0xcb41, lo: 0x91, hi: 0x91}, + {value: 0x2659, lo: 0x90, hi: 0x90}, + {value: 0x2661, lo: 0x91, hi: 0x91}, {value: 0x0040, lo: 0x92, hi: 0x9f}, {value: 0x0018, lo: 0xa0, hi: 0xa5}, {value: 0x0040, lo: 0xa6, hi: 0xbf}, - // Block 0x10b, offset 0x7d2 + // Block 0x10d, offset 0x7dc {value: 0x0000, lo: 0x06}, {value: 0x0018, lo: 0x80, hi: 0x97}, {value: 0x0040, lo: 0x98, hi: 0x9f}, @@ -4674,29 +4829,29 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xad, hi: 0xaf}, {value: 0x0018, lo: 0xb0, hi: 0xbc}, {value: 0x0040, lo: 0xbd, hi: 0xbf}, - // Block 0x10c, offset 0x7d9 + // Block 0x10e, offset 0x7e3 {value: 0x0000, lo: 0x02}, {value: 0x0018, lo: 0x80, hi: 0xb3}, {value: 0x0040, lo: 0xb4, hi: 0xbf}, - // Block 0x10d, offset 0x7dc + // Block 0x10f, offset 0x7e6 {value: 0x0000, lo: 0x04}, {value: 0x0018, lo: 0x80, hi: 0x98}, {value: 0x0040, lo: 0x99, hi: 0x9f}, {value: 0x0018, lo: 0xa0, hi: 0xab}, {value: 0x0040, lo: 0xac, hi: 0xbf}, - // Block 0x10e, offset 0x7e1 + // Block 0x110, offset 0x7eb {value: 0x0000, lo: 0x03}, {value: 0x0018, lo: 0x80, hi: 0x8b}, {value: 0x0040, lo: 0x8c, hi: 0x8f}, {value: 0x0018, lo: 0x90, hi: 0xbf}, - // Block 0x10f, offset 0x7e5 + // Block 0x111, offset 0x7ef {value: 0x0000, lo: 0x05}, {value: 0x0018, lo: 0x80, hi: 0x87}, {value: 0x0040, lo: 0x88, hi: 0x8f}, {value: 0x0018, lo: 0x90, hi: 0x99}, {value: 0x0040, lo: 0x9a, hi: 0x9f}, {value: 0x0018, lo: 0xa0, hi: 0xbf}, - // Block 0x110, offset 0x7eb + // Block 0x112, offset 0x7f5 {value: 0x0000, lo: 0x06}, {value: 0x0018, lo: 0x80, hi: 0x87}, {value: 0x0040, lo: 0x88, hi: 0x8f}, @@ -4704,17 +4859,17 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xae, hi: 0xaf}, {value: 0x0018, lo: 0xb0, hi: 0xb1}, {value: 0x0040, lo: 0xb2, hi: 0xbf}, - // Block 0x111, offset 0x7f2 + // Block 0x113, offset 0x7fc {value: 0x0000, lo: 0x03}, {value: 0x0018, lo: 0x80, hi: 0xb8}, {value: 0x0040, lo: 0xb9, hi: 0xb9}, {value: 0x0018, lo: 0xba, hi: 0xbf}, - // Block 0x112, offset 0x7f6 + // Block 0x114, offset 0x800 {value: 0x0000, lo: 0x03}, {value: 0x0018, lo: 0x80, hi: 0x8b}, {value: 0x0040, lo: 0x8c, hi: 0x8c}, {value: 0x0018, lo: 0x8d, hi: 0xbf}, - // Block 0x113, offset 0x7fa + // Block 0x115, offset 0x804 {value: 0x0000, lo: 0x08}, {value: 0x0018, lo: 0x80, hi: 0x93}, {value: 0x0040, lo: 0x94, hi: 0x9f}, @@ -4724,7 +4879,7 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xb5, hi: 0xb7}, {value: 0x0018, lo: 0xb8, hi: 0xba}, {value: 0x0040, lo: 0xbb, hi: 0xbf}, - // Block 0x114, offset 0x803 + // Block 0x116, offset 0x80d {value: 0x0000, lo: 0x06}, {value: 0x0018, lo: 0x80, hi: 0x86}, {value: 0x0040, lo: 0x87, hi: 0x8f}, @@ -4732,109 +4887,74 @@ var idnaSparseValues = [2146]valueRange{ {value: 0x0040, lo: 0xa9, hi: 0xaf}, {value: 0x0018, lo: 0xb0, hi: 0xb6}, {value: 0x0040, lo: 0xb7, hi: 0xbf}, - // Block 0x115, offset 0x80a + // Block 0x117, offset 0x814 {value: 0x0000, lo: 0x04}, {value: 0x0018, lo: 0x80, hi: 0x82}, {value: 0x0040, lo: 0x83, hi: 0x8f}, {value: 0x0018, lo: 0x90, hi: 0x96}, {value: 0x0040, lo: 0x97, hi: 0xbf}, - // Block 0x116, offset 0x80f + // Block 0x118, offset 0x819 {value: 0x0000, lo: 0x03}, {value: 0x0018, lo: 0x80, hi: 0x92}, {value: 0x0040, lo: 0x93, hi: 0x93}, {value: 0x0018, lo: 0x94, hi: 0xbf}, - // Block 0x117, offset 0x813 + // Block 0x119, offset 0x81d {value: 0x0000, lo: 0x0d}, {value: 0x0018, lo: 0x80, hi: 0x8a}, {value: 0x0040, lo: 0x8b, hi: 0xaf}, - {value: 0x1f41, lo: 0xb0, hi: 0xb0}, - {value: 0x00c9, lo: 0xb1, hi: 0xb1}, - {value: 0x0069, lo: 0xb2, hi: 0xb2}, - {value: 0x0079, lo: 0xb3, hi: 0xb3}, - {value: 0x1f51, lo: 0xb4, hi: 0xb4}, - {value: 0x1f61, lo: 0xb5, hi: 0xb5}, - {value: 0x1f71, lo: 0xb6, hi: 0xb6}, - {value: 0x1f81, lo: 0xb7, hi: 0xb7}, - {value: 0x1f91, lo: 0xb8, hi: 0xb8}, - {value: 0x1fa1, lo: 0xb9, hi: 0xb9}, + {value: 0x06e1, lo: 0xb0, hi: 0xb0}, + {value: 0x0049, lo: 0xb1, hi: 0xb1}, + {value: 0x0029, lo: 0xb2, hi: 0xb2}, + {value: 0x0031, lo: 0xb3, hi: 0xb3}, + {value: 0x06e9, lo: 0xb4, hi: 0xb4}, + {value: 0x06f1, lo: 0xb5, hi: 0xb5}, + {value: 0x06f9, lo: 0xb6, hi: 0xb6}, + {value: 0x0701, lo: 0xb7, hi: 0xb7}, + {value: 0x0709, lo: 0xb8, hi: 0xb8}, + {value: 0x0711, lo: 0xb9, hi: 0xb9}, {value: 0x0040, lo: 0xba, hi: 0xbf}, - // Block 0x118, offset 0x821 + // Block 0x11a, offset 0x82b {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0x9d}, {value: 0x0040, lo: 0x9e, hi: 0xbf}, - // Block 0x119, offset 0x824 + // Block 0x11b, offset 0x82e {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0xb4}, {value: 0x0040, lo: 0xb5, hi: 0xbf}, - // Block 0x11a, offset 0x827 + // Block 0x11c, offset 0x831 {value: 0x0000, lo: 0x03}, {value: 0x0008, lo: 0x80, hi: 0x9d}, {value: 0x0040, lo: 0x9e, hi: 0x9f}, {value: 0x0008, lo: 0xa0, hi: 0xbf}, - // Block 0x11b, offset 0x82b + // Block 0x11d, offset 0x835 {value: 0x0000, lo: 0x03}, {value: 0x0008, lo: 0x80, hi: 0xa1}, {value: 0x0040, lo: 0xa2, hi: 0xaf}, {value: 0x0008, lo: 0xb0, hi: 0xbf}, - // Block 0x11c, offset 0x82f + // Block 0x11e, offset 0x839 {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0xa0}, {value: 0x0040, lo: 0xa1, hi: 0xbf}, - // Block 0x11d, offset 0x832 - {value: 0x0020, lo: 0x0f}, - {value: 0xdf21, lo: 0x80, hi: 0x89}, - {value: 0x8e35, lo: 0x8a, hi: 0x8a}, - {value: 0xe061, lo: 0x8b, hi: 0x9c}, - {value: 0x8e55, lo: 0x9d, hi: 0x9d}, - {value: 0xe2a1, lo: 0x9e, hi: 0xa2}, - {value: 0x8e75, lo: 0xa3, hi: 0xa3}, - {value: 0xe341, lo: 0xa4, hi: 0xab}, - {value: 0x7f0d, lo: 0xac, hi: 0xac}, - {value: 0xe441, lo: 0xad, hi: 0xaf}, - {value: 0x8e95, lo: 0xb0, hi: 0xb0}, - {value: 0xe4a1, lo: 0xb1, hi: 0xb6}, - {value: 0x8eb5, lo: 0xb7, hi: 0xb9}, - {value: 0xe561, lo: 0xba, hi: 0xba}, - {value: 0x8f15, lo: 0xbb, hi: 0xbb}, - {value: 0xe581, lo: 0xbc, hi: 0xbf}, - // Block 0x11e, offset 0x842 - {value: 0x0020, lo: 0x10}, - {value: 0x93b5, lo: 0x80, hi: 0x80}, - {value: 0xf101, lo: 0x81, hi: 0x86}, - {value: 0x93d5, lo: 0x87, hi: 0x8a}, - {value: 0xda61, lo: 0x8b, hi: 0x8b}, - {value: 0xf1c1, lo: 0x8c, hi: 0x96}, - {value: 0x9455, lo: 0x97, hi: 0x97}, - {value: 0xf321, lo: 0x98, hi: 0xa3}, - {value: 0x9475, lo: 0xa4, hi: 0xa6}, - {value: 0xf4a1, lo: 0xa7, hi: 0xaa}, - {value: 0x94d5, lo: 0xab, hi: 0xab}, - {value: 0xf521, lo: 0xac, hi: 0xac}, - {value: 0x94f5, lo: 0xad, hi: 0xad}, - {value: 0xf541, lo: 0xae, hi: 0xaf}, - {value: 0x9515, lo: 0xb0, hi: 0xb1}, - {value: 0xf581, lo: 0xb2, hi: 0xbe}, - {value: 0x2040, lo: 0xbf, hi: 0xbf}, - // Block 0x11f, offset 0x853 + // Block 0x11f, offset 0x83c {value: 0x0000, lo: 0x02}, {value: 0x0008, lo: 0x80, hi: 0x8a}, {value: 0x0040, lo: 0x8b, hi: 0xbf}, - // Block 0x120, offset 0x856 + // Block 0x120, offset 0x83f {value: 0x0000, lo: 0x04}, {value: 0x0040, lo: 0x80, hi: 0x80}, {value: 0x0340, lo: 0x81, hi: 0x81}, {value: 0x0040, lo: 0x82, hi: 0x9f}, {value: 0x0340, lo: 0xa0, hi: 0xbf}, - // Block 0x121, offset 0x85b + // Block 0x121, offset 0x844 {value: 0x0000, lo: 0x01}, {value: 0x0340, lo: 0x80, hi: 0xbf}, - // Block 0x122, offset 0x85d + // Block 0x122, offset 0x846 {value: 0x0000, lo: 0x01}, {value: 0x33c0, lo: 0x80, hi: 0xbf}, - // Block 0x123, offset 0x85f + // Block 0x123, offset 0x848 {value: 0x0000, lo: 0x02}, {value: 0x33c0, lo: 0x80, hi: 0xaf}, {value: 0x0040, lo: 0xb0, hi: 0xbf}, } -// Total table size 43370 bytes (42KiB); checksum: EBD909C0 +// Total table size 44953 bytes (43KiB); checksum: D51909DD diff --git a/idna/tables15.0.0.go b/idna/tables15.0.0.go new file mode 100644 index 000000000..40033778f --- /dev/null +++ b/idna/tables15.0.0.go @@ -0,0 +1,5145 @@ +// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. + +//go:build go1.21 +// +build go1.21 + +package idna + +// UnicodeVersion is the Unicode version from which the tables in this package are derived. +const UnicodeVersion = "15.0.0" + +var mappings string = "" + // Size: 6704 bytes + " ̈a ̄23 ́ ̧1o1⁄41⁄23⁄4i̇l·ʼnsdžⱥⱦhjrwy ̆ ̇ ̊ ̨ ̃ ̋lẍ́ ι; ̈́եւاٴوٴۇٴيٴक" + + "़ख़ग़ज़ड़ढ़फ़य़ড়ঢ়য়ਲ਼ਸ਼ਖ਼ਗ਼ਜ਼ਫ਼ଡ଼ଢ଼ําໍາຫນຫມགྷཌྷདྷབྷཛྷཀྵཱཱིུྲྀྲཱྀླྀླཱ" + + "ཱྀྀྒྷྜྷྡྷྦྷྫྷྐྵвдостъѣæbdeǝgikmnȣptuɐɑəɛɜŋɔɯvβγδφχρнɒcɕðfɟɡɥɨɩɪʝɭʟɱɰɲɳ" + + "ɴɵɸʂʃƫʉʊʋʌzʐʑʒθssάέήίόύώἀιἁιἂιἃιἄιἅιἆιἇιἠιἡιἢιἣιἤιἥιἦιἧιὠιὡιὢιὣιὤιὥιὦιὧ" + + "ιὰιαιάιᾶιι ̈͂ὴιηιήιῆι ̓̀ ̓́ ̓͂ΐ ̔̀ ̔́ ̔͂ΰ ̈̀`ὼιωιώιῶι′′′′′‵‵‵‵‵!!???!!?" + + "′′′′0456789+=()rsħnoqsmtmωåאבגדπ1⁄71⁄91⁄101⁄32⁄31⁄52⁄53⁄54⁄51⁄65⁄61⁄83" + + "⁄85⁄87⁄81⁄iiivviviiiixxi0⁄3∫∫∫∫∫∮∮∮∮∮1011121314151617181920(10)(11)(12" + + ")(13)(14)(15)(16)(17)(18)(19)(20)∫∫∫∫==⫝̸ɫɽȿɀ. ゙ ゚よりコト(ᄀ)(ᄂ)(ᄃ)(ᄅ)(ᄆ)(ᄇ)" + + "(ᄉ)(ᄋ)(ᄌ)(ᄎ)(ᄏ)(ᄐ)(ᄑ)(ᄒ)(가)(나)(다)(라)(마)(바)(사)(아)(자)(차)(카)(타)(파)(하)(주)(오전" + + ")(오후)(一)(二)(三)(四)(五)(六)(七)(八)(九)(十)(月)(火)(水)(木)(金)(土)(日)(株)(有)(社)(名)(特)(" + + "財)(祝)(労)(代)(呼)(学)(監)(企)(資)(協)(祭)(休)(自)(至)21222324252627282930313233343" + + "5참고주의3637383940414243444546474849501月2月3月4月5月6月7月8月9月10月11月12月hgev令和アパート" + + "アルファアンペアアールイニングインチウォンエスクードエーカーオンスオームカイリカラットカロリーガロンガンマギガギニーキュリーギルダーキロキロ" + + "グラムキロメートルキロワットグラムグラムトンクルゼイロクローネケースコルナコーポサイクルサンチームシリングセンチセントダースデシドルトンナノ" + + "ノットハイツパーセントパーツバーレルピアストルピクルピコビルファラッドフィートブッシェルフランヘクタールペソペニヒヘルツペンスページベータポ" + + "イントボルトホンポンドホールホーンマイクロマイルマッハマルクマンションミクロンミリミリバールメガメガトンメートルヤードヤールユアンリットルリ" + + "ラルピールーブルレムレントゲンワット0点1点2点3点4点5点6点7点8点9点10点11点12点13点14点15点16点17点18点19点20" + + "点21点22点23点24点daauovpcdmiu平成昭和大正明治株式会社panamakakbmbgbkcalpfnfmgkghzmldlk" + + "lfmnmmmcmkmm2m3m∕sm∕s2rad∕srad∕s2psnsmspvnvmvkvpwnwmwkwbqcccdc∕kgdbgyhah" + + "pinkkktlmlnlxphprsrsvwbv∕ma∕m1日2日3日4日5日6日7日8日9日10日11日12日13日14日15日16日17日1" + + "8日19日20日21日22日23日24日25日26日27日28日29日30日31日ьɦɬʞʇœʍ𤋮𢡊𢡄𣏕𥉉𥳐𧻓fffiflstմնմեմիվնմ" + + "խיִײַעהכלםרתשׁשׂשּׁשּׂאַאָאּבּגּדּהּוּזּטּיּךּכּלּמּנּסּףּפּצּקּרּשּתּו" + + "ֹבֿכֿפֿאלٱٻپڀٺٿٹڤڦڄڃچڇڍڌڎڈژڑکگڳڱںڻۀہھےۓڭۇۆۈۋۅۉېىئائەئوئۇئۆئۈئېئىیئجئحئم" + + "ئيبجبحبخبمبىبيتجتحتختمتىتيثجثمثىثيجحجمحجحمخجخحخمسجسحسخسمصحصمضجضحضخضمطحط" + + "مظمعجعمغجغمفجفحفخفمفىفيقحقمقىقيكاكجكحكخكلكمكىكيلجلحلخلملىليمجمحمخمممىمي" + + "نجنحنخنمنىنيهجهمهىهييجيحيخيميىييذٰرٰىٰ ٌّ ٍّ َّ ُّ ِّ ّٰئرئزئنبربزبنترت" + + "زتنثرثزثنمانرنزننيريزينئخئهبهتهصخلهنههٰيهثهسهشمشهـَّـُّـِّطىطيعىعيغىغيس" + + "ىسيشىشيحىحيجىجيخىخيصىصيضىضيشجشحشخشرسرصرضراًتجمتحجتحمتخمتمجتمحتمخجمححميح" + + "مىسحجسجحسجىسمحسمجسممصححصممشحمشجيشمخشممضحىضخمطمحطممطميعجمعممعمىغممغميغمى" + + "فخمقمحقمملحملحيلحىلججلخملمحمحجمحممحيمجحمجممخجمخممجخهمجهممنحمنحىنجمنجىنم" + + "ينمىيممبخيتجيتجىتخيتخىتميتمىجميجحىجمىسخىصحيشحيضحيلجيلمييحييجييميمميقمين" + + "حيعميكمينجحمخيلجمكممجحيحجيمجيفميبحيسخينجيصلےقلےاللهاكبرمحمدصلعمرسولعليه" + + "وسلمصلىصلى الله عليه وسلمجل جلالهریال,:!?_{}[]#&*-<>\\$%@ـًـَـُـِـّـْءآ" + + "أؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهويلآلألإلا\x22'/^|~¢£¬¦¥ːˑʙɓʣꭦʥʤɖɗᶑɘɞʩɤɢ" + + "ɠʛʜɧʄʪʫꞎɮʎøɶɷɺɾʀʨʦꭧʧʈⱱʏʡʢʘǀǁǂ𝅗𝅥𝅘𝅥𝅘𝅥𝅮𝅘𝅥𝅯𝅘𝅥𝅰𝅘𝅥𝅱𝅘𝅥𝅲𝆹𝅥𝆺𝅥𝆹𝅥𝅮𝆺𝅥𝅮𝆹𝅥𝅯𝆺𝅥𝅯ıȷαεζηκ" + + "λμνξοστυψ∇∂ϝабгежзиклмпруфхцчшыэюꚉәіјөүӏґѕџҫꙑұٮڡٯ0,1,2,3,4,5,6,7,8,9,(a" + + ")(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)(m)(n)(o)(p)(q)(r)(s)(t)(u)(v)(w)(x)(y" + + ")(z)〔s〕wzhvsdppvwcmcmdmrdjほかココサ手字双デ二多解天交映無料前後再新初終生販声吹演投捕一三遊左中右指走打禁空合満有月申" + + "割営配〔本〕〔三〕〔二〕〔安〕〔点〕〔打〕〔盗〕〔勝〕〔敗〕得可丽丸乁你侮侻倂偺備僧像㒞免兔兤具㒹內冗冤仌冬况凵刃㓟刻剆剷㔕勇勉勤勺包匆北卉" + + "卑博即卽卿灰及叟叫叱吆咞吸呈周咢哶唐啓啣善喙喫喳嗂圖嘆圗噑噴切壮城埴堍型堲報墬売壷夆夢奢姬娛娧姘婦㛮嬈嬾寃寘寧寳寿将尢㞁屠屮峀岍嵃嵮嵫嵼巡巢" + + "㠯巽帨帽幩㡢㡼庰庳庶廊廾舁弢㣇形彫㣣徚忍志忹悁㤺㤜悔惇慈慌慎慺憎憲憤憯懞懲懶成戛扝抱拔捐挽拼捨掃揤搢揅掩㨮摩摾撝摷㩬敏敬旣書晉㬙暑㬈㫤冒冕最" + + "暜肭䏙朗望朡杞杓㭉柺枅桒梅梎栟椔㮝楂榣槪檨櫛㰘次歔㱎歲殟殺殻汎沿泍汧洖派海流浩浸涅洴港湮㴳滋滇淹潮濆瀹瀞瀛㶖灊災灷炭煅熜爨爵牐犀犕獺王㺬玥㺸" + + "瑇瑜瑱璅瓊㼛甤甾異瘐㿼䀈直眞真睊䀹瞋䁆䂖硎碌磌䃣祖福秫䄯穀穊穏䈂篆築䈧糒䊠糨糣紀絣䌁緇縂繅䌴䍙罺羕翺者聠聰䏕育脃䐋脾媵舄辞䑫芑芋芝劳花芳芽苦" + + "若茝荣莭茣莽菧著荓菊菌菜䔫蓱蓳蔖蕤䕝䕡䕫虐虜虧虩蚩蚈蜎蛢蝹蜨蝫螆蟡蠁䗹衠衣裗裞䘵裺㒻䚾䛇誠諭變豕貫賁贛起跋趼跰軔輸邔郱鄑鄛鈸鋗鋘鉼鏹鐕開䦕閷" + + "䧦雃嶲霣䩮䩶韠䪲頋頩飢䬳餩馧駂駾䯎鬒鱀鳽䳎䳭鵧䳸麻䵖黹黾鼅鼏鼖鼻" + +var mappingIndex = []uint16{ // 1729 elements + // Entry 0 - 3F + 0x0000, 0x0000, 0x0001, 0x0004, 0x0005, 0x0008, 0x0009, 0x000a, + 0x000d, 0x0010, 0x0011, 0x0012, 0x0017, 0x001c, 0x0021, 0x0024, + 0x0027, 0x002a, 0x002b, 0x002e, 0x0031, 0x0034, 0x0035, 0x0036, + 0x0037, 0x0038, 0x0039, 0x003c, 0x003f, 0x0042, 0x0045, 0x0048, + 0x004b, 0x004c, 0x004d, 0x0051, 0x0054, 0x0055, 0x005a, 0x005e, + 0x0062, 0x0066, 0x006a, 0x006e, 0x0074, 0x007a, 0x0080, 0x0086, + 0x008c, 0x0092, 0x0098, 0x009e, 0x00a4, 0x00aa, 0x00b0, 0x00b6, + 0x00bc, 0x00c2, 0x00c8, 0x00ce, 0x00d4, 0x00da, 0x00e0, 0x00e6, + // Entry 40 - 7F + 0x00ec, 0x00f2, 0x00f8, 0x00fe, 0x0104, 0x010a, 0x0110, 0x0116, + 0x011c, 0x0122, 0x0128, 0x012e, 0x0137, 0x013d, 0x0146, 0x014c, + 0x0152, 0x0158, 0x015e, 0x0164, 0x016a, 0x0170, 0x0172, 0x0174, + 0x0176, 0x0178, 0x017a, 0x017c, 0x017e, 0x0180, 0x0181, 0x0182, + 0x0183, 0x0185, 0x0186, 0x0187, 0x0188, 0x0189, 0x018a, 0x018c, + 0x018d, 0x018e, 0x018f, 0x0191, 0x0193, 0x0195, 0x0197, 0x0199, + 0x019b, 0x019d, 0x019f, 0x01a0, 0x01a2, 0x01a4, 0x01a6, 0x01a8, + 0x01aa, 0x01ac, 0x01ae, 0x01b0, 0x01b1, 0x01b3, 0x01b5, 0x01b6, + // Entry 80 - BF + 0x01b8, 0x01ba, 0x01bc, 0x01be, 0x01c0, 0x01c2, 0x01c4, 0x01c6, + 0x01c8, 0x01ca, 0x01cc, 0x01ce, 0x01d0, 0x01d2, 0x01d4, 0x01d6, + 0x01d8, 0x01da, 0x01dc, 0x01de, 0x01e0, 0x01e2, 0x01e4, 0x01e5, + 0x01e7, 0x01e9, 0x01eb, 0x01ed, 0x01ef, 0x01f1, 0x01f3, 0x01f5, + 0x01f7, 0x01f9, 0x01fb, 0x01fd, 0x0202, 0x0207, 0x020c, 0x0211, + 0x0216, 0x021b, 0x0220, 0x0225, 0x022a, 0x022f, 0x0234, 0x0239, + 0x023e, 0x0243, 0x0248, 0x024d, 0x0252, 0x0257, 0x025c, 0x0261, + 0x0266, 0x026b, 0x0270, 0x0275, 0x027a, 0x027e, 0x0282, 0x0287, + // Entry C0 - FF + 0x0289, 0x028e, 0x0293, 0x0297, 0x029b, 0x02a0, 0x02a5, 0x02aa, + 0x02af, 0x02b1, 0x02b6, 0x02bb, 0x02c0, 0x02c2, 0x02c7, 0x02c8, + 0x02cd, 0x02d1, 0x02d5, 0x02da, 0x02e0, 0x02e9, 0x02ef, 0x02f8, + 0x02fa, 0x02fc, 0x02fe, 0x0300, 0x030c, 0x030d, 0x030e, 0x030f, + 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, + 0x0319, 0x031b, 0x031d, 0x031e, 0x0320, 0x0322, 0x0324, 0x0326, + 0x0328, 0x032a, 0x032c, 0x032e, 0x0330, 0x0335, 0x033a, 0x0340, + 0x0345, 0x034a, 0x034f, 0x0354, 0x0359, 0x035e, 0x0363, 0x0368, + // Entry 100 - 13F + 0x036d, 0x0372, 0x0377, 0x037c, 0x0380, 0x0382, 0x0384, 0x0386, + 0x038a, 0x038c, 0x038e, 0x0393, 0x0399, 0x03a2, 0x03a8, 0x03b1, + 0x03b3, 0x03b5, 0x03b7, 0x03b9, 0x03bb, 0x03bd, 0x03bf, 0x03c1, + 0x03c3, 0x03c5, 0x03c7, 0x03cb, 0x03cf, 0x03d3, 0x03d7, 0x03db, + 0x03df, 0x03e3, 0x03e7, 0x03eb, 0x03ef, 0x03f3, 0x03ff, 0x0401, + 0x0406, 0x0408, 0x040a, 0x040c, 0x040e, 0x040f, 0x0413, 0x0417, + 0x041d, 0x0423, 0x0428, 0x042d, 0x0432, 0x0437, 0x043c, 0x0441, + 0x0446, 0x044b, 0x0450, 0x0455, 0x045a, 0x045f, 0x0464, 0x0469, + // Entry 140 - 17F + 0x046e, 0x0473, 0x0478, 0x047d, 0x0482, 0x0487, 0x048c, 0x0491, + 0x0496, 0x049b, 0x04a0, 0x04a5, 0x04aa, 0x04af, 0x04b4, 0x04bc, + 0x04c4, 0x04c9, 0x04ce, 0x04d3, 0x04d8, 0x04dd, 0x04e2, 0x04e7, + 0x04ec, 0x04f1, 0x04f6, 0x04fb, 0x0500, 0x0505, 0x050a, 0x050f, + 0x0514, 0x0519, 0x051e, 0x0523, 0x0528, 0x052d, 0x0532, 0x0537, + 0x053c, 0x0541, 0x0546, 0x054b, 0x0550, 0x0555, 0x055a, 0x055f, + 0x0564, 0x0569, 0x056e, 0x0573, 0x0578, 0x057a, 0x057c, 0x057e, + 0x0580, 0x0582, 0x0584, 0x0586, 0x0588, 0x058a, 0x058c, 0x058e, + // Entry 180 - 1BF + 0x0590, 0x0592, 0x0594, 0x0596, 0x059c, 0x05a2, 0x05a4, 0x05a6, + 0x05a8, 0x05aa, 0x05ac, 0x05ae, 0x05b0, 0x05b2, 0x05b4, 0x05b6, + 0x05b8, 0x05ba, 0x05bc, 0x05be, 0x05c0, 0x05c4, 0x05c8, 0x05cc, + 0x05d0, 0x05d4, 0x05d8, 0x05dc, 0x05e0, 0x05e4, 0x05e9, 0x05ee, + 0x05f3, 0x05f5, 0x05f7, 0x05fd, 0x0609, 0x0615, 0x0621, 0x062a, + 0x0636, 0x063f, 0x0648, 0x0657, 0x0663, 0x066c, 0x0675, 0x067e, + 0x068a, 0x0696, 0x069f, 0x06a8, 0x06ae, 0x06b7, 0x06c3, 0x06cf, + 0x06d5, 0x06e4, 0x06f6, 0x0705, 0x070e, 0x071d, 0x072c, 0x0738, + // Entry 1C0 - 1FF + 0x0741, 0x074a, 0x0753, 0x075f, 0x076e, 0x077a, 0x0783, 0x078c, + 0x0795, 0x079b, 0x07a1, 0x07a7, 0x07ad, 0x07b6, 0x07bf, 0x07ce, + 0x07d7, 0x07e3, 0x07f2, 0x07fb, 0x0801, 0x0807, 0x0816, 0x0822, + 0x0831, 0x083a, 0x0849, 0x084f, 0x0858, 0x0861, 0x086a, 0x0873, + 0x087c, 0x0888, 0x0891, 0x0897, 0x08a0, 0x08a9, 0x08b2, 0x08be, + 0x08c7, 0x08d0, 0x08d9, 0x08e8, 0x08f4, 0x08fa, 0x0909, 0x090f, + 0x091b, 0x0927, 0x0930, 0x0939, 0x0942, 0x094e, 0x0954, 0x095d, + 0x0969, 0x096f, 0x097e, 0x0987, 0x098b, 0x098f, 0x0993, 0x0997, + // Entry 200 - 23F + 0x099b, 0x099f, 0x09a3, 0x09a7, 0x09ab, 0x09af, 0x09b4, 0x09b9, + 0x09be, 0x09c3, 0x09c8, 0x09cd, 0x09d2, 0x09d7, 0x09dc, 0x09e1, + 0x09e6, 0x09eb, 0x09f0, 0x09f5, 0x09fa, 0x09fc, 0x09fe, 0x0a00, + 0x0a02, 0x0a04, 0x0a06, 0x0a0c, 0x0a12, 0x0a18, 0x0a1e, 0x0a2a, + 0x0a2c, 0x0a2e, 0x0a30, 0x0a32, 0x0a34, 0x0a36, 0x0a38, 0x0a3c, + 0x0a3e, 0x0a40, 0x0a42, 0x0a44, 0x0a46, 0x0a48, 0x0a4a, 0x0a4c, + 0x0a4e, 0x0a50, 0x0a52, 0x0a54, 0x0a56, 0x0a58, 0x0a5a, 0x0a5f, + 0x0a65, 0x0a6c, 0x0a74, 0x0a76, 0x0a78, 0x0a7a, 0x0a7c, 0x0a7e, + // Entry 240 - 27F + 0x0a80, 0x0a82, 0x0a84, 0x0a86, 0x0a88, 0x0a8a, 0x0a8c, 0x0a8e, + 0x0a90, 0x0a96, 0x0a98, 0x0a9a, 0x0a9c, 0x0a9e, 0x0aa0, 0x0aa2, + 0x0aa4, 0x0aa6, 0x0aa8, 0x0aaa, 0x0aac, 0x0aae, 0x0ab0, 0x0ab2, + 0x0ab4, 0x0ab9, 0x0abe, 0x0ac2, 0x0ac6, 0x0aca, 0x0ace, 0x0ad2, + 0x0ad6, 0x0ada, 0x0ade, 0x0ae2, 0x0ae7, 0x0aec, 0x0af1, 0x0af6, + 0x0afb, 0x0b00, 0x0b05, 0x0b0a, 0x0b0f, 0x0b14, 0x0b19, 0x0b1e, + 0x0b23, 0x0b28, 0x0b2d, 0x0b32, 0x0b37, 0x0b3c, 0x0b41, 0x0b46, + 0x0b4b, 0x0b50, 0x0b52, 0x0b54, 0x0b56, 0x0b58, 0x0b5a, 0x0b5c, + // Entry 280 - 2BF + 0x0b5e, 0x0b62, 0x0b66, 0x0b6a, 0x0b6e, 0x0b72, 0x0b76, 0x0b7a, + 0x0b7c, 0x0b7e, 0x0b80, 0x0b82, 0x0b86, 0x0b8a, 0x0b8e, 0x0b92, + 0x0b96, 0x0b9a, 0x0b9e, 0x0ba0, 0x0ba2, 0x0ba4, 0x0ba6, 0x0ba8, + 0x0baa, 0x0bac, 0x0bb0, 0x0bb4, 0x0bba, 0x0bc0, 0x0bc4, 0x0bc8, + 0x0bcc, 0x0bd0, 0x0bd4, 0x0bd8, 0x0bdc, 0x0be0, 0x0be4, 0x0be8, + 0x0bec, 0x0bf0, 0x0bf4, 0x0bf8, 0x0bfc, 0x0c00, 0x0c04, 0x0c08, + 0x0c0c, 0x0c10, 0x0c14, 0x0c18, 0x0c1c, 0x0c20, 0x0c24, 0x0c28, + 0x0c2c, 0x0c30, 0x0c34, 0x0c36, 0x0c38, 0x0c3a, 0x0c3c, 0x0c3e, + // Entry 2C0 - 2FF + 0x0c40, 0x0c42, 0x0c44, 0x0c46, 0x0c48, 0x0c4a, 0x0c4c, 0x0c4e, + 0x0c50, 0x0c52, 0x0c54, 0x0c56, 0x0c58, 0x0c5a, 0x0c5c, 0x0c5e, + 0x0c60, 0x0c62, 0x0c64, 0x0c66, 0x0c68, 0x0c6a, 0x0c6c, 0x0c6e, + 0x0c70, 0x0c72, 0x0c74, 0x0c76, 0x0c78, 0x0c7a, 0x0c7c, 0x0c7e, + 0x0c80, 0x0c82, 0x0c86, 0x0c8a, 0x0c8e, 0x0c92, 0x0c96, 0x0c9a, + 0x0c9e, 0x0ca2, 0x0ca4, 0x0ca8, 0x0cac, 0x0cb0, 0x0cb4, 0x0cb8, + 0x0cbc, 0x0cc0, 0x0cc4, 0x0cc8, 0x0ccc, 0x0cd0, 0x0cd4, 0x0cd8, + 0x0cdc, 0x0ce0, 0x0ce4, 0x0ce8, 0x0cec, 0x0cf0, 0x0cf4, 0x0cf8, + // Entry 300 - 33F + 0x0cfc, 0x0d00, 0x0d04, 0x0d08, 0x0d0c, 0x0d10, 0x0d14, 0x0d18, + 0x0d1c, 0x0d20, 0x0d24, 0x0d28, 0x0d2c, 0x0d30, 0x0d34, 0x0d38, + 0x0d3c, 0x0d40, 0x0d44, 0x0d48, 0x0d4c, 0x0d50, 0x0d54, 0x0d58, + 0x0d5c, 0x0d60, 0x0d64, 0x0d68, 0x0d6c, 0x0d70, 0x0d74, 0x0d78, + 0x0d7c, 0x0d80, 0x0d84, 0x0d88, 0x0d8c, 0x0d90, 0x0d94, 0x0d98, + 0x0d9c, 0x0da0, 0x0da4, 0x0da8, 0x0dac, 0x0db0, 0x0db4, 0x0db8, + 0x0dbc, 0x0dc0, 0x0dc4, 0x0dc8, 0x0dcc, 0x0dd0, 0x0dd4, 0x0dd8, + 0x0ddc, 0x0de0, 0x0de4, 0x0de8, 0x0dec, 0x0df0, 0x0df4, 0x0df8, + // Entry 340 - 37F + 0x0dfc, 0x0e00, 0x0e04, 0x0e08, 0x0e0c, 0x0e10, 0x0e14, 0x0e18, + 0x0e1d, 0x0e22, 0x0e27, 0x0e2c, 0x0e31, 0x0e36, 0x0e3a, 0x0e3e, + 0x0e42, 0x0e46, 0x0e4a, 0x0e4e, 0x0e52, 0x0e56, 0x0e5a, 0x0e5e, + 0x0e62, 0x0e66, 0x0e6a, 0x0e6e, 0x0e72, 0x0e76, 0x0e7a, 0x0e7e, + 0x0e82, 0x0e86, 0x0e8a, 0x0e8e, 0x0e92, 0x0e96, 0x0e9a, 0x0e9e, + 0x0ea2, 0x0ea6, 0x0eaa, 0x0eae, 0x0eb2, 0x0eb6, 0x0ebc, 0x0ec2, + 0x0ec8, 0x0ecc, 0x0ed0, 0x0ed4, 0x0ed8, 0x0edc, 0x0ee0, 0x0ee4, + 0x0ee8, 0x0eec, 0x0ef0, 0x0ef4, 0x0ef8, 0x0efc, 0x0f00, 0x0f04, + // Entry 380 - 3BF + 0x0f08, 0x0f0c, 0x0f10, 0x0f14, 0x0f18, 0x0f1c, 0x0f20, 0x0f24, + 0x0f28, 0x0f2c, 0x0f30, 0x0f34, 0x0f38, 0x0f3e, 0x0f44, 0x0f4a, + 0x0f50, 0x0f56, 0x0f5c, 0x0f62, 0x0f68, 0x0f6e, 0x0f74, 0x0f7a, + 0x0f80, 0x0f86, 0x0f8c, 0x0f92, 0x0f98, 0x0f9e, 0x0fa4, 0x0faa, + 0x0fb0, 0x0fb6, 0x0fbc, 0x0fc2, 0x0fc8, 0x0fce, 0x0fd4, 0x0fda, + 0x0fe0, 0x0fe6, 0x0fec, 0x0ff2, 0x0ff8, 0x0ffe, 0x1004, 0x100a, + 0x1010, 0x1016, 0x101c, 0x1022, 0x1028, 0x102e, 0x1034, 0x103a, + 0x1040, 0x1046, 0x104c, 0x1052, 0x1058, 0x105e, 0x1064, 0x106a, + // Entry 3C0 - 3FF + 0x1070, 0x1076, 0x107c, 0x1082, 0x1088, 0x108e, 0x1094, 0x109a, + 0x10a0, 0x10a6, 0x10ac, 0x10b2, 0x10b8, 0x10be, 0x10c4, 0x10ca, + 0x10d0, 0x10d6, 0x10dc, 0x10e2, 0x10e8, 0x10ee, 0x10f4, 0x10fa, + 0x1100, 0x1106, 0x110c, 0x1112, 0x1118, 0x111e, 0x1124, 0x112a, + 0x1130, 0x1136, 0x113c, 0x1142, 0x1148, 0x114e, 0x1154, 0x115a, + 0x1160, 0x1166, 0x116c, 0x1172, 0x1178, 0x1180, 0x1188, 0x1190, + 0x1198, 0x11a0, 0x11a8, 0x11b0, 0x11b6, 0x11d7, 0x11e6, 0x11ee, + 0x11ef, 0x11f0, 0x11f1, 0x11f2, 0x11f3, 0x11f4, 0x11f5, 0x11f6, + // Entry 400 - 43F + 0x11f7, 0x11f8, 0x11f9, 0x11fa, 0x11fb, 0x11fc, 0x11fd, 0x11fe, + 0x11ff, 0x1200, 0x1201, 0x1205, 0x1209, 0x120d, 0x1211, 0x1215, + 0x1219, 0x121b, 0x121d, 0x121f, 0x1221, 0x1223, 0x1225, 0x1227, + 0x1229, 0x122b, 0x122d, 0x122f, 0x1231, 0x1233, 0x1235, 0x1237, + 0x1239, 0x123b, 0x123d, 0x123f, 0x1241, 0x1243, 0x1245, 0x1247, + 0x1249, 0x124b, 0x124d, 0x124f, 0x1251, 0x1253, 0x1255, 0x1257, + 0x1259, 0x125b, 0x125d, 0x125f, 0x1263, 0x1267, 0x126b, 0x126f, + 0x1270, 0x1271, 0x1272, 0x1273, 0x1274, 0x1275, 0x1277, 0x1279, + // Entry 440 - 47F + 0x127b, 0x127d, 0x127f, 0x1281, 0x1283, 0x1285, 0x1287, 0x1289, + 0x128c, 0x128e, 0x1290, 0x1292, 0x1294, 0x1297, 0x1299, 0x129b, + 0x129d, 0x129f, 0x12a1, 0x12a3, 0x12a5, 0x12a7, 0x12a9, 0x12ab, + 0x12ad, 0x12af, 0x12b2, 0x12b4, 0x12b6, 0x12b8, 0x12ba, 0x12bc, + 0x12be, 0x12c0, 0x12c2, 0x12c4, 0x12c6, 0x12c9, 0x12cb, 0x12cd, + 0x12d0, 0x12d2, 0x12d4, 0x12d6, 0x12d8, 0x12da, 0x12dc, 0x12de, + 0x12e6, 0x12ee, 0x12fa, 0x1306, 0x1312, 0x131e, 0x132a, 0x1332, + 0x133a, 0x1346, 0x1352, 0x135e, 0x136a, 0x136c, 0x136e, 0x1370, + // Entry 480 - 4BF + 0x1372, 0x1374, 0x1376, 0x1378, 0x137a, 0x137c, 0x137e, 0x1380, + 0x1382, 0x1384, 0x1386, 0x1388, 0x138a, 0x138d, 0x1390, 0x1392, + 0x1394, 0x1396, 0x1398, 0x139a, 0x139c, 0x139e, 0x13a0, 0x13a2, + 0x13a4, 0x13a6, 0x13a8, 0x13aa, 0x13ac, 0x13ae, 0x13b0, 0x13b2, + 0x13b4, 0x13b6, 0x13b8, 0x13ba, 0x13bc, 0x13bf, 0x13c1, 0x13c3, + 0x13c5, 0x13c7, 0x13c9, 0x13cb, 0x13cd, 0x13cf, 0x13d1, 0x13d3, + 0x13d6, 0x13d8, 0x13da, 0x13dc, 0x13de, 0x13e0, 0x13e2, 0x13e4, + 0x13e6, 0x13e8, 0x13ea, 0x13ec, 0x13ee, 0x13f0, 0x13f2, 0x13f5, + // Entry 4C0 - 4FF + 0x13f8, 0x13fb, 0x13fe, 0x1401, 0x1404, 0x1407, 0x140a, 0x140d, + 0x1410, 0x1413, 0x1416, 0x1419, 0x141c, 0x141f, 0x1422, 0x1425, + 0x1428, 0x142b, 0x142e, 0x1431, 0x1434, 0x1437, 0x143a, 0x143d, + 0x1440, 0x1447, 0x1449, 0x144b, 0x144d, 0x1450, 0x1452, 0x1454, + 0x1456, 0x1458, 0x145a, 0x1460, 0x1466, 0x1469, 0x146c, 0x146f, + 0x1472, 0x1475, 0x1478, 0x147b, 0x147e, 0x1481, 0x1484, 0x1487, + 0x148a, 0x148d, 0x1490, 0x1493, 0x1496, 0x1499, 0x149c, 0x149f, + 0x14a2, 0x14a5, 0x14a8, 0x14ab, 0x14ae, 0x14b1, 0x14b4, 0x14b7, + // Entry 500 - 53F + 0x14ba, 0x14bd, 0x14c0, 0x14c3, 0x14c6, 0x14c9, 0x14cc, 0x14cf, + 0x14d2, 0x14d5, 0x14d8, 0x14db, 0x14de, 0x14e1, 0x14e4, 0x14e7, + 0x14ea, 0x14ed, 0x14f6, 0x14ff, 0x1508, 0x1511, 0x151a, 0x1523, + 0x152c, 0x1535, 0x153e, 0x1541, 0x1544, 0x1547, 0x154a, 0x154d, + 0x1550, 0x1553, 0x1556, 0x1559, 0x155c, 0x155f, 0x1562, 0x1565, + 0x1568, 0x156b, 0x156e, 0x1571, 0x1574, 0x1577, 0x157a, 0x157d, + 0x1580, 0x1583, 0x1586, 0x1589, 0x158c, 0x158f, 0x1592, 0x1595, + 0x1598, 0x159b, 0x159e, 0x15a1, 0x15a4, 0x15a7, 0x15aa, 0x15ad, + // Entry 540 - 57F + 0x15b0, 0x15b3, 0x15b6, 0x15b9, 0x15bc, 0x15bf, 0x15c2, 0x15c5, + 0x15c8, 0x15cb, 0x15ce, 0x15d1, 0x15d4, 0x15d7, 0x15da, 0x15dd, + 0x15e0, 0x15e3, 0x15e6, 0x15e9, 0x15ec, 0x15ef, 0x15f2, 0x15f5, + 0x15f8, 0x15fb, 0x15fe, 0x1601, 0x1604, 0x1607, 0x160a, 0x160d, + 0x1610, 0x1613, 0x1616, 0x1619, 0x161c, 0x161f, 0x1622, 0x1625, + 0x1628, 0x162b, 0x162e, 0x1631, 0x1634, 0x1637, 0x163a, 0x163d, + 0x1640, 0x1643, 0x1646, 0x1649, 0x164c, 0x164f, 0x1652, 0x1655, + 0x1658, 0x165b, 0x165e, 0x1661, 0x1664, 0x1667, 0x166a, 0x166d, + // Entry 580 - 5BF + 0x1670, 0x1673, 0x1676, 0x1679, 0x167c, 0x167f, 0x1682, 0x1685, + 0x1688, 0x168b, 0x168e, 0x1691, 0x1694, 0x1697, 0x169a, 0x169d, + 0x16a0, 0x16a3, 0x16a6, 0x16a9, 0x16ac, 0x16af, 0x16b2, 0x16b5, + 0x16b8, 0x16bb, 0x16be, 0x16c1, 0x16c4, 0x16c7, 0x16ca, 0x16cd, + 0x16d0, 0x16d3, 0x16d6, 0x16d9, 0x16dc, 0x16df, 0x16e2, 0x16e5, + 0x16e8, 0x16eb, 0x16ee, 0x16f1, 0x16f4, 0x16f7, 0x16fa, 0x16fd, + 0x1700, 0x1703, 0x1706, 0x1709, 0x170c, 0x170f, 0x1712, 0x1715, + 0x1718, 0x171b, 0x171e, 0x1721, 0x1724, 0x1727, 0x172a, 0x172d, + // Entry 5C0 - 5FF + 0x1730, 0x1733, 0x1736, 0x1739, 0x173c, 0x173f, 0x1742, 0x1745, + 0x1748, 0x174b, 0x174e, 0x1751, 0x1754, 0x1757, 0x175a, 0x175d, + 0x1760, 0x1763, 0x1766, 0x1769, 0x176c, 0x176f, 0x1772, 0x1775, + 0x1778, 0x177b, 0x177e, 0x1781, 0x1784, 0x1787, 0x178a, 0x178d, + 0x1790, 0x1793, 0x1796, 0x1799, 0x179c, 0x179f, 0x17a2, 0x17a5, + 0x17a8, 0x17ab, 0x17ae, 0x17b1, 0x17b4, 0x17b7, 0x17ba, 0x17bd, + 0x17c0, 0x17c3, 0x17c6, 0x17c9, 0x17cc, 0x17cf, 0x17d2, 0x17d5, + 0x17d8, 0x17db, 0x17de, 0x17e1, 0x17e4, 0x17e7, 0x17ea, 0x17ed, + // Entry 600 - 63F + 0x17f0, 0x17f3, 0x17f6, 0x17f9, 0x17fc, 0x17ff, 0x1802, 0x1805, + 0x1808, 0x180b, 0x180e, 0x1811, 0x1814, 0x1817, 0x181a, 0x181d, + 0x1820, 0x1823, 0x1826, 0x1829, 0x182c, 0x182f, 0x1832, 0x1835, + 0x1838, 0x183b, 0x183e, 0x1841, 0x1844, 0x1847, 0x184a, 0x184d, + 0x1850, 0x1853, 0x1856, 0x1859, 0x185c, 0x185f, 0x1862, 0x1865, + 0x1868, 0x186b, 0x186e, 0x1871, 0x1874, 0x1877, 0x187a, 0x187d, + 0x1880, 0x1883, 0x1886, 0x1889, 0x188c, 0x188f, 0x1892, 0x1895, + 0x1898, 0x189b, 0x189e, 0x18a1, 0x18a4, 0x18a7, 0x18aa, 0x18ad, + // Entry 640 - 67F + 0x18b0, 0x18b3, 0x18b6, 0x18b9, 0x18bc, 0x18bf, 0x18c2, 0x18c5, + 0x18c8, 0x18cb, 0x18ce, 0x18d1, 0x18d4, 0x18d7, 0x18da, 0x18dd, + 0x18e0, 0x18e3, 0x18e6, 0x18e9, 0x18ec, 0x18ef, 0x18f2, 0x18f5, + 0x18f8, 0x18fb, 0x18fe, 0x1901, 0x1904, 0x1907, 0x190a, 0x190d, + 0x1910, 0x1913, 0x1916, 0x1919, 0x191c, 0x191f, 0x1922, 0x1925, + 0x1928, 0x192b, 0x192e, 0x1931, 0x1934, 0x1937, 0x193a, 0x193d, + 0x1940, 0x1943, 0x1946, 0x1949, 0x194c, 0x194f, 0x1952, 0x1955, + 0x1958, 0x195b, 0x195e, 0x1961, 0x1964, 0x1967, 0x196a, 0x196d, + // Entry 680 - 6BF + 0x1970, 0x1973, 0x1976, 0x1979, 0x197c, 0x197f, 0x1982, 0x1985, + 0x1988, 0x198b, 0x198e, 0x1991, 0x1994, 0x1997, 0x199a, 0x199d, + 0x19a0, 0x19a3, 0x19a6, 0x19a9, 0x19ac, 0x19af, 0x19b2, 0x19b5, + 0x19b8, 0x19bb, 0x19be, 0x19c1, 0x19c4, 0x19c7, 0x19ca, 0x19cd, + 0x19d0, 0x19d3, 0x19d6, 0x19d9, 0x19dc, 0x19df, 0x19e2, 0x19e5, + 0x19e8, 0x19eb, 0x19ee, 0x19f1, 0x19f4, 0x19f7, 0x19fa, 0x19fd, + 0x1a00, 0x1a03, 0x1a06, 0x1a09, 0x1a0c, 0x1a0f, 0x1a12, 0x1a15, + 0x1a18, 0x1a1b, 0x1a1e, 0x1a21, 0x1a24, 0x1a27, 0x1a2a, 0x1a2d, + // Entry 6C0 - 6FF + 0x1a30, +} // Size: 3482 bytes + +var xorData string = "" + // Size: 4907 bytes + "\x02\x0c\x09\x02\xb0\xec\x02\xad\xd8\x02\xad\xd9\x02\x06\x07\x02\x0f\x12" + + "\x02\x0f\x1f\x02\x0f\x1d\x02\x01\x13\x02\x0f\x16\x02\x0f\x0b\x02\x0f3" + + "\x02\x0f7\x02\x0f?\x02\x0f/\x02\x0f*\x02\x0c&\x02\x0c*\x02\x0c;\x02\x0c9" + + "\x02\x0c%\x02\xab\xed\x02\xab\xe2\x02\xab\xe3\x02\xa9\xe0\x02\xa9\xe1" + + "\x02\xa9\xe6\x02\xa3\xcb\x02\xa3\xc8\x02\xa3\xc9\x02\x01#\x02\x01\x08" + + "\x02\x0e>\x02\x0e'\x02\x0f\x03\x02\x03\x0d\x02\x03\x09\x02\x03\x17\x02" + + "\x03\x0e\x02\x02\x03\x02\x011\x02\x01\x00\x02\x01\x10\x02\x03<\x02\x07" + + "\x0d\x02\x02\x0c\x02\x0c0\x02\x01\x03\x02\x01\x01\x02\x01 \x02\x01\x22" + + "\x02\x01)\x02\x01\x0a\x02\x01\x0c\x02\x02\x06\x02\x02\x02\x02\x03\x10" + + "\x03\x037 \x03\x0b+\x03\x021\x00\x02\x01\x04\x02\x01\x02\x02\x019\x02" + + "\x03\x1c\x02\x02$\x03\x80p$\x02\x03:\x02\x03\x0a\x03\xc1r.\x03\xc1r,\x03" + + "\xc1r\x02\x02\x02:\x02\x02>\x02\x02,\x02\x02\x10\x02\x02\x00\x03\xc1s<" + + "\x03\xc1s*\x03\xc2L$\x03\xc2L;\x02\x09)\x02\x0a\x19\x03\x83\xab\xe3\x03" + + "\x83\xab\xf2\x03 4\xe0\x03\x81\xab\xea\x03\x81\xab\xf3\x03 4\xef\x03\x96" + + "\xe1\xcd\x03\x84\xe5\xc3\x02\x0d\x11\x03\x8b\xec\xcb\x03\x94\xec\xcf\x03" + + "\x9a\xec\xc2\x03\x8b\xec\xdb\x03\x94\xec\xdf\x03\x9a\xec\xd2\x03\x01\x0c" + + "!\x03\x01\x0c#\x03ʠ\x9d\x03ʣ\x9c\x03ʢ\x9f\x03ʥ\x9e\x03ʤ\x91\x03ʧ\x90\x03" + + "ʦ\x93\x03ʩ\x92\x03ʨ\x95\x03\xca\xf3\xb5\x03\xca\xf0\xb4\x03\xca\xf1\xb7" + + "\x03\xca\xf6\xb6\x03\xca\xf7\x89\x03\xca\xf4\x88\x03\xca\xf5\x8b\x03\xca" + + "\xfa\x8a\x03\xca\xfb\x8d\x03\xca\xf8\x8c\x03\xca\xf9\x8f\x03\xca\xfe\x8e" + + "\x03\xca\xff\x81\x03\xca\xfc\x80\x03\xca\xfd\x83\x03\xca\xe2\x82\x03\xca" + + "\xe3\x85\x03\xca\xe0\x84\x03\xca\xe1\x87\x03\xca\xe6\x86\x03\xca\xe7\x99" + + "\x03\xca\xe4\x98\x03\xca\xe5\x9b\x03\xca\xea\x9a\x03\xca\xeb\x9d\x03\xca" + + "\xe8\x9c\x03ؓ\x89\x03ߔ\x8b\x02\x010\x03\x03\x04\x1e\x03\x04\x15\x12\x03" + + "\x0b\x05,\x03\x06\x04\x00\x03\x06\x04)\x03\x06\x044\x03\x06\x04<\x03\x06" + + "\x05\x1d\x03\x06\x06\x00\x03\x06\x06\x0a\x03\x06\x06'\x03\x06\x062\x03" + + "\x0786\x03\x079/\x03\x079 \x03\x07:\x0e\x03\x07:\x1b\x03\x07:%\x03\x07;/" + + "\x03\x07;%\x03\x074\x11\x03\x076\x09\x03\x077*\x03\x070\x01\x03\x070\x0f" + + "\x03\x070.\x03\x071\x16\x03\x071\x04\x03\x0710\x03\x072\x18\x03\x072-" + + "\x03\x073\x14\x03\x073>\x03\x07'\x09\x03\x07 \x00\x03\x07\x1f\x0b\x03" + + "\x07\x18#\x03\x07\x18(\x03\x07\x186\x03\x07\x18\x03\x03\x07\x19\x16\x03" + + "\x07\x116\x03\x07\x12'\x03\x07\x13\x10\x03\x07\x0c&\x03\x07\x0c\x08\x03" + + "\x07\x0c\x13\x03\x07\x0d\x02\x03\x07\x0d\x1c\x03\x07\x0b5\x03\x07\x0b" + + "\x0a\x03\x07\x0b\x01\x03\x07\x0b\x0f\x03\x07\x05\x00\x03\x07\x05\x09\x03" + + "\x07\x05\x0b\x03\x07\x07\x01\x03\x07\x07\x08\x03\x07\x00<\x03\x07\x00+" + + "\x03\x07\x01)\x03\x07\x01\x1b\x03\x07\x01\x08\x03\x07\x03?\x03\x0445\x03" + + "\x044\x08\x03\x0454\x03\x04)/\x03\x04)5\x03\x04+\x05\x03\x04+\x14\x03" + + "\x04+ \x03\x04+<\x03\x04*&\x03\x04*\x22\x03\x04&8\x03\x04!\x01\x03\x04!" + + "\x22\x03\x04\x11+\x03\x04\x10.\x03\x04\x104\x03\x04\x13=\x03\x04\x12\x04" + + "\x03\x04\x12\x0a\x03\x04\x0d\x1d\x03\x04\x0d\x07\x03\x04\x0d \x03\x05<>" + + "\x03\x055<\x03\x055!\x03\x055#\x03\x055&\x03\x054\x1d\x03\x054\x02\x03" + + "\x054\x07\x03\x0571\x03\x053\x1a\x03\x053\x16\x03\x05.<\x03\x05.\x07\x03" + + "\x05):\x03\x05)<\x03\x05)\x0c\x03\x05)\x15\x03\x05+-\x03\x05+5\x03\x05$" + + "\x1e\x03\x05$\x14\x03\x05'\x04\x03\x05'\x14\x03\x05&\x02\x03\x05\x226" + + "\x03\x05\x22\x0c\x03\x05\x22\x1c\x03\x05\x19\x0a\x03\x05\x1b\x09\x03\x05" + + "\x1b\x0c\x03\x05\x14\x07\x03\x05\x16?\x03\x05\x16\x0c\x03\x05\x0c\x05" + + "\x03\x05\x0e\x0f\x03\x05\x01\x0e\x03\x05\x00(\x03\x05\x030\x03\x05\x03" + + "\x06\x03\x0a==\x03\x0a=1\x03\x0a=,\x03\x0a=\x0c\x03\x0a??\x03\x0a<\x08" + + "\x03\x0a9!\x03\x0a9)\x03\x0a97\x03\x0a99\x03\x0a6\x0a\x03\x0a6\x1c\x03" + + "\x0a6\x17\x03\x0a7'\x03\x0a78\x03\x0a73\x03\x0a'\x01\x03\x0a'&\x03\x0a" + + "\x1f\x0e\x03\x0a\x1f\x03\x03\x0a\x1f3\x03\x0a\x1b/\x03\x0a\x18\x19\x03" + + "\x0a\x19\x01\x03\x0a\x16\x14\x03\x0a\x0e\x22\x03\x0a\x0f\x10\x03\x0a\x0f" + + "\x02\x03\x0a\x0f \x03\x0a\x0c\x04\x03\x0a\x0b>\x03\x0a\x0b+\x03\x0a\x08/" + + "\x03\x0a\x046\x03\x0a\x05\x14\x03\x0a\x00\x04\x03\x0a\x00\x10\x03\x0a" + + "\x00\x14\x03\x0b<3\x03\x0b;*\x03\x0b9\x22\x03\x0b9)\x03\x0b97\x03\x0b+" + + "\x10\x03\x0b((\x03\x0b&5\x03\x0b$\x1c\x03\x0b$\x12\x03\x0b%\x04\x03\x0b#" + + "<\x03\x0b#0\x03\x0b#\x0d\x03\x0b#\x19\x03\x0b!:\x03\x0b!\x1f\x03\x0b!" + + "\x00\x03\x0b\x1e5\x03\x0b\x1c\x1d\x03\x0b\x1d-\x03\x0b\x1d(\x03\x0b\x18." + + "\x03\x0b\x18 \x03\x0b\x18\x16\x03\x0b\x14\x13\x03\x0b\x15$\x03\x0b\x15" + + "\x22\x03\x0b\x12\x1b\x03\x0b\x12\x10\x03\x0b\x132\x03\x0b\x13=\x03\x0b" + + "\x12\x18\x03\x0b\x0c&\x03\x0b\x061\x03\x0b\x06:\x03\x0b\x05#\x03\x0b\x05" + + "<\x03\x0b\x04\x0b\x03\x0b\x04\x04\x03\x0b\x04\x1b\x03\x0b\x042\x03\x0b" + + "\x041\x03\x0b\x03\x03\x03\x0b\x03\x1d\x03\x0b\x03/\x03\x0b\x03+\x03\x0b" + + "\x02\x1b\x03\x0b\x02\x00\x03\x0b\x01\x1e\x03\x0b\x01\x08\x03\x0b\x015" + + "\x03\x06\x0d9\x03\x06\x0d=\x03\x06\x0d?\x03\x02\x001\x03\x02\x003\x03" + + "\x02\x02\x19\x03\x02\x006\x03\x02\x02\x1b\x03\x02\x004\x03\x02\x00<\x03" + + "\x02\x02\x0a\x03\x02\x02\x0e\x03\x02\x01\x1a\x03\x02\x01\x07\x03\x02\x01" + + "\x05\x03\x02\x01\x0b\x03\x02\x01%\x03\x02\x01\x0c\x03\x02\x01\x04\x03" + + "\x02\x01\x1c\x03\x02\x00.\x03\x02\x002\x03\x02\x00>\x03\x02\x00\x12\x03" + + "\x02\x00\x16\x03\x02\x011\x03\x02\x013\x03\x02\x02 \x03\x02\x02%\x03\x02" + + "\x02$\x03\x02\x028\x03\x02\x02;\x03\x02\x024\x03\x02\x012\x03\x02\x022" + + "\x03\x02\x02/\x03\x02\x01,\x03\x02\x01\x13\x03\x02\x01\x16\x03\x02\x01" + + "\x11\x03\x02\x01\x1e\x03\x02\x01\x15\x03\x02\x01\x17\x03\x02\x01\x0f\x03" + + "\x02\x01\x08\x03\x02\x00?\x03\x02\x03\x07\x03\x02\x03\x0d\x03\x02\x03" + + "\x13\x03\x02\x03\x1d\x03\x02\x03\x1f\x03\x02\x00\x03\x03\x02\x00\x0d\x03" + + "\x02\x00\x01\x03\x02\x00\x1b\x03\x02\x00\x19\x03\x02\x00\x18\x03\x02\x00" + + "\x13\x03\x02\x00/\x03\x07>\x12\x03\x07<\x1f\x03\x07>\x1d\x03\x06\x1d\x0e" + + "\x03\x07>\x1c\x03\x07>:\x03\x07>\x13\x03\x04\x12+\x03\x07?\x03\x03\x07>" + + "\x02\x03\x06\x224\x03\x06\x1a.\x03\x07<%\x03\x06\x1c\x0b\x03\x0609\x03" + + "\x05\x1f\x01\x03\x04'\x08\x03\x93\xfd\xf5\x03\x02\x0d \x03\x02\x0d#\x03" + + "\x02\x0d!\x03\x02\x0d&\x03\x02\x0d\x22\x03\x02\x0d/\x03\x02\x0d,\x03\x02" + + "\x0d$\x03\x02\x0d'\x03\x02\x0d%\x03\x02\x0d;\x03\x02\x0d=\x03\x02\x0d?" + + "\x03\x099.\x03\x08\x0b7\x03\x08\x02\x14\x03\x08\x14\x0d\x03\x08.:\x03" + + "\x089'\x03\x0f\x0b\x18\x03\x0f\x1c1\x03\x0f\x17&\x03\x0f9\x1f\x03\x0f0" + + "\x0c\x03\x0e\x0a9\x03\x0e\x056\x03\x0e\x1c#\x03\x0f\x13\x0e\x03\x072\x00" + + "\x03\x070\x0d\x03\x072\x0b\x03\x06\x11\x18\x03\x070\x10\x03\x06\x0f(\x03" + + "\x072\x05\x03\x06\x0f,\x03\x073\x15\x03\x06\x07\x08\x03\x05\x16\x02\x03" + + "\x04\x0b \x03\x05:8\x03\x05\x16%\x03\x0a\x0d\x1f\x03\x06\x16\x10\x03\x05" + + "\x1d5\x03\x05*;\x03\x05\x16\x1b\x03\x04.-\x03\x06\x1a\x19\x03\x04\x03," + + "\x03\x0b87\x03\x04/\x0a\x03\x06\x00,\x03\x04-\x01\x03\x04\x1e-\x03\x06/(" + + "\x03\x0a\x0b5\x03\x06\x0e7\x03\x06\x07.\x03\x0597\x03\x0a*%\x03\x0760" + + "\x03\x06\x0c;\x03\x05'\x00\x03\x072.\x03\x072\x08\x03\x06=\x01\x03\x06" + + "\x05\x1b\x03\x06\x06\x12\x03\x06$=\x03\x06'\x0d\x03\x04\x11\x0f\x03\x076" + + ",\x03\x06\x07;\x03\x06.,\x03\x86\xf9\xea\x03\x8f\xff\xeb\x02\x092\x02" + + "\x095\x02\x094\x02\x09;\x02\x09>\x02\x098\x02\x09*\x02\x09/\x02\x09,\x02" + + "\x09%\x02\x09&\x02\x09#\x02\x09 \x02\x08!\x02\x08%\x02\x08$\x02\x08+\x02" + + "\x08.\x02\x08*\x02\x08&\x02\x088\x02\x08>\x02\x084\x02\x086\x02\x080\x02" + + "\x08\x10\x02\x08\x17\x02\x08\x12\x02\x08\x1d\x02\x08\x1f\x02\x08\x13\x02" + + "\x08\x15\x02\x08\x14\x02\x08\x0c\x03\x8b\xfd\xd0\x03\x81\xec\xc6\x03\x87" + + "\xe0\x8a\x03-2\xe3\x03\x80\xef\xe4\x03-2\xea\x03\x88\xe6\xeb\x03\x8e\xe6" + + "\xe8\x03\x84\xe6\xe9\x03\x97\xe6\xee\x03-2\xf9\x03-2\xf6\x03\x8e\xe3\xad" + + "\x03\x80\xe3\x92\x03\x88\xe3\x90\x03\x8e\xe3\x90\x03\x80\xe3\x97\x03\x88" + + "\xe3\x95\x03\x88\xfe\xcb\x03\x8e\xfe\xca\x03\x84\xfe\xcd\x03\x91\xef\xc9" + + "\x03-2\xc1\x03-2\xc0\x03-2\xcb\x03\x88@\x09\x03\x8e@\x08\x03\x8f\xe0\xf5" + + "\x03\x8e\xe6\xf9\x03\x8e\xe0\xfa\x03\x93\xff\xf4\x03\x84\xee\xd3\x03\x0b" + + "(\x04\x023 \x03\x0b)\x08\x021;\x02\x01*\x03\x0b#\x10\x03\x0b 0\x03\x0b!" + + "\x10\x03\x0b!0\x03\x07\x15\x08\x03\x09?5\x03\x07\x1f\x08\x03\x07\x17\x0b" + + "\x03\x09\x1f\x15\x03\x0b\x1c7\x03\x0a+#\x03\x06\x1a\x1b\x03\x06\x1a\x14" + + "\x03\x0a\x01\x18\x03\x06#\x1b\x03\x0a2\x0c\x03\x0a\x01\x04\x03\x09#;\x03" + + "\x08='\x03\x08\x1a\x0a\x03\x07\x03\x0a\x111\x03\x09\x1b\x09\x03\x073.\x03\x07" + + "\x01\x00\x03\x09/,\x03\x07#>\x03\x07\x048\x03\x0a\x1f\x22\x03\x098>\x03" + + "\x09\x11\x00\x03\x08/\x17\x03\x06'\x22\x03\x0b\x1a+\x03\x0a\x22\x19\x03" + + "\x0a/1\x03\x0974\x03\x09\x0f\x22\x03\x08,\x22\x03\x08?\x14\x03\x07$5\x03" + + "\x07<3\x03\x07=*\x03\x07\x13\x18\x03\x068\x0a\x03\x06\x09\x16\x03\x06" + + "\x13\x00\x03\x08\x067\x03\x08\x01\x03\x03\x08\x12\x1d\x03\x07+7\x03\x06(" + + ";\x03\x06\x1c?\x03\x07\x0e\x17\x03\x0a\x06\x1d\x03\x0a\x19\x07\x03\x08" + + "\x14$\x03\x07$;\x03\x08,$\x03\x08\x06\x0d\x03\x07\x16\x0a\x03\x06>>\x03" + + "\x0a\x06\x12\x03\x0a\x14)\x03\x09\x0d\x1f\x03\x09\x12\x17\x03\x09\x19" + + "\x01\x03\x08\x11 \x03\x08\x1d'\x03\x06<\x1a\x03\x0a.\x00\x03\x07'\x18" + + "\x03\x0a\x22\x08\x03\x08\x0d\x0a\x03\x08\x13)\x03\x07*)\x03\x06<,\x03" + + "\x07\x0b\x1a\x03\x09.\x14\x03\x09\x0d\x1e\x03\x07\x0e#\x03\x0b\x1d'\x03" + + "\x0a\x0a8\x03\x09%2\x03\x08+&\x03\x080\x12\x03\x0a)4\x03\x08\x06\x1f\x03" + + "\x0b\x1b\x1a\x03\x0a\x1b\x0f\x03\x0b\x1d*\x03\x09\x16$\x03\x090\x11\x03" + + "\x08\x11\x08\x03\x0a*(\x03\x0a\x042\x03\x089,\x03\x074'\x03\x07\x0f\x05" + + "\x03\x09\x0b\x0a\x03\x07\x1b\x01\x03\x09\x17:\x03\x09.\x0d\x03\x07.\x11" + + "\x03\x09+\x15\x03\x080\x13\x03\x0b\x1f\x19\x03\x0a \x11\x03\x0a\x220\x03" + + "\x09\x07;\x03\x08\x16\x1c\x03\x07,\x13\x03\x07\x0e/\x03\x06\x221\x03\x0a" + + ".\x0a\x03\x0a7\x02\x03\x0a\x032\x03\x0a\x1d.\x03\x091\x06\x03\x09\x19:" + + "\x03\x08\x02/\x03\x060+\x03\x06\x0f-\x03\x06\x1c\x1f\x03\x06\x1d\x07\x03" + + "\x0a,\x11\x03\x09=\x0d\x03\x09\x0b;\x03\x07\x1b/\x03\x0a\x1f:\x03\x09 " + + "\x1f\x03\x09.\x10\x03\x094\x0b\x03\x09\x1a1\x03\x08#\x1a\x03\x084\x1d" + + "\x03\x08\x01\x1f\x03\x08\x11\x22\x03\x07'8\x03\x07\x1a>\x03\x0757\x03" + + "\x06&9\x03\x06+\x11\x03\x0a.\x0b\x03\x0a,>\x03\x0a4#\x03\x08%\x17\x03" + + "\x07\x05\x22\x03\x07\x0c\x0b\x03\x0a\x1d+\x03\x0a\x19\x16\x03\x09+\x1f" + + "\x03\x09\x08\x0b\x03\x08\x16\x18\x03\x08+\x12\x03\x0b\x1d\x0c\x03\x0a=" + + "\x10\x03\x0a\x09\x0d\x03\x0a\x10\x11\x03\x09&0\x03\x08(\x1f\x03\x087\x07" + + "\x03\x08\x185\x03\x07'6\x03\x06.\x05\x03\x06=\x04\x03\x06;;\x03\x06\x06," + + "\x03\x0b\x18>\x03\x08\x00\x18\x03\x06 \x03\x03\x06<\x00\x03\x09%\x18\x03" + + "\x0b\x1c<\x03\x0a%!\x03\x0a\x09\x12\x03\x0a\x16\x02\x03\x090'\x03\x09" + + "\x0e=\x03\x08 \x0e\x03\x08>\x03\x03\x074>\x03\x06&?\x03\x06\x19\x09\x03" + + "\x06?(\x03\x0a-\x0e\x03\x09:3\x03\x098:\x03\x09\x12\x0b\x03\x09\x1d\x17" + + "\x03\x087\x05\x03\x082\x14\x03\x08\x06%\x03\x08\x13\x1f\x03\x06\x06\x0e" + + "\x03\x0a\x22<\x03\x09/<\x03\x06>+\x03\x0a'?\x03\x0a\x13\x0c\x03\x09\x10<" + + "\x03\x07\x1b=\x03\x0a\x19\x13\x03\x09\x22\x1d\x03\x09\x07\x0d\x03\x08)" + + "\x1c\x03\x06=\x1a\x03\x0a/4\x03\x0a7\x11\x03\x0a\x16:\x03\x09?3\x03\x09:" + + "/\x03\x09\x05\x0a\x03\x09\x14\x06\x03\x087\x22\x03\x080\x07\x03\x08\x1a" + + "\x1f\x03\x07\x04(\x03\x07\x04\x09\x03\x06 %\x03\x06<\x08\x03\x0a+\x14" + + "\x03\x09\x1d\x16\x03\x0a70\x03\x08 >\x03\x0857\x03\x070\x0a\x03\x06=\x12" + + "\x03\x06\x16%\x03\x06\x1d,\x03\x099#\x03\x09\x10>\x03\x07 \x1e\x03\x08" + + "\x0c<\x03\x08\x0b\x18\x03\x08\x15+\x03\x08,:\x03\x08%\x22\x03\x07\x0a$" + + "\x03\x0b\x1c=\x03\x07+\x08\x03\x0a/\x05\x03\x0a \x07\x03\x0a\x12'\x03" + + "\x09#\x11\x03\x08\x1b\x15\x03\x0a\x06\x01\x03\x09\x1c\x1b\x03\x0922\x03" + + "\x07\x14<\x03\x07\x09\x04\x03\x061\x04\x03\x07\x0e\x01\x03\x0a\x13\x18" + + "\x03\x0a-\x0c\x03\x0a?\x0d\x03\x0a\x09\x0a\x03\x091&\x03\x0a/\x0b\x03" + + "\x08$<\x03\x083\x1d\x03\x08\x0c$\x03\x08\x0d\x07\x03\x08\x0d?\x03\x08" + + "\x0e\x14\x03\x065\x0a\x03\x08\x1a#\x03\x08\x16#\x03\x0702\x03\x07\x03" + + "\x1a\x03\x06(\x1d\x03\x06+\x1b\x03\x06\x0b\x05\x03\x06\x0b\x17\x03\x06" + + "\x0c\x04\x03\x06\x1e\x19\x03\x06+0\x03\x062\x18\x03\x0b\x16\x1e\x03\x0a+" + + "\x16\x03\x0a-?\x03\x0a#:\x03\x0a#\x10\x03\x0a%$\x03\x0a>+\x03\x0a01\x03" + + "\x0a1\x10\x03\x0a\x099\x03\x0a\x0a\x12\x03\x0a\x19\x1f\x03\x0a\x19\x12" + + "\x03\x09*)\x03\x09-\x16\x03\x09.1\x03\x09.2\x03\x09<\x0e\x03\x09> \x03" + + "\x093\x12\x03\x09\x0b\x01\x03\x09\x1c2\x03\x09\x11\x1c\x03\x09\x15%\x03" + + "\x08,&\x03\x08!\x22\x03\x089(\x03\x08\x0b\x1a\x03\x08\x0d2\x03\x08\x0c" + + "\x04\x03\x08\x0c\x06\x03\x08\x0c\x1f\x03\x08\x0c\x0c\x03\x08\x0f\x1f\x03" + + "\x08\x0f\x1d\x03\x08\x00\x14\x03\x08\x03\x14\x03\x08\x06\x16\x03\x08\x1e" + + "#\x03\x08\x11\x11\x03\x08\x10\x18\x03\x08\x14(\x03\x07)\x1e\x03\x07.1" + + "\x03\x07 $\x03\x07 '\x03\x078\x08\x03\x07\x0d0\x03\x07\x0f7\x03\x07\x05#" + + "\x03\x07\x05\x1a\x03\x07\x1a7\x03\x07\x1d-\x03\x07\x17\x10\x03\x06)\x1f" + + "\x03\x062\x0b\x03\x066\x16\x03\x06\x09\x11\x03\x09(\x1e\x03\x07!5\x03" + + "\x0b\x11\x16\x03\x0a/\x04\x03\x0a,\x1a\x03\x0b\x173\x03\x0a,1\x03\x0a/5" + + "\x03\x0a\x221\x03\x0a\x22\x0d\x03\x0a?%\x03\x0a<,\x03\x0a?#\x03\x0a>\x19" + + "\x03\x0a\x08&\x03\x0a\x0b\x0e\x03\x0a\x0c:\x03\x0a\x0c+\x03\x0a\x03\x22" + + "\x03\x0a\x06)\x03\x0a\x11\x10\x03\x0a\x11\x1a\x03\x0a\x17-\x03\x0a\x14(" + + "\x03\x09)\x1e\x03\x09/\x09\x03\x09.\x00\x03\x09,\x07\x03\x09/*\x03\x09-9" + + "\x03\x09\x228\x03\x09%\x09\x03\x09:\x12\x03\x09;\x1d\x03\x09?\x06\x03" + + "\x093%\x03\x096\x05\x03\x096\x08\x03\x097\x02\x03\x09\x07,\x03\x09\x04," + + "\x03\x09\x1f\x16\x03\x09\x11\x03\x03\x09\x11\x12\x03\x09\x168\x03\x08*" + + "\x05\x03\x08/2\x03\x084:\x03\x08\x22+\x03\x08 0\x03\x08&\x0a\x03\x08;" + + "\x10\x03\x08>$\x03\x08>\x18\x03\x0829\x03\x082:\x03\x081,\x03\x081<\x03" + + "\x081\x1c\x03\x087#\x03\x087*\x03\x08\x09'\x03\x08\x00\x1d\x03\x08\x05-" + + "\x03\x08\x1f4\x03\x08\x1d\x04\x03\x08\x16\x0f\x03\x07*7\x03\x07'!\x03" + + "\x07%\x1b\x03\x077\x0c\x03\x07\x0c1\x03\x07\x0c.\x03\x07\x00\x06\x03\x07" + + "\x01\x02\x03\x07\x010\x03\x07\x06=\x03\x07\x01\x03\x03\x07\x01\x13\x03" + + "\x07\x06\x06\x03\x07\x05\x0a\x03\x07\x1f\x09\x03\x07\x17:\x03\x06*1\x03" + + "\x06-\x1d\x03\x06\x223\x03\x062:\x03\x060$\x03\x066\x1e\x03\x064\x12\x03" + + "\x0645\x03\x06\x0b\x00\x03\x06\x0b7\x03\x06\x07\x1f\x03\x06\x15\x12\x03" + + "\x0c\x05\x0f\x03\x0b+\x0b\x03\x0b+-\x03\x06\x16\x1b\x03\x06\x15\x17\x03" + + "\x89\xca\xea\x03\x89\xca\xe8\x03\x0c8\x10\x03\x0c8\x01\x03\x0c8\x0f\x03" + + "\x0d8%\x03\x0d8!\x03\x0c8-\x03\x0c8/\x03\x0c8+\x03\x0c87\x03\x0c85\x03" + + "\x0c9\x09\x03\x0c9\x0d\x03\x0c9\x0f\x03\x0c9\x0b\x03\xcfu\x0c\x03\xcfu" + + "\x0f\x03\xcfu\x0e\x03\xcfu\x09\x03\x0c9\x10\x03\x0d9\x0c\x03\xcf`;\x03" + + "\xcf`>\x03\xcf`9\x03\xcf`8\x03\xcf`7\x03\xcf`*\x03\xcf`-\x03\xcf`,\x03" + + "\x0d\x1b\x1a\x03\x0d\x1b&\x03\x0c=.\x03\x0c=%\x03\x0c>\x1e\x03\x0c>\x14" + + "\x03\x0c?\x06\x03\x0c?\x0b\x03\x0c?\x0c\x03\x0c?\x0d\x03\x0c?\x02\x03" + + "\x0c>\x0f\x03\x0c>\x08\x03\x0c>\x09\x03\x0c>,\x03\x0c>\x0c\x03\x0c?\x13" + + "\x03\x0c?\x16\x03\x0c?\x15\x03\x0c?\x1c\x03\x0c?\x1f\x03\x0c?\x1d\x03" + + "\x0c?\x1a\x03\x0c?\x17\x03\x0c?\x08\x03\x0c?\x09\x03\x0c?\x0e\x03\x0c?" + + "\x04\x03\x0c?\x05\x03\x0c" + + "\x03\x0c=2\x03\x0c=6\x03\x0c<\x07\x03\x0c<\x05\x03\x0e:!\x03\x0e:#\x03" + + "\x0e8\x09\x03\x0e:&\x03\x0e8\x0b\x03\x0e:$\x03\x0e:,\x03\x0e8\x1a\x03" + + "\x0e8\x1e\x03\x0e:*\x03\x0e:7\x03\x0e:5\x03\x0e:;\x03\x0e:\x15\x03\x0e:<" + + "\x03\x0e:4\x03\x0e:'\x03\x0e:-\x03\x0e:%\x03\x0e:?\x03\x0e:=\x03\x0e:)" + + "\x03\x0e:/\x03\xcfs'\x03\x0d=\x0f\x03\x0d+*\x03\x0d99\x03\x0d9;\x03\x0d9" + + "?\x03\x0d)\x0d\x03\x0d(%\x02\x01\x18\x02\x01(\x02\x03'\x02\x03)\x02\x03+" + + "\x02\x03/\x02\x03\x19\x02\x03\x1b\x02\x03\x1f\x03\x0d\x22\x18\x03\x0d" + + "\x22\x1a\x03\x0d\x22'\x03\x0d\x22/\x03\x0d\x223\x03\x0d\x22$\x02\x01\x1e" + + "\x03\x0f$!\x03\x0f87\x03\x0f4\x0e\x03\x0f5\x1d\x03\x06'\x03\x03\x0f\x08" + + "\x18\x03\x0f\x0d\x1b\x03\x0e2=\x03\x0e;\x08\x03\x0e:\x0b\x03\x0e\x06$" + + "\x03\x0e\x0d)\x03\x0e\x16\x1f\x03\x0e\x16\x1b\x03\x0d$\x0a\x03\x05,\x1d" + + "\x03\x0d. \x03\x0d.#\x03\x0c(/\x03\x09%\x02\x03\x0d90\x03\x0d\x0e4\x03" + + "\x0d\x0d\x0f\x03\x0c#\x00\x03\x0c,\x1e\x03\x0c2\x0e\x03\x0c\x01\x17\x03" + + "\x0c\x09:\x03\x0e\x173\x03\x0c\x08\x03\x03\x0c\x11\x07\x03\x0c\x10\x18" + + "\x03\x0c\x1f\x1c\x03\x0c\x19\x0e\x03\x0c\x1a\x1f\x03\x0f0>\x03\x0b->\x03" + + "\x0b<+\x03\x0b8\x13\x03\x0b\x043\x03\x0b\x14\x03\x03\x0b\x16%\x03\x0d" + + "\x22&\x03\x0b\x1a\x1a\x03\x0b\x1a\x04\x03\x0a%9\x03\x0a&2\x03\x0a&0\x03" + + "\x0a!\x1a\x03\x0a!7\x03\x0a5\x10\x03\x0a=4\x03\x0a?\x0e\x03\x0a>\x10\x03" + + "\x0a\x00 \x03\x0a\x0f:\x03\x0a\x0f9\x03\x0a\x0b\x0a\x03\x0a\x17%\x03\x0a" + + "\x1b-\x03\x09-\x1a\x03\x09,4\x03\x09.,\x03\x09)\x09\x03\x096!\x03\x091" + + "\x1f\x03\x093\x16\x03\x0c+\x1f\x03\x098 \x03\x098=\x03\x0c(\x1a\x03\x0c(" + + "\x16\x03\x09\x0a+\x03\x09\x16\x12\x03\x09\x13\x0e\x03\x09\x153\x03\x08)!" + + "\x03\x09\x1a\x01\x03\x09\x18\x01\x03\x08%#\x03\x08>\x22\x03\x08\x05%\x03" + + "\x08\x02*\x03\x08\x15;\x03\x08\x1b7\x03\x0f\x07\x1d\x03\x0f\x04\x03\x03" + + "\x070\x0c\x03\x07;\x0b\x03\x07\x08\x17\x03\x07\x12\x06\x03\x06/-\x03\x06" + + "71\x03\x065+\x03\x06>7\x03\x06\x049\x03\x05+\x1e\x03\x05,\x17\x03\x05 " + + "\x1d\x03\x05\x22\x05\x03\x050\x1d" + +// lookup returns the trie value for the first UTF-8 encoding in s and +// the width in bytes of this encoding. The size will be 0 if s does not +// hold enough bytes to complete the encoding. len(s) must be greater than 0. +func (t *idnaTrie) lookup(s []byte) (v uint16, sz int) { + c0 := s[0] + switch { + case c0 < 0x80: // is ASCII + return idnaValues[c0], 1 + case c0 < 0xC2: + return 0, 1 // Illegal UTF-8: not a starter, not ASCII. + case c0 < 0xE0: // 2-byte UTF-8 + if len(s) < 2 { + return 0, 0 + } + i := idnaIndex[c0] + c1 := s[1] + if c1 < 0x80 || 0xC0 <= c1 { + return 0, 1 // Illegal UTF-8: not a continuation byte. + } + return t.lookupValue(uint32(i), c1), 2 + case c0 < 0xF0: // 3-byte UTF-8 + if len(s) < 3 { + return 0, 0 + } + i := idnaIndex[c0] + c1 := s[1] + if c1 < 0x80 || 0xC0 <= c1 { + return 0, 1 // Illegal UTF-8: not a continuation byte. + } + o := uint32(i)<<6 + uint32(c1) + i = idnaIndex[o] + c2 := s[2] + if c2 < 0x80 || 0xC0 <= c2 { + return 0, 2 // Illegal UTF-8: not a continuation byte. + } + return t.lookupValue(uint32(i), c2), 3 + case c0 < 0xF8: // 4-byte UTF-8 + if len(s) < 4 { + return 0, 0 + } + i := idnaIndex[c0] + c1 := s[1] + if c1 < 0x80 || 0xC0 <= c1 { + return 0, 1 // Illegal UTF-8: not a continuation byte. + } + o := uint32(i)<<6 + uint32(c1) + i = idnaIndex[o] + c2 := s[2] + if c2 < 0x80 || 0xC0 <= c2 { + return 0, 2 // Illegal UTF-8: not a continuation byte. + } + o = uint32(i)<<6 + uint32(c2) + i = idnaIndex[o] + c3 := s[3] + if c3 < 0x80 || 0xC0 <= c3 { + return 0, 3 // Illegal UTF-8: not a continuation byte. + } + return t.lookupValue(uint32(i), c3), 4 + } + // Illegal rune + return 0, 1 +} + +// lookupUnsafe returns the trie value for the first UTF-8 encoding in s. +// s must start with a full and valid UTF-8 encoded rune. +func (t *idnaTrie) lookupUnsafe(s []byte) uint16 { + c0 := s[0] + if c0 < 0x80 { // is ASCII + return idnaValues[c0] + } + i := idnaIndex[c0] + if c0 < 0xE0 { // 2-byte UTF-8 + return t.lookupValue(uint32(i), s[1]) + } + i = idnaIndex[uint32(i)<<6+uint32(s[1])] + if c0 < 0xF0 { // 3-byte UTF-8 + return t.lookupValue(uint32(i), s[2]) + } + i = idnaIndex[uint32(i)<<6+uint32(s[2])] + if c0 < 0xF8 { // 4-byte UTF-8 + return t.lookupValue(uint32(i), s[3]) + } + return 0 +} + +// lookupString returns the trie value for the first UTF-8 encoding in s and +// the width in bytes of this encoding. The size will be 0 if s does not +// hold enough bytes to complete the encoding. len(s) must be greater than 0. +func (t *idnaTrie) lookupString(s string) (v uint16, sz int) { + c0 := s[0] + switch { + case c0 < 0x80: // is ASCII + return idnaValues[c0], 1 + case c0 < 0xC2: + return 0, 1 // Illegal UTF-8: not a starter, not ASCII. + case c0 < 0xE0: // 2-byte UTF-8 + if len(s) < 2 { + return 0, 0 + } + i := idnaIndex[c0] + c1 := s[1] + if c1 < 0x80 || 0xC0 <= c1 { + return 0, 1 // Illegal UTF-8: not a continuation byte. + } + return t.lookupValue(uint32(i), c1), 2 + case c0 < 0xF0: // 3-byte UTF-8 + if len(s) < 3 { + return 0, 0 + } + i := idnaIndex[c0] + c1 := s[1] + if c1 < 0x80 || 0xC0 <= c1 { + return 0, 1 // Illegal UTF-8: not a continuation byte. + } + o := uint32(i)<<6 + uint32(c1) + i = idnaIndex[o] + c2 := s[2] + if c2 < 0x80 || 0xC0 <= c2 { + return 0, 2 // Illegal UTF-8: not a continuation byte. + } + return t.lookupValue(uint32(i), c2), 3 + case c0 < 0xF8: // 4-byte UTF-8 + if len(s) < 4 { + return 0, 0 + } + i := idnaIndex[c0] + c1 := s[1] + if c1 < 0x80 || 0xC0 <= c1 { + return 0, 1 // Illegal UTF-8: not a continuation byte. + } + o := uint32(i)<<6 + uint32(c1) + i = idnaIndex[o] + c2 := s[2] + if c2 < 0x80 || 0xC0 <= c2 { + return 0, 2 // Illegal UTF-8: not a continuation byte. + } + o = uint32(i)<<6 + uint32(c2) + i = idnaIndex[o] + c3 := s[3] + if c3 < 0x80 || 0xC0 <= c3 { + return 0, 3 // Illegal UTF-8: not a continuation byte. + } + return t.lookupValue(uint32(i), c3), 4 + } + // Illegal rune + return 0, 1 +} + +// lookupStringUnsafe returns the trie value for the first UTF-8 encoding in s. +// s must start with a full and valid UTF-8 encoded rune. +func (t *idnaTrie) lookupStringUnsafe(s string) uint16 { + c0 := s[0] + if c0 < 0x80 { // is ASCII + return idnaValues[c0] + } + i := idnaIndex[c0] + if c0 < 0xE0 { // 2-byte UTF-8 + return t.lookupValue(uint32(i), s[1]) + } + i = idnaIndex[uint32(i)<<6+uint32(s[1])] + if c0 < 0xF0 { // 3-byte UTF-8 + return t.lookupValue(uint32(i), s[2]) + } + i = idnaIndex[uint32(i)<<6+uint32(s[2])] + if c0 < 0xF8 { // 4-byte UTF-8 + return t.lookupValue(uint32(i), s[3]) + } + return 0 +} + +// idnaTrie. Total size: 31598 bytes (30.86 KiB). Checksum: d3118eda0d6b5360. +type idnaTrie struct{} + +func newIdnaTrie(i int) *idnaTrie { + return &idnaTrie{} +} + +// lookupValue determines the type of block n and looks up the value for b. +func (t *idnaTrie) lookupValue(n uint32, b byte) uint16 { + switch { + case n < 133: + return uint16(idnaValues[n<<6+uint32(b)]) + default: + n -= 133 + return uint16(idnaSparse.lookup(n, b)) + } +} + +// idnaValues: 135 blocks, 8640 entries, 17280 bytes +// The third block is the zero block. +var idnaValues = [8640]uint16{ + // Block 0x0, offset 0x0 + 0x00: 0x0080, 0x01: 0x0080, 0x02: 0x0080, 0x03: 0x0080, 0x04: 0x0080, 0x05: 0x0080, + 0x06: 0x0080, 0x07: 0x0080, 0x08: 0x0080, 0x09: 0x0080, 0x0a: 0x0080, 0x0b: 0x0080, + 0x0c: 0x0080, 0x0d: 0x0080, 0x0e: 0x0080, 0x0f: 0x0080, 0x10: 0x0080, 0x11: 0x0080, + 0x12: 0x0080, 0x13: 0x0080, 0x14: 0x0080, 0x15: 0x0080, 0x16: 0x0080, 0x17: 0x0080, + 0x18: 0x0080, 0x19: 0x0080, 0x1a: 0x0080, 0x1b: 0x0080, 0x1c: 0x0080, 0x1d: 0x0080, + 0x1e: 0x0080, 0x1f: 0x0080, 0x20: 0x0080, 0x21: 0x0080, 0x22: 0x0080, 0x23: 0x0080, + 0x24: 0x0080, 0x25: 0x0080, 0x26: 0x0080, 0x27: 0x0080, 0x28: 0x0080, 0x29: 0x0080, + 0x2a: 0x0080, 0x2b: 0x0080, 0x2c: 0x0080, 0x2d: 0x0008, 0x2e: 0x0008, 0x2f: 0x0080, + 0x30: 0x0008, 0x31: 0x0008, 0x32: 0x0008, 0x33: 0x0008, 0x34: 0x0008, 0x35: 0x0008, + 0x36: 0x0008, 0x37: 0x0008, 0x38: 0x0008, 0x39: 0x0008, 0x3a: 0x0080, 0x3b: 0x0080, + 0x3c: 0x0080, 0x3d: 0x0080, 0x3e: 0x0080, 0x3f: 0x0080, + // Block 0x1, offset 0x40 + 0x40: 0x0080, 0x41: 0xe105, 0x42: 0xe105, 0x43: 0xe105, 0x44: 0xe105, 0x45: 0xe105, + 0x46: 0xe105, 0x47: 0xe105, 0x48: 0xe105, 0x49: 0xe105, 0x4a: 0xe105, 0x4b: 0xe105, + 0x4c: 0xe105, 0x4d: 0xe105, 0x4e: 0xe105, 0x4f: 0xe105, 0x50: 0xe105, 0x51: 0xe105, + 0x52: 0xe105, 0x53: 0xe105, 0x54: 0xe105, 0x55: 0xe105, 0x56: 0xe105, 0x57: 0xe105, + 0x58: 0xe105, 0x59: 0xe105, 0x5a: 0xe105, 0x5b: 0x0080, 0x5c: 0x0080, 0x5d: 0x0080, + 0x5e: 0x0080, 0x5f: 0x0080, 0x60: 0x0080, 0x61: 0x0008, 0x62: 0x0008, 0x63: 0x0008, + 0x64: 0x0008, 0x65: 0x0008, 0x66: 0x0008, 0x67: 0x0008, 0x68: 0x0008, 0x69: 0x0008, + 0x6a: 0x0008, 0x6b: 0x0008, 0x6c: 0x0008, 0x6d: 0x0008, 0x6e: 0x0008, 0x6f: 0x0008, + 0x70: 0x0008, 0x71: 0x0008, 0x72: 0x0008, 0x73: 0x0008, 0x74: 0x0008, 0x75: 0x0008, + 0x76: 0x0008, 0x77: 0x0008, 0x78: 0x0008, 0x79: 0x0008, 0x7a: 0x0008, 0x7b: 0x0080, + 0x7c: 0x0080, 0x7d: 0x0080, 0x7e: 0x0080, 0x7f: 0x0080, + // Block 0x2, offset 0x80 + // Block 0x3, offset 0xc0 + 0xc0: 0x0040, 0xc1: 0x0040, 0xc2: 0x0040, 0xc3: 0x0040, 0xc4: 0x0040, 0xc5: 0x0040, + 0xc6: 0x0040, 0xc7: 0x0040, 0xc8: 0x0040, 0xc9: 0x0040, 0xca: 0x0040, 0xcb: 0x0040, + 0xcc: 0x0040, 0xcd: 0x0040, 0xce: 0x0040, 0xcf: 0x0040, 0xd0: 0x0040, 0xd1: 0x0040, + 0xd2: 0x0040, 0xd3: 0x0040, 0xd4: 0x0040, 0xd5: 0x0040, 0xd6: 0x0040, 0xd7: 0x0040, + 0xd8: 0x0040, 0xd9: 0x0040, 0xda: 0x0040, 0xdb: 0x0040, 0xdc: 0x0040, 0xdd: 0x0040, + 0xde: 0x0040, 0xdf: 0x0040, 0xe0: 0x000a, 0xe1: 0x0018, 0xe2: 0x0018, 0xe3: 0x0018, + 0xe4: 0x0018, 0xe5: 0x0018, 0xe6: 0x0018, 0xe7: 0x0018, 0xe8: 0x0012, 0xe9: 0x0018, + 0xea: 0x0019, 0xeb: 0x0018, 0xec: 0x0018, 0xed: 0x03c0, 0xee: 0x0018, 0xef: 0x0022, + 0xf0: 0x0018, 0xf1: 0x0018, 0xf2: 0x0029, 0xf3: 0x0031, 0xf4: 0x003a, 0xf5: 0x0005, + 0xf6: 0x0018, 0xf7: 0x0008, 0xf8: 0x0042, 0xf9: 0x0049, 0xfa: 0x0051, 0xfb: 0x0018, + 0xfc: 0x0059, 0xfd: 0x0061, 0xfe: 0x0069, 0xff: 0x0018, + // Block 0x4, offset 0x100 + 0x100: 0xe00d, 0x101: 0x0008, 0x102: 0xe00d, 0x103: 0x0008, 0x104: 0xe00d, 0x105: 0x0008, + 0x106: 0xe00d, 0x107: 0x0008, 0x108: 0xe00d, 0x109: 0x0008, 0x10a: 0xe00d, 0x10b: 0x0008, + 0x10c: 0xe00d, 0x10d: 0x0008, 0x10e: 0xe00d, 0x10f: 0x0008, 0x110: 0xe00d, 0x111: 0x0008, + 0x112: 0xe00d, 0x113: 0x0008, 0x114: 0xe00d, 0x115: 0x0008, 0x116: 0xe00d, 0x117: 0x0008, + 0x118: 0xe00d, 0x119: 0x0008, 0x11a: 0xe00d, 0x11b: 0x0008, 0x11c: 0xe00d, 0x11d: 0x0008, + 0x11e: 0xe00d, 0x11f: 0x0008, 0x120: 0xe00d, 0x121: 0x0008, 0x122: 0xe00d, 0x123: 0x0008, + 0x124: 0xe00d, 0x125: 0x0008, 0x126: 0xe00d, 0x127: 0x0008, 0x128: 0xe00d, 0x129: 0x0008, + 0x12a: 0xe00d, 0x12b: 0x0008, 0x12c: 0xe00d, 0x12d: 0x0008, 0x12e: 0xe00d, 0x12f: 0x0008, + 0x130: 0x0071, 0x131: 0x0008, 0x132: 0x0035, 0x133: 0x004d, 0x134: 0xe00d, 0x135: 0x0008, + 0x136: 0xe00d, 0x137: 0x0008, 0x138: 0x0008, 0x139: 0xe01d, 0x13a: 0x0008, 0x13b: 0xe03d, + 0x13c: 0x0008, 0x13d: 0xe01d, 0x13e: 0x0008, 0x13f: 0x0079, + // Block 0x5, offset 0x140 + 0x140: 0x0079, 0x141: 0xe01d, 0x142: 0x0008, 0x143: 0xe03d, 0x144: 0x0008, 0x145: 0xe01d, + 0x146: 0x0008, 0x147: 0xe07d, 0x148: 0x0008, 0x149: 0x0081, 0x14a: 0xe00d, 0x14b: 0x0008, + 0x14c: 0xe00d, 0x14d: 0x0008, 0x14e: 0xe00d, 0x14f: 0x0008, 0x150: 0xe00d, 0x151: 0x0008, + 0x152: 0xe00d, 0x153: 0x0008, 0x154: 0xe00d, 0x155: 0x0008, 0x156: 0xe00d, 0x157: 0x0008, + 0x158: 0xe00d, 0x159: 0x0008, 0x15a: 0xe00d, 0x15b: 0x0008, 0x15c: 0xe00d, 0x15d: 0x0008, + 0x15e: 0xe00d, 0x15f: 0x0008, 0x160: 0xe00d, 0x161: 0x0008, 0x162: 0xe00d, 0x163: 0x0008, + 0x164: 0xe00d, 0x165: 0x0008, 0x166: 0xe00d, 0x167: 0x0008, 0x168: 0xe00d, 0x169: 0x0008, + 0x16a: 0xe00d, 0x16b: 0x0008, 0x16c: 0xe00d, 0x16d: 0x0008, 0x16e: 0xe00d, 0x16f: 0x0008, + 0x170: 0xe00d, 0x171: 0x0008, 0x172: 0xe00d, 0x173: 0x0008, 0x174: 0xe00d, 0x175: 0x0008, + 0x176: 0xe00d, 0x177: 0x0008, 0x178: 0x0065, 0x179: 0xe01d, 0x17a: 0x0008, 0x17b: 0xe03d, + 0x17c: 0x0008, 0x17d: 0xe01d, 0x17e: 0x0008, 0x17f: 0x0089, + // Block 0x6, offset 0x180 + 0x180: 0x0008, 0x181: 0x007d, 0x182: 0xe00d, 0x183: 0x0008, 0x184: 0xe00d, 0x185: 0x0008, + 0x186: 0x007d, 0x187: 0xe07d, 0x188: 0x0008, 0x189: 0x0095, 0x18a: 0x00ad, 0x18b: 0xe03d, + 0x18c: 0x0008, 0x18d: 0x0008, 0x18e: 0x00c5, 0x18f: 0x00dd, 0x190: 0x00f5, 0x191: 0xe01d, + 0x192: 0x0008, 0x193: 0x010d, 0x194: 0x0125, 0x195: 0x0008, 0x196: 0x013d, 0x197: 0x013d, + 0x198: 0xe00d, 0x199: 0x0008, 0x19a: 0x0008, 0x19b: 0x0008, 0x19c: 0x010d, 0x19d: 0x0155, + 0x19e: 0x0008, 0x19f: 0x016d, 0x1a0: 0xe00d, 0x1a1: 0x0008, 0x1a2: 0xe00d, 0x1a3: 0x0008, + 0x1a4: 0xe00d, 0x1a5: 0x0008, 0x1a6: 0x0185, 0x1a7: 0xe07d, 0x1a8: 0x0008, 0x1a9: 0x019d, + 0x1aa: 0x0008, 0x1ab: 0x0008, 0x1ac: 0xe00d, 0x1ad: 0x0008, 0x1ae: 0x0185, 0x1af: 0xe0fd, + 0x1b0: 0x0008, 0x1b1: 0x01b5, 0x1b2: 0x01cd, 0x1b3: 0xe03d, 0x1b4: 0x0008, 0x1b5: 0xe01d, + 0x1b6: 0x0008, 0x1b7: 0x01e5, 0x1b8: 0xe00d, 0x1b9: 0x0008, 0x1ba: 0x0008, 0x1bb: 0x0008, + 0x1bc: 0xe00d, 0x1bd: 0x0008, 0x1be: 0x0008, 0x1bf: 0x0008, + // Block 0x7, offset 0x1c0 + 0x1c0: 0x0008, 0x1c1: 0x0008, 0x1c2: 0x0008, 0x1c3: 0x0008, 0x1c4: 0x0091, 0x1c5: 0x0091, + 0x1c6: 0x0091, 0x1c7: 0x01fd, 0x1c8: 0x0215, 0x1c9: 0x022d, 0x1ca: 0x0245, 0x1cb: 0x025d, + 0x1cc: 0x0275, 0x1cd: 0xe01d, 0x1ce: 0x0008, 0x1cf: 0xe0fd, 0x1d0: 0x0008, 0x1d1: 0xe01d, + 0x1d2: 0x0008, 0x1d3: 0xe03d, 0x1d4: 0x0008, 0x1d5: 0xe01d, 0x1d6: 0x0008, 0x1d7: 0xe07d, + 0x1d8: 0x0008, 0x1d9: 0xe01d, 0x1da: 0x0008, 0x1db: 0xe03d, 0x1dc: 0x0008, 0x1dd: 0x0008, + 0x1de: 0xe00d, 0x1df: 0x0008, 0x1e0: 0xe00d, 0x1e1: 0x0008, 0x1e2: 0xe00d, 0x1e3: 0x0008, + 0x1e4: 0xe00d, 0x1e5: 0x0008, 0x1e6: 0xe00d, 0x1e7: 0x0008, 0x1e8: 0xe00d, 0x1e9: 0x0008, + 0x1ea: 0xe00d, 0x1eb: 0x0008, 0x1ec: 0xe00d, 0x1ed: 0x0008, 0x1ee: 0xe00d, 0x1ef: 0x0008, + 0x1f0: 0x0008, 0x1f1: 0x028d, 0x1f2: 0x02a5, 0x1f3: 0x02bd, 0x1f4: 0xe00d, 0x1f5: 0x0008, + 0x1f6: 0x02d5, 0x1f7: 0x02ed, 0x1f8: 0xe00d, 0x1f9: 0x0008, 0x1fa: 0xe00d, 0x1fb: 0x0008, + 0x1fc: 0xe00d, 0x1fd: 0x0008, 0x1fe: 0xe00d, 0x1ff: 0x0008, + // Block 0x8, offset 0x200 + 0x200: 0xe00d, 0x201: 0x0008, 0x202: 0xe00d, 0x203: 0x0008, 0x204: 0xe00d, 0x205: 0x0008, + 0x206: 0xe00d, 0x207: 0x0008, 0x208: 0xe00d, 0x209: 0x0008, 0x20a: 0xe00d, 0x20b: 0x0008, + 0x20c: 0xe00d, 0x20d: 0x0008, 0x20e: 0xe00d, 0x20f: 0x0008, 0x210: 0xe00d, 0x211: 0x0008, + 0x212: 0xe00d, 0x213: 0x0008, 0x214: 0xe00d, 0x215: 0x0008, 0x216: 0xe00d, 0x217: 0x0008, + 0x218: 0xe00d, 0x219: 0x0008, 0x21a: 0xe00d, 0x21b: 0x0008, 0x21c: 0xe00d, 0x21d: 0x0008, + 0x21e: 0xe00d, 0x21f: 0x0008, 0x220: 0x0305, 0x221: 0x0008, 0x222: 0xe00d, 0x223: 0x0008, + 0x224: 0xe00d, 0x225: 0x0008, 0x226: 0xe00d, 0x227: 0x0008, 0x228: 0xe00d, 0x229: 0x0008, + 0x22a: 0xe00d, 0x22b: 0x0008, 0x22c: 0xe00d, 0x22d: 0x0008, 0x22e: 0xe00d, 0x22f: 0x0008, + 0x230: 0xe00d, 0x231: 0x0008, 0x232: 0xe00d, 0x233: 0x0008, 0x234: 0x0008, 0x235: 0x0008, + 0x236: 0x0008, 0x237: 0x0008, 0x238: 0x0008, 0x239: 0x0008, 0x23a: 0x0099, 0x23b: 0xe03d, + 0x23c: 0x0008, 0x23d: 0x031d, 0x23e: 0x00a1, 0x23f: 0x0008, + // Block 0x9, offset 0x240 + 0x240: 0x0008, 0x241: 0x0008, 0x242: 0x0018, 0x243: 0x0018, 0x244: 0x0018, 0x245: 0x0018, + 0x246: 0x0008, 0x247: 0x0008, 0x248: 0x0008, 0x249: 0x0008, 0x24a: 0x0008, 0x24b: 0x0008, + 0x24c: 0x0008, 0x24d: 0x0008, 0x24e: 0x0008, 0x24f: 0x0008, 0x250: 0x0008, 0x251: 0x0008, + 0x252: 0x0018, 0x253: 0x0018, 0x254: 0x0018, 0x255: 0x0018, 0x256: 0x0018, 0x257: 0x0018, + 0x258: 0x00d2, 0x259: 0x00da, 0x25a: 0x00e2, 0x25b: 0x00ea, 0x25c: 0x00f2, 0x25d: 0x00fa, + 0x25e: 0x0018, 0x25f: 0x0018, 0x260: 0x03ad, 0x261: 0x0101, 0x262: 0x0089, 0x263: 0x0109, + 0x264: 0x03c5, 0x265: 0x0018, 0x266: 0x0018, 0x267: 0x0018, 0x268: 0x0018, 0x269: 0x0018, + 0x26a: 0x0018, 0x26b: 0x0018, 0x26c: 0x0008, 0x26d: 0x0018, 0x26e: 0x0008, 0x26f: 0x0018, + 0x270: 0x0018, 0x271: 0x0018, 0x272: 0x0018, 0x273: 0x0018, 0x274: 0x0018, 0x275: 0x0018, + 0x276: 0x0018, 0x277: 0x0018, 0x278: 0x0018, 0x279: 0x0018, 0x27a: 0x0018, 0x27b: 0x0018, + 0x27c: 0x0018, 0x27d: 0x0018, 0x27e: 0x0018, 0x27f: 0x0018, + // Block 0xa, offset 0x280 + 0x280: 0x03dd, 0x281: 0x03dd, 0x282: 0x3308, 0x283: 0x03f5, 0x284: 0x0111, 0x285: 0x040d, + 0x286: 0x3308, 0x287: 0x3308, 0x288: 0x3308, 0x289: 0x3308, 0x28a: 0x3308, 0x28b: 0x3308, + 0x28c: 0x3308, 0x28d: 0x3308, 0x28e: 0x3308, 0x28f: 0x33c0, 0x290: 0x3308, 0x291: 0x3308, + 0x292: 0x3308, 0x293: 0x3308, 0x294: 0x3308, 0x295: 0x3308, 0x296: 0x3308, 0x297: 0x3308, + 0x298: 0x3308, 0x299: 0x3308, 0x29a: 0x3308, 0x29b: 0x3308, 0x29c: 0x3308, 0x29d: 0x3308, + 0x29e: 0x3308, 0x29f: 0x3308, 0x2a0: 0x3308, 0x2a1: 0x3308, 0x2a2: 0x3308, 0x2a3: 0x3308, + 0x2a4: 0x3308, 0x2a5: 0x3308, 0x2a6: 0x3308, 0x2a7: 0x3308, 0x2a8: 0x3308, 0x2a9: 0x3308, + 0x2aa: 0x3308, 0x2ab: 0x3308, 0x2ac: 0x3308, 0x2ad: 0x3308, 0x2ae: 0x3308, 0x2af: 0x3308, + 0x2b0: 0xe00d, 0x2b1: 0x0008, 0x2b2: 0xe00d, 0x2b3: 0x0008, 0x2b4: 0x0425, 0x2b5: 0x0008, + 0x2b6: 0xe00d, 0x2b7: 0x0008, 0x2b8: 0x0040, 0x2b9: 0x0040, 0x2ba: 0x011a, 0x2bb: 0x0008, + 0x2bc: 0x0008, 0x2bd: 0x0008, 0x2be: 0x0122, 0x2bf: 0x043d, + // Block 0xb, offset 0x2c0 + 0x2c0: 0x0040, 0x2c1: 0x0040, 0x2c2: 0x0040, 0x2c3: 0x0040, 0x2c4: 0x003a, 0x2c5: 0x012a, + 0x2c6: 0xe155, 0x2c7: 0x0455, 0x2c8: 0xe12d, 0x2c9: 0xe13d, 0x2ca: 0xe12d, 0x2cb: 0x0040, + 0x2cc: 0x03dd, 0x2cd: 0x0040, 0x2ce: 0x046d, 0x2cf: 0x0485, 0x2d0: 0x0008, 0x2d1: 0xe105, + 0x2d2: 0xe105, 0x2d3: 0xe105, 0x2d4: 0xe105, 0x2d5: 0xe105, 0x2d6: 0xe105, 0x2d7: 0xe105, + 0x2d8: 0xe105, 0x2d9: 0xe105, 0x2da: 0xe105, 0x2db: 0xe105, 0x2dc: 0xe105, 0x2dd: 0xe105, + 0x2de: 0xe105, 0x2df: 0xe105, 0x2e0: 0x049d, 0x2e1: 0x049d, 0x2e2: 0x0040, 0x2e3: 0x049d, + 0x2e4: 0x049d, 0x2e5: 0x049d, 0x2e6: 0x049d, 0x2e7: 0x049d, 0x2e8: 0x049d, 0x2e9: 0x049d, + 0x2ea: 0x049d, 0x2eb: 0x049d, 0x2ec: 0x0008, 0x2ed: 0x0008, 0x2ee: 0x0008, 0x2ef: 0x0008, + 0x2f0: 0x0008, 0x2f1: 0x0008, 0x2f2: 0x0008, 0x2f3: 0x0008, 0x2f4: 0x0008, 0x2f5: 0x0008, + 0x2f6: 0x0008, 0x2f7: 0x0008, 0x2f8: 0x0008, 0x2f9: 0x0008, 0x2fa: 0x0008, 0x2fb: 0x0008, + 0x2fc: 0x0008, 0x2fd: 0x0008, 0x2fe: 0x0008, 0x2ff: 0x0008, + // Block 0xc, offset 0x300 + 0x300: 0x0008, 0x301: 0x0008, 0x302: 0xe00f, 0x303: 0x0008, 0x304: 0x0008, 0x305: 0x0008, + 0x306: 0x0008, 0x307: 0x0008, 0x308: 0x0008, 0x309: 0x0008, 0x30a: 0x0008, 0x30b: 0x0008, + 0x30c: 0x0008, 0x30d: 0x0008, 0x30e: 0x0008, 0x30f: 0xe0c5, 0x310: 0x04b5, 0x311: 0x04cd, + 0x312: 0xe0bd, 0x313: 0xe0f5, 0x314: 0xe0fd, 0x315: 0xe09d, 0x316: 0xe0b5, 0x317: 0x0008, + 0x318: 0xe00d, 0x319: 0x0008, 0x31a: 0xe00d, 0x31b: 0x0008, 0x31c: 0xe00d, 0x31d: 0x0008, + 0x31e: 0xe00d, 0x31f: 0x0008, 0x320: 0xe00d, 0x321: 0x0008, 0x322: 0xe00d, 0x323: 0x0008, + 0x324: 0xe00d, 0x325: 0x0008, 0x326: 0xe00d, 0x327: 0x0008, 0x328: 0xe00d, 0x329: 0x0008, + 0x32a: 0xe00d, 0x32b: 0x0008, 0x32c: 0xe00d, 0x32d: 0x0008, 0x32e: 0xe00d, 0x32f: 0x0008, + 0x330: 0x04e5, 0x331: 0xe185, 0x332: 0xe18d, 0x333: 0x0008, 0x334: 0x04fd, 0x335: 0x03dd, + 0x336: 0x0018, 0x337: 0xe07d, 0x338: 0x0008, 0x339: 0xe1d5, 0x33a: 0xe00d, 0x33b: 0x0008, + 0x33c: 0x0008, 0x33d: 0x0515, 0x33e: 0x052d, 0x33f: 0x052d, + // Block 0xd, offset 0x340 + 0x340: 0x0008, 0x341: 0x0008, 0x342: 0x0008, 0x343: 0x0008, 0x344: 0x0008, 0x345: 0x0008, + 0x346: 0x0008, 0x347: 0x0008, 0x348: 0x0008, 0x349: 0x0008, 0x34a: 0x0008, 0x34b: 0x0008, + 0x34c: 0x0008, 0x34d: 0x0008, 0x34e: 0x0008, 0x34f: 0x0008, 0x350: 0x0008, 0x351: 0x0008, + 0x352: 0x0008, 0x353: 0x0008, 0x354: 0x0008, 0x355: 0x0008, 0x356: 0x0008, 0x357: 0x0008, + 0x358: 0x0008, 0x359: 0x0008, 0x35a: 0x0008, 0x35b: 0x0008, 0x35c: 0x0008, 0x35d: 0x0008, + 0x35e: 0x0008, 0x35f: 0x0008, 0x360: 0xe00d, 0x361: 0x0008, 0x362: 0xe00d, 0x363: 0x0008, + 0x364: 0xe00d, 0x365: 0x0008, 0x366: 0xe00d, 0x367: 0x0008, 0x368: 0xe00d, 0x369: 0x0008, + 0x36a: 0xe00d, 0x36b: 0x0008, 0x36c: 0xe00d, 0x36d: 0x0008, 0x36e: 0xe00d, 0x36f: 0x0008, + 0x370: 0xe00d, 0x371: 0x0008, 0x372: 0xe00d, 0x373: 0x0008, 0x374: 0xe00d, 0x375: 0x0008, + 0x376: 0xe00d, 0x377: 0x0008, 0x378: 0xe00d, 0x379: 0x0008, 0x37a: 0xe00d, 0x37b: 0x0008, + 0x37c: 0xe00d, 0x37d: 0x0008, 0x37e: 0xe00d, 0x37f: 0x0008, + // Block 0xe, offset 0x380 + 0x380: 0xe00d, 0x381: 0x0008, 0x382: 0x0018, 0x383: 0x3308, 0x384: 0x3308, 0x385: 0x3308, + 0x386: 0x3308, 0x387: 0x3308, 0x388: 0x3318, 0x389: 0x3318, 0x38a: 0xe00d, 0x38b: 0x0008, + 0x38c: 0xe00d, 0x38d: 0x0008, 0x38e: 0xe00d, 0x38f: 0x0008, 0x390: 0xe00d, 0x391: 0x0008, + 0x392: 0xe00d, 0x393: 0x0008, 0x394: 0xe00d, 0x395: 0x0008, 0x396: 0xe00d, 0x397: 0x0008, + 0x398: 0xe00d, 0x399: 0x0008, 0x39a: 0xe00d, 0x39b: 0x0008, 0x39c: 0xe00d, 0x39d: 0x0008, + 0x39e: 0xe00d, 0x39f: 0x0008, 0x3a0: 0xe00d, 0x3a1: 0x0008, 0x3a2: 0xe00d, 0x3a3: 0x0008, + 0x3a4: 0xe00d, 0x3a5: 0x0008, 0x3a6: 0xe00d, 0x3a7: 0x0008, 0x3a8: 0xe00d, 0x3a9: 0x0008, + 0x3aa: 0xe00d, 0x3ab: 0x0008, 0x3ac: 0xe00d, 0x3ad: 0x0008, 0x3ae: 0xe00d, 0x3af: 0x0008, + 0x3b0: 0xe00d, 0x3b1: 0x0008, 0x3b2: 0xe00d, 0x3b3: 0x0008, 0x3b4: 0xe00d, 0x3b5: 0x0008, + 0x3b6: 0xe00d, 0x3b7: 0x0008, 0x3b8: 0xe00d, 0x3b9: 0x0008, 0x3ba: 0xe00d, 0x3bb: 0x0008, + 0x3bc: 0xe00d, 0x3bd: 0x0008, 0x3be: 0xe00d, 0x3bf: 0x0008, + // Block 0xf, offset 0x3c0 + 0x3c0: 0x0040, 0x3c1: 0xe01d, 0x3c2: 0x0008, 0x3c3: 0xe03d, 0x3c4: 0x0008, 0x3c5: 0xe01d, + 0x3c6: 0x0008, 0x3c7: 0xe07d, 0x3c8: 0x0008, 0x3c9: 0xe01d, 0x3ca: 0x0008, 0x3cb: 0xe03d, + 0x3cc: 0x0008, 0x3cd: 0xe01d, 0x3ce: 0x0008, 0x3cf: 0x0008, 0x3d0: 0xe00d, 0x3d1: 0x0008, + 0x3d2: 0xe00d, 0x3d3: 0x0008, 0x3d4: 0xe00d, 0x3d5: 0x0008, 0x3d6: 0xe00d, 0x3d7: 0x0008, + 0x3d8: 0xe00d, 0x3d9: 0x0008, 0x3da: 0xe00d, 0x3db: 0x0008, 0x3dc: 0xe00d, 0x3dd: 0x0008, + 0x3de: 0xe00d, 0x3df: 0x0008, 0x3e0: 0xe00d, 0x3e1: 0x0008, 0x3e2: 0xe00d, 0x3e3: 0x0008, + 0x3e4: 0xe00d, 0x3e5: 0x0008, 0x3e6: 0xe00d, 0x3e7: 0x0008, 0x3e8: 0xe00d, 0x3e9: 0x0008, + 0x3ea: 0xe00d, 0x3eb: 0x0008, 0x3ec: 0xe00d, 0x3ed: 0x0008, 0x3ee: 0xe00d, 0x3ef: 0x0008, + 0x3f0: 0xe00d, 0x3f1: 0x0008, 0x3f2: 0xe00d, 0x3f3: 0x0008, 0x3f4: 0xe00d, 0x3f5: 0x0008, + 0x3f6: 0xe00d, 0x3f7: 0x0008, 0x3f8: 0xe00d, 0x3f9: 0x0008, 0x3fa: 0xe00d, 0x3fb: 0x0008, + 0x3fc: 0xe00d, 0x3fd: 0x0008, 0x3fe: 0xe00d, 0x3ff: 0x0008, + // Block 0x10, offset 0x400 + 0x400: 0xe00d, 0x401: 0x0008, 0x402: 0xe00d, 0x403: 0x0008, 0x404: 0xe00d, 0x405: 0x0008, + 0x406: 0xe00d, 0x407: 0x0008, 0x408: 0xe00d, 0x409: 0x0008, 0x40a: 0xe00d, 0x40b: 0x0008, + 0x40c: 0xe00d, 0x40d: 0x0008, 0x40e: 0xe00d, 0x40f: 0x0008, 0x410: 0xe00d, 0x411: 0x0008, + 0x412: 0xe00d, 0x413: 0x0008, 0x414: 0xe00d, 0x415: 0x0008, 0x416: 0xe00d, 0x417: 0x0008, + 0x418: 0xe00d, 0x419: 0x0008, 0x41a: 0xe00d, 0x41b: 0x0008, 0x41c: 0xe00d, 0x41d: 0x0008, + 0x41e: 0xe00d, 0x41f: 0x0008, 0x420: 0xe00d, 0x421: 0x0008, 0x422: 0xe00d, 0x423: 0x0008, + 0x424: 0xe00d, 0x425: 0x0008, 0x426: 0xe00d, 0x427: 0x0008, 0x428: 0xe00d, 0x429: 0x0008, + 0x42a: 0xe00d, 0x42b: 0x0008, 0x42c: 0xe00d, 0x42d: 0x0008, 0x42e: 0xe00d, 0x42f: 0x0008, + 0x430: 0x0040, 0x431: 0x03f5, 0x432: 0x03f5, 0x433: 0x03f5, 0x434: 0x03f5, 0x435: 0x03f5, + 0x436: 0x03f5, 0x437: 0x03f5, 0x438: 0x03f5, 0x439: 0x03f5, 0x43a: 0x03f5, 0x43b: 0x03f5, + 0x43c: 0x03f5, 0x43d: 0x03f5, 0x43e: 0x03f5, 0x43f: 0x03f5, + // Block 0x11, offset 0x440 + 0x440: 0x0840, 0x441: 0x0840, 0x442: 0x0840, 0x443: 0x0840, 0x444: 0x0840, 0x445: 0x0840, + 0x446: 0x0018, 0x447: 0x0018, 0x448: 0x0818, 0x449: 0x0018, 0x44a: 0x0018, 0x44b: 0x0818, + 0x44c: 0x0018, 0x44d: 0x0818, 0x44e: 0x0018, 0x44f: 0x0018, 0x450: 0x3308, 0x451: 0x3308, + 0x452: 0x3308, 0x453: 0x3308, 0x454: 0x3308, 0x455: 0x3308, 0x456: 0x3308, 0x457: 0x3308, + 0x458: 0x3308, 0x459: 0x3308, 0x45a: 0x3308, 0x45b: 0x0818, 0x45c: 0x0b40, 0x45d: 0x0818, + 0x45e: 0x0818, 0x45f: 0x0818, 0x460: 0x0a08, 0x461: 0x0808, 0x462: 0x0c08, 0x463: 0x0c08, + 0x464: 0x0c08, 0x465: 0x0c08, 0x466: 0x0a08, 0x467: 0x0c08, 0x468: 0x0a08, 0x469: 0x0c08, + 0x46a: 0x0a08, 0x46b: 0x0a08, 0x46c: 0x0a08, 0x46d: 0x0a08, 0x46e: 0x0a08, 0x46f: 0x0c08, + 0x470: 0x0c08, 0x471: 0x0c08, 0x472: 0x0c08, 0x473: 0x0a08, 0x474: 0x0a08, 0x475: 0x0a08, + 0x476: 0x0a08, 0x477: 0x0a08, 0x478: 0x0a08, 0x479: 0x0a08, 0x47a: 0x0a08, 0x47b: 0x0a08, + 0x47c: 0x0a08, 0x47d: 0x0a08, 0x47e: 0x0a08, 0x47f: 0x0a08, + // Block 0x12, offset 0x480 + 0x480: 0x0818, 0x481: 0x0a08, 0x482: 0x0a08, 0x483: 0x0a08, 0x484: 0x0a08, 0x485: 0x0a08, + 0x486: 0x0a08, 0x487: 0x0a08, 0x488: 0x0c08, 0x489: 0x0a08, 0x48a: 0x0a08, 0x48b: 0x3308, + 0x48c: 0x3308, 0x48d: 0x3308, 0x48e: 0x3308, 0x48f: 0x3308, 0x490: 0x3308, 0x491: 0x3308, + 0x492: 0x3308, 0x493: 0x3308, 0x494: 0x3308, 0x495: 0x3308, 0x496: 0x3308, 0x497: 0x3308, + 0x498: 0x3308, 0x499: 0x3308, 0x49a: 0x3308, 0x49b: 0x3308, 0x49c: 0x3308, 0x49d: 0x3308, + 0x49e: 0x3308, 0x49f: 0x3308, 0x4a0: 0x0808, 0x4a1: 0x0808, 0x4a2: 0x0808, 0x4a3: 0x0808, + 0x4a4: 0x0808, 0x4a5: 0x0808, 0x4a6: 0x0808, 0x4a7: 0x0808, 0x4a8: 0x0808, 0x4a9: 0x0808, + 0x4aa: 0x0018, 0x4ab: 0x0818, 0x4ac: 0x0818, 0x4ad: 0x0818, 0x4ae: 0x0a08, 0x4af: 0x0a08, + 0x4b0: 0x3308, 0x4b1: 0x0c08, 0x4b2: 0x0c08, 0x4b3: 0x0c08, 0x4b4: 0x0808, 0x4b5: 0x0139, + 0x4b6: 0x0141, 0x4b7: 0x0149, 0x4b8: 0x0151, 0x4b9: 0x0a08, 0x4ba: 0x0a08, 0x4bb: 0x0a08, + 0x4bc: 0x0a08, 0x4bd: 0x0a08, 0x4be: 0x0a08, 0x4bf: 0x0a08, + // Block 0x13, offset 0x4c0 + 0x4c0: 0x0c08, 0x4c1: 0x0a08, 0x4c2: 0x0a08, 0x4c3: 0x0c08, 0x4c4: 0x0c08, 0x4c5: 0x0c08, + 0x4c6: 0x0c08, 0x4c7: 0x0c08, 0x4c8: 0x0c08, 0x4c9: 0x0c08, 0x4ca: 0x0c08, 0x4cb: 0x0c08, + 0x4cc: 0x0a08, 0x4cd: 0x0c08, 0x4ce: 0x0a08, 0x4cf: 0x0c08, 0x4d0: 0x0a08, 0x4d1: 0x0a08, + 0x4d2: 0x0c08, 0x4d3: 0x0c08, 0x4d4: 0x0818, 0x4d5: 0x0c08, 0x4d6: 0x3308, 0x4d7: 0x3308, + 0x4d8: 0x3308, 0x4d9: 0x3308, 0x4da: 0x3308, 0x4db: 0x3308, 0x4dc: 0x3308, 0x4dd: 0x0840, + 0x4de: 0x0018, 0x4df: 0x3308, 0x4e0: 0x3308, 0x4e1: 0x3308, 0x4e2: 0x3308, 0x4e3: 0x3308, + 0x4e4: 0x3308, 0x4e5: 0x0808, 0x4e6: 0x0808, 0x4e7: 0x3308, 0x4e8: 0x3308, 0x4e9: 0x0018, + 0x4ea: 0x3308, 0x4eb: 0x3308, 0x4ec: 0x3308, 0x4ed: 0x3308, 0x4ee: 0x0c08, 0x4ef: 0x0c08, + 0x4f0: 0x0008, 0x4f1: 0x0008, 0x4f2: 0x0008, 0x4f3: 0x0008, 0x4f4: 0x0008, 0x4f5: 0x0008, + 0x4f6: 0x0008, 0x4f7: 0x0008, 0x4f8: 0x0008, 0x4f9: 0x0008, 0x4fa: 0x0a08, 0x4fb: 0x0a08, + 0x4fc: 0x0a08, 0x4fd: 0x0808, 0x4fe: 0x0808, 0x4ff: 0x0a08, + // Block 0x14, offset 0x500 + 0x500: 0x0818, 0x501: 0x0818, 0x502: 0x0818, 0x503: 0x0818, 0x504: 0x0818, 0x505: 0x0818, + 0x506: 0x0818, 0x507: 0x0818, 0x508: 0x0818, 0x509: 0x0818, 0x50a: 0x0818, 0x50b: 0x0818, + 0x50c: 0x0818, 0x50d: 0x0818, 0x50e: 0x0040, 0x50f: 0x0b40, 0x510: 0x0c08, 0x511: 0x3308, + 0x512: 0x0a08, 0x513: 0x0a08, 0x514: 0x0a08, 0x515: 0x0c08, 0x516: 0x0c08, 0x517: 0x0c08, + 0x518: 0x0c08, 0x519: 0x0c08, 0x51a: 0x0a08, 0x51b: 0x0a08, 0x51c: 0x0a08, 0x51d: 0x0a08, + 0x51e: 0x0c08, 0x51f: 0x0a08, 0x520: 0x0a08, 0x521: 0x0a08, 0x522: 0x0a08, 0x523: 0x0a08, + 0x524: 0x0a08, 0x525: 0x0a08, 0x526: 0x0a08, 0x527: 0x0a08, 0x528: 0x0c08, 0x529: 0x0a08, + 0x52a: 0x0c08, 0x52b: 0x0a08, 0x52c: 0x0c08, 0x52d: 0x0a08, 0x52e: 0x0a08, 0x52f: 0x0c08, + 0x530: 0x3308, 0x531: 0x3308, 0x532: 0x3308, 0x533: 0x3308, 0x534: 0x3308, 0x535: 0x3308, + 0x536: 0x3308, 0x537: 0x3308, 0x538: 0x3308, 0x539: 0x3308, 0x53a: 0x3308, 0x53b: 0x3308, + 0x53c: 0x3308, 0x53d: 0x3308, 0x53e: 0x3308, 0x53f: 0x3308, + // Block 0x15, offset 0x540 + 0x540: 0x0c08, 0x541: 0x0a08, 0x542: 0x0a08, 0x543: 0x0a08, 0x544: 0x0a08, 0x545: 0x0a08, + 0x546: 0x0c08, 0x547: 0x0c08, 0x548: 0x0a08, 0x549: 0x0c08, 0x54a: 0x0a08, 0x54b: 0x0a08, + 0x54c: 0x0a08, 0x54d: 0x0a08, 0x54e: 0x0a08, 0x54f: 0x0a08, 0x550: 0x0a08, 0x551: 0x0a08, + 0x552: 0x0a08, 0x553: 0x0a08, 0x554: 0x0c08, 0x555: 0x0a08, 0x556: 0x0c08, 0x557: 0x0c08, + 0x558: 0x0c08, 0x559: 0x3308, 0x55a: 0x3308, 0x55b: 0x3308, 0x55c: 0x0040, 0x55d: 0x0040, + 0x55e: 0x0818, 0x55f: 0x0040, 0x560: 0x0a08, 0x561: 0x0808, 0x562: 0x0a08, 0x563: 0x0a08, + 0x564: 0x0a08, 0x565: 0x0a08, 0x566: 0x0808, 0x567: 0x0c08, 0x568: 0x0a08, 0x569: 0x0c08, + 0x56a: 0x0c08, 0x56b: 0x0040, 0x56c: 0x0040, 0x56d: 0x0040, 0x56e: 0x0040, 0x56f: 0x0040, + 0x570: 0x0c08, 0x571: 0x0c08, 0x572: 0x0c08, 0x573: 0x0c08, 0x574: 0x0c08, 0x575: 0x0c08, + 0x576: 0x0c08, 0x577: 0x0c08, 0x578: 0x0c08, 0x579: 0x0c08, 0x57a: 0x0c08, 0x57b: 0x0c08, + 0x57c: 0x0c08, 0x57d: 0x0c08, 0x57e: 0x0c08, 0x57f: 0x0c08, + // Block 0x16, offset 0x580 + 0x580: 0x0c08, 0x581: 0x0c08, 0x582: 0x0c08, 0x583: 0x0808, 0x584: 0x0808, 0x585: 0x0808, + 0x586: 0x0a08, 0x587: 0x0808, 0x588: 0x0818, 0x589: 0x0a08, 0x58a: 0x0a08, 0x58b: 0x0a08, + 0x58c: 0x0a08, 0x58d: 0x0a08, 0x58e: 0x0c08, 0x58f: 0x0040, 0x590: 0x0840, 0x591: 0x0840, + 0x592: 0x0040, 0x593: 0x0040, 0x594: 0x0040, 0x595: 0x0040, 0x596: 0x0040, 0x597: 0x0040, + 0x598: 0x3308, 0x599: 0x3308, 0x59a: 0x3308, 0x59b: 0x3308, 0x59c: 0x3308, 0x59d: 0x3308, + 0x59e: 0x3308, 0x59f: 0x3308, 0x5a0: 0x0a08, 0x5a1: 0x0a08, 0x5a2: 0x0a08, 0x5a3: 0x0a08, + 0x5a4: 0x0a08, 0x5a5: 0x0a08, 0x5a6: 0x0a08, 0x5a7: 0x0a08, 0x5a8: 0x0a08, 0x5a9: 0x0a08, + 0x5aa: 0x0c08, 0x5ab: 0x0c08, 0x5ac: 0x0c08, 0x5ad: 0x0808, 0x5ae: 0x0c08, 0x5af: 0x0a08, + 0x5b0: 0x0a08, 0x5b1: 0x0c08, 0x5b2: 0x0c08, 0x5b3: 0x0a08, 0x5b4: 0x0a08, 0x5b5: 0x0a08, + 0x5b6: 0x0a08, 0x5b7: 0x0a08, 0x5b8: 0x0a08, 0x5b9: 0x0c08, 0x5ba: 0x0a08, 0x5bb: 0x0a08, + 0x5bc: 0x0a08, 0x5bd: 0x0a08, 0x5be: 0x0a08, 0x5bf: 0x0a08, + // Block 0x17, offset 0x5c0 + 0x5c0: 0x3008, 0x5c1: 0x3308, 0x5c2: 0x3308, 0x5c3: 0x3308, 0x5c4: 0x3308, 0x5c5: 0x3308, + 0x5c6: 0x3308, 0x5c7: 0x3308, 0x5c8: 0x3308, 0x5c9: 0x3008, 0x5ca: 0x3008, 0x5cb: 0x3008, + 0x5cc: 0x3008, 0x5cd: 0x3b08, 0x5ce: 0x3008, 0x5cf: 0x3008, 0x5d0: 0x0008, 0x5d1: 0x3308, + 0x5d2: 0x3308, 0x5d3: 0x3308, 0x5d4: 0x3308, 0x5d5: 0x3308, 0x5d6: 0x3308, 0x5d7: 0x3308, + 0x5d8: 0x0159, 0x5d9: 0x0161, 0x5da: 0x0169, 0x5db: 0x0171, 0x5dc: 0x0179, 0x5dd: 0x0181, + 0x5de: 0x0189, 0x5df: 0x0191, 0x5e0: 0x0008, 0x5e1: 0x0008, 0x5e2: 0x3308, 0x5e3: 0x3308, + 0x5e4: 0x0018, 0x5e5: 0x0018, 0x5e6: 0x0008, 0x5e7: 0x0008, 0x5e8: 0x0008, 0x5e9: 0x0008, + 0x5ea: 0x0008, 0x5eb: 0x0008, 0x5ec: 0x0008, 0x5ed: 0x0008, 0x5ee: 0x0008, 0x5ef: 0x0008, + 0x5f0: 0x0018, 0x5f1: 0x0008, 0x5f2: 0x0008, 0x5f3: 0x0008, 0x5f4: 0x0008, 0x5f5: 0x0008, + 0x5f6: 0x0008, 0x5f7: 0x0008, 0x5f8: 0x0008, 0x5f9: 0x0008, 0x5fa: 0x0008, 0x5fb: 0x0008, + 0x5fc: 0x0008, 0x5fd: 0x0008, 0x5fe: 0x0008, 0x5ff: 0x0008, + // Block 0x18, offset 0x600 + 0x600: 0x0008, 0x601: 0x3308, 0x602: 0x3008, 0x603: 0x3008, 0x604: 0x0040, 0x605: 0x0008, + 0x606: 0x0008, 0x607: 0x0008, 0x608: 0x0008, 0x609: 0x0008, 0x60a: 0x0008, 0x60b: 0x0008, + 0x60c: 0x0008, 0x60d: 0x0040, 0x60e: 0x0040, 0x60f: 0x0008, 0x610: 0x0008, 0x611: 0x0040, + 0x612: 0x0040, 0x613: 0x0008, 0x614: 0x0008, 0x615: 0x0008, 0x616: 0x0008, 0x617: 0x0008, + 0x618: 0x0008, 0x619: 0x0008, 0x61a: 0x0008, 0x61b: 0x0008, 0x61c: 0x0008, 0x61d: 0x0008, + 0x61e: 0x0008, 0x61f: 0x0008, 0x620: 0x0008, 0x621: 0x0008, 0x622: 0x0008, 0x623: 0x0008, + 0x624: 0x0008, 0x625: 0x0008, 0x626: 0x0008, 0x627: 0x0008, 0x628: 0x0008, 0x629: 0x0040, + 0x62a: 0x0008, 0x62b: 0x0008, 0x62c: 0x0008, 0x62d: 0x0008, 0x62e: 0x0008, 0x62f: 0x0008, + 0x630: 0x0008, 0x631: 0x0040, 0x632: 0x0008, 0x633: 0x0040, 0x634: 0x0040, 0x635: 0x0040, + 0x636: 0x0008, 0x637: 0x0008, 0x638: 0x0008, 0x639: 0x0008, 0x63a: 0x0040, 0x63b: 0x0040, + 0x63c: 0x3308, 0x63d: 0x0008, 0x63e: 0x3008, 0x63f: 0x3008, + // Block 0x19, offset 0x640 + 0x640: 0x3008, 0x641: 0x3308, 0x642: 0x3308, 0x643: 0x3308, 0x644: 0x3308, 0x645: 0x0040, + 0x646: 0x0040, 0x647: 0x3008, 0x648: 0x3008, 0x649: 0x0040, 0x64a: 0x0040, 0x64b: 0x3008, + 0x64c: 0x3008, 0x64d: 0x3b08, 0x64e: 0x0008, 0x64f: 0x0040, 0x650: 0x0040, 0x651: 0x0040, + 0x652: 0x0040, 0x653: 0x0040, 0x654: 0x0040, 0x655: 0x0040, 0x656: 0x0040, 0x657: 0x3008, + 0x658: 0x0040, 0x659: 0x0040, 0x65a: 0x0040, 0x65b: 0x0040, 0x65c: 0x0199, 0x65d: 0x01a1, + 0x65e: 0x0040, 0x65f: 0x01a9, 0x660: 0x0008, 0x661: 0x0008, 0x662: 0x3308, 0x663: 0x3308, + 0x664: 0x0040, 0x665: 0x0040, 0x666: 0x0008, 0x667: 0x0008, 0x668: 0x0008, 0x669: 0x0008, + 0x66a: 0x0008, 0x66b: 0x0008, 0x66c: 0x0008, 0x66d: 0x0008, 0x66e: 0x0008, 0x66f: 0x0008, + 0x670: 0x0008, 0x671: 0x0008, 0x672: 0x0018, 0x673: 0x0018, 0x674: 0x0018, 0x675: 0x0018, + 0x676: 0x0018, 0x677: 0x0018, 0x678: 0x0018, 0x679: 0x0018, 0x67a: 0x0018, 0x67b: 0x0018, + 0x67c: 0x0008, 0x67d: 0x0018, 0x67e: 0x3308, 0x67f: 0x0040, + // Block 0x1a, offset 0x680 + 0x680: 0x0040, 0x681: 0x3308, 0x682: 0x3308, 0x683: 0x3008, 0x684: 0x0040, 0x685: 0x0008, + 0x686: 0x0008, 0x687: 0x0008, 0x688: 0x0008, 0x689: 0x0008, 0x68a: 0x0008, 0x68b: 0x0040, + 0x68c: 0x0040, 0x68d: 0x0040, 0x68e: 0x0040, 0x68f: 0x0008, 0x690: 0x0008, 0x691: 0x0040, + 0x692: 0x0040, 0x693: 0x0008, 0x694: 0x0008, 0x695: 0x0008, 0x696: 0x0008, 0x697: 0x0008, + 0x698: 0x0008, 0x699: 0x0008, 0x69a: 0x0008, 0x69b: 0x0008, 0x69c: 0x0008, 0x69d: 0x0008, + 0x69e: 0x0008, 0x69f: 0x0008, 0x6a0: 0x0008, 0x6a1: 0x0008, 0x6a2: 0x0008, 0x6a3: 0x0008, + 0x6a4: 0x0008, 0x6a5: 0x0008, 0x6a6: 0x0008, 0x6a7: 0x0008, 0x6a8: 0x0008, 0x6a9: 0x0040, + 0x6aa: 0x0008, 0x6ab: 0x0008, 0x6ac: 0x0008, 0x6ad: 0x0008, 0x6ae: 0x0008, 0x6af: 0x0008, + 0x6b0: 0x0008, 0x6b1: 0x0040, 0x6b2: 0x0008, 0x6b3: 0x01b1, 0x6b4: 0x0040, 0x6b5: 0x0008, + 0x6b6: 0x01b9, 0x6b7: 0x0040, 0x6b8: 0x0008, 0x6b9: 0x0008, 0x6ba: 0x0040, 0x6bb: 0x0040, + 0x6bc: 0x3308, 0x6bd: 0x0040, 0x6be: 0x3008, 0x6bf: 0x3008, + // Block 0x1b, offset 0x6c0 + 0x6c0: 0x3008, 0x6c1: 0x3308, 0x6c2: 0x3308, 0x6c3: 0x0040, 0x6c4: 0x0040, 0x6c5: 0x0040, + 0x6c6: 0x0040, 0x6c7: 0x3308, 0x6c8: 0x3308, 0x6c9: 0x0040, 0x6ca: 0x0040, 0x6cb: 0x3308, + 0x6cc: 0x3308, 0x6cd: 0x3b08, 0x6ce: 0x0040, 0x6cf: 0x0040, 0x6d0: 0x0040, 0x6d1: 0x3308, + 0x6d2: 0x0040, 0x6d3: 0x0040, 0x6d4: 0x0040, 0x6d5: 0x0040, 0x6d6: 0x0040, 0x6d7: 0x0040, + 0x6d8: 0x0040, 0x6d9: 0x01c1, 0x6da: 0x01c9, 0x6db: 0x01d1, 0x6dc: 0x0008, 0x6dd: 0x0040, + 0x6de: 0x01d9, 0x6df: 0x0040, 0x6e0: 0x0040, 0x6e1: 0x0040, 0x6e2: 0x0040, 0x6e3: 0x0040, + 0x6e4: 0x0040, 0x6e5: 0x0040, 0x6e6: 0x0008, 0x6e7: 0x0008, 0x6e8: 0x0008, 0x6e9: 0x0008, + 0x6ea: 0x0008, 0x6eb: 0x0008, 0x6ec: 0x0008, 0x6ed: 0x0008, 0x6ee: 0x0008, 0x6ef: 0x0008, + 0x6f0: 0x3308, 0x6f1: 0x3308, 0x6f2: 0x0008, 0x6f3: 0x0008, 0x6f4: 0x0008, 0x6f5: 0x3308, + 0x6f6: 0x0018, 0x6f7: 0x0040, 0x6f8: 0x0040, 0x6f9: 0x0040, 0x6fa: 0x0040, 0x6fb: 0x0040, + 0x6fc: 0x0040, 0x6fd: 0x0040, 0x6fe: 0x0040, 0x6ff: 0x0040, + // Block 0x1c, offset 0x700 + 0x700: 0x0040, 0x701: 0x3308, 0x702: 0x3308, 0x703: 0x3008, 0x704: 0x0040, 0x705: 0x0008, + 0x706: 0x0008, 0x707: 0x0008, 0x708: 0x0008, 0x709: 0x0008, 0x70a: 0x0008, 0x70b: 0x0008, + 0x70c: 0x0008, 0x70d: 0x0008, 0x70e: 0x0040, 0x70f: 0x0008, 0x710: 0x0008, 0x711: 0x0008, + 0x712: 0x0040, 0x713: 0x0008, 0x714: 0x0008, 0x715: 0x0008, 0x716: 0x0008, 0x717: 0x0008, + 0x718: 0x0008, 0x719: 0x0008, 0x71a: 0x0008, 0x71b: 0x0008, 0x71c: 0x0008, 0x71d: 0x0008, + 0x71e: 0x0008, 0x71f: 0x0008, 0x720: 0x0008, 0x721: 0x0008, 0x722: 0x0008, 0x723: 0x0008, + 0x724: 0x0008, 0x725: 0x0008, 0x726: 0x0008, 0x727: 0x0008, 0x728: 0x0008, 0x729: 0x0040, + 0x72a: 0x0008, 0x72b: 0x0008, 0x72c: 0x0008, 0x72d: 0x0008, 0x72e: 0x0008, 0x72f: 0x0008, + 0x730: 0x0008, 0x731: 0x0040, 0x732: 0x0008, 0x733: 0x0008, 0x734: 0x0040, 0x735: 0x0008, + 0x736: 0x0008, 0x737: 0x0008, 0x738: 0x0008, 0x739: 0x0008, 0x73a: 0x0040, 0x73b: 0x0040, + 0x73c: 0x3308, 0x73d: 0x0008, 0x73e: 0x3008, 0x73f: 0x3008, + // Block 0x1d, offset 0x740 + 0x740: 0x3008, 0x741: 0x3308, 0x742: 0x3308, 0x743: 0x3308, 0x744: 0x3308, 0x745: 0x3308, + 0x746: 0x0040, 0x747: 0x3308, 0x748: 0x3308, 0x749: 0x3008, 0x74a: 0x0040, 0x74b: 0x3008, + 0x74c: 0x3008, 0x74d: 0x3b08, 0x74e: 0x0040, 0x74f: 0x0040, 0x750: 0x0008, 0x751: 0x0040, + 0x752: 0x0040, 0x753: 0x0040, 0x754: 0x0040, 0x755: 0x0040, 0x756: 0x0040, 0x757: 0x0040, + 0x758: 0x0040, 0x759: 0x0040, 0x75a: 0x0040, 0x75b: 0x0040, 0x75c: 0x0040, 0x75d: 0x0040, + 0x75e: 0x0040, 0x75f: 0x0040, 0x760: 0x0008, 0x761: 0x0008, 0x762: 0x3308, 0x763: 0x3308, + 0x764: 0x0040, 0x765: 0x0040, 0x766: 0x0008, 0x767: 0x0008, 0x768: 0x0008, 0x769: 0x0008, + 0x76a: 0x0008, 0x76b: 0x0008, 0x76c: 0x0008, 0x76d: 0x0008, 0x76e: 0x0008, 0x76f: 0x0008, + 0x770: 0x0018, 0x771: 0x0018, 0x772: 0x0040, 0x773: 0x0040, 0x774: 0x0040, 0x775: 0x0040, + 0x776: 0x0040, 0x777: 0x0040, 0x778: 0x0040, 0x779: 0x0008, 0x77a: 0x3308, 0x77b: 0x3308, + 0x77c: 0x3308, 0x77d: 0x3308, 0x77e: 0x3308, 0x77f: 0x3308, + // Block 0x1e, offset 0x780 + 0x780: 0x0040, 0x781: 0x3308, 0x782: 0x3008, 0x783: 0x3008, 0x784: 0x0040, 0x785: 0x0008, + 0x786: 0x0008, 0x787: 0x0008, 0x788: 0x0008, 0x789: 0x0008, 0x78a: 0x0008, 0x78b: 0x0008, + 0x78c: 0x0008, 0x78d: 0x0040, 0x78e: 0x0040, 0x78f: 0x0008, 0x790: 0x0008, 0x791: 0x0040, + 0x792: 0x0040, 0x793: 0x0008, 0x794: 0x0008, 0x795: 0x0008, 0x796: 0x0008, 0x797: 0x0008, + 0x798: 0x0008, 0x799: 0x0008, 0x79a: 0x0008, 0x79b: 0x0008, 0x79c: 0x0008, 0x79d: 0x0008, + 0x79e: 0x0008, 0x79f: 0x0008, 0x7a0: 0x0008, 0x7a1: 0x0008, 0x7a2: 0x0008, 0x7a3: 0x0008, + 0x7a4: 0x0008, 0x7a5: 0x0008, 0x7a6: 0x0008, 0x7a7: 0x0008, 0x7a8: 0x0008, 0x7a9: 0x0040, + 0x7aa: 0x0008, 0x7ab: 0x0008, 0x7ac: 0x0008, 0x7ad: 0x0008, 0x7ae: 0x0008, 0x7af: 0x0008, + 0x7b0: 0x0008, 0x7b1: 0x0040, 0x7b2: 0x0008, 0x7b3: 0x0008, 0x7b4: 0x0040, 0x7b5: 0x0008, + 0x7b6: 0x0008, 0x7b7: 0x0008, 0x7b8: 0x0008, 0x7b9: 0x0008, 0x7ba: 0x0040, 0x7bb: 0x0040, + 0x7bc: 0x3308, 0x7bd: 0x0008, 0x7be: 0x3008, 0x7bf: 0x3308, + // Block 0x1f, offset 0x7c0 + 0x7c0: 0x3008, 0x7c1: 0x3308, 0x7c2: 0x3308, 0x7c3: 0x3308, 0x7c4: 0x3308, 0x7c5: 0x0040, + 0x7c6: 0x0040, 0x7c7: 0x3008, 0x7c8: 0x3008, 0x7c9: 0x0040, 0x7ca: 0x0040, 0x7cb: 0x3008, + 0x7cc: 0x3008, 0x7cd: 0x3b08, 0x7ce: 0x0040, 0x7cf: 0x0040, 0x7d0: 0x0040, 0x7d1: 0x0040, + 0x7d2: 0x0040, 0x7d3: 0x0040, 0x7d4: 0x0040, 0x7d5: 0x3308, 0x7d6: 0x3308, 0x7d7: 0x3008, + 0x7d8: 0x0040, 0x7d9: 0x0040, 0x7da: 0x0040, 0x7db: 0x0040, 0x7dc: 0x01e1, 0x7dd: 0x01e9, + 0x7de: 0x0040, 0x7df: 0x0008, 0x7e0: 0x0008, 0x7e1: 0x0008, 0x7e2: 0x3308, 0x7e3: 0x3308, + 0x7e4: 0x0040, 0x7e5: 0x0040, 0x7e6: 0x0008, 0x7e7: 0x0008, 0x7e8: 0x0008, 0x7e9: 0x0008, + 0x7ea: 0x0008, 0x7eb: 0x0008, 0x7ec: 0x0008, 0x7ed: 0x0008, 0x7ee: 0x0008, 0x7ef: 0x0008, + 0x7f0: 0x0018, 0x7f1: 0x0008, 0x7f2: 0x0018, 0x7f3: 0x0018, 0x7f4: 0x0018, 0x7f5: 0x0018, + 0x7f6: 0x0018, 0x7f7: 0x0018, 0x7f8: 0x0040, 0x7f9: 0x0040, 0x7fa: 0x0040, 0x7fb: 0x0040, + 0x7fc: 0x0040, 0x7fd: 0x0040, 0x7fe: 0x0040, 0x7ff: 0x0040, + // Block 0x20, offset 0x800 + 0x800: 0x0040, 0x801: 0x0040, 0x802: 0x3308, 0x803: 0x0008, 0x804: 0x0040, 0x805: 0x0008, + 0x806: 0x0008, 0x807: 0x0008, 0x808: 0x0008, 0x809: 0x0008, 0x80a: 0x0008, 0x80b: 0x0040, + 0x80c: 0x0040, 0x80d: 0x0040, 0x80e: 0x0008, 0x80f: 0x0008, 0x810: 0x0008, 0x811: 0x0040, + 0x812: 0x0008, 0x813: 0x0008, 0x814: 0x0008, 0x815: 0x0008, 0x816: 0x0040, 0x817: 0x0040, + 0x818: 0x0040, 0x819: 0x0008, 0x81a: 0x0008, 0x81b: 0x0040, 0x81c: 0x0008, 0x81d: 0x0040, + 0x81e: 0x0008, 0x81f: 0x0008, 0x820: 0x0040, 0x821: 0x0040, 0x822: 0x0040, 0x823: 0x0008, + 0x824: 0x0008, 0x825: 0x0040, 0x826: 0x0040, 0x827: 0x0040, 0x828: 0x0008, 0x829: 0x0008, + 0x82a: 0x0008, 0x82b: 0x0040, 0x82c: 0x0040, 0x82d: 0x0040, 0x82e: 0x0008, 0x82f: 0x0008, + 0x830: 0x0008, 0x831: 0x0008, 0x832: 0x0008, 0x833: 0x0008, 0x834: 0x0008, 0x835: 0x0008, + 0x836: 0x0008, 0x837: 0x0008, 0x838: 0x0008, 0x839: 0x0008, 0x83a: 0x0040, 0x83b: 0x0040, + 0x83c: 0x0040, 0x83d: 0x0040, 0x83e: 0x3008, 0x83f: 0x3008, + // Block 0x21, offset 0x840 + 0x840: 0x3308, 0x841: 0x3008, 0x842: 0x3008, 0x843: 0x3008, 0x844: 0x3008, 0x845: 0x0040, + 0x846: 0x3308, 0x847: 0x3308, 0x848: 0x3308, 0x849: 0x0040, 0x84a: 0x3308, 0x84b: 0x3308, + 0x84c: 0x3308, 0x84d: 0x3b08, 0x84e: 0x0040, 0x84f: 0x0040, 0x850: 0x0040, 0x851: 0x0040, + 0x852: 0x0040, 0x853: 0x0040, 0x854: 0x0040, 0x855: 0x3308, 0x856: 0x3308, 0x857: 0x0040, + 0x858: 0x0008, 0x859: 0x0008, 0x85a: 0x0008, 0x85b: 0x0040, 0x85c: 0x0040, 0x85d: 0x0008, + 0x85e: 0x0040, 0x85f: 0x0040, 0x860: 0x0008, 0x861: 0x0008, 0x862: 0x3308, 0x863: 0x3308, + 0x864: 0x0040, 0x865: 0x0040, 0x866: 0x0008, 0x867: 0x0008, 0x868: 0x0008, 0x869: 0x0008, + 0x86a: 0x0008, 0x86b: 0x0008, 0x86c: 0x0008, 0x86d: 0x0008, 0x86e: 0x0008, 0x86f: 0x0008, + 0x870: 0x0040, 0x871: 0x0040, 0x872: 0x0040, 0x873: 0x0040, 0x874: 0x0040, 0x875: 0x0040, + 0x876: 0x0040, 0x877: 0x0018, 0x878: 0x0018, 0x879: 0x0018, 0x87a: 0x0018, 0x87b: 0x0018, + 0x87c: 0x0018, 0x87d: 0x0018, 0x87e: 0x0018, 0x87f: 0x0018, + // Block 0x22, offset 0x880 + 0x880: 0x0008, 0x881: 0x3308, 0x882: 0x3008, 0x883: 0x3008, 0x884: 0x0018, 0x885: 0x0008, + 0x886: 0x0008, 0x887: 0x0008, 0x888: 0x0008, 0x889: 0x0008, 0x88a: 0x0008, 0x88b: 0x0008, + 0x88c: 0x0008, 0x88d: 0x0040, 0x88e: 0x0008, 0x88f: 0x0008, 0x890: 0x0008, 0x891: 0x0040, + 0x892: 0x0008, 0x893: 0x0008, 0x894: 0x0008, 0x895: 0x0008, 0x896: 0x0008, 0x897: 0x0008, + 0x898: 0x0008, 0x899: 0x0008, 0x89a: 0x0008, 0x89b: 0x0008, 0x89c: 0x0008, 0x89d: 0x0008, + 0x89e: 0x0008, 0x89f: 0x0008, 0x8a0: 0x0008, 0x8a1: 0x0008, 0x8a2: 0x0008, 0x8a3: 0x0008, + 0x8a4: 0x0008, 0x8a5: 0x0008, 0x8a6: 0x0008, 0x8a7: 0x0008, 0x8a8: 0x0008, 0x8a9: 0x0040, + 0x8aa: 0x0008, 0x8ab: 0x0008, 0x8ac: 0x0008, 0x8ad: 0x0008, 0x8ae: 0x0008, 0x8af: 0x0008, + 0x8b0: 0x0008, 0x8b1: 0x0008, 0x8b2: 0x0008, 0x8b3: 0x0008, 0x8b4: 0x0040, 0x8b5: 0x0008, + 0x8b6: 0x0008, 0x8b7: 0x0008, 0x8b8: 0x0008, 0x8b9: 0x0008, 0x8ba: 0x0040, 0x8bb: 0x0040, + 0x8bc: 0x3308, 0x8bd: 0x0008, 0x8be: 0x3008, 0x8bf: 0x3308, + // Block 0x23, offset 0x8c0 + 0x8c0: 0x3008, 0x8c1: 0x3008, 0x8c2: 0x3008, 0x8c3: 0x3008, 0x8c4: 0x3008, 0x8c5: 0x0040, + 0x8c6: 0x3308, 0x8c7: 0x3008, 0x8c8: 0x3008, 0x8c9: 0x0040, 0x8ca: 0x3008, 0x8cb: 0x3008, + 0x8cc: 0x3308, 0x8cd: 0x3b08, 0x8ce: 0x0040, 0x8cf: 0x0040, 0x8d0: 0x0040, 0x8d1: 0x0040, + 0x8d2: 0x0040, 0x8d3: 0x0040, 0x8d4: 0x0040, 0x8d5: 0x3008, 0x8d6: 0x3008, 0x8d7: 0x0040, + 0x8d8: 0x0040, 0x8d9: 0x0040, 0x8da: 0x0040, 0x8db: 0x0040, 0x8dc: 0x0040, 0x8dd: 0x0008, + 0x8de: 0x0008, 0x8df: 0x0040, 0x8e0: 0x0008, 0x8e1: 0x0008, 0x8e2: 0x3308, 0x8e3: 0x3308, + 0x8e4: 0x0040, 0x8e5: 0x0040, 0x8e6: 0x0008, 0x8e7: 0x0008, 0x8e8: 0x0008, 0x8e9: 0x0008, + 0x8ea: 0x0008, 0x8eb: 0x0008, 0x8ec: 0x0008, 0x8ed: 0x0008, 0x8ee: 0x0008, 0x8ef: 0x0008, + 0x8f0: 0x0040, 0x8f1: 0x0008, 0x8f2: 0x0008, 0x8f3: 0x3008, 0x8f4: 0x0040, 0x8f5: 0x0040, + 0x8f6: 0x0040, 0x8f7: 0x0040, 0x8f8: 0x0040, 0x8f9: 0x0040, 0x8fa: 0x0040, 0x8fb: 0x0040, + 0x8fc: 0x0040, 0x8fd: 0x0040, 0x8fe: 0x0040, 0x8ff: 0x0040, + // Block 0x24, offset 0x900 + 0x900: 0x3008, 0x901: 0x3308, 0x902: 0x3308, 0x903: 0x3308, 0x904: 0x3308, 0x905: 0x0040, + 0x906: 0x3008, 0x907: 0x3008, 0x908: 0x3008, 0x909: 0x0040, 0x90a: 0x3008, 0x90b: 0x3008, + 0x90c: 0x3008, 0x90d: 0x3b08, 0x90e: 0x0008, 0x90f: 0x0018, 0x910: 0x0040, 0x911: 0x0040, + 0x912: 0x0040, 0x913: 0x0040, 0x914: 0x0008, 0x915: 0x0008, 0x916: 0x0008, 0x917: 0x3008, + 0x918: 0x0018, 0x919: 0x0018, 0x91a: 0x0018, 0x91b: 0x0018, 0x91c: 0x0018, 0x91d: 0x0018, + 0x91e: 0x0018, 0x91f: 0x0008, 0x920: 0x0008, 0x921: 0x0008, 0x922: 0x3308, 0x923: 0x3308, + 0x924: 0x0040, 0x925: 0x0040, 0x926: 0x0008, 0x927: 0x0008, 0x928: 0x0008, 0x929: 0x0008, + 0x92a: 0x0008, 0x92b: 0x0008, 0x92c: 0x0008, 0x92d: 0x0008, 0x92e: 0x0008, 0x92f: 0x0008, + 0x930: 0x0018, 0x931: 0x0018, 0x932: 0x0018, 0x933: 0x0018, 0x934: 0x0018, 0x935: 0x0018, + 0x936: 0x0018, 0x937: 0x0018, 0x938: 0x0018, 0x939: 0x0018, 0x93a: 0x0008, 0x93b: 0x0008, + 0x93c: 0x0008, 0x93d: 0x0008, 0x93e: 0x0008, 0x93f: 0x0008, + // Block 0x25, offset 0x940 + 0x940: 0x0040, 0x941: 0x0008, 0x942: 0x0008, 0x943: 0x0040, 0x944: 0x0008, 0x945: 0x0040, + 0x946: 0x0008, 0x947: 0x0008, 0x948: 0x0008, 0x949: 0x0008, 0x94a: 0x0008, 0x94b: 0x0040, + 0x94c: 0x0008, 0x94d: 0x0008, 0x94e: 0x0008, 0x94f: 0x0008, 0x950: 0x0008, 0x951: 0x0008, + 0x952: 0x0008, 0x953: 0x0008, 0x954: 0x0008, 0x955: 0x0008, 0x956: 0x0008, 0x957: 0x0008, + 0x958: 0x0008, 0x959: 0x0008, 0x95a: 0x0008, 0x95b: 0x0008, 0x95c: 0x0008, 0x95d: 0x0008, + 0x95e: 0x0008, 0x95f: 0x0008, 0x960: 0x0008, 0x961: 0x0008, 0x962: 0x0008, 0x963: 0x0008, + 0x964: 0x0040, 0x965: 0x0008, 0x966: 0x0040, 0x967: 0x0008, 0x968: 0x0008, 0x969: 0x0008, + 0x96a: 0x0008, 0x96b: 0x0008, 0x96c: 0x0008, 0x96d: 0x0008, 0x96e: 0x0008, 0x96f: 0x0008, + 0x970: 0x0008, 0x971: 0x3308, 0x972: 0x0008, 0x973: 0x01f9, 0x974: 0x3308, 0x975: 0x3308, + 0x976: 0x3308, 0x977: 0x3308, 0x978: 0x3308, 0x979: 0x3308, 0x97a: 0x3b08, 0x97b: 0x3308, + 0x97c: 0x3308, 0x97d: 0x0008, 0x97e: 0x0040, 0x97f: 0x0040, + // Block 0x26, offset 0x980 + 0x980: 0x0008, 0x981: 0x0008, 0x982: 0x0008, 0x983: 0x0211, 0x984: 0x0008, 0x985: 0x0008, + 0x986: 0x0008, 0x987: 0x0008, 0x988: 0x0040, 0x989: 0x0008, 0x98a: 0x0008, 0x98b: 0x0008, + 0x98c: 0x0008, 0x98d: 0x0219, 0x98e: 0x0008, 0x98f: 0x0008, 0x990: 0x0008, 0x991: 0x0008, + 0x992: 0x0221, 0x993: 0x0008, 0x994: 0x0008, 0x995: 0x0008, 0x996: 0x0008, 0x997: 0x0229, + 0x998: 0x0008, 0x999: 0x0008, 0x99a: 0x0008, 0x99b: 0x0008, 0x99c: 0x0231, 0x99d: 0x0008, + 0x99e: 0x0008, 0x99f: 0x0008, 0x9a0: 0x0008, 0x9a1: 0x0008, 0x9a2: 0x0008, 0x9a3: 0x0008, + 0x9a4: 0x0008, 0x9a5: 0x0008, 0x9a6: 0x0008, 0x9a7: 0x0008, 0x9a8: 0x0008, 0x9a9: 0x0239, + 0x9aa: 0x0008, 0x9ab: 0x0008, 0x9ac: 0x0008, 0x9ad: 0x0040, 0x9ae: 0x0040, 0x9af: 0x0040, + 0x9b0: 0x0040, 0x9b1: 0x3308, 0x9b2: 0x3308, 0x9b3: 0x0241, 0x9b4: 0x3308, 0x9b5: 0x0249, + 0x9b6: 0x0251, 0x9b7: 0x0259, 0x9b8: 0x0261, 0x9b9: 0x0269, 0x9ba: 0x3308, 0x9bb: 0x3308, + 0x9bc: 0x3308, 0x9bd: 0x3308, 0x9be: 0x3308, 0x9bf: 0x3008, + // Block 0x27, offset 0x9c0 + 0x9c0: 0x3308, 0x9c1: 0x0271, 0x9c2: 0x3308, 0x9c3: 0x3308, 0x9c4: 0x3b08, 0x9c5: 0x0018, + 0x9c6: 0x3308, 0x9c7: 0x3308, 0x9c8: 0x0008, 0x9c9: 0x0008, 0x9ca: 0x0008, 0x9cb: 0x0008, + 0x9cc: 0x0008, 0x9cd: 0x3308, 0x9ce: 0x3308, 0x9cf: 0x3308, 0x9d0: 0x3308, 0x9d1: 0x3308, + 0x9d2: 0x3308, 0x9d3: 0x0279, 0x9d4: 0x3308, 0x9d5: 0x3308, 0x9d6: 0x3308, 0x9d7: 0x3308, + 0x9d8: 0x0040, 0x9d9: 0x3308, 0x9da: 0x3308, 0x9db: 0x3308, 0x9dc: 0x3308, 0x9dd: 0x0281, + 0x9de: 0x3308, 0x9df: 0x3308, 0x9e0: 0x3308, 0x9e1: 0x3308, 0x9e2: 0x0289, 0x9e3: 0x3308, + 0x9e4: 0x3308, 0x9e5: 0x3308, 0x9e6: 0x3308, 0x9e7: 0x0291, 0x9e8: 0x3308, 0x9e9: 0x3308, + 0x9ea: 0x3308, 0x9eb: 0x3308, 0x9ec: 0x0299, 0x9ed: 0x3308, 0x9ee: 0x3308, 0x9ef: 0x3308, + 0x9f0: 0x3308, 0x9f1: 0x3308, 0x9f2: 0x3308, 0x9f3: 0x3308, 0x9f4: 0x3308, 0x9f5: 0x3308, + 0x9f6: 0x3308, 0x9f7: 0x3308, 0x9f8: 0x3308, 0x9f9: 0x02a1, 0x9fa: 0x3308, 0x9fb: 0x3308, + 0x9fc: 0x3308, 0x9fd: 0x0040, 0x9fe: 0x0018, 0x9ff: 0x0018, + // Block 0x28, offset 0xa00 + 0xa00: 0x0008, 0xa01: 0x0008, 0xa02: 0x0008, 0xa03: 0x0008, 0xa04: 0x0008, 0xa05: 0x0008, + 0xa06: 0x0008, 0xa07: 0x0008, 0xa08: 0x0008, 0xa09: 0x0008, 0xa0a: 0x0008, 0xa0b: 0x0008, + 0xa0c: 0x0008, 0xa0d: 0x0008, 0xa0e: 0x0008, 0xa0f: 0x0008, 0xa10: 0x0008, 0xa11: 0x0008, + 0xa12: 0x0008, 0xa13: 0x0008, 0xa14: 0x0008, 0xa15: 0x0008, 0xa16: 0x0008, 0xa17: 0x0008, + 0xa18: 0x0008, 0xa19: 0x0008, 0xa1a: 0x0008, 0xa1b: 0x0008, 0xa1c: 0x0008, 0xa1d: 0x0008, + 0xa1e: 0x0008, 0xa1f: 0x0008, 0xa20: 0x0008, 0xa21: 0x0008, 0xa22: 0x0008, 0xa23: 0x0008, + 0xa24: 0x0008, 0xa25: 0x0008, 0xa26: 0x0008, 0xa27: 0x0008, 0xa28: 0x0008, 0xa29: 0x0008, + 0xa2a: 0x0008, 0xa2b: 0x0008, 0xa2c: 0x0019, 0xa2d: 0x02e1, 0xa2e: 0x02e9, 0xa2f: 0x0008, + 0xa30: 0x02f1, 0xa31: 0x02f9, 0xa32: 0x0301, 0xa33: 0x0309, 0xa34: 0x00a9, 0xa35: 0x0311, + 0xa36: 0x00b1, 0xa37: 0x0319, 0xa38: 0x0101, 0xa39: 0x0321, 0xa3a: 0x0329, 0xa3b: 0x0008, + 0xa3c: 0x0051, 0xa3d: 0x0331, 0xa3e: 0x0339, 0xa3f: 0x00b9, + // Block 0x29, offset 0xa40 + 0xa40: 0x0341, 0xa41: 0x0349, 0xa42: 0x00c1, 0xa43: 0x0019, 0xa44: 0x0351, 0xa45: 0x0359, + 0xa46: 0x05b5, 0xa47: 0x02e9, 0xa48: 0x02f1, 0xa49: 0x02f9, 0xa4a: 0x0361, 0xa4b: 0x0369, + 0xa4c: 0x0371, 0xa4d: 0x0309, 0xa4e: 0x0008, 0xa4f: 0x0319, 0xa50: 0x0321, 0xa51: 0x0379, + 0xa52: 0x0051, 0xa53: 0x0381, 0xa54: 0x05cd, 0xa55: 0x05cd, 0xa56: 0x0339, 0xa57: 0x0341, + 0xa58: 0x0349, 0xa59: 0x05b5, 0xa5a: 0x0389, 0xa5b: 0x0391, 0xa5c: 0x05e5, 0xa5d: 0x0399, + 0xa5e: 0x03a1, 0xa5f: 0x03a9, 0xa60: 0x03b1, 0xa61: 0x03b9, 0xa62: 0x0311, 0xa63: 0x00b9, + 0xa64: 0x0349, 0xa65: 0x0391, 0xa66: 0x0399, 0xa67: 0x03a1, 0xa68: 0x03c1, 0xa69: 0x03b1, + 0xa6a: 0x03b9, 0xa6b: 0x0008, 0xa6c: 0x0008, 0xa6d: 0x0008, 0xa6e: 0x0008, 0xa6f: 0x0008, + 0xa70: 0x0008, 0xa71: 0x0008, 0xa72: 0x0008, 0xa73: 0x0008, 0xa74: 0x0008, 0xa75: 0x0008, + 0xa76: 0x0008, 0xa77: 0x0008, 0xa78: 0x03c9, 0xa79: 0x0008, 0xa7a: 0x0008, 0xa7b: 0x0008, + 0xa7c: 0x0008, 0xa7d: 0x0008, 0xa7e: 0x0008, 0xa7f: 0x0008, + // Block 0x2a, offset 0xa80 + 0xa80: 0x0008, 0xa81: 0x0008, 0xa82: 0x0008, 0xa83: 0x0008, 0xa84: 0x0008, 0xa85: 0x0008, + 0xa86: 0x0008, 0xa87: 0x0008, 0xa88: 0x0008, 0xa89: 0x0008, 0xa8a: 0x0008, 0xa8b: 0x0008, + 0xa8c: 0x0008, 0xa8d: 0x0008, 0xa8e: 0x0008, 0xa8f: 0x0008, 0xa90: 0x0008, 0xa91: 0x0008, + 0xa92: 0x0008, 0xa93: 0x0008, 0xa94: 0x0008, 0xa95: 0x0008, 0xa96: 0x0008, 0xa97: 0x0008, + 0xa98: 0x0008, 0xa99: 0x0008, 0xa9a: 0x0008, 0xa9b: 0x03d1, 0xa9c: 0x03d9, 0xa9d: 0x03e1, + 0xa9e: 0x03e9, 0xa9f: 0x0371, 0xaa0: 0x03f1, 0xaa1: 0x03f9, 0xaa2: 0x0401, 0xaa3: 0x0409, + 0xaa4: 0x0411, 0xaa5: 0x0419, 0xaa6: 0x0421, 0xaa7: 0x05fd, 0xaa8: 0x0429, 0xaa9: 0x0431, + 0xaaa: 0xe17d, 0xaab: 0x0439, 0xaac: 0x0441, 0xaad: 0x0449, 0xaae: 0x0451, 0xaaf: 0x0459, + 0xab0: 0x0461, 0xab1: 0x0469, 0xab2: 0x0471, 0xab3: 0x0479, 0xab4: 0x0481, 0xab5: 0x0489, + 0xab6: 0x0491, 0xab7: 0x0499, 0xab8: 0x0615, 0xab9: 0x04a1, 0xaba: 0x04a9, 0xabb: 0x04b1, + 0xabc: 0x04b9, 0xabd: 0x04c1, 0xabe: 0x04c9, 0xabf: 0x04d1, + // Block 0x2b, offset 0xac0 + 0xac0: 0xe00d, 0xac1: 0x0008, 0xac2: 0xe00d, 0xac3: 0x0008, 0xac4: 0xe00d, 0xac5: 0x0008, + 0xac6: 0xe00d, 0xac7: 0x0008, 0xac8: 0xe00d, 0xac9: 0x0008, 0xaca: 0xe00d, 0xacb: 0x0008, + 0xacc: 0xe00d, 0xacd: 0x0008, 0xace: 0xe00d, 0xacf: 0x0008, 0xad0: 0xe00d, 0xad1: 0x0008, + 0xad2: 0xe00d, 0xad3: 0x0008, 0xad4: 0xe00d, 0xad5: 0x0008, 0xad6: 0xe00d, 0xad7: 0x0008, + 0xad8: 0xe00d, 0xad9: 0x0008, 0xada: 0xe00d, 0xadb: 0x0008, 0xadc: 0xe00d, 0xadd: 0x0008, + 0xade: 0xe00d, 0xadf: 0x0008, 0xae0: 0xe00d, 0xae1: 0x0008, 0xae2: 0xe00d, 0xae3: 0x0008, + 0xae4: 0xe00d, 0xae5: 0x0008, 0xae6: 0xe00d, 0xae7: 0x0008, 0xae8: 0xe00d, 0xae9: 0x0008, + 0xaea: 0xe00d, 0xaeb: 0x0008, 0xaec: 0xe00d, 0xaed: 0x0008, 0xaee: 0xe00d, 0xaef: 0x0008, + 0xaf0: 0xe00d, 0xaf1: 0x0008, 0xaf2: 0xe00d, 0xaf3: 0x0008, 0xaf4: 0xe00d, 0xaf5: 0x0008, + 0xaf6: 0xe00d, 0xaf7: 0x0008, 0xaf8: 0xe00d, 0xaf9: 0x0008, 0xafa: 0xe00d, 0xafb: 0x0008, + 0xafc: 0xe00d, 0xafd: 0x0008, 0xafe: 0xe00d, 0xaff: 0x0008, + // Block 0x2c, offset 0xb00 + 0xb00: 0xe00d, 0xb01: 0x0008, 0xb02: 0xe00d, 0xb03: 0x0008, 0xb04: 0xe00d, 0xb05: 0x0008, + 0xb06: 0xe00d, 0xb07: 0x0008, 0xb08: 0xe00d, 0xb09: 0x0008, 0xb0a: 0xe00d, 0xb0b: 0x0008, + 0xb0c: 0xe00d, 0xb0d: 0x0008, 0xb0e: 0xe00d, 0xb0f: 0x0008, 0xb10: 0xe00d, 0xb11: 0x0008, + 0xb12: 0xe00d, 0xb13: 0x0008, 0xb14: 0xe00d, 0xb15: 0x0008, 0xb16: 0x0008, 0xb17: 0x0008, + 0xb18: 0x0008, 0xb19: 0x0008, 0xb1a: 0x062d, 0xb1b: 0x064d, 0xb1c: 0x0008, 0xb1d: 0x0008, + 0xb1e: 0x04d9, 0xb1f: 0x0008, 0xb20: 0xe00d, 0xb21: 0x0008, 0xb22: 0xe00d, 0xb23: 0x0008, + 0xb24: 0xe00d, 0xb25: 0x0008, 0xb26: 0xe00d, 0xb27: 0x0008, 0xb28: 0xe00d, 0xb29: 0x0008, + 0xb2a: 0xe00d, 0xb2b: 0x0008, 0xb2c: 0xe00d, 0xb2d: 0x0008, 0xb2e: 0xe00d, 0xb2f: 0x0008, + 0xb30: 0xe00d, 0xb31: 0x0008, 0xb32: 0xe00d, 0xb33: 0x0008, 0xb34: 0xe00d, 0xb35: 0x0008, + 0xb36: 0xe00d, 0xb37: 0x0008, 0xb38: 0xe00d, 0xb39: 0x0008, 0xb3a: 0xe00d, 0xb3b: 0x0008, + 0xb3c: 0xe00d, 0xb3d: 0x0008, 0xb3e: 0xe00d, 0xb3f: 0x0008, + // Block 0x2d, offset 0xb40 + 0xb40: 0x0008, 0xb41: 0x0008, 0xb42: 0x0008, 0xb43: 0x0008, 0xb44: 0x0008, 0xb45: 0x0008, + 0xb46: 0x0040, 0xb47: 0x0040, 0xb48: 0xe045, 0xb49: 0xe045, 0xb4a: 0xe045, 0xb4b: 0xe045, + 0xb4c: 0xe045, 0xb4d: 0xe045, 0xb4e: 0x0040, 0xb4f: 0x0040, 0xb50: 0x0008, 0xb51: 0x0008, + 0xb52: 0x0008, 0xb53: 0x0008, 0xb54: 0x0008, 0xb55: 0x0008, 0xb56: 0x0008, 0xb57: 0x0008, + 0xb58: 0x0040, 0xb59: 0xe045, 0xb5a: 0x0040, 0xb5b: 0xe045, 0xb5c: 0x0040, 0xb5d: 0xe045, + 0xb5e: 0x0040, 0xb5f: 0xe045, 0xb60: 0x0008, 0xb61: 0x0008, 0xb62: 0x0008, 0xb63: 0x0008, + 0xb64: 0x0008, 0xb65: 0x0008, 0xb66: 0x0008, 0xb67: 0x0008, 0xb68: 0xe045, 0xb69: 0xe045, + 0xb6a: 0xe045, 0xb6b: 0xe045, 0xb6c: 0xe045, 0xb6d: 0xe045, 0xb6e: 0xe045, 0xb6f: 0xe045, + 0xb70: 0x0008, 0xb71: 0x04e1, 0xb72: 0x0008, 0xb73: 0x04e9, 0xb74: 0x0008, 0xb75: 0x04f1, + 0xb76: 0x0008, 0xb77: 0x04f9, 0xb78: 0x0008, 0xb79: 0x0501, 0xb7a: 0x0008, 0xb7b: 0x0509, + 0xb7c: 0x0008, 0xb7d: 0x0511, 0xb7e: 0x0040, 0xb7f: 0x0040, + // Block 0x2e, offset 0xb80 + 0xb80: 0x0519, 0xb81: 0x0521, 0xb82: 0x0529, 0xb83: 0x0531, 0xb84: 0x0539, 0xb85: 0x0541, + 0xb86: 0x0549, 0xb87: 0x0551, 0xb88: 0x0519, 0xb89: 0x0521, 0xb8a: 0x0529, 0xb8b: 0x0531, + 0xb8c: 0x0539, 0xb8d: 0x0541, 0xb8e: 0x0549, 0xb8f: 0x0551, 0xb90: 0x0559, 0xb91: 0x0561, + 0xb92: 0x0569, 0xb93: 0x0571, 0xb94: 0x0579, 0xb95: 0x0581, 0xb96: 0x0589, 0xb97: 0x0591, + 0xb98: 0x0559, 0xb99: 0x0561, 0xb9a: 0x0569, 0xb9b: 0x0571, 0xb9c: 0x0579, 0xb9d: 0x0581, + 0xb9e: 0x0589, 0xb9f: 0x0591, 0xba0: 0x0599, 0xba1: 0x05a1, 0xba2: 0x05a9, 0xba3: 0x05b1, + 0xba4: 0x05b9, 0xba5: 0x05c1, 0xba6: 0x05c9, 0xba7: 0x05d1, 0xba8: 0x0599, 0xba9: 0x05a1, + 0xbaa: 0x05a9, 0xbab: 0x05b1, 0xbac: 0x05b9, 0xbad: 0x05c1, 0xbae: 0x05c9, 0xbaf: 0x05d1, + 0xbb0: 0x0008, 0xbb1: 0x0008, 0xbb2: 0x05d9, 0xbb3: 0x05e1, 0xbb4: 0x05e9, 0xbb5: 0x0040, + 0xbb6: 0x0008, 0xbb7: 0x05f1, 0xbb8: 0xe045, 0xbb9: 0xe045, 0xbba: 0x0665, 0xbbb: 0x04e1, + 0xbbc: 0x05e1, 0xbbd: 0x067e, 0xbbe: 0x05f9, 0xbbf: 0x069e, + // Block 0x2f, offset 0xbc0 + 0xbc0: 0x06be, 0xbc1: 0x0602, 0xbc2: 0x0609, 0xbc3: 0x0611, 0xbc4: 0x0619, 0xbc5: 0x0040, + 0xbc6: 0x0008, 0xbc7: 0x0621, 0xbc8: 0x06dd, 0xbc9: 0x04e9, 0xbca: 0x06f5, 0xbcb: 0x04f1, + 0xbcc: 0x0611, 0xbcd: 0x062a, 0xbce: 0x0632, 0xbcf: 0x063a, 0xbd0: 0x0008, 0xbd1: 0x0008, + 0xbd2: 0x0008, 0xbd3: 0x0641, 0xbd4: 0x0040, 0xbd5: 0x0040, 0xbd6: 0x0008, 0xbd7: 0x0008, + 0xbd8: 0xe045, 0xbd9: 0xe045, 0xbda: 0x070d, 0xbdb: 0x04f9, 0xbdc: 0x0040, 0xbdd: 0x064a, + 0xbde: 0x0652, 0xbdf: 0x065a, 0xbe0: 0x0008, 0xbe1: 0x0008, 0xbe2: 0x0008, 0xbe3: 0x0661, + 0xbe4: 0x0008, 0xbe5: 0x0008, 0xbe6: 0x0008, 0xbe7: 0x0008, 0xbe8: 0xe045, 0xbe9: 0xe045, + 0xbea: 0x0725, 0xbeb: 0x0509, 0xbec: 0xe04d, 0xbed: 0x066a, 0xbee: 0x012a, 0xbef: 0x0672, + 0xbf0: 0x0040, 0xbf1: 0x0040, 0xbf2: 0x0679, 0xbf3: 0x0681, 0xbf4: 0x0689, 0xbf5: 0x0040, + 0xbf6: 0x0008, 0xbf7: 0x0691, 0xbf8: 0x073d, 0xbf9: 0x0501, 0xbfa: 0x0515, 0xbfb: 0x0511, + 0xbfc: 0x0681, 0xbfd: 0x0756, 0xbfe: 0x0776, 0xbff: 0x0040, + // Block 0x30, offset 0xc00 + 0xc00: 0x000a, 0xc01: 0x000a, 0xc02: 0x000a, 0xc03: 0x000a, 0xc04: 0x000a, 0xc05: 0x000a, + 0xc06: 0x000a, 0xc07: 0x000a, 0xc08: 0x000a, 0xc09: 0x000a, 0xc0a: 0x000a, 0xc0b: 0x03c0, + 0xc0c: 0x0003, 0xc0d: 0x0003, 0xc0e: 0x0340, 0xc0f: 0x0b40, 0xc10: 0x0018, 0xc11: 0xe00d, + 0xc12: 0x0018, 0xc13: 0x0018, 0xc14: 0x0018, 0xc15: 0x0018, 0xc16: 0x0018, 0xc17: 0x0796, + 0xc18: 0x0018, 0xc19: 0x0018, 0xc1a: 0x0018, 0xc1b: 0x0018, 0xc1c: 0x0018, 0xc1d: 0x0018, + 0xc1e: 0x0018, 0xc1f: 0x0018, 0xc20: 0x0018, 0xc21: 0x0018, 0xc22: 0x0018, 0xc23: 0x0018, + 0xc24: 0x0040, 0xc25: 0x0040, 0xc26: 0x0040, 0xc27: 0x0018, 0xc28: 0x0040, 0xc29: 0x0040, + 0xc2a: 0x0340, 0xc2b: 0x0340, 0xc2c: 0x0340, 0xc2d: 0x0340, 0xc2e: 0x0340, 0xc2f: 0x000a, + 0xc30: 0x0018, 0xc31: 0x0018, 0xc32: 0x0018, 0xc33: 0x0699, 0xc34: 0x06a1, 0xc35: 0x0018, + 0xc36: 0x06a9, 0xc37: 0x06b1, 0xc38: 0x0018, 0xc39: 0x0018, 0xc3a: 0x0018, 0xc3b: 0x0018, + 0xc3c: 0x06ba, 0xc3d: 0x0018, 0xc3e: 0x07b6, 0xc3f: 0x0018, + // Block 0x31, offset 0xc40 + 0xc40: 0x0018, 0xc41: 0x0018, 0xc42: 0x0018, 0xc43: 0x0018, 0xc44: 0x0018, 0xc45: 0x0018, + 0xc46: 0x0018, 0xc47: 0x06c2, 0xc48: 0x06ca, 0xc49: 0x06d2, 0xc4a: 0x0018, 0xc4b: 0x0018, + 0xc4c: 0x0018, 0xc4d: 0x0018, 0xc4e: 0x0018, 0xc4f: 0x0018, 0xc50: 0x0018, 0xc51: 0x0018, + 0xc52: 0x0018, 0xc53: 0x0018, 0xc54: 0x0018, 0xc55: 0x0018, 0xc56: 0x0018, 0xc57: 0x06d9, + 0xc58: 0x0018, 0xc59: 0x0018, 0xc5a: 0x0018, 0xc5b: 0x0018, 0xc5c: 0x0018, 0xc5d: 0x0018, + 0xc5e: 0x0018, 0xc5f: 0x000a, 0xc60: 0x03c0, 0xc61: 0x0340, 0xc62: 0x0340, 0xc63: 0x0340, + 0xc64: 0x03c0, 0xc65: 0x0040, 0xc66: 0x0040, 0xc67: 0x0040, 0xc68: 0x0040, 0xc69: 0x0040, + 0xc6a: 0x0340, 0xc6b: 0x0340, 0xc6c: 0x0340, 0xc6d: 0x0340, 0xc6e: 0x0340, 0xc6f: 0x0340, + 0xc70: 0x06e1, 0xc71: 0x0311, 0xc72: 0x0040, 0xc73: 0x0040, 0xc74: 0x06e9, 0xc75: 0x06f1, + 0xc76: 0x06f9, 0xc77: 0x0701, 0xc78: 0x0709, 0xc79: 0x0711, 0xc7a: 0x071a, 0xc7b: 0x07d5, + 0xc7c: 0x0722, 0xc7d: 0x072a, 0xc7e: 0x0732, 0xc7f: 0x0329, + // Block 0x32, offset 0xc80 + 0xc80: 0x06e1, 0xc81: 0x0049, 0xc82: 0x0029, 0xc83: 0x0031, 0xc84: 0x06e9, 0xc85: 0x06f1, + 0xc86: 0x06f9, 0xc87: 0x0701, 0xc88: 0x0709, 0xc89: 0x0711, 0xc8a: 0x071a, 0xc8b: 0x07ed, + 0xc8c: 0x0722, 0xc8d: 0x072a, 0xc8e: 0x0732, 0xc8f: 0x0040, 0xc90: 0x0019, 0xc91: 0x02f9, + 0xc92: 0x0051, 0xc93: 0x0109, 0xc94: 0x0361, 0xc95: 0x00a9, 0xc96: 0x0319, 0xc97: 0x0101, + 0xc98: 0x0321, 0xc99: 0x0329, 0xc9a: 0x0339, 0xc9b: 0x0089, 0xc9c: 0x0341, 0xc9d: 0x0040, + 0xc9e: 0x0040, 0xc9f: 0x0040, 0xca0: 0x0018, 0xca1: 0x0018, 0xca2: 0x0018, 0xca3: 0x0018, + 0xca4: 0x0018, 0xca5: 0x0018, 0xca6: 0x0018, 0xca7: 0x0018, 0xca8: 0x0739, 0xca9: 0x0018, + 0xcaa: 0x0018, 0xcab: 0x0018, 0xcac: 0x0018, 0xcad: 0x0018, 0xcae: 0x0018, 0xcaf: 0x0018, + 0xcb0: 0x0018, 0xcb1: 0x0018, 0xcb2: 0x0018, 0xcb3: 0x0018, 0xcb4: 0x0018, 0xcb5: 0x0018, + 0xcb6: 0x0018, 0xcb7: 0x0018, 0xcb8: 0x0018, 0xcb9: 0x0018, 0xcba: 0x0018, 0xcbb: 0x0018, + 0xcbc: 0x0018, 0xcbd: 0x0018, 0xcbe: 0x0018, 0xcbf: 0x0018, + // Block 0x33, offset 0xcc0 + 0xcc0: 0x0806, 0xcc1: 0x0826, 0xcc2: 0x03d9, 0xcc3: 0x0845, 0xcc4: 0x0018, 0xcc5: 0x0866, + 0xcc6: 0x0886, 0xcc7: 0x0369, 0xcc8: 0x0018, 0xcc9: 0x08a5, 0xcca: 0x0309, 0xccb: 0x00a9, + 0xccc: 0x00a9, 0xccd: 0x00a9, 0xcce: 0x00a9, 0xccf: 0x0741, 0xcd0: 0x0311, 0xcd1: 0x0311, + 0xcd2: 0x0101, 0xcd3: 0x0101, 0xcd4: 0x0018, 0xcd5: 0x0329, 0xcd6: 0x0749, 0xcd7: 0x0018, + 0xcd8: 0x0018, 0xcd9: 0x0339, 0xcda: 0x0751, 0xcdb: 0x00b9, 0xcdc: 0x00b9, 0xcdd: 0x00b9, + 0xcde: 0x0018, 0xcdf: 0x0018, 0xce0: 0x0759, 0xce1: 0x08c5, 0xce2: 0x0761, 0xce3: 0x0018, + 0xce4: 0x04b1, 0xce5: 0x0018, 0xce6: 0x0769, 0xce7: 0x0018, 0xce8: 0x04b1, 0xce9: 0x0018, + 0xcea: 0x0319, 0xceb: 0x0771, 0xcec: 0x02e9, 0xced: 0x03d9, 0xcee: 0x0018, 0xcef: 0x02f9, + 0xcf0: 0x02f9, 0xcf1: 0x03f1, 0xcf2: 0x0040, 0xcf3: 0x0321, 0xcf4: 0x0051, 0xcf5: 0x0779, + 0xcf6: 0x0781, 0xcf7: 0x0789, 0xcf8: 0x0791, 0xcf9: 0x0311, 0xcfa: 0x0018, 0xcfb: 0x08e5, + 0xcfc: 0x0799, 0xcfd: 0x03a1, 0xcfe: 0x03a1, 0xcff: 0x0799, + // Block 0x34, offset 0xd00 + 0xd00: 0x0905, 0xd01: 0x0018, 0xd02: 0x0018, 0xd03: 0x0018, 0xd04: 0x0018, 0xd05: 0x02f1, + 0xd06: 0x02f1, 0xd07: 0x02f9, 0xd08: 0x0311, 0xd09: 0x00b1, 0xd0a: 0x0018, 0xd0b: 0x0018, + 0xd0c: 0x0018, 0xd0d: 0x0018, 0xd0e: 0x0008, 0xd0f: 0x0018, 0xd10: 0x07a1, 0xd11: 0x07a9, + 0xd12: 0x07b1, 0xd13: 0x07b9, 0xd14: 0x07c1, 0xd15: 0x07c9, 0xd16: 0x07d1, 0xd17: 0x07d9, + 0xd18: 0x07e1, 0xd19: 0x07e9, 0xd1a: 0x07f1, 0xd1b: 0x07f9, 0xd1c: 0x0801, 0xd1d: 0x0809, + 0xd1e: 0x0811, 0xd1f: 0x0819, 0xd20: 0x0311, 0xd21: 0x0821, 0xd22: 0x091d, 0xd23: 0x0829, + 0xd24: 0x0391, 0xd25: 0x0831, 0xd26: 0x093d, 0xd27: 0x0839, 0xd28: 0x0841, 0xd29: 0x0109, + 0xd2a: 0x0849, 0xd2b: 0x095d, 0xd2c: 0x0101, 0xd2d: 0x03d9, 0xd2e: 0x02f1, 0xd2f: 0x0321, + 0xd30: 0x0311, 0xd31: 0x0821, 0xd32: 0x097d, 0xd33: 0x0829, 0xd34: 0x0391, 0xd35: 0x0831, + 0xd36: 0x099d, 0xd37: 0x0839, 0xd38: 0x0841, 0xd39: 0x0109, 0xd3a: 0x0849, 0xd3b: 0x09bd, + 0xd3c: 0x0101, 0xd3d: 0x03d9, 0xd3e: 0x02f1, 0xd3f: 0x0321, + // Block 0x35, offset 0xd40 + 0xd40: 0x0018, 0xd41: 0x0018, 0xd42: 0x0018, 0xd43: 0x0018, 0xd44: 0x0018, 0xd45: 0x0018, + 0xd46: 0x0018, 0xd47: 0x0018, 0xd48: 0x0018, 0xd49: 0x0018, 0xd4a: 0x0018, 0xd4b: 0x0040, + 0xd4c: 0x0040, 0xd4d: 0x0040, 0xd4e: 0x0040, 0xd4f: 0x0040, 0xd50: 0x0040, 0xd51: 0x0040, + 0xd52: 0x0040, 0xd53: 0x0040, 0xd54: 0x0040, 0xd55: 0x0040, 0xd56: 0x0040, 0xd57: 0x0040, + 0xd58: 0x0040, 0xd59: 0x0040, 0xd5a: 0x0040, 0xd5b: 0x0040, 0xd5c: 0x0040, 0xd5d: 0x0040, + 0xd5e: 0x0040, 0xd5f: 0x0040, 0xd60: 0x0049, 0xd61: 0x0029, 0xd62: 0x0031, 0xd63: 0x06e9, + 0xd64: 0x06f1, 0xd65: 0x06f9, 0xd66: 0x0701, 0xd67: 0x0709, 0xd68: 0x0711, 0xd69: 0x0879, + 0xd6a: 0x0881, 0xd6b: 0x0889, 0xd6c: 0x0891, 0xd6d: 0x0899, 0xd6e: 0x08a1, 0xd6f: 0x08a9, + 0xd70: 0x08b1, 0xd71: 0x08b9, 0xd72: 0x08c1, 0xd73: 0x08c9, 0xd74: 0x0a1e, 0xd75: 0x0a3e, + 0xd76: 0x0a5e, 0xd77: 0x0a7e, 0xd78: 0x0a9e, 0xd79: 0x0abe, 0xd7a: 0x0ade, 0xd7b: 0x0afe, + 0xd7c: 0x0b1e, 0xd7d: 0x08d2, 0xd7e: 0x08da, 0xd7f: 0x08e2, + // Block 0x36, offset 0xd80 + 0xd80: 0x08ea, 0xd81: 0x08f2, 0xd82: 0x08fa, 0xd83: 0x0902, 0xd84: 0x090a, 0xd85: 0x0912, + 0xd86: 0x091a, 0xd87: 0x0922, 0xd88: 0x0040, 0xd89: 0x0040, 0xd8a: 0x0040, 0xd8b: 0x0040, + 0xd8c: 0x0040, 0xd8d: 0x0040, 0xd8e: 0x0040, 0xd8f: 0x0040, 0xd90: 0x0040, 0xd91: 0x0040, + 0xd92: 0x0040, 0xd93: 0x0040, 0xd94: 0x0040, 0xd95: 0x0040, 0xd96: 0x0040, 0xd97: 0x0040, + 0xd98: 0x0040, 0xd99: 0x0040, 0xd9a: 0x0040, 0xd9b: 0x0040, 0xd9c: 0x0b3e, 0xd9d: 0x0b5e, + 0xd9e: 0x0b7e, 0xd9f: 0x0b9e, 0xda0: 0x0bbe, 0xda1: 0x0bde, 0xda2: 0x0bfe, 0xda3: 0x0c1e, + 0xda4: 0x0c3e, 0xda5: 0x0c5e, 0xda6: 0x0c7e, 0xda7: 0x0c9e, 0xda8: 0x0cbe, 0xda9: 0x0cde, + 0xdaa: 0x0cfe, 0xdab: 0x0d1e, 0xdac: 0x0d3e, 0xdad: 0x0d5e, 0xdae: 0x0d7e, 0xdaf: 0x0d9e, + 0xdb0: 0x0dbe, 0xdb1: 0x0dde, 0xdb2: 0x0dfe, 0xdb3: 0x0e1e, 0xdb4: 0x0e3e, 0xdb5: 0x0e5e, + 0xdb6: 0x0019, 0xdb7: 0x02e9, 0xdb8: 0x03d9, 0xdb9: 0x02f1, 0xdba: 0x02f9, 0xdbb: 0x03f1, + 0xdbc: 0x0309, 0xdbd: 0x00a9, 0xdbe: 0x0311, 0xdbf: 0x00b1, + // Block 0x37, offset 0xdc0 + 0xdc0: 0x0319, 0xdc1: 0x0101, 0xdc2: 0x0321, 0xdc3: 0x0329, 0xdc4: 0x0051, 0xdc5: 0x0339, + 0xdc6: 0x0751, 0xdc7: 0x00b9, 0xdc8: 0x0089, 0xdc9: 0x0341, 0xdca: 0x0349, 0xdcb: 0x0391, + 0xdcc: 0x00c1, 0xdcd: 0x0109, 0xdce: 0x00c9, 0xdcf: 0x04b1, 0xdd0: 0x0019, 0xdd1: 0x02e9, + 0xdd2: 0x03d9, 0xdd3: 0x02f1, 0xdd4: 0x02f9, 0xdd5: 0x03f1, 0xdd6: 0x0309, 0xdd7: 0x00a9, + 0xdd8: 0x0311, 0xdd9: 0x00b1, 0xdda: 0x0319, 0xddb: 0x0101, 0xddc: 0x0321, 0xddd: 0x0329, + 0xdde: 0x0051, 0xddf: 0x0339, 0xde0: 0x0751, 0xde1: 0x00b9, 0xde2: 0x0089, 0xde3: 0x0341, + 0xde4: 0x0349, 0xde5: 0x0391, 0xde6: 0x00c1, 0xde7: 0x0109, 0xde8: 0x00c9, 0xde9: 0x04b1, + 0xdea: 0x06e1, 0xdeb: 0x0018, 0xdec: 0x0018, 0xded: 0x0018, 0xdee: 0x0018, 0xdef: 0x0018, + 0xdf0: 0x0018, 0xdf1: 0x0018, 0xdf2: 0x0018, 0xdf3: 0x0018, 0xdf4: 0x0018, 0xdf5: 0x0018, + 0xdf6: 0x0018, 0xdf7: 0x0018, 0xdf8: 0x0018, 0xdf9: 0x0018, 0xdfa: 0x0018, 0xdfb: 0x0018, + 0xdfc: 0x0018, 0xdfd: 0x0018, 0xdfe: 0x0018, 0xdff: 0x0018, + // Block 0x38, offset 0xe00 + 0xe00: 0x0008, 0xe01: 0x0008, 0xe02: 0x0008, 0xe03: 0x0008, 0xe04: 0x0008, 0xe05: 0x0008, + 0xe06: 0x0008, 0xe07: 0x0008, 0xe08: 0x0008, 0xe09: 0x0008, 0xe0a: 0x0008, 0xe0b: 0x0008, + 0xe0c: 0x0008, 0xe0d: 0x0008, 0xe0e: 0x0008, 0xe0f: 0x0008, 0xe10: 0x0008, 0xe11: 0x0008, + 0xe12: 0x0008, 0xe13: 0x0008, 0xe14: 0x0008, 0xe15: 0x0008, 0xe16: 0x0008, 0xe17: 0x0008, + 0xe18: 0x0008, 0xe19: 0x0008, 0xe1a: 0x0008, 0xe1b: 0x0008, 0xe1c: 0x0008, 0xe1d: 0x0008, + 0xe1e: 0x0008, 0xe1f: 0x0008, 0xe20: 0xe00d, 0xe21: 0x0008, 0xe22: 0x0941, 0xe23: 0x0ed5, + 0xe24: 0x0949, 0xe25: 0x0008, 0xe26: 0x0008, 0xe27: 0xe07d, 0xe28: 0x0008, 0xe29: 0xe01d, + 0xe2a: 0x0008, 0xe2b: 0xe03d, 0xe2c: 0x0008, 0xe2d: 0x0359, 0xe2e: 0x0441, 0xe2f: 0x0351, + 0xe30: 0x03d1, 0xe31: 0x0008, 0xe32: 0xe00d, 0xe33: 0x0008, 0xe34: 0x0008, 0xe35: 0xe01d, + 0xe36: 0x0008, 0xe37: 0x0008, 0xe38: 0x0008, 0xe39: 0x0008, 0xe3a: 0x0008, 0xe3b: 0x0008, + 0xe3c: 0x00b1, 0xe3d: 0x0391, 0xe3e: 0x0951, 0xe3f: 0x0959, + // Block 0x39, offset 0xe40 + 0xe40: 0xe00d, 0xe41: 0x0008, 0xe42: 0xe00d, 0xe43: 0x0008, 0xe44: 0xe00d, 0xe45: 0x0008, + 0xe46: 0xe00d, 0xe47: 0x0008, 0xe48: 0xe00d, 0xe49: 0x0008, 0xe4a: 0xe00d, 0xe4b: 0x0008, + 0xe4c: 0xe00d, 0xe4d: 0x0008, 0xe4e: 0xe00d, 0xe4f: 0x0008, 0xe50: 0xe00d, 0xe51: 0x0008, + 0xe52: 0xe00d, 0xe53: 0x0008, 0xe54: 0xe00d, 0xe55: 0x0008, 0xe56: 0xe00d, 0xe57: 0x0008, + 0xe58: 0xe00d, 0xe59: 0x0008, 0xe5a: 0xe00d, 0xe5b: 0x0008, 0xe5c: 0xe00d, 0xe5d: 0x0008, + 0xe5e: 0xe00d, 0xe5f: 0x0008, 0xe60: 0xe00d, 0xe61: 0x0008, 0xe62: 0xe00d, 0xe63: 0x0008, + 0xe64: 0x0008, 0xe65: 0x0018, 0xe66: 0x0018, 0xe67: 0x0018, 0xe68: 0x0018, 0xe69: 0x0018, + 0xe6a: 0x0018, 0xe6b: 0xe03d, 0xe6c: 0x0008, 0xe6d: 0xe01d, 0xe6e: 0x0008, 0xe6f: 0x3308, + 0xe70: 0x3308, 0xe71: 0x3308, 0xe72: 0xe00d, 0xe73: 0x0008, 0xe74: 0x0040, 0xe75: 0x0040, + 0xe76: 0x0040, 0xe77: 0x0040, 0xe78: 0x0040, 0xe79: 0x0018, 0xe7a: 0x0018, 0xe7b: 0x0018, + 0xe7c: 0x0018, 0xe7d: 0x0018, 0xe7e: 0x0018, 0xe7f: 0x0018, + // Block 0x3a, offset 0xe80 + 0xe80: 0x2715, 0xe81: 0x2735, 0xe82: 0x2755, 0xe83: 0x2775, 0xe84: 0x2795, 0xe85: 0x27b5, + 0xe86: 0x27d5, 0xe87: 0x27f5, 0xe88: 0x2815, 0xe89: 0x2835, 0xe8a: 0x2855, 0xe8b: 0x2875, + 0xe8c: 0x2895, 0xe8d: 0x28b5, 0xe8e: 0x28d5, 0xe8f: 0x28f5, 0xe90: 0x2915, 0xe91: 0x2935, + 0xe92: 0x2955, 0xe93: 0x2975, 0xe94: 0x2995, 0xe95: 0x29b5, 0xe96: 0x0040, 0xe97: 0x0040, + 0xe98: 0x0040, 0xe99: 0x0040, 0xe9a: 0x0040, 0xe9b: 0x0040, 0xe9c: 0x0040, 0xe9d: 0x0040, + 0xe9e: 0x0040, 0xe9f: 0x0040, 0xea0: 0x0040, 0xea1: 0x0040, 0xea2: 0x0040, 0xea3: 0x0040, + 0xea4: 0x0040, 0xea5: 0x0040, 0xea6: 0x0040, 0xea7: 0x0040, 0xea8: 0x0040, 0xea9: 0x0040, + 0xeaa: 0x0040, 0xeab: 0x0040, 0xeac: 0x0040, 0xead: 0x0040, 0xeae: 0x0040, 0xeaf: 0x0040, + 0xeb0: 0x0040, 0xeb1: 0x0040, 0xeb2: 0x0040, 0xeb3: 0x0040, 0xeb4: 0x0040, 0xeb5: 0x0040, + 0xeb6: 0x0040, 0xeb7: 0x0040, 0xeb8: 0x0040, 0xeb9: 0x0040, 0xeba: 0x0040, 0xebb: 0x0040, + 0xebc: 0x0040, 0xebd: 0x0040, 0xebe: 0x0040, 0xebf: 0x0040, + // Block 0x3b, offset 0xec0 + 0xec0: 0x000a, 0xec1: 0x0018, 0xec2: 0x0961, 0xec3: 0x0018, 0xec4: 0x0018, 0xec5: 0x0008, + 0xec6: 0x0008, 0xec7: 0x0008, 0xec8: 0x0018, 0xec9: 0x0018, 0xeca: 0x0018, 0xecb: 0x0018, + 0xecc: 0x0018, 0xecd: 0x0018, 0xece: 0x0018, 0xecf: 0x0018, 0xed0: 0x0018, 0xed1: 0x0018, + 0xed2: 0x0018, 0xed3: 0x0018, 0xed4: 0x0018, 0xed5: 0x0018, 0xed6: 0x0018, 0xed7: 0x0018, + 0xed8: 0x0018, 0xed9: 0x0018, 0xeda: 0x0018, 0xedb: 0x0018, 0xedc: 0x0018, 0xedd: 0x0018, + 0xede: 0x0018, 0xedf: 0x0018, 0xee0: 0x0018, 0xee1: 0x0018, 0xee2: 0x0018, 0xee3: 0x0018, + 0xee4: 0x0018, 0xee5: 0x0018, 0xee6: 0x0018, 0xee7: 0x0018, 0xee8: 0x0018, 0xee9: 0x0018, + 0xeea: 0x3308, 0xeeb: 0x3308, 0xeec: 0x3308, 0xeed: 0x3308, 0xeee: 0x3018, 0xeef: 0x3018, + 0xef0: 0x0018, 0xef1: 0x0018, 0xef2: 0x0018, 0xef3: 0x0018, 0xef4: 0x0018, 0xef5: 0x0018, + 0xef6: 0xe125, 0xef7: 0x0018, 0xef8: 0x29d5, 0xef9: 0x29f5, 0xefa: 0x2a15, 0xefb: 0x0018, + 0xefc: 0x0008, 0xefd: 0x0018, 0xefe: 0x0018, 0xeff: 0x0018, + // Block 0x3c, offset 0xf00 + 0xf00: 0x2b55, 0xf01: 0x2b75, 0xf02: 0x2b95, 0xf03: 0x2bb5, 0xf04: 0x2bd5, 0xf05: 0x2bf5, + 0xf06: 0x2bf5, 0xf07: 0x2bf5, 0xf08: 0x2c15, 0xf09: 0x2c15, 0xf0a: 0x2c15, 0xf0b: 0x2c15, + 0xf0c: 0x2c35, 0xf0d: 0x2c35, 0xf0e: 0x2c35, 0xf0f: 0x2c55, 0xf10: 0x2c75, 0xf11: 0x2c75, + 0xf12: 0x2a95, 0xf13: 0x2a95, 0xf14: 0x2c75, 0xf15: 0x2c75, 0xf16: 0x2c95, 0xf17: 0x2c95, + 0xf18: 0x2c75, 0xf19: 0x2c75, 0xf1a: 0x2a95, 0xf1b: 0x2a95, 0xf1c: 0x2c75, 0xf1d: 0x2c75, + 0xf1e: 0x2c55, 0xf1f: 0x2c55, 0xf20: 0x2cb5, 0xf21: 0x2cb5, 0xf22: 0x2cd5, 0xf23: 0x2cd5, + 0xf24: 0x0040, 0xf25: 0x2cf5, 0xf26: 0x2d15, 0xf27: 0x2d35, 0xf28: 0x2d35, 0xf29: 0x2d55, + 0xf2a: 0x2d75, 0xf2b: 0x2d95, 0xf2c: 0x2db5, 0xf2d: 0x2dd5, 0xf2e: 0x2df5, 0xf2f: 0x2e15, + 0xf30: 0x2e35, 0xf31: 0x2e55, 0xf32: 0x2e55, 0xf33: 0x2e75, 0xf34: 0x2e95, 0xf35: 0x2e95, + 0xf36: 0x2eb5, 0xf37: 0x2ed5, 0xf38: 0x2e75, 0xf39: 0x2ef5, 0xf3a: 0x2f15, 0xf3b: 0x2ef5, + 0xf3c: 0x2e75, 0xf3d: 0x2f35, 0xf3e: 0x2f55, 0xf3f: 0x2f75, + // Block 0x3d, offset 0xf40 + 0xf40: 0x2f95, 0xf41: 0x2fb5, 0xf42: 0x2d15, 0xf43: 0x2cf5, 0xf44: 0x2fd5, 0xf45: 0x2ff5, + 0xf46: 0x3015, 0xf47: 0x3035, 0xf48: 0x3055, 0xf49: 0x3075, 0xf4a: 0x3095, 0xf4b: 0x30b5, + 0xf4c: 0x30d5, 0xf4d: 0x30f5, 0xf4e: 0x3115, 0xf4f: 0x0040, 0xf50: 0x0018, 0xf51: 0x0018, + 0xf52: 0x3135, 0xf53: 0x3155, 0xf54: 0x3175, 0xf55: 0x3195, 0xf56: 0x31b5, 0xf57: 0x31d5, + 0xf58: 0x31f5, 0xf59: 0x3215, 0xf5a: 0x3235, 0xf5b: 0x3255, 0xf5c: 0x3175, 0xf5d: 0x3275, + 0xf5e: 0x3295, 0xf5f: 0x32b5, 0xf60: 0x0008, 0xf61: 0x0008, 0xf62: 0x0008, 0xf63: 0x0008, + 0xf64: 0x0008, 0xf65: 0x0008, 0xf66: 0x0008, 0xf67: 0x0008, 0xf68: 0x0008, 0xf69: 0x0008, + 0xf6a: 0x0008, 0xf6b: 0x0008, 0xf6c: 0x0008, 0xf6d: 0x0008, 0xf6e: 0x0008, 0xf6f: 0x0008, + 0xf70: 0x0008, 0xf71: 0x0008, 0xf72: 0x0008, 0xf73: 0x0008, 0xf74: 0x0008, 0xf75: 0x0008, + 0xf76: 0x0008, 0xf77: 0x0008, 0xf78: 0x0008, 0xf79: 0x0008, 0xf7a: 0x0008, 0xf7b: 0x0008, + 0xf7c: 0x0008, 0xf7d: 0x0008, 0xf7e: 0x0008, 0xf7f: 0x0008, + // Block 0x3e, offset 0xf80 + 0xf80: 0x0b82, 0xf81: 0x0b8a, 0xf82: 0x0b92, 0xf83: 0x0b9a, 0xf84: 0x32d5, 0xf85: 0x32f5, + 0xf86: 0x3315, 0xf87: 0x3335, 0xf88: 0x0018, 0xf89: 0x0018, 0xf8a: 0x0018, 0xf8b: 0x0018, + 0xf8c: 0x0018, 0xf8d: 0x0018, 0xf8e: 0x0018, 0xf8f: 0x0018, 0xf90: 0x3355, 0xf91: 0x0ba1, + 0xf92: 0x0ba9, 0xf93: 0x0bb1, 0xf94: 0x0bb9, 0xf95: 0x0bc1, 0xf96: 0x0bc9, 0xf97: 0x0bd1, + 0xf98: 0x0bd9, 0xf99: 0x0be1, 0xf9a: 0x0be9, 0xf9b: 0x0bf1, 0xf9c: 0x0bf9, 0xf9d: 0x0c01, + 0xf9e: 0x0c09, 0xf9f: 0x0c11, 0xfa0: 0x3375, 0xfa1: 0x3395, 0xfa2: 0x33b5, 0xfa3: 0x33d5, + 0xfa4: 0x33f5, 0xfa5: 0x33f5, 0xfa6: 0x3415, 0xfa7: 0x3435, 0xfa8: 0x3455, 0xfa9: 0x3475, + 0xfaa: 0x3495, 0xfab: 0x34b5, 0xfac: 0x34d5, 0xfad: 0x34f5, 0xfae: 0x3515, 0xfaf: 0x3535, + 0xfb0: 0x3555, 0xfb1: 0x3575, 0xfb2: 0x3595, 0xfb3: 0x35b5, 0xfb4: 0x35d5, 0xfb5: 0x35f5, + 0xfb6: 0x3615, 0xfb7: 0x3635, 0xfb8: 0x3655, 0xfb9: 0x3675, 0xfba: 0x3695, 0xfbb: 0x36b5, + 0xfbc: 0x0c19, 0xfbd: 0x0c21, 0xfbe: 0x36d5, 0xfbf: 0x0018, + // Block 0x3f, offset 0xfc0 + 0xfc0: 0x36f5, 0xfc1: 0x3715, 0xfc2: 0x3735, 0xfc3: 0x3755, 0xfc4: 0x3775, 0xfc5: 0x3795, + 0xfc6: 0x37b5, 0xfc7: 0x37d5, 0xfc8: 0x37f5, 0xfc9: 0x3815, 0xfca: 0x3835, 0xfcb: 0x3855, + 0xfcc: 0x3875, 0xfcd: 0x3895, 0xfce: 0x38b5, 0xfcf: 0x38d5, 0xfd0: 0x38f5, 0xfd1: 0x3915, + 0xfd2: 0x3935, 0xfd3: 0x3955, 0xfd4: 0x3975, 0xfd5: 0x3995, 0xfd6: 0x39b5, 0xfd7: 0x39d5, + 0xfd8: 0x39f5, 0xfd9: 0x3a15, 0xfda: 0x3a35, 0xfdb: 0x3a55, 0xfdc: 0x3a75, 0xfdd: 0x3a95, + 0xfde: 0x3ab5, 0xfdf: 0x3ad5, 0xfe0: 0x3af5, 0xfe1: 0x3b15, 0xfe2: 0x3b35, 0xfe3: 0x3b55, + 0xfe4: 0x3b75, 0xfe5: 0x3b95, 0xfe6: 0x1295, 0xfe7: 0x3bb5, 0xfe8: 0x3bd5, 0xfe9: 0x3bf5, + 0xfea: 0x3c15, 0xfeb: 0x3c35, 0xfec: 0x3c55, 0xfed: 0x3c75, 0xfee: 0x23b5, 0xfef: 0x3c95, + 0xff0: 0x3cb5, 0xff1: 0x0c29, 0xff2: 0x0c31, 0xff3: 0x0c39, 0xff4: 0x0c41, 0xff5: 0x0c49, + 0xff6: 0x0c51, 0xff7: 0x0c59, 0xff8: 0x0c61, 0xff9: 0x0c69, 0xffa: 0x0c71, 0xffb: 0x0c79, + 0xffc: 0x0c81, 0xffd: 0x0c89, 0xffe: 0x0c91, 0xfff: 0x0c99, + // Block 0x40, offset 0x1000 + 0x1000: 0x0ca1, 0x1001: 0x0ca9, 0x1002: 0x0cb1, 0x1003: 0x0cb9, 0x1004: 0x0cc1, 0x1005: 0x0cc9, + 0x1006: 0x0cd1, 0x1007: 0x0cd9, 0x1008: 0x0ce1, 0x1009: 0x0ce9, 0x100a: 0x0cf1, 0x100b: 0x0cf9, + 0x100c: 0x0d01, 0x100d: 0x3cd5, 0x100e: 0x0d09, 0x100f: 0x3cf5, 0x1010: 0x3d15, 0x1011: 0x3d2d, + 0x1012: 0x3d45, 0x1013: 0x3d5d, 0x1014: 0x3d75, 0x1015: 0x3d75, 0x1016: 0x3d5d, 0x1017: 0x3d8d, + 0x1018: 0x07d5, 0x1019: 0x3da5, 0x101a: 0x3dbd, 0x101b: 0x3dd5, 0x101c: 0x3ded, 0x101d: 0x3e05, + 0x101e: 0x3e1d, 0x101f: 0x3e35, 0x1020: 0x3e4d, 0x1021: 0x3e65, 0x1022: 0x3e7d, 0x1023: 0x3e95, + 0x1024: 0x3ead, 0x1025: 0x3ead, 0x1026: 0x3ec5, 0x1027: 0x3ec5, 0x1028: 0x3edd, 0x1029: 0x3edd, + 0x102a: 0x3ef5, 0x102b: 0x3f0d, 0x102c: 0x3f25, 0x102d: 0x3f3d, 0x102e: 0x3f55, 0x102f: 0x3f55, + 0x1030: 0x3f6d, 0x1031: 0x3f6d, 0x1032: 0x3f6d, 0x1033: 0x3f85, 0x1034: 0x3f9d, 0x1035: 0x3fb5, + 0x1036: 0x3fcd, 0x1037: 0x3fb5, 0x1038: 0x3fe5, 0x1039: 0x3ffd, 0x103a: 0x3f85, 0x103b: 0x4015, + 0x103c: 0x402d, 0x103d: 0x402d, 0x103e: 0x402d, 0x103f: 0x0d11, + // Block 0x41, offset 0x1040 + 0x1040: 0x10f9, 0x1041: 0x1101, 0x1042: 0x40a5, 0x1043: 0x1109, 0x1044: 0x1111, 0x1045: 0x1119, + 0x1046: 0x1121, 0x1047: 0x1129, 0x1048: 0x40c5, 0x1049: 0x1131, 0x104a: 0x1139, 0x104b: 0x1141, + 0x104c: 0x40e5, 0x104d: 0x40e5, 0x104e: 0x1149, 0x104f: 0x1151, 0x1050: 0x1159, 0x1051: 0x4105, + 0x1052: 0x4125, 0x1053: 0x4145, 0x1054: 0x4165, 0x1055: 0x4185, 0x1056: 0x1161, 0x1057: 0x1169, + 0x1058: 0x1171, 0x1059: 0x1179, 0x105a: 0x1181, 0x105b: 0x41a5, 0x105c: 0x1189, 0x105d: 0x1191, + 0x105e: 0x1199, 0x105f: 0x41c5, 0x1060: 0x41e5, 0x1061: 0x11a1, 0x1062: 0x4205, 0x1063: 0x4225, + 0x1064: 0x4245, 0x1065: 0x11a9, 0x1066: 0x4265, 0x1067: 0x11b1, 0x1068: 0x11b9, 0x1069: 0x10f9, + 0x106a: 0x4285, 0x106b: 0x42a5, 0x106c: 0x42c5, 0x106d: 0x42e5, 0x106e: 0x11c1, 0x106f: 0x11c9, + 0x1070: 0x11d1, 0x1071: 0x11d9, 0x1072: 0x4305, 0x1073: 0x11e1, 0x1074: 0x11e9, 0x1075: 0x11f1, + 0x1076: 0x4325, 0x1077: 0x11f9, 0x1078: 0x1201, 0x1079: 0x11f9, 0x107a: 0x1209, 0x107b: 0x1211, + 0x107c: 0x4345, 0x107d: 0x1219, 0x107e: 0x1221, 0x107f: 0x1219, + // Block 0x42, offset 0x1080 + 0x1080: 0x4365, 0x1081: 0x4385, 0x1082: 0x0040, 0x1083: 0x1229, 0x1084: 0x1231, 0x1085: 0x1239, + 0x1086: 0x1241, 0x1087: 0x0040, 0x1088: 0x1249, 0x1089: 0x1251, 0x108a: 0x1259, 0x108b: 0x1261, + 0x108c: 0x1269, 0x108d: 0x1271, 0x108e: 0x1199, 0x108f: 0x1279, 0x1090: 0x1281, 0x1091: 0x1289, + 0x1092: 0x43a5, 0x1093: 0x1291, 0x1094: 0x1121, 0x1095: 0x43c5, 0x1096: 0x43e5, 0x1097: 0x1299, + 0x1098: 0x0040, 0x1099: 0x4405, 0x109a: 0x12a1, 0x109b: 0x12a9, 0x109c: 0x12b1, 0x109d: 0x12b9, + 0x109e: 0x12c1, 0x109f: 0x12c9, 0x10a0: 0x12d1, 0x10a1: 0x12d9, 0x10a2: 0x12e1, 0x10a3: 0x12e9, + 0x10a4: 0x12f1, 0x10a5: 0x12f9, 0x10a6: 0x1301, 0x10a7: 0x1309, 0x10a8: 0x1311, 0x10a9: 0x1319, + 0x10aa: 0x1321, 0x10ab: 0x1329, 0x10ac: 0x1331, 0x10ad: 0x1339, 0x10ae: 0x1341, 0x10af: 0x1349, + 0x10b0: 0x1351, 0x10b1: 0x1359, 0x10b2: 0x1361, 0x10b3: 0x1369, 0x10b4: 0x1371, 0x10b5: 0x1379, + 0x10b6: 0x1381, 0x10b7: 0x1389, 0x10b8: 0x1391, 0x10b9: 0x1399, 0x10ba: 0x13a1, 0x10bb: 0x13a9, + 0x10bc: 0x13b1, 0x10bd: 0x13b9, 0x10be: 0x13c1, 0x10bf: 0x4425, + // Block 0x43, offset 0x10c0 + 0x10c0: 0xe00d, 0x10c1: 0x0008, 0x10c2: 0xe00d, 0x10c3: 0x0008, 0x10c4: 0xe00d, 0x10c5: 0x0008, + 0x10c6: 0xe00d, 0x10c7: 0x0008, 0x10c8: 0xe00d, 0x10c9: 0x0008, 0x10ca: 0xe00d, 0x10cb: 0x0008, + 0x10cc: 0xe00d, 0x10cd: 0x0008, 0x10ce: 0xe00d, 0x10cf: 0x0008, 0x10d0: 0xe00d, 0x10d1: 0x0008, + 0x10d2: 0xe00d, 0x10d3: 0x0008, 0x10d4: 0xe00d, 0x10d5: 0x0008, 0x10d6: 0xe00d, 0x10d7: 0x0008, + 0x10d8: 0xe00d, 0x10d9: 0x0008, 0x10da: 0xe00d, 0x10db: 0x0008, 0x10dc: 0xe00d, 0x10dd: 0x0008, + 0x10de: 0xe00d, 0x10df: 0x0008, 0x10e0: 0xe00d, 0x10e1: 0x0008, 0x10e2: 0xe00d, 0x10e3: 0x0008, + 0x10e4: 0xe00d, 0x10e5: 0x0008, 0x10e6: 0xe00d, 0x10e7: 0x0008, 0x10e8: 0xe00d, 0x10e9: 0x0008, + 0x10ea: 0xe00d, 0x10eb: 0x0008, 0x10ec: 0xe00d, 0x10ed: 0x0008, 0x10ee: 0x0008, 0x10ef: 0x3308, + 0x10f0: 0x3318, 0x10f1: 0x3318, 0x10f2: 0x3318, 0x10f3: 0x0018, 0x10f4: 0x3308, 0x10f5: 0x3308, + 0x10f6: 0x3308, 0x10f7: 0x3308, 0x10f8: 0x3308, 0x10f9: 0x3308, 0x10fa: 0x3308, 0x10fb: 0x3308, + 0x10fc: 0x3308, 0x10fd: 0x3308, 0x10fe: 0x0018, 0x10ff: 0x0008, + // Block 0x44, offset 0x1100 + 0x1100: 0xe00d, 0x1101: 0x0008, 0x1102: 0xe00d, 0x1103: 0x0008, 0x1104: 0xe00d, 0x1105: 0x0008, + 0x1106: 0xe00d, 0x1107: 0x0008, 0x1108: 0xe00d, 0x1109: 0x0008, 0x110a: 0xe00d, 0x110b: 0x0008, + 0x110c: 0xe00d, 0x110d: 0x0008, 0x110e: 0xe00d, 0x110f: 0x0008, 0x1110: 0xe00d, 0x1111: 0x0008, + 0x1112: 0xe00d, 0x1113: 0x0008, 0x1114: 0xe00d, 0x1115: 0x0008, 0x1116: 0xe00d, 0x1117: 0x0008, + 0x1118: 0xe00d, 0x1119: 0x0008, 0x111a: 0xe00d, 0x111b: 0x0008, 0x111c: 0x02d1, 0x111d: 0x13c9, + 0x111e: 0x3308, 0x111f: 0x3308, 0x1120: 0x0008, 0x1121: 0x0008, 0x1122: 0x0008, 0x1123: 0x0008, + 0x1124: 0x0008, 0x1125: 0x0008, 0x1126: 0x0008, 0x1127: 0x0008, 0x1128: 0x0008, 0x1129: 0x0008, + 0x112a: 0x0008, 0x112b: 0x0008, 0x112c: 0x0008, 0x112d: 0x0008, 0x112e: 0x0008, 0x112f: 0x0008, + 0x1130: 0x0008, 0x1131: 0x0008, 0x1132: 0x0008, 0x1133: 0x0008, 0x1134: 0x0008, 0x1135: 0x0008, + 0x1136: 0x0008, 0x1137: 0x0008, 0x1138: 0x0008, 0x1139: 0x0008, 0x113a: 0x0008, 0x113b: 0x0008, + 0x113c: 0x0008, 0x113d: 0x0008, 0x113e: 0x0008, 0x113f: 0x0008, + // Block 0x45, offset 0x1140 + 0x1140: 0x0018, 0x1141: 0x0018, 0x1142: 0x0018, 0x1143: 0x0018, 0x1144: 0x0018, 0x1145: 0x0018, + 0x1146: 0x0018, 0x1147: 0x0018, 0x1148: 0x0018, 0x1149: 0x0018, 0x114a: 0x0018, 0x114b: 0x0018, + 0x114c: 0x0018, 0x114d: 0x0018, 0x114e: 0x0018, 0x114f: 0x0018, 0x1150: 0x0018, 0x1151: 0x0018, + 0x1152: 0x0018, 0x1153: 0x0018, 0x1154: 0x0018, 0x1155: 0x0018, 0x1156: 0x0018, 0x1157: 0x0008, + 0x1158: 0x0008, 0x1159: 0x0008, 0x115a: 0x0008, 0x115b: 0x0008, 0x115c: 0x0008, 0x115d: 0x0008, + 0x115e: 0x0008, 0x115f: 0x0008, 0x1160: 0x0018, 0x1161: 0x0018, 0x1162: 0xe00d, 0x1163: 0x0008, + 0x1164: 0xe00d, 0x1165: 0x0008, 0x1166: 0xe00d, 0x1167: 0x0008, 0x1168: 0xe00d, 0x1169: 0x0008, + 0x116a: 0xe00d, 0x116b: 0x0008, 0x116c: 0xe00d, 0x116d: 0x0008, 0x116e: 0xe00d, 0x116f: 0x0008, + 0x1170: 0x0008, 0x1171: 0x0008, 0x1172: 0xe00d, 0x1173: 0x0008, 0x1174: 0xe00d, 0x1175: 0x0008, + 0x1176: 0xe00d, 0x1177: 0x0008, 0x1178: 0xe00d, 0x1179: 0x0008, 0x117a: 0xe00d, 0x117b: 0x0008, + 0x117c: 0xe00d, 0x117d: 0x0008, 0x117e: 0xe00d, 0x117f: 0x0008, + // Block 0x46, offset 0x1180 + 0x1180: 0xe00d, 0x1181: 0x0008, 0x1182: 0xe00d, 0x1183: 0x0008, 0x1184: 0xe00d, 0x1185: 0x0008, + 0x1186: 0xe00d, 0x1187: 0x0008, 0x1188: 0xe00d, 0x1189: 0x0008, 0x118a: 0xe00d, 0x118b: 0x0008, + 0x118c: 0xe00d, 0x118d: 0x0008, 0x118e: 0xe00d, 0x118f: 0x0008, 0x1190: 0xe00d, 0x1191: 0x0008, + 0x1192: 0xe00d, 0x1193: 0x0008, 0x1194: 0xe00d, 0x1195: 0x0008, 0x1196: 0xe00d, 0x1197: 0x0008, + 0x1198: 0xe00d, 0x1199: 0x0008, 0x119a: 0xe00d, 0x119b: 0x0008, 0x119c: 0xe00d, 0x119d: 0x0008, + 0x119e: 0xe00d, 0x119f: 0x0008, 0x11a0: 0xe00d, 0x11a1: 0x0008, 0x11a2: 0xe00d, 0x11a3: 0x0008, + 0x11a4: 0xe00d, 0x11a5: 0x0008, 0x11a6: 0xe00d, 0x11a7: 0x0008, 0x11a8: 0xe00d, 0x11a9: 0x0008, + 0x11aa: 0xe00d, 0x11ab: 0x0008, 0x11ac: 0xe00d, 0x11ad: 0x0008, 0x11ae: 0xe00d, 0x11af: 0x0008, + 0x11b0: 0xe0fd, 0x11b1: 0x0008, 0x11b2: 0x0008, 0x11b3: 0x0008, 0x11b4: 0x0008, 0x11b5: 0x0008, + 0x11b6: 0x0008, 0x11b7: 0x0008, 0x11b8: 0x0008, 0x11b9: 0xe01d, 0x11ba: 0x0008, 0x11bb: 0xe03d, + 0x11bc: 0x0008, 0x11bd: 0x4445, 0x11be: 0xe00d, 0x11bf: 0x0008, + // Block 0x47, offset 0x11c0 + 0x11c0: 0xe00d, 0x11c1: 0x0008, 0x11c2: 0xe00d, 0x11c3: 0x0008, 0x11c4: 0xe00d, 0x11c5: 0x0008, + 0x11c6: 0xe00d, 0x11c7: 0x0008, 0x11c8: 0x0008, 0x11c9: 0x0018, 0x11ca: 0x0018, 0x11cb: 0xe03d, + 0x11cc: 0x0008, 0x11cd: 0x0409, 0x11ce: 0x0008, 0x11cf: 0x0008, 0x11d0: 0xe00d, 0x11d1: 0x0008, + 0x11d2: 0xe00d, 0x11d3: 0x0008, 0x11d4: 0x0008, 0x11d5: 0x0008, 0x11d6: 0xe00d, 0x11d7: 0x0008, + 0x11d8: 0xe00d, 0x11d9: 0x0008, 0x11da: 0xe00d, 0x11db: 0x0008, 0x11dc: 0xe00d, 0x11dd: 0x0008, + 0x11de: 0xe00d, 0x11df: 0x0008, 0x11e0: 0xe00d, 0x11e1: 0x0008, 0x11e2: 0xe00d, 0x11e3: 0x0008, + 0x11e4: 0xe00d, 0x11e5: 0x0008, 0x11e6: 0xe00d, 0x11e7: 0x0008, 0x11e8: 0xe00d, 0x11e9: 0x0008, + 0x11ea: 0x13d1, 0x11eb: 0x0371, 0x11ec: 0x0401, 0x11ed: 0x13d9, 0x11ee: 0x0421, 0x11ef: 0x0008, + 0x11f0: 0x13e1, 0x11f1: 0x13e9, 0x11f2: 0x0429, 0x11f3: 0x4465, 0x11f4: 0xe00d, 0x11f5: 0x0008, + 0x11f6: 0xe00d, 0x11f7: 0x0008, 0x11f8: 0xe00d, 0x11f9: 0x0008, 0x11fa: 0xe00d, 0x11fb: 0x0008, + 0x11fc: 0xe00d, 0x11fd: 0x0008, 0x11fe: 0xe00d, 0x11ff: 0x0008, + // Block 0x48, offset 0x1200 + 0x1200: 0xe00d, 0x1201: 0x0008, 0x1202: 0xe00d, 0x1203: 0x0008, 0x1204: 0x03f5, 0x1205: 0x0479, + 0x1206: 0x447d, 0x1207: 0xe07d, 0x1208: 0x0008, 0x1209: 0xe01d, 0x120a: 0x0008, 0x120b: 0x0040, + 0x120c: 0x0040, 0x120d: 0x0040, 0x120e: 0x0040, 0x120f: 0x0040, 0x1210: 0xe00d, 0x1211: 0x0008, + 0x1212: 0x0040, 0x1213: 0x0008, 0x1214: 0x0040, 0x1215: 0x0008, 0x1216: 0xe00d, 0x1217: 0x0008, + 0x1218: 0xe00d, 0x1219: 0x0008, 0x121a: 0x0040, 0x121b: 0x0040, 0x121c: 0x0040, 0x121d: 0x0040, + 0x121e: 0x0040, 0x121f: 0x0040, 0x1220: 0x0040, 0x1221: 0x0040, 0x1222: 0x0040, 0x1223: 0x0040, + 0x1224: 0x0040, 0x1225: 0x0040, 0x1226: 0x0040, 0x1227: 0x0040, 0x1228: 0x0040, 0x1229: 0x0040, + 0x122a: 0x0040, 0x122b: 0x0040, 0x122c: 0x0040, 0x122d: 0x0040, 0x122e: 0x0040, 0x122f: 0x0040, + 0x1230: 0x0040, 0x1231: 0x0040, 0x1232: 0x03d9, 0x1233: 0x03f1, 0x1234: 0x0751, 0x1235: 0xe01d, + 0x1236: 0x0008, 0x1237: 0x0008, 0x1238: 0x0741, 0x1239: 0x13f1, 0x123a: 0x0008, 0x123b: 0x0008, + 0x123c: 0x0008, 0x123d: 0x0008, 0x123e: 0x0008, 0x123f: 0x0008, + // Block 0x49, offset 0x1240 + 0x1240: 0x650d, 0x1241: 0x652d, 0x1242: 0x654d, 0x1243: 0x656d, 0x1244: 0x658d, 0x1245: 0x65ad, + 0x1246: 0x65cd, 0x1247: 0x65ed, 0x1248: 0x660d, 0x1249: 0x662d, 0x124a: 0x664d, 0x124b: 0x666d, + 0x124c: 0x668d, 0x124d: 0x66ad, 0x124e: 0x0008, 0x124f: 0x0008, 0x1250: 0x66cd, 0x1251: 0x0008, + 0x1252: 0x66ed, 0x1253: 0x0008, 0x1254: 0x0008, 0x1255: 0x670d, 0x1256: 0x672d, 0x1257: 0x674d, + 0x1258: 0x676d, 0x1259: 0x678d, 0x125a: 0x67ad, 0x125b: 0x67cd, 0x125c: 0x67ed, 0x125d: 0x680d, + 0x125e: 0x682d, 0x125f: 0x0008, 0x1260: 0x684d, 0x1261: 0x0008, 0x1262: 0x686d, 0x1263: 0x0008, + 0x1264: 0x0008, 0x1265: 0x688d, 0x1266: 0x68ad, 0x1267: 0x0008, 0x1268: 0x0008, 0x1269: 0x0008, + 0x126a: 0x68cd, 0x126b: 0x68ed, 0x126c: 0x690d, 0x126d: 0x692d, 0x126e: 0x694d, 0x126f: 0x696d, + 0x1270: 0x698d, 0x1271: 0x69ad, 0x1272: 0x69cd, 0x1273: 0x69ed, 0x1274: 0x6a0d, 0x1275: 0x6a2d, + 0x1276: 0x6a4d, 0x1277: 0x6a6d, 0x1278: 0x6a8d, 0x1279: 0x6aad, 0x127a: 0x6acd, 0x127b: 0x6aed, + 0x127c: 0x6b0d, 0x127d: 0x6b2d, 0x127e: 0x6b4d, 0x127f: 0x6b6d, + // Block 0x4a, offset 0x1280 + 0x1280: 0x7acd, 0x1281: 0x7aed, 0x1282: 0x7b0d, 0x1283: 0x7b2d, 0x1284: 0x7b4d, 0x1285: 0x7b6d, + 0x1286: 0x7b8d, 0x1287: 0x7bad, 0x1288: 0x7bcd, 0x1289: 0x7bed, 0x128a: 0x7c0d, 0x128b: 0x7c2d, + 0x128c: 0x7c4d, 0x128d: 0x7c6d, 0x128e: 0x7c8d, 0x128f: 0x1409, 0x1290: 0x1411, 0x1291: 0x1419, + 0x1292: 0x7cad, 0x1293: 0x7ccd, 0x1294: 0x7ced, 0x1295: 0x1421, 0x1296: 0x1429, 0x1297: 0x1431, + 0x1298: 0x7d0d, 0x1299: 0x7d2d, 0x129a: 0x0040, 0x129b: 0x0040, 0x129c: 0x0040, 0x129d: 0x0040, + 0x129e: 0x0040, 0x129f: 0x0040, 0x12a0: 0x0040, 0x12a1: 0x0040, 0x12a2: 0x0040, 0x12a3: 0x0040, + 0x12a4: 0x0040, 0x12a5: 0x0040, 0x12a6: 0x0040, 0x12a7: 0x0040, 0x12a8: 0x0040, 0x12a9: 0x0040, + 0x12aa: 0x0040, 0x12ab: 0x0040, 0x12ac: 0x0040, 0x12ad: 0x0040, 0x12ae: 0x0040, 0x12af: 0x0040, + 0x12b0: 0x0040, 0x12b1: 0x0040, 0x12b2: 0x0040, 0x12b3: 0x0040, 0x12b4: 0x0040, 0x12b5: 0x0040, + 0x12b6: 0x0040, 0x12b7: 0x0040, 0x12b8: 0x0040, 0x12b9: 0x0040, 0x12ba: 0x0040, 0x12bb: 0x0040, + 0x12bc: 0x0040, 0x12bd: 0x0040, 0x12be: 0x0040, 0x12bf: 0x0040, + // Block 0x4b, offset 0x12c0 + 0x12c0: 0x1439, 0x12c1: 0x1441, 0x12c2: 0x1449, 0x12c3: 0x7d4d, 0x12c4: 0x7d6d, 0x12c5: 0x1451, + 0x12c6: 0x1451, 0x12c7: 0x0040, 0x12c8: 0x0040, 0x12c9: 0x0040, 0x12ca: 0x0040, 0x12cb: 0x0040, + 0x12cc: 0x0040, 0x12cd: 0x0040, 0x12ce: 0x0040, 0x12cf: 0x0040, 0x12d0: 0x0040, 0x12d1: 0x0040, + 0x12d2: 0x0040, 0x12d3: 0x1459, 0x12d4: 0x1461, 0x12d5: 0x1469, 0x12d6: 0x1471, 0x12d7: 0x1479, + 0x12d8: 0x0040, 0x12d9: 0x0040, 0x12da: 0x0040, 0x12db: 0x0040, 0x12dc: 0x0040, 0x12dd: 0x1481, + 0x12de: 0x3308, 0x12df: 0x1489, 0x12e0: 0x1491, 0x12e1: 0x0779, 0x12e2: 0x0791, 0x12e3: 0x1499, + 0x12e4: 0x14a1, 0x12e5: 0x14a9, 0x12e6: 0x14b1, 0x12e7: 0x14b9, 0x12e8: 0x14c1, 0x12e9: 0x071a, + 0x12ea: 0x14c9, 0x12eb: 0x14d1, 0x12ec: 0x14d9, 0x12ed: 0x14e1, 0x12ee: 0x14e9, 0x12ef: 0x14f1, + 0x12f0: 0x14f9, 0x12f1: 0x1501, 0x12f2: 0x1509, 0x12f3: 0x1511, 0x12f4: 0x1519, 0x12f5: 0x1521, + 0x12f6: 0x1529, 0x12f7: 0x0040, 0x12f8: 0x1531, 0x12f9: 0x1539, 0x12fa: 0x1541, 0x12fb: 0x1549, + 0x12fc: 0x1551, 0x12fd: 0x0040, 0x12fe: 0x1559, 0x12ff: 0x0040, + // Block 0x4c, offset 0x1300 + 0x1300: 0x1561, 0x1301: 0x1569, 0x1302: 0x0040, 0x1303: 0x1571, 0x1304: 0x1579, 0x1305: 0x0040, + 0x1306: 0x1581, 0x1307: 0x1589, 0x1308: 0x1591, 0x1309: 0x1599, 0x130a: 0x15a1, 0x130b: 0x15a9, + 0x130c: 0x15b1, 0x130d: 0x15b9, 0x130e: 0x15c1, 0x130f: 0x15c9, 0x1310: 0x15d1, 0x1311: 0x15d1, + 0x1312: 0x15d9, 0x1313: 0x15d9, 0x1314: 0x15d9, 0x1315: 0x15d9, 0x1316: 0x15e1, 0x1317: 0x15e1, + 0x1318: 0x15e1, 0x1319: 0x15e1, 0x131a: 0x15e9, 0x131b: 0x15e9, 0x131c: 0x15e9, 0x131d: 0x15e9, + 0x131e: 0x15f1, 0x131f: 0x15f1, 0x1320: 0x15f1, 0x1321: 0x15f1, 0x1322: 0x15f9, 0x1323: 0x15f9, + 0x1324: 0x15f9, 0x1325: 0x15f9, 0x1326: 0x1601, 0x1327: 0x1601, 0x1328: 0x1601, 0x1329: 0x1601, + 0x132a: 0x1609, 0x132b: 0x1609, 0x132c: 0x1609, 0x132d: 0x1609, 0x132e: 0x1611, 0x132f: 0x1611, + 0x1330: 0x1611, 0x1331: 0x1611, 0x1332: 0x1619, 0x1333: 0x1619, 0x1334: 0x1619, 0x1335: 0x1619, + 0x1336: 0x1621, 0x1337: 0x1621, 0x1338: 0x1621, 0x1339: 0x1621, 0x133a: 0x1629, 0x133b: 0x1629, + 0x133c: 0x1629, 0x133d: 0x1629, 0x133e: 0x1631, 0x133f: 0x1631, + // Block 0x4d, offset 0x1340 + 0x1340: 0x1631, 0x1341: 0x1631, 0x1342: 0x1639, 0x1343: 0x1639, 0x1344: 0x1641, 0x1345: 0x1641, + 0x1346: 0x1649, 0x1347: 0x1649, 0x1348: 0x1651, 0x1349: 0x1651, 0x134a: 0x1659, 0x134b: 0x1659, + 0x134c: 0x1661, 0x134d: 0x1661, 0x134e: 0x1669, 0x134f: 0x1669, 0x1350: 0x1669, 0x1351: 0x1669, + 0x1352: 0x1671, 0x1353: 0x1671, 0x1354: 0x1671, 0x1355: 0x1671, 0x1356: 0x1679, 0x1357: 0x1679, + 0x1358: 0x1679, 0x1359: 0x1679, 0x135a: 0x1681, 0x135b: 0x1681, 0x135c: 0x1681, 0x135d: 0x1681, + 0x135e: 0x1689, 0x135f: 0x1689, 0x1360: 0x1691, 0x1361: 0x1691, 0x1362: 0x1691, 0x1363: 0x1691, + 0x1364: 0x1699, 0x1365: 0x1699, 0x1366: 0x16a1, 0x1367: 0x16a1, 0x1368: 0x16a1, 0x1369: 0x16a1, + 0x136a: 0x16a9, 0x136b: 0x16a9, 0x136c: 0x16a9, 0x136d: 0x16a9, 0x136e: 0x16b1, 0x136f: 0x16b1, + 0x1370: 0x16b9, 0x1371: 0x16b9, 0x1372: 0x0818, 0x1373: 0x0818, 0x1374: 0x0818, 0x1375: 0x0818, + 0x1376: 0x0818, 0x1377: 0x0818, 0x1378: 0x0818, 0x1379: 0x0818, 0x137a: 0x0818, 0x137b: 0x0818, + 0x137c: 0x0818, 0x137d: 0x0818, 0x137e: 0x0818, 0x137f: 0x0818, + // Block 0x4e, offset 0x1380 + 0x1380: 0x0818, 0x1381: 0x0818, 0x1382: 0x0818, 0x1383: 0x0040, 0x1384: 0x0040, 0x1385: 0x0040, + 0x1386: 0x0040, 0x1387: 0x0040, 0x1388: 0x0040, 0x1389: 0x0040, 0x138a: 0x0040, 0x138b: 0x0040, + 0x138c: 0x0040, 0x138d: 0x0040, 0x138e: 0x0040, 0x138f: 0x0040, 0x1390: 0x0040, 0x1391: 0x0040, + 0x1392: 0x0040, 0x1393: 0x16c1, 0x1394: 0x16c1, 0x1395: 0x16c1, 0x1396: 0x16c1, 0x1397: 0x16c9, + 0x1398: 0x16c9, 0x1399: 0x16d1, 0x139a: 0x16d1, 0x139b: 0x16d9, 0x139c: 0x16d9, 0x139d: 0x0149, + 0x139e: 0x16e1, 0x139f: 0x16e1, 0x13a0: 0x16e9, 0x13a1: 0x16e9, 0x13a2: 0x16f1, 0x13a3: 0x16f1, + 0x13a4: 0x16f9, 0x13a5: 0x16f9, 0x13a6: 0x16f9, 0x13a7: 0x16f9, 0x13a8: 0x1701, 0x13a9: 0x1701, + 0x13aa: 0x1709, 0x13ab: 0x1709, 0x13ac: 0x1711, 0x13ad: 0x1711, 0x13ae: 0x1719, 0x13af: 0x1719, + 0x13b0: 0x1721, 0x13b1: 0x1721, 0x13b2: 0x1729, 0x13b3: 0x1729, 0x13b4: 0x1731, 0x13b5: 0x1731, + 0x13b6: 0x1739, 0x13b7: 0x1739, 0x13b8: 0x1739, 0x13b9: 0x1741, 0x13ba: 0x1741, 0x13bb: 0x1741, + 0x13bc: 0x1749, 0x13bd: 0x1749, 0x13be: 0x1749, 0x13bf: 0x1749, + // Block 0x4f, offset 0x13c0 + 0x13c0: 0x1949, 0x13c1: 0x1951, 0x13c2: 0x1959, 0x13c3: 0x1961, 0x13c4: 0x1969, 0x13c5: 0x1971, + 0x13c6: 0x1979, 0x13c7: 0x1981, 0x13c8: 0x1989, 0x13c9: 0x1991, 0x13ca: 0x1999, 0x13cb: 0x19a1, + 0x13cc: 0x19a9, 0x13cd: 0x19b1, 0x13ce: 0x19b9, 0x13cf: 0x19c1, 0x13d0: 0x19c9, 0x13d1: 0x19d1, + 0x13d2: 0x19d9, 0x13d3: 0x19e1, 0x13d4: 0x19e9, 0x13d5: 0x19f1, 0x13d6: 0x19f9, 0x13d7: 0x1a01, + 0x13d8: 0x1a09, 0x13d9: 0x1a11, 0x13da: 0x1a19, 0x13db: 0x1a21, 0x13dc: 0x1a29, 0x13dd: 0x1a31, + 0x13de: 0x1a3a, 0x13df: 0x1a42, 0x13e0: 0x1a4a, 0x13e1: 0x1a52, 0x13e2: 0x1a5a, 0x13e3: 0x1a62, + 0x13e4: 0x1a69, 0x13e5: 0x1a71, 0x13e6: 0x1761, 0x13e7: 0x1a79, 0x13e8: 0x1741, 0x13e9: 0x1769, + 0x13ea: 0x1a81, 0x13eb: 0x1a89, 0x13ec: 0x1789, 0x13ed: 0x1a91, 0x13ee: 0x1791, 0x13ef: 0x1799, + 0x13f0: 0x1a99, 0x13f1: 0x1aa1, 0x13f2: 0x17b9, 0x13f3: 0x1aa9, 0x13f4: 0x17c1, 0x13f5: 0x17c9, + 0x13f6: 0x1ab1, 0x13f7: 0x1ab9, 0x13f8: 0x17d9, 0x13f9: 0x1ac1, 0x13fa: 0x17e1, 0x13fb: 0x17e9, + 0x13fc: 0x18d1, 0x13fd: 0x18d9, 0x13fe: 0x18f1, 0x13ff: 0x18f9, + // Block 0x50, offset 0x1400 + 0x1400: 0x1901, 0x1401: 0x1921, 0x1402: 0x1929, 0x1403: 0x1931, 0x1404: 0x1939, 0x1405: 0x1959, + 0x1406: 0x1961, 0x1407: 0x1969, 0x1408: 0x1ac9, 0x1409: 0x1989, 0x140a: 0x1ad1, 0x140b: 0x1ad9, + 0x140c: 0x19b9, 0x140d: 0x1ae1, 0x140e: 0x19c1, 0x140f: 0x19c9, 0x1410: 0x1a31, 0x1411: 0x1ae9, + 0x1412: 0x1af1, 0x1413: 0x1a09, 0x1414: 0x1af9, 0x1415: 0x1a11, 0x1416: 0x1a19, 0x1417: 0x1751, + 0x1418: 0x1759, 0x1419: 0x1b01, 0x141a: 0x1761, 0x141b: 0x1b09, 0x141c: 0x1771, 0x141d: 0x1779, + 0x141e: 0x1781, 0x141f: 0x1789, 0x1420: 0x1b11, 0x1421: 0x17a1, 0x1422: 0x17a9, 0x1423: 0x17b1, + 0x1424: 0x17b9, 0x1425: 0x1b19, 0x1426: 0x17d9, 0x1427: 0x17f1, 0x1428: 0x17f9, 0x1429: 0x1801, + 0x142a: 0x1809, 0x142b: 0x1811, 0x142c: 0x1821, 0x142d: 0x1829, 0x142e: 0x1831, 0x142f: 0x1839, + 0x1430: 0x1841, 0x1431: 0x1849, 0x1432: 0x1b21, 0x1433: 0x1851, 0x1434: 0x1859, 0x1435: 0x1861, + 0x1436: 0x1869, 0x1437: 0x1871, 0x1438: 0x1879, 0x1439: 0x1889, 0x143a: 0x1891, 0x143b: 0x1899, + 0x143c: 0x18a1, 0x143d: 0x18a9, 0x143e: 0x18b1, 0x143f: 0x18b9, + // Block 0x51, offset 0x1440 + 0x1440: 0x18c1, 0x1441: 0x18c9, 0x1442: 0x18e1, 0x1443: 0x18e9, 0x1444: 0x1909, 0x1445: 0x1911, + 0x1446: 0x1919, 0x1447: 0x1921, 0x1448: 0x1929, 0x1449: 0x1941, 0x144a: 0x1949, 0x144b: 0x1951, + 0x144c: 0x1959, 0x144d: 0x1b29, 0x144e: 0x1971, 0x144f: 0x1979, 0x1450: 0x1981, 0x1451: 0x1989, + 0x1452: 0x19a1, 0x1453: 0x19a9, 0x1454: 0x19b1, 0x1455: 0x19b9, 0x1456: 0x1b31, 0x1457: 0x19d1, + 0x1458: 0x19d9, 0x1459: 0x1b39, 0x145a: 0x19f1, 0x145b: 0x19f9, 0x145c: 0x1a01, 0x145d: 0x1a09, + 0x145e: 0x1b41, 0x145f: 0x1761, 0x1460: 0x1b09, 0x1461: 0x1789, 0x1462: 0x1b11, 0x1463: 0x17b9, + 0x1464: 0x1b19, 0x1465: 0x17d9, 0x1466: 0x1b49, 0x1467: 0x1841, 0x1468: 0x1b51, 0x1469: 0x1b59, + 0x146a: 0x1b61, 0x146b: 0x1921, 0x146c: 0x1929, 0x146d: 0x1959, 0x146e: 0x19b9, 0x146f: 0x1b31, + 0x1470: 0x1a09, 0x1471: 0x1b41, 0x1472: 0x1b69, 0x1473: 0x1b71, 0x1474: 0x1b79, 0x1475: 0x1b81, + 0x1476: 0x1b89, 0x1477: 0x1b91, 0x1478: 0x1b99, 0x1479: 0x1ba1, 0x147a: 0x1ba9, 0x147b: 0x1bb1, + 0x147c: 0x1bb9, 0x147d: 0x1bc1, 0x147e: 0x1bc9, 0x147f: 0x1bd1, + // Block 0x52, offset 0x1480 + 0x1480: 0x1bd9, 0x1481: 0x1be1, 0x1482: 0x1be9, 0x1483: 0x1bf1, 0x1484: 0x1bf9, 0x1485: 0x1c01, + 0x1486: 0x1c09, 0x1487: 0x1c11, 0x1488: 0x1c19, 0x1489: 0x1c21, 0x148a: 0x1c29, 0x148b: 0x1c31, + 0x148c: 0x1b59, 0x148d: 0x1c39, 0x148e: 0x1c41, 0x148f: 0x1c49, 0x1490: 0x1c51, 0x1491: 0x1b81, + 0x1492: 0x1b89, 0x1493: 0x1b91, 0x1494: 0x1b99, 0x1495: 0x1ba1, 0x1496: 0x1ba9, 0x1497: 0x1bb1, + 0x1498: 0x1bb9, 0x1499: 0x1bc1, 0x149a: 0x1bc9, 0x149b: 0x1bd1, 0x149c: 0x1bd9, 0x149d: 0x1be1, + 0x149e: 0x1be9, 0x149f: 0x1bf1, 0x14a0: 0x1bf9, 0x14a1: 0x1c01, 0x14a2: 0x1c09, 0x14a3: 0x1c11, + 0x14a4: 0x1c19, 0x14a5: 0x1c21, 0x14a6: 0x1c29, 0x14a7: 0x1c31, 0x14a8: 0x1b59, 0x14a9: 0x1c39, + 0x14aa: 0x1c41, 0x14ab: 0x1c49, 0x14ac: 0x1c51, 0x14ad: 0x1c21, 0x14ae: 0x1c29, 0x14af: 0x1c31, + 0x14b0: 0x1b59, 0x14b1: 0x1b51, 0x14b2: 0x1b61, 0x14b3: 0x1881, 0x14b4: 0x1829, 0x14b5: 0x1831, + 0x14b6: 0x1839, 0x14b7: 0x1c21, 0x14b8: 0x1c29, 0x14b9: 0x1c31, 0x14ba: 0x1881, 0x14bb: 0x1889, + 0x14bc: 0x1c59, 0x14bd: 0x1c59, 0x14be: 0x0018, 0x14bf: 0x0018, + // Block 0x53, offset 0x14c0 + 0x14c0: 0x0018, 0x14c1: 0x0018, 0x14c2: 0x0018, 0x14c3: 0x0018, 0x14c4: 0x0018, 0x14c5: 0x0018, + 0x14c6: 0x0018, 0x14c7: 0x0018, 0x14c8: 0x0018, 0x14c9: 0x0018, 0x14ca: 0x0018, 0x14cb: 0x0018, + 0x14cc: 0x0018, 0x14cd: 0x0018, 0x14ce: 0x0018, 0x14cf: 0x0018, 0x14d0: 0x1c61, 0x14d1: 0x1c69, + 0x14d2: 0x1c69, 0x14d3: 0x1c71, 0x14d4: 0x1c79, 0x14d5: 0x1c81, 0x14d6: 0x1c89, 0x14d7: 0x1c91, + 0x14d8: 0x1c99, 0x14d9: 0x1c99, 0x14da: 0x1ca1, 0x14db: 0x1ca9, 0x14dc: 0x1cb1, 0x14dd: 0x1cb9, + 0x14de: 0x1cc1, 0x14df: 0x1cc9, 0x14e0: 0x1cc9, 0x14e1: 0x1cd1, 0x14e2: 0x1cd9, 0x14e3: 0x1cd9, + 0x14e4: 0x1ce1, 0x14e5: 0x1ce1, 0x14e6: 0x1ce9, 0x14e7: 0x1cf1, 0x14e8: 0x1cf1, 0x14e9: 0x1cf9, + 0x14ea: 0x1d01, 0x14eb: 0x1d01, 0x14ec: 0x1d09, 0x14ed: 0x1d09, 0x14ee: 0x1d11, 0x14ef: 0x1d19, + 0x14f0: 0x1d19, 0x14f1: 0x1d21, 0x14f2: 0x1d21, 0x14f3: 0x1d29, 0x14f4: 0x1d31, 0x14f5: 0x1d39, + 0x14f6: 0x1d41, 0x14f7: 0x1d41, 0x14f8: 0x1d49, 0x14f9: 0x1d51, 0x14fa: 0x1d59, 0x14fb: 0x1d61, + 0x14fc: 0x1d69, 0x14fd: 0x1d69, 0x14fe: 0x1d71, 0x14ff: 0x1d79, + // Block 0x54, offset 0x1500 + 0x1500: 0x1f29, 0x1501: 0x1f31, 0x1502: 0x1f39, 0x1503: 0x1f11, 0x1504: 0x1d39, 0x1505: 0x1ce9, + 0x1506: 0x1f41, 0x1507: 0x1f49, 0x1508: 0x0040, 0x1509: 0x0040, 0x150a: 0x0040, 0x150b: 0x0040, + 0x150c: 0x0040, 0x150d: 0x0040, 0x150e: 0x0040, 0x150f: 0x0018, 0x1510: 0x0040, 0x1511: 0x0040, + 0x1512: 0x0040, 0x1513: 0x0040, 0x1514: 0x0040, 0x1515: 0x0040, 0x1516: 0x0040, 0x1517: 0x0040, + 0x1518: 0x0040, 0x1519: 0x0040, 0x151a: 0x0040, 0x151b: 0x0040, 0x151c: 0x0040, 0x151d: 0x0040, + 0x151e: 0x0040, 0x151f: 0x0040, 0x1520: 0x0040, 0x1521: 0x0040, 0x1522: 0x0040, 0x1523: 0x0040, + 0x1524: 0x0040, 0x1525: 0x0040, 0x1526: 0x0040, 0x1527: 0x0040, 0x1528: 0x0040, 0x1529: 0x0040, + 0x152a: 0x0040, 0x152b: 0x0040, 0x152c: 0x0040, 0x152d: 0x0040, 0x152e: 0x0040, 0x152f: 0x0040, + 0x1530: 0x1f51, 0x1531: 0x1f59, 0x1532: 0x1f61, 0x1533: 0x1f69, 0x1534: 0x1f71, 0x1535: 0x1f79, + 0x1536: 0x1f81, 0x1537: 0x1f89, 0x1538: 0x1f91, 0x1539: 0x1f99, 0x153a: 0x1fa2, 0x153b: 0x1faa, + 0x153c: 0x1fb1, 0x153d: 0x0018, 0x153e: 0x0018, 0x153f: 0x0018, + // Block 0x55, offset 0x1540 + 0x1540: 0x33c0, 0x1541: 0x33c0, 0x1542: 0x33c0, 0x1543: 0x33c0, 0x1544: 0x33c0, 0x1545: 0x33c0, + 0x1546: 0x33c0, 0x1547: 0x33c0, 0x1548: 0x33c0, 0x1549: 0x33c0, 0x154a: 0x33c0, 0x154b: 0x33c0, + 0x154c: 0x33c0, 0x154d: 0x33c0, 0x154e: 0x33c0, 0x154f: 0x33c0, 0x1550: 0x1fba, 0x1551: 0x7d8d, + 0x1552: 0x0040, 0x1553: 0x1fc2, 0x1554: 0x0122, 0x1555: 0x1fca, 0x1556: 0x1fd2, 0x1557: 0x7dad, + 0x1558: 0x7dcd, 0x1559: 0x0040, 0x155a: 0x0040, 0x155b: 0x0040, 0x155c: 0x0040, 0x155d: 0x0040, + 0x155e: 0x0040, 0x155f: 0x0040, 0x1560: 0x3308, 0x1561: 0x3308, 0x1562: 0x3308, 0x1563: 0x3308, + 0x1564: 0x3308, 0x1565: 0x3308, 0x1566: 0x3308, 0x1567: 0x3308, 0x1568: 0x3308, 0x1569: 0x3308, + 0x156a: 0x3308, 0x156b: 0x3308, 0x156c: 0x3308, 0x156d: 0x3308, 0x156e: 0x3308, 0x156f: 0x3308, + 0x1570: 0x0040, 0x1571: 0x7ded, 0x1572: 0x7e0d, 0x1573: 0x1fda, 0x1574: 0x1fda, 0x1575: 0x072a, + 0x1576: 0x0732, 0x1577: 0x1fe2, 0x1578: 0x1fea, 0x1579: 0x7e2d, 0x157a: 0x7e4d, 0x157b: 0x7e6d, + 0x157c: 0x7e2d, 0x157d: 0x7e8d, 0x157e: 0x7ead, 0x157f: 0x7e8d, + // Block 0x56, offset 0x1580 + 0x1580: 0x7ecd, 0x1581: 0x7eed, 0x1582: 0x7f0d, 0x1583: 0x7eed, 0x1584: 0x7f2d, 0x1585: 0x0018, + 0x1586: 0x0018, 0x1587: 0x1ff2, 0x1588: 0x1ffa, 0x1589: 0x7f4e, 0x158a: 0x7f6e, 0x158b: 0x7f8e, + 0x158c: 0x7fae, 0x158d: 0x1fda, 0x158e: 0x1fda, 0x158f: 0x1fda, 0x1590: 0x1fba, 0x1591: 0x7fcd, + 0x1592: 0x0040, 0x1593: 0x0040, 0x1594: 0x0122, 0x1595: 0x1fc2, 0x1596: 0x1fd2, 0x1597: 0x1fca, + 0x1598: 0x7fed, 0x1599: 0x072a, 0x159a: 0x0732, 0x159b: 0x1fe2, 0x159c: 0x1fea, 0x159d: 0x7ecd, + 0x159e: 0x7f2d, 0x159f: 0x2002, 0x15a0: 0x200a, 0x15a1: 0x2012, 0x15a2: 0x071a, 0x15a3: 0x2019, + 0x15a4: 0x2022, 0x15a5: 0x202a, 0x15a6: 0x0722, 0x15a7: 0x0040, 0x15a8: 0x2032, 0x15a9: 0x203a, + 0x15aa: 0x2042, 0x15ab: 0x204a, 0x15ac: 0x0040, 0x15ad: 0x0040, 0x15ae: 0x0040, 0x15af: 0x0040, + 0x15b0: 0x800e, 0x15b1: 0x2051, 0x15b2: 0x802e, 0x15b3: 0x0808, 0x15b4: 0x804e, 0x15b5: 0x0040, + 0x15b6: 0x806e, 0x15b7: 0x2059, 0x15b8: 0x808e, 0x15b9: 0x2061, 0x15ba: 0x80ae, 0x15bb: 0x2069, + 0x15bc: 0x80ce, 0x15bd: 0x2071, 0x15be: 0x80ee, 0x15bf: 0x2079, + // Block 0x57, offset 0x15c0 + 0x15c0: 0x2081, 0x15c1: 0x2089, 0x15c2: 0x2089, 0x15c3: 0x2091, 0x15c4: 0x2091, 0x15c5: 0x2099, + 0x15c6: 0x2099, 0x15c7: 0x20a1, 0x15c8: 0x20a1, 0x15c9: 0x20a9, 0x15ca: 0x20a9, 0x15cb: 0x20a9, + 0x15cc: 0x20a9, 0x15cd: 0x20b1, 0x15ce: 0x20b1, 0x15cf: 0x20b9, 0x15d0: 0x20b9, 0x15d1: 0x20b9, + 0x15d2: 0x20b9, 0x15d3: 0x20c1, 0x15d4: 0x20c1, 0x15d5: 0x20c9, 0x15d6: 0x20c9, 0x15d7: 0x20c9, + 0x15d8: 0x20c9, 0x15d9: 0x20d1, 0x15da: 0x20d1, 0x15db: 0x20d1, 0x15dc: 0x20d1, 0x15dd: 0x20d9, + 0x15de: 0x20d9, 0x15df: 0x20d9, 0x15e0: 0x20d9, 0x15e1: 0x20e1, 0x15e2: 0x20e1, 0x15e3: 0x20e1, + 0x15e4: 0x20e1, 0x15e5: 0x20e9, 0x15e6: 0x20e9, 0x15e7: 0x20e9, 0x15e8: 0x20e9, 0x15e9: 0x20f1, + 0x15ea: 0x20f1, 0x15eb: 0x20f9, 0x15ec: 0x20f9, 0x15ed: 0x2101, 0x15ee: 0x2101, 0x15ef: 0x2109, + 0x15f0: 0x2109, 0x15f1: 0x2111, 0x15f2: 0x2111, 0x15f3: 0x2111, 0x15f4: 0x2111, 0x15f5: 0x2119, + 0x15f6: 0x2119, 0x15f7: 0x2119, 0x15f8: 0x2119, 0x15f9: 0x2121, 0x15fa: 0x2121, 0x15fb: 0x2121, + 0x15fc: 0x2121, 0x15fd: 0x2129, 0x15fe: 0x2129, 0x15ff: 0x2129, + // Block 0x58, offset 0x1600 + 0x1600: 0x2129, 0x1601: 0x2131, 0x1602: 0x2131, 0x1603: 0x2131, 0x1604: 0x2131, 0x1605: 0x2139, + 0x1606: 0x2139, 0x1607: 0x2139, 0x1608: 0x2139, 0x1609: 0x2141, 0x160a: 0x2141, 0x160b: 0x2141, + 0x160c: 0x2141, 0x160d: 0x2149, 0x160e: 0x2149, 0x160f: 0x2149, 0x1610: 0x2149, 0x1611: 0x2151, + 0x1612: 0x2151, 0x1613: 0x2151, 0x1614: 0x2151, 0x1615: 0x2159, 0x1616: 0x2159, 0x1617: 0x2159, + 0x1618: 0x2159, 0x1619: 0x2161, 0x161a: 0x2161, 0x161b: 0x2161, 0x161c: 0x2161, 0x161d: 0x2169, + 0x161e: 0x2169, 0x161f: 0x2169, 0x1620: 0x2169, 0x1621: 0x2171, 0x1622: 0x2171, 0x1623: 0x2171, + 0x1624: 0x2171, 0x1625: 0x2179, 0x1626: 0x2179, 0x1627: 0x2179, 0x1628: 0x2179, 0x1629: 0x2181, + 0x162a: 0x2181, 0x162b: 0x2181, 0x162c: 0x2181, 0x162d: 0x2189, 0x162e: 0x2189, 0x162f: 0x1701, + 0x1630: 0x1701, 0x1631: 0x2191, 0x1632: 0x2191, 0x1633: 0x2191, 0x1634: 0x2191, 0x1635: 0x2199, + 0x1636: 0x2199, 0x1637: 0x21a1, 0x1638: 0x21a1, 0x1639: 0x21a9, 0x163a: 0x21a9, 0x163b: 0x21b1, + 0x163c: 0x21b1, 0x163d: 0x0040, 0x163e: 0x0040, 0x163f: 0x03c0, + // Block 0x59, offset 0x1640 + 0x1640: 0x0040, 0x1641: 0x1fca, 0x1642: 0x21ba, 0x1643: 0x2002, 0x1644: 0x203a, 0x1645: 0x2042, + 0x1646: 0x200a, 0x1647: 0x21c2, 0x1648: 0x072a, 0x1649: 0x0732, 0x164a: 0x2012, 0x164b: 0x071a, + 0x164c: 0x1fba, 0x164d: 0x2019, 0x164e: 0x0961, 0x164f: 0x21ca, 0x1650: 0x06e1, 0x1651: 0x0049, + 0x1652: 0x0029, 0x1653: 0x0031, 0x1654: 0x06e9, 0x1655: 0x06f1, 0x1656: 0x06f9, 0x1657: 0x0701, + 0x1658: 0x0709, 0x1659: 0x0711, 0x165a: 0x1fc2, 0x165b: 0x0122, 0x165c: 0x2022, 0x165d: 0x0722, + 0x165e: 0x202a, 0x165f: 0x1fd2, 0x1660: 0x204a, 0x1661: 0x0019, 0x1662: 0x02e9, 0x1663: 0x03d9, + 0x1664: 0x02f1, 0x1665: 0x02f9, 0x1666: 0x03f1, 0x1667: 0x0309, 0x1668: 0x00a9, 0x1669: 0x0311, + 0x166a: 0x00b1, 0x166b: 0x0319, 0x166c: 0x0101, 0x166d: 0x0321, 0x166e: 0x0329, 0x166f: 0x0051, + 0x1670: 0x0339, 0x1671: 0x0751, 0x1672: 0x00b9, 0x1673: 0x0089, 0x1674: 0x0341, 0x1675: 0x0349, + 0x1676: 0x0391, 0x1677: 0x00c1, 0x1678: 0x0109, 0x1679: 0x00c9, 0x167a: 0x04b1, 0x167b: 0x1ff2, + 0x167c: 0x2032, 0x167d: 0x1ffa, 0x167e: 0x21d2, 0x167f: 0x1fda, + // Block 0x5a, offset 0x1680 + 0x1680: 0x0672, 0x1681: 0x0019, 0x1682: 0x02e9, 0x1683: 0x03d9, 0x1684: 0x02f1, 0x1685: 0x02f9, + 0x1686: 0x03f1, 0x1687: 0x0309, 0x1688: 0x00a9, 0x1689: 0x0311, 0x168a: 0x00b1, 0x168b: 0x0319, + 0x168c: 0x0101, 0x168d: 0x0321, 0x168e: 0x0329, 0x168f: 0x0051, 0x1690: 0x0339, 0x1691: 0x0751, + 0x1692: 0x00b9, 0x1693: 0x0089, 0x1694: 0x0341, 0x1695: 0x0349, 0x1696: 0x0391, 0x1697: 0x00c1, + 0x1698: 0x0109, 0x1699: 0x00c9, 0x169a: 0x04b1, 0x169b: 0x1fe2, 0x169c: 0x21da, 0x169d: 0x1fea, + 0x169e: 0x21e2, 0x169f: 0x810d, 0x16a0: 0x812d, 0x16a1: 0x0961, 0x16a2: 0x814d, 0x16a3: 0x814d, + 0x16a4: 0x816d, 0x16a5: 0x818d, 0x16a6: 0x81ad, 0x16a7: 0x81cd, 0x16a8: 0x81ed, 0x16a9: 0x820d, + 0x16aa: 0x822d, 0x16ab: 0x824d, 0x16ac: 0x826d, 0x16ad: 0x828d, 0x16ae: 0x82ad, 0x16af: 0x82cd, + 0x16b0: 0x82ed, 0x16b1: 0x830d, 0x16b2: 0x832d, 0x16b3: 0x834d, 0x16b4: 0x836d, 0x16b5: 0x838d, + 0x16b6: 0x83ad, 0x16b7: 0x83cd, 0x16b8: 0x83ed, 0x16b9: 0x840d, 0x16ba: 0x842d, 0x16bb: 0x844d, + 0x16bc: 0x81ed, 0x16bd: 0x846d, 0x16be: 0x848d, 0x16bf: 0x824d, + // Block 0x5b, offset 0x16c0 + 0x16c0: 0x84ad, 0x16c1: 0x84cd, 0x16c2: 0x84ed, 0x16c3: 0x850d, 0x16c4: 0x852d, 0x16c5: 0x854d, + 0x16c6: 0x856d, 0x16c7: 0x858d, 0x16c8: 0x850d, 0x16c9: 0x85ad, 0x16ca: 0x850d, 0x16cb: 0x85cd, + 0x16cc: 0x85cd, 0x16cd: 0x85ed, 0x16ce: 0x85ed, 0x16cf: 0x860d, 0x16d0: 0x854d, 0x16d1: 0x862d, + 0x16d2: 0x864d, 0x16d3: 0x862d, 0x16d4: 0x866d, 0x16d5: 0x864d, 0x16d6: 0x868d, 0x16d7: 0x868d, + 0x16d8: 0x86ad, 0x16d9: 0x86ad, 0x16da: 0x86cd, 0x16db: 0x86cd, 0x16dc: 0x864d, 0x16dd: 0x814d, + 0x16de: 0x86ed, 0x16df: 0x870d, 0x16e0: 0x0040, 0x16e1: 0x872d, 0x16e2: 0x874d, 0x16e3: 0x876d, + 0x16e4: 0x878d, 0x16e5: 0x876d, 0x16e6: 0x87ad, 0x16e7: 0x87cd, 0x16e8: 0x87ed, 0x16e9: 0x87ed, + 0x16ea: 0x880d, 0x16eb: 0x880d, 0x16ec: 0x882d, 0x16ed: 0x882d, 0x16ee: 0x880d, 0x16ef: 0x880d, + 0x16f0: 0x884d, 0x16f1: 0x886d, 0x16f2: 0x888d, 0x16f3: 0x88ad, 0x16f4: 0x88cd, 0x16f5: 0x88ed, + 0x16f6: 0x88ed, 0x16f7: 0x88ed, 0x16f8: 0x890d, 0x16f9: 0x890d, 0x16fa: 0x890d, 0x16fb: 0x890d, + 0x16fc: 0x87ed, 0x16fd: 0x87ed, 0x16fe: 0x87ed, 0x16ff: 0x0040, + // Block 0x5c, offset 0x1700 + 0x1700: 0x0040, 0x1701: 0x0040, 0x1702: 0x874d, 0x1703: 0x872d, 0x1704: 0x892d, 0x1705: 0x872d, + 0x1706: 0x874d, 0x1707: 0x872d, 0x1708: 0x0040, 0x1709: 0x0040, 0x170a: 0x894d, 0x170b: 0x874d, + 0x170c: 0x896d, 0x170d: 0x892d, 0x170e: 0x896d, 0x170f: 0x874d, 0x1710: 0x0040, 0x1711: 0x0040, + 0x1712: 0x898d, 0x1713: 0x89ad, 0x1714: 0x88ad, 0x1715: 0x896d, 0x1716: 0x892d, 0x1717: 0x896d, + 0x1718: 0x0040, 0x1719: 0x0040, 0x171a: 0x89cd, 0x171b: 0x89ed, 0x171c: 0x89cd, 0x171d: 0x0040, + 0x171e: 0x0040, 0x171f: 0x0040, 0x1720: 0x21e9, 0x1721: 0x21f1, 0x1722: 0x21f9, 0x1723: 0x8a0e, + 0x1724: 0x2201, 0x1725: 0x2209, 0x1726: 0x8a2d, 0x1727: 0x0040, 0x1728: 0x8a4d, 0x1729: 0x8a6d, + 0x172a: 0x8a8d, 0x172b: 0x8a6d, 0x172c: 0x8aad, 0x172d: 0x8acd, 0x172e: 0x8aed, 0x172f: 0x0040, + 0x1730: 0x0040, 0x1731: 0x0040, 0x1732: 0x0040, 0x1733: 0x0040, 0x1734: 0x0040, 0x1735: 0x0040, + 0x1736: 0x0040, 0x1737: 0x0040, 0x1738: 0x0040, 0x1739: 0x0340, 0x173a: 0x0340, 0x173b: 0x0340, + 0x173c: 0x0040, 0x173d: 0x0040, 0x173e: 0x0040, 0x173f: 0x0040, + // Block 0x5d, offset 0x1740 + 0x1740: 0x0008, 0x1741: 0x0008, 0x1742: 0x0008, 0x1743: 0x0008, 0x1744: 0x0008, 0x1745: 0x0008, + 0x1746: 0x0008, 0x1747: 0x0008, 0x1748: 0x0008, 0x1749: 0x0008, 0x174a: 0x0008, 0x174b: 0x0008, + 0x174c: 0x0008, 0x174d: 0x0008, 0x174e: 0x0008, 0x174f: 0x0008, 0x1750: 0x0008, 0x1751: 0x0008, + 0x1752: 0x0008, 0x1753: 0x0008, 0x1754: 0x0008, 0x1755: 0x0008, 0x1756: 0x0008, 0x1757: 0x0008, + 0x1758: 0x0008, 0x1759: 0x0008, 0x175a: 0x0008, 0x175b: 0x0008, 0x175c: 0x0008, 0x175d: 0x0008, + 0x175e: 0x0008, 0x175f: 0x0008, 0x1760: 0x0008, 0x1761: 0x0008, 0x1762: 0x0008, 0x1763: 0x0008, + 0x1764: 0x0040, 0x1765: 0x0040, 0x1766: 0x0040, 0x1767: 0x0040, 0x1768: 0x0040, 0x1769: 0x0040, + 0x176a: 0x0040, 0x176b: 0x0040, 0x176c: 0x0040, 0x176d: 0x0040, 0x176e: 0x0040, 0x176f: 0x0018, + 0x1770: 0x8b3d, 0x1771: 0x8b55, 0x1772: 0x8b6d, 0x1773: 0x8b55, 0x1774: 0x8b85, 0x1775: 0x8b55, + 0x1776: 0x8b6d, 0x1777: 0x8b55, 0x1778: 0x8b3d, 0x1779: 0x8b9d, 0x177a: 0x8bb5, 0x177b: 0x0040, + 0x177c: 0x8bcd, 0x177d: 0x8b9d, 0x177e: 0x8bb5, 0x177f: 0x8b9d, + // Block 0x5e, offset 0x1780 + 0x1780: 0xe13d, 0x1781: 0xe14d, 0x1782: 0xe15d, 0x1783: 0xe14d, 0x1784: 0xe17d, 0x1785: 0xe14d, + 0x1786: 0xe15d, 0x1787: 0xe14d, 0x1788: 0xe13d, 0x1789: 0xe1cd, 0x178a: 0xe1dd, 0x178b: 0x0040, + 0x178c: 0xe1fd, 0x178d: 0xe1cd, 0x178e: 0xe1dd, 0x178f: 0xe1cd, 0x1790: 0xe13d, 0x1791: 0xe14d, + 0x1792: 0xe15d, 0x1793: 0x0040, 0x1794: 0xe17d, 0x1795: 0xe14d, 0x1796: 0x0040, 0x1797: 0x0008, + 0x1798: 0x0008, 0x1799: 0x0008, 0x179a: 0x0008, 0x179b: 0x0008, 0x179c: 0x0008, 0x179d: 0x0008, + 0x179e: 0x0008, 0x179f: 0x0008, 0x17a0: 0x0008, 0x17a1: 0x0008, 0x17a2: 0x0040, 0x17a3: 0x0008, + 0x17a4: 0x0008, 0x17a5: 0x0008, 0x17a6: 0x0008, 0x17a7: 0x0008, 0x17a8: 0x0008, 0x17a9: 0x0008, + 0x17aa: 0x0008, 0x17ab: 0x0008, 0x17ac: 0x0008, 0x17ad: 0x0008, 0x17ae: 0x0008, 0x17af: 0x0008, + 0x17b0: 0x0008, 0x17b1: 0x0008, 0x17b2: 0x0040, 0x17b3: 0x0008, 0x17b4: 0x0008, 0x17b5: 0x0008, + 0x17b6: 0x0008, 0x17b7: 0x0008, 0x17b8: 0x0008, 0x17b9: 0x0008, 0x17ba: 0x0040, 0x17bb: 0x0008, + 0x17bc: 0x0008, 0x17bd: 0x0040, 0x17be: 0x0040, 0x17bf: 0x0040, + // Block 0x5f, offset 0x17c0 + 0x17c0: 0x0008, 0x17c1: 0x2211, 0x17c2: 0x2219, 0x17c3: 0x02e1, 0x17c4: 0x2221, 0x17c5: 0x2229, + 0x17c6: 0x0040, 0x17c7: 0x2231, 0x17c8: 0x2239, 0x17c9: 0x2241, 0x17ca: 0x2249, 0x17cb: 0x2251, + 0x17cc: 0x2259, 0x17cd: 0x2261, 0x17ce: 0x2269, 0x17cf: 0x2271, 0x17d0: 0x2279, 0x17d1: 0x2281, + 0x17d2: 0x2289, 0x17d3: 0x2291, 0x17d4: 0x2299, 0x17d5: 0x0741, 0x17d6: 0x22a1, 0x17d7: 0x22a9, + 0x17d8: 0x22b1, 0x17d9: 0x22b9, 0x17da: 0x22c1, 0x17db: 0x13d9, 0x17dc: 0x8be5, 0x17dd: 0x22c9, + 0x17de: 0x22d1, 0x17df: 0x8c05, 0x17e0: 0x22d9, 0x17e1: 0x8c25, 0x17e2: 0x22e1, 0x17e3: 0x22e9, + 0x17e4: 0x22f1, 0x17e5: 0x0751, 0x17e6: 0x22f9, 0x17e7: 0x8c45, 0x17e8: 0x0949, 0x17e9: 0x2301, + 0x17ea: 0x2309, 0x17eb: 0x2311, 0x17ec: 0x2319, 0x17ed: 0x2321, 0x17ee: 0x2329, 0x17ef: 0x2331, + 0x17f0: 0x2339, 0x17f1: 0x0040, 0x17f2: 0x2341, 0x17f3: 0x2349, 0x17f4: 0x2351, 0x17f5: 0x2359, + 0x17f6: 0x2361, 0x17f7: 0x2369, 0x17f8: 0x2371, 0x17f9: 0x8c65, 0x17fa: 0x8c85, 0x17fb: 0x0040, + 0x17fc: 0x0040, 0x17fd: 0x0040, 0x17fe: 0x0040, 0x17ff: 0x0040, + // Block 0x60, offset 0x1800 + 0x1800: 0x0a08, 0x1801: 0x0a08, 0x1802: 0x0a08, 0x1803: 0x0a08, 0x1804: 0x0a08, 0x1805: 0x0c08, + 0x1806: 0x0808, 0x1807: 0x0c08, 0x1808: 0x0818, 0x1809: 0x0c08, 0x180a: 0x0c08, 0x180b: 0x0808, + 0x180c: 0x0808, 0x180d: 0x0908, 0x180e: 0x0c08, 0x180f: 0x0c08, 0x1810: 0x0c08, 0x1811: 0x0c08, + 0x1812: 0x0c08, 0x1813: 0x0a08, 0x1814: 0x0a08, 0x1815: 0x0a08, 0x1816: 0x0a08, 0x1817: 0x0908, + 0x1818: 0x0a08, 0x1819: 0x0a08, 0x181a: 0x0a08, 0x181b: 0x0a08, 0x181c: 0x0a08, 0x181d: 0x0c08, + 0x181e: 0x0a08, 0x181f: 0x0a08, 0x1820: 0x0a08, 0x1821: 0x0c08, 0x1822: 0x0808, 0x1823: 0x0808, + 0x1824: 0x0c08, 0x1825: 0x3308, 0x1826: 0x3308, 0x1827: 0x0040, 0x1828: 0x0040, 0x1829: 0x0040, + 0x182a: 0x0040, 0x182b: 0x0a18, 0x182c: 0x0a18, 0x182d: 0x0a18, 0x182e: 0x0a18, 0x182f: 0x0c18, + 0x1830: 0x0818, 0x1831: 0x0818, 0x1832: 0x0818, 0x1833: 0x0818, 0x1834: 0x0818, 0x1835: 0x0818, + 0x1836: 0x0818, 0x1837: 0x0040, 0x1838: 0x0040, 0x1839: 0x0040, 0x183a: 0x0040, 0x183b: 0x0040, + 0x183c: 0x0040, 0x183d: 0x0040, 0x183e: 0x0040, 0x183f: 0x0040, + // Block 0x61, offset 0x1840 + 0x1840: 0x0a08, 0x1841: 0x0c08, 0x1842: 0x0a08, 0x1843: 0x0c08, 0x1844: 0x0c08, 0x1845: 0x0c08, + 0x1846: 0x0a08, 0x1847: 0x0a08, 0x1848: 0x0a08, 0x1849: 0x0c08, 0x184a: 0x0a08, 0x184b: 0x0a08, + 0x184c: 0x0c08, 0x184d: 0x0a08, 0x184e: 0x0c08, 0x184f: 0x0c08, 0x1850: 0x0a08, 0x1851: 0x0c08, + 0x1852: 0x0040, 0x1853: 0x0040, 0x1854: 0x0040, 0x1855: 0x0040, 0x1856: 0x0040, 0x1857: 0x0040, + 0x1858: 0x0040, 0x1859: 0x0818, 0x185a: 0x0818, 0x185b: 0x0818, 0x185c: 0x0818, 0x185d: 0x0040, + 0x185e: 0x0040, 0x185f: 0x0040, 0x1860: 0x0040, 0x1861: 0x0040, 0x1862: 0x0040, 0x1863: 0x0040, + 0x1864: 0x0040, 0x1865: 0x0040, 0x1866: 0x0040, 0x1867: 0x0040, 0x1868: 0x0040, 0x1869: 0x0c18, + 0x186a: 0x0c18, 0x186b: 0x0c18, 0x186c: 0x0c18, 0x186d: 0x0a18, 0x186e: 0x0a18, 0x186f: 0x0818, + 0x1870: 0x0040, 0x1871: 0x0040, 0x1872: 0x0040, 0x1873: 0x0040, 0x1874: 0x0040, 0x1875: 0x0040, + 0x1876: 0x0040, 0x1877: 0x0040, 0x1878: 0x0040, 0x1879: 0x0040, 0x187a: 0x0040, 0x187b: 0x0040, + 0x187c: 0x0040, 0x187d: 0x0040, 0x187e: 0x0040, 0x187f: 0x0040, + // Block 0x62, offset 0x1880 + 0x1880: 0x3308, 0x1881: 0x3308, 0x1882: 0x3008, 0x1883: 0x3008, 0x1884: 0x0040, 0x1885: 0x0008, + 0x1886: 0x0008, 0x1887: 0x0008, 0x1888: 0x0008, 0x1889: 0x0008, 0x188a: 0x0008, 0x188b: 0x0008, + 0x188c: 0x0008, 0x188d: 0x0040, 0x188e: 0x0040, 0x188f: 0x0008, 0x1890: 0x0008, 0x1891: 0x0040, + 0x1892: 0x0040, 0x1893: 0x0008, 0x1894: 0x0008, 0x1895: 0x0008, 0x1896: 0x0008, 0x1897: 0x0008, + 0x1898: 0x0008, 0x1899: 0x0008, 0x189a: 0x0008, 0x189b: 0x0008, 0x189c: 0x0008, 0x189d: 0x0008, + 0x189e: 0x0008, 0x189f: 0x0008, 0x18a0: 0x0008, 0x18a1: 0x0008, 0x18a2: 0x0008, 0x18a3: 0x0008, + 0x18a4: 0x0008, 0x18a5: 0x0008, 0x18a6: 0x0008, 0x18a7: 0x0008, 0x18a8: 0x0008, 0x18a9: 0x0040, + 0x18aa: 0x0008, 0x18ab: 0x0008, 0x18ac: 0x0008, 0x18ad: 0x0008, 0x18ae: 0x0008, 0x18af: 0x0008, + 0x18b0: 0x0008, 0x18b1: 0x0040, 0x18b2: 0x0008, 0x18b3: 0x0008, 0x18b4: 0x0040, 0x18b5: 0x0008, + 0x18b6: 0x0008, 0x18b7: 0x0008, 0x18b8: 0x0008, 0x18b9: 0x0008, 0x18ba: 0x0040, 0x18bb: 0x3308, + 0x18bc: 0x3308, 0x18bd: 0x0008, 0x18be: 0x3008, 0x18bf: 0x3008, + // Block 0x63, offset 0x18c0 + 0x18c0: 0x3308, 0x18c1: 0x3008, 0x18c2: 0x3008, 0x18c3: 0x3008, 0x18c4: 0x3008, 0x18c5: 0x0040, + 0x18c6: 0x0040, 0x18c7: 0x3008, 0x18c8: 0x3008, 0x18c9: 0x0040, 0x18ca: 0x0040, 0x18cb: 0x3008, + 0x18cc: 0x3008, 0x18cd: 0x3808, 0x18ce: 0x0040, 0x18cf: 0x0040, 0x18d0: 0x0008, 0x18d1: 0x0040, + 0x18d2: 0x0040, 0x18d3: 0x0040, 0x18d4: 0x0040, 0x18d5: 0x0040, 0x18d6: 0x0040, 0x18d7: 0x3008, + 0x18d8: 0x0040, 0x18d9: 0x0040, 0x18da: 0x0040, 0x18db: 0x0040, 0x18dc: 0x0040, 0x18dd: 0x0008, + 0x18de: 0x0008, 0x18df: 0x0008, 0x18e0: 0x0008, 0x18e1: 0x0008, 0x18e2: 0x3008, 0x18e3: 0x3008, + 0x18e4: 0x0040, 0x18e5: 0x0040, 0x18e6: 0x3308, 0x18e7: 0x3308, 0x18e8: 0x3308, 0x18e9: 0x3308, + 0x18ea: 0x3308, 0x18eb: 0x3308, 0x18ec: 0x3308, 0x18ed: 0x0040, 0x18ee: 0x0040, 0x18ef: 0x0040, + 0x18f0: 0x3308, 0x18f1: 0x3308, 0x18f2: 0x3308, 0x18f3: 0x3308, 0x18f4: 0x3308, 0x18f5: 0x0040, + 0x18f6: 0x0040, 0x18f7: 0x0040, 0x18f8: 0x0040, 0x18f9: 0x0040, 0x18fa: 0x0040, 0x18fb: 0x0040, + 0x18fc: 0x0040, 0x18fd: 0x0040, 0x18fe: 0x0040, 0x18ff: 0x0040, + // Block 0x64, offset 0x1900 + 0x1900: 0x0008, 0x1901: 0x0008, 0x1902: 0x0008, 0x1903: 0x0008, 0x1904: 0x0008, 0x1905: 0x0008, + 0x1906: 0x0008, 0x1907: 0x0040, 0x1908: 0x0040, 0x1909: 0x0008, 0x190a: 0x0040, 0x190b: 0x0040, + 0x190c: 0x0008, 0x190d: 0x0008, 0x190e: 0x0008, 0x190f: 0x0008, 0x1910: 0x0008, 0x1911: 0x0008, + 0x1912: 0x0008, 0x1913: 0x0008, 0x1914: 0x0040, 0x1915: 0x0008, 0x1916: 0x0008, 0x1917: 0x0040, + 0x1918: 0x0008, 0x1919: 0x0008, 0x191a: 0x0008, 0x191b: 0x0008, 0x191c: 0x0008, 0x191d: 0x0008, + 0x191e: 0x0008, 0x191f: 0x0008, 0x1920: 0x0008, 0x1921: 0x0008, 0x1922: 0x0008, 0x1923: 0x0008, + 0x1924: 0x0008, 0x1925: 0x0008, 0x1926: 0x0008, 0x1927: 0x0008, 0x1928: 0x0008, 0x1929: 0x0008, + 0x192a: 0x0008, 0x192b: 0x0008, 0x192c: 0x0008, 0x192d: 0x0008, 0x192e: 0x0008, 0x192f: 0x0008, + 0x1930: 0x3008, 0x1931: 0x3008, 0x1932: 0x3008, 0x1933: 0x3008, 0x1934: 0x3008, 0x1935: 0x3008, + 0x1936: 0x0040, 0x1937: 0x3008, 0x1938: 0x3008, 0x1939: 0x0040, 0x193a: 0x0040, 0x193b: 0x3308, + 0x193c: 0x3308, 0x193d: 0x3808, 0x193e: 0x3b08, 0x193f: 0x0008, + // Block 0x65, offset 0x1940 + 0x1940: 0x0019, 0x1941: 0x02e9, 0x1942: 0x03d9, 0x1943: 0x02f1, 0x1944: 0x02f9, 0x1945: 0x03f1, + 0x1946: 0x0309, 0x1947: 0x00a9, 0x1948: 0x0311, 0x1949: 0x00b1, 0x194a: 0x0319, 0x194b: 0x0101, + 0x194c: 0x0321, 0x194d: 0x0329, 0x194e: 0x0051, 0x194f: 0x0339, 0x1950: 0x0751, 0x1951: 0x00b9, + 0x1952: 0x0089, 0x1953: 0x0341, 0x1954: 0x0349, 0x1955: 0x0391, 0x1956: 0x00c1, 0x1957: 0x0109, + 0x1958: 0x00c9, 0x1959: 0x04b1, 0x195a: 0x0019, 0x195b: 0x02e9, 0x195c: 0x03d9, 0x195d: 0x02f1, + 0x195e: 0x02f9, 0x195f: 0x03f1, 0x1960: 0x0309, 0x1961: 0x00a9, 0x1962: 0x0311, 0x1963: 0x00b1, + 0x1964: 0x0319, 0x1965: 0x0101, 0x1966: 0x0321, 0x1967: 0x0329, 0x1968: 0x0051, 0x1969: 0x0339, + 0x196a: 0x0751, 0x196b: 0x00b9, 0x196c: 0x0089, 0x196d: 0x0341, 0x196e: 0x0349, 0x196f: 0x0391, + 0x1970: 0x00c1, 0x1971: 0x0109, 0x1972: 0x00c9, 0x1973: 0x04b1, 0x1974: 0x0019, 0x1975: 0x02e9, + 0x1976: 0x03d9, 0x1977: 0x02f1, 0x1978: 0x02f9, 0x1979: 0x03f1, 0x197a: 0x0309, 0x197b: 0x00a9, + 0x197c: 0x0311, 0x197d: 0x00b1, 0x197e: 0x0319, 0x197f: 0x0101, + // Block 0x66, offset 0x1980 + 0x1980: 0x0321, 0x1981: 0x0329, 0x1982: 0x0051, 0x1983: 0x0339, 0x1984: 0x0751, 0x1985: 0x00b9, + 0x1986: 0x0089, 0x1987: 0x0341, 0x1988: 0x0349, 0x1989: 0x0391, 0x198a: 0x00c1, 0x198b: 0x0109, + 0x198c: 0x00c9, 0x198d: 0x04b1, 0x198e: 0x0019, 0x198f: 0x02e9, 0x1990: 0x03d9, 0x1991: 0x02f1, + 0x1992: 0x02f9, 0x1993: 0x03f1, 0x1994: 0x0309, 0x1995: 0x0040, 0x1996: 0x0311, 0x1997: 0x00b1, + 0x1998: 0x0319, 0x1999: 0x0101, 0x199a: 0x0321, 0x199b: 0x0329, 0x199c: 0x0051, 0x199d: 0x0339, + 0x199e: 0x0751, 0x199f: 0x00b9, 0x19a0: 0x0089, 0x19a1: 0x0341, 0x19a2: 0x0349, 0x19a3: 0x0391, + 0x19a4: 0x00c1, 0x19a5: 0x0109, 0x19a6: 0x00c9, 0x19a7: 0x04b1, 0x19a8: 0x0019, 0x19a9: 0x02e9, + 0x19aa: 0x03d9, 0x19ab: 0x02f1, 0x19ac: 0x02f9, 0x19ad: 0x03f1, 0x19ae: 0x0309, 0x19af: 0x00a9, + 0x19b0: 0x0311, 0x19b1: 0x00b1, 0x19b2: 0x0319, 0x19b3: 0x0101, 0x19b4: 0x0321, 0x19b5: 0x0329, + 0x19b6: 0x0051, 0x19b7: 0x0339, 0x19b8: 0x0751, 0x19b9: 0x00b9, 0x19ba: 0x0089, 0x19bb: 0x0341, + 0x19bc: 0x0349, 0x19bd: 0x0391, 0x19be: 0x00c1, 0x19bf: 0x0109, + // Block 0x67, offset 0x19c0 + 0x19c0: 0x00c9, 0x19c1: 0x04b1, 0x19c2: 0x0019, 0x19c3: 0x02e9, 0x19c4: 0x03d9, 0x19c5: 0x02f1, + 0x19c6: 0x02f9, 0x19c7: 0x03f1, 0x19c8: 0x0309, 0x19c9: 0x00a9, 0x19ca: 0x0311, 0x19cb: 0x00b1, + 0x19cc: 0x0319, 0x19cd: 0x0101, 0x19ce: 0x0321, 0x19cf: 0x0329, 0x19d0: 0x0051, 0x19d1: 0x0339, + 0x19d2: 0x0751, 0x19d3: 0x00b9, 0x19d4: 0x0089, 0x19d5: 0x0341, 0x19d6: 0x0349, 0x19d7: 0x0391, + 0x19d8: 0x00c1, 0x19d9: 0x0109, 0x19da: 0x00c9, 0x19db: 0x04b1, 0x19dc: 0x0019, 0x19dd: 0x0040, + 0x19de: 0x03d9, 0x19df: 0x02f1, 0x19e0: 0x0040, 0x19e1: 0x0040, 0x19e2: 0x0309, 0x19e3: 0x0040, + 0x19e4: 0x0040, 0x19e5: 0x00b1, 0x19e6: 0x0319, 0x19e7: 0x0040, 0x19e8: 0x0040, 0x19e9: 0x0329, + 0x19ea: 0x0051, 0x19eb: 0x0339, 0x19ec: 0x0751, 0x19ed: 0x0040, 0x19ee: 0x0089, 0x19ef: 0x0341, + 0x19f0: 0x0349, 0x19f1: 0x0391, 0x19f2: 0x00c1, 0x19f3: 0x0109, 0x19f4: 0x00c9, 0x19f5: 0x04b1, + 0x19f6: 0x0019, 0x19f7: 0x02e9, 0x19f8: 0x03d9, 0x19f9: 0x02f1, 0x19fa: 0x0040, 0x19fb: 0x03f1, + 0x19fc: 0x0040, 0x19fd: 0x00a9, 0x19fe: 0x0311, 0x19ff: 0x00b1, + // Block 0x68, offset 0x1a00 + 0x1a00: 0x0319, 0x1a01: 0x0101, 0x1a02: 0x0321, 0x1a03: 0x0329, 0x1a04: 0x0040, 0x1a05: 0x0339, + 0x1a06: 0x0751, 0x1a07: 0x00b9, 0x1a08: 0x0089, 0x1a09: 0x0341, 0x1a0a: 0x0349, 0x1a0b: 0x0391, + 0x1a0c: 0x00c1, 0x1a0d: 0x0109, 0x1a0e: 0x00c9, 0x1a0f: 0x04b1, 0x1a10: 0x0019, 0x1a11: 0x02e9, + 0x1a12: 0x03d9, 0x1a13: 0x02f1, 0x1a14: 0x02f9, 0x1a15: 0x03f1, 0x1a16: 0x0309, 0x1a17: 0x00a9, + 0x1a18: 0x0311, 0x1a19: 0x00b1, 0x1a1a: 0x0319, 0x1a1b: 0x0101, 0x1a1c: 0x0321, 0x1a1d: 0x0329, + 0x1a1e: 0x0051, 0x1a1f: 0x0339, 0x1a20: 0x0751, 0x1a21: 0x00b9, 0x1a22: 0x0089, 0x1a23: 0x0341, + 0x1a24: 0x0349, 0x1a25: 0x0391, 0x1a26: 0x00c1, 0x1a27: 0x0109, 0x1a28: 0x00c9, 0x1a29: 0x04b1, + 0x1a2a: 0x0019, 0x1a2b: 0x02e9, 0x1a2c: 0x03d9, 0x1a2d: 0x02f1, 0x1a2e: 0x02f9, 0x1a2f: 0x03f1, + 0x1a30: 0x0309, 0x1a31: 0x00a9, 0x1a32: 0x0311, 0x1a33: 0x00b1, 0x1a34: 0x0319, 0x1a35: 0x0101, + 0x1a36: 0x0321, 0x1a37: 0x0329, 0x1a38: 0x0051, 0x1a39: 0x0339, 0x1a3a: 0x0751, 0x1a3b: 0x00b9, + 0x1a3c: 0x0089, 0x1a3d: 0x0341, 0x1a3e: 0x0349, 0x1a3f: 0x0391, + // Block 0x69, offset 0x1a40 + 0x1a40: 0x00c1, 0x1a41: 0x0109, 0x1a42: 0x00c9, 0x1a43: 0x04b1, 0x1a44: 0x0019, 0x1a45: 0x02e9, + 0x1a46: 0x0040, 0x1a47: 0x02f1, 0x1a48: 0x02f9, 0x1a49: 0x03f1, 0x1a4a: 0x0309, 0x1a4b: 0x0040, + 0x1a4c: 0x0040, 0x1a4d: 0x00b1, 0x1a4e: 0x0319, 0x1a4f: 0x0101, 0x1a50: 0x0321, 0x1a51: 0x0329, + 0x1a52: 0x0051, 0x1a53: 0x0339, 0x1a54: 0x0751, 0x1a55: 0x0040, 0x1a56: 0x0089, 0x1a57: 0x0341, + 0x1a58: 0x0349, 0x1a59: 0x0391, 0x1a5a: 0x00c1, 0x1a5b: 0x0109, 0x1a5c: 0x00c9, 0x1a5d: 0x0040, + 0x1a5e: 0x0019, 0x1a5f: 0x02e9, 0x1a60: 0x03d9, 0x1a61: 0x02f1, 0x1a62: 0x02f9, 0x1a63: 0x03f1, + 0x1a64: 0x0309, 0x1a65: 0x00a9, 0x1a66: 0x0311, 0x1a67: 0x00b1, 0x1a68: 0x0319, 0x1a69: 0x0101, + 0x1a6a: 0x0321, 0x1a6b: 0x0329, 0x1a6c: 0x0051, 0x1a6d: 0x0339, 0x1a6e: 0x0751, 0x1a6f: 0x00b9, + 0x1a70: 0x0089, 0x1a71: 0x0341, 0x1a72: 0x0349, 0x1a73: 0x0391, 0x1a74: 0x00c1, 0x1a75: 0x0109, + 0x1a76: 0x00c9, 0x1a77: 0x04b1, 0x1a78: 0x0019, 0x1a79: 0x02e9, 0x1a7a: 0x0040, 0x1a7b: 0x02f1, + 0x1a7c: 0x02f9, 0x1a7d: 0x03f1, 0x1a7e: 0x0309, 0x1a7f: 0x0040, + // Block 0x6a, offset 0x1a80 + 0x1a80: 0x0311, 0x1a81: 0x00b1, 0x1a82: 0x0319, 0x1a83: 0x0101, 0x1a84: 0x0321, 0x1a85: 0x0040, + 0x1a86: 0x0051, 0x1a87: 0x0040, 0x1a88: 0x0040, 0x1a89: 0x0040, 0x1a8a: 0x0089, 0x1a8b: 0x0341, + 0x1a8c: 0x0349, 0x1a8d: 0x0391, 0x1a8e: 0x00c1, 0x1a8f: 0x0109, 0x1a90: 0x00c9, 0x1a91: 0x0040, + 0x1a92: 0x0019, 0x1a93: 0x02e9, 0x1a94: 0x03d9, 0x1a95: 0x02f1, 0x1a96: 0x02f9, 0x1a97: 0x03f1, + 0x1a98: 0x0309, 0x1a99: 0x00a9, 0x1a9a: 0x0311, 0x1a9b: 0x00b1, 0x1a9c: 0x0319, 0x1a9d: 0x0101, + 0x1a9e: 0x0321, 0x1a9f: 0x0329, 0x1aa0: 0x0051, 0x1aa1: 0x0339, 0x1aa2: 0x0751, 0x1aa3: 0x00b9, + 0x1aa4: 0x0089, 0x1aa5: 0x0341, 0x1aa6: 0x0349, 0x1aa7: 0x0391, 0x1aa8: 0x00c1, 0x1aa9: 0x0109, + 0x1aaa: 0x00c9, 0x1aab: 0x04b1, 0x1aac: 0x0019, 0x1aad: 0x02e9, 0x1aae: 0x03d9, 0x1aaf: 0x02f1, + 0x1ab0: 0x02f9, 0x1ab1: 0x03f1, 0x1ab2: 0x0309, 0x1ab3: 0x00a9, 0x1ab4: 0x0311, 0x1ab5: 0x00b1, + 0x1ab6: 0x0319, 0x1ab7: 0x0101, 0x1ab8: 0x0321, 0x1ab9: 0x0329, 0x1aba: 0x0051, 0x1abb: 0x0339, + 0x1abc: 0x0751, 0x1abd: 0x00b9, 0x1abe: 0x0089, 0x1abf: 0x0341, + // Block 0x6b, offset 0x1ac0 + 0x1ac0: 0x0349, 0x1ac1: 0x0391, 0x1ac2: 0x00c1, 0x1ac3: 0x0109, 0x1ac4: 0x00c9, 0x1ac5: 0x04b1, + 0x1ac6: 0x0019, 0x1ac7: 0x02e9, 0x1ac8: 0x03d9, 0x1ac9: 0x02f1, 0x1aca: 0x02f9, 0x1acb: 0x03f1, + 0x1acc: 0x0309, 0x1acd: 0x00a9, 0x1ace: 0x0311, 0x1acf: 0x00b1, 0x1ad0: 0x0319, 0x1ad1: 0x0101, + 0x1ad2: 0x0321, 0x1ad3: 0x0329, 0x1ad4: 0x0051, 0x1ad5: 0x0339, 0x1ad6: 0x0751, 0x1ad7: 0x00b9, + 0x1ad8: 0x0089, 0x1ad9: 0x0341, 0x1ada: 0x0349, 0x1adb: 0x0391, 0x1adc: 0x00c1, 0x1add: 0x0109, + 0x1ade: 0x00c9, 0x1adf: 0x04b1, 0x1ae0: 0x0019, 0x1ae1: 0x02e9, 0x1ae2: 0x03d9, 0x1ae3: 0x02f1, + 0x1ae4: 0x02f9, 0x1ae5: 0x03f1, 0x1ae6: 0x0309, 0x1ae7: 0x00a9, 0x1ae8: 0x0311, 0x1ae9: 0x00b1, + 0x1aea: 0x0319, 0x1aeb: 0x0101, 0x1aec: 0x0321, 0x1aed: 0x0329, 0x1aee: 0x0051, 0x1aef: 0x0339, + 0x1af0: 0x0751, 0x1af1: 0x00b9, 0x1af2: 0x0089, 0x1af3: 0x0341, 0x1af4: 0x0349, 0x1af5: 0x0391, + 0x1af6: 0x00c1, 0x1af7: 0x0109, 0x1af8: 0x00c9, 0x1af9: 0x04b1, 0x1afa: 0x0019, 0x1afb: 0x02e9, + 0x1afc: 0x03d9, 0x1afd: 0x02f1, 0x1afe: 0x02f9, 0x1aff: 0x03f1, + // Block 0x6c, offset 0x1b00 + 0x1b00: 0x0309, 0x1b01: 0x00a9, 0x1b02: 0x0311, 0x1b03: 0x00b1, 0x1b04: 0x0319, 0x1b05: 0x0101, + 0x1b06: 0x0321, 0x1b07: 0x0329, 0x1b08: 0x0051, 0x1b09: 0x0339, 0x1b0a: 0x0751, 0x1b0b: 0x00b9, + 0x1b0c: 0x0089, 0x1b0d: 0x0341, 0x1b0e: 0x0349, 0x1b0f: 0x0391, 0x1b10: 0x00c1, 0x1b11: 0x0109, + 0x1b12: 0x00c9, 0x1b13: 0x04b1, 0x1b14: 0x0019, 0x1b15: 0x02e9, 0x1b16: 0x03d9, 0x1b17: 0x02f1, + 0x1b18: 0x02f9, 0x1b19: 0x03f1, 0x1b1a: 0x0309, 0x1b1b: 0x00a9, 0x1b1c: 0x0311, 0x1b1d: 0x00b1, + 0x1b1e: 0x0319, 0x1b1f: 0x0101, 0x1b20: 0x0321, 0x1b21: 0x0329, 0x1b22: 0x0051, 0x1b23: 0x0339, + 0x1b24: 0x0751, 0x1b25: 0x00b9, 0x1b26: 0x0089, 0x1b27: 0x0341, 0x1b28: 0x0349, 0x1b29: 0x0391, + 0x1b2a: 0x00c1, 0x1b2b: 0x0109, 0x1b2c: 0x00c9, 0x1b2d: 0x04b1, 0x1b2e: 0x0019, 0x1b2f: 0x02e9, + 0x1b30: 0x03d9, 0x1b31: 0x02f1, 0x1b32: 0x02f9, 0x1b33: 0x03f1, 0x1b34: 0x0309, 0x1b35: 0x00a9, + 0x1b36: 0x0311, 0x1b37: 0x00b1, 0x1b38: 0x0319, 0x1b39: 0x0101, 0x1b3a: 0x0321, 0x1b3b: 0x0329, + 0x1b3c: 0x0051, 0x1b3d: 0x0339, 0x1b3e: 0x0751, 0x1b3f: 0x00b9, + // Block 0x6d, offset 0x1b40 + 0x1b40: 0x0089, 0x1b41: 0x0341, 0x1b42: 0x0349, 0x1b43: 0x0391, 0x1b44: 0x00c1, 0x1b45: 0x0109, + 0x1b46: 0x00c9, 0x1b47: 0x04b1, 0x1b48: 0x0019, 0x1b49: 0x02e9, 0x1b4a: 0x03d9, 0x1b4b: 0x02f1, + 0x1b4c: 0x02f9, 0x1b4d: 0x03f1, 0x1b4e: 0x0309, 0x1b4f: 0x00a9, 0x1b50: 0x0311, 0x1b51: 0x00b1, + 0x1b52: 0x0319, 0x1b53: 0x0101, 0x1b54: 0x0321, 0x1b55: 0x0329, 0x1b56: 0x0051, 0x1b57: 0x0339, + 0x1b58: 0x0751, 0x1b59: 0x00b9, 0x1b5a: 0x0089, 0x1b5b: 0x0341, 0x1b5c: 0x0349, 0x1b5d: 0x0391, + 0x1b5e: 0x00c1, 0x1b5f: 0x0109, 0x1b60: 0x00c9, 0x1b61: 0x04b1, 0x1b62: 0x0019, 0x1b63: 0x02e9, + 0x1b64: 0x03d9, 0x1b65: 0x02f1, 0x1b66: 0x02f9, 0x1b67: 0x03f1, 0x1b68: 0x0309, 0x1b69: 0x00a9, + 0x1b6a: 0x0311, 0x1b6b: 0x00b1, 0x1b6c: 0x0319, 0x1b6d: 0x0101, 0x1b6e: 0x0321, 0x1b6f: 0x0329, + 0x1b70: 0x0051, 0x1b71: 0x0339, 0x1b72: 0x0751, 0x1b73: 0x00b9, 0x1b74: 0x0089, 0x1b75: 0x0341, + 0x1b76: 0x0349, 0x1b77: 0x0391, 0x1b78: 0x00c1, 0x1b79: 0x0109, 0x1b7a: 0x00c9, 0x1b7b: 0x04b1, + 0x1b7c: 0x0019, 0x1b7d: 0x02e9, 0x1b7e: 0x03d9, 0x1b7f: 0x02f1, + // Block 0x6e, offset 0x1b80 + 0x1b80: 0x02f9, 0x1b81: 0x03f1, 0x1b82: 0x0309, 0x1b83: 0x00a9, 0x1b84: 0x0311, 0x1b85: 0x00b1, + 0x1b86: 0x0319, 0x1b87: 0x0101, 0x1b88: 0x0321, 0x1b89: 0x0329, 0x1b8a: 0x0051, 0x1b8b: 0x0339, + 0x1b8c: 0x0751, 0x1b8d: 0x00b9, 0x1b8e: 0x0089, 0x1b8f: 0x0341, 0x1b90: 0x0349, 0x1b91: 0x0391, + 0x1b92: 0x00c1, 0x1b93: 0x0109, 0x1b94: 0x00c9, 0x1b95: 0x04b1, 0x1b96: 0x0019, 0x1b97: 0x02e9, + 0x1b98: 0x03d9, 0x1b99: 0x02f1, 0x1b9a: 0x02f9, 0x1b9b: 0x03f1, 0x1b9c: 0x0309, 0x1b9d: 0x00a9, + 0x1b9e: 0x0311, 0x1b9f: 0x00b1, 0x1ba0: 0x0319, 0x1ba1: 0x0101, 0x1ba2: 0x0321, 0x1ba3: 0x0329, + 0x1ba4: 0x0051, 0x1ba5: 0x0339, 0x1ba6: 0x0751, 0x1ba7: 0x00b9, 0x1ba8: 0x0089, 0x1ba9: 0x0341, + 0x1baa: 0x0349, 0x1bab: 0x0391, 0x1bac: 0x00c1, 0x1bad: 0x0109, 0x1bae: 0x00c9, 0x1baf: 0x04b1, + 0x1bb0: 0x0019, 0x1bb1: 0x02e9, 0x1bb2: 0x03d9, 0x1bb3: 0x02f1, 0x1bb4: 0x02f9, 0x1bb5: 0x03f1, + 0x1bb6: 0x0309, 0x1bb7: 0x00a9, 0x1bb8: 0x0311, 0x1bb9: 0x00b1, 0x1bba: 0x0319, 0x1bbb: 0x0101, + 0x1bbc: 0x0321, 0x1bbd: 0x0329, 0x1bbe: 0x0051, 0x1bbf: 0x0339, + // Block 0x6f, offset 0x1bc0 + 0x1bc0: 0x0751, 0x1bc1: 0x00b9, 0x1bc2: 0x0089, 0x1bc3: 0x0341, 0x1bc4: 0x0349, 0x1bc5: 0x0391, + 0x1bc6: 0x00c1, 0x1bc7: 0x0109, 0x1bc8: 0x00c9, 0x1bc9: 0x04b1, 0x1bca: 0x0019, 0x1bcb: 0x02e9, + 0x1bcc: 0x03d9, 0x1bcd: 0x02f1, 0x1bce: 0x02f9, 0x1bcf: 0x03f1, 0x1bd0: 0x0309, 0x1bd1: 0x00a9, + 0x1bd2: 0x0311, 0x1bd3: 0x00b1, 0x1bd4: 0x0319, 0x1bd5: 0x0101, 0x1bd6: 0x0321, 0x1bd7: 0x0329, + 0x1bd8: 0x0051, 0x1bd9: 0x0339, 0x1bda: 0x0751, 0x1bdb: 0x00b9, 0x1bdc: 0x0089, 0x1bdd: 0x0341, + 0x1bde: 0x0349, 0x1bdf: 0x0391, 0x1be0: 0x00c1, 0x1be1: 0x0109, 0x1be2: 0x00c9, 0x1be3: 0x04b1, + 0x1be4: 0x23e1, 0x1be5: 0x23e9, 0x1be6: 0x0040, 0x1be7: 0x0040, 0x1be8: 0x23f1, 0x1be9: 0x0399, + 0x1bea: 0x03a1, 0x1beb: 0x03a9, 0x1bec: 0x23f9, 0x1bed: 0x2401, 0x1bee: 0x2409, 0x1bef: 0x04d1, + 0x1bf0: 0x05f9, 0x1bf1: 0x2411, 0x1bf2: 0x2419, 0x1bf3: 0x2421, 0x1bf4: 0x2429, 0x1bf5: 0x2431, + 0x1bf6: 0x2439, 0x1bf7: 0x0799, 0x1bf8: 0x03c1, 0x1bf9: 0x04d1, 0x1bfa: 0x2441, 0x1bfb: 0x2449, + 0x1bfc: 0x2451, 0x1bfd: 0x03b1, 0x1bfe: 0x03b9, 0x1bff: 0x2459, + // Block 0x70, offset 0x1c00 + 0x1c00: 0x0769, 0x1c01: 0x2461, 0x1c02: 0x23f1, 0x1c03: 0x0399, 0x1c04: 0x03a1, 0x1c05: 0x03a9, + 0x1c06: 0x23f9, 0x1c07: 0x2401, 0x1c08: 0x2409, 0x1c09: 0x04d1, 0x1c0a: 0x05f9, 0x1c0b: 0x2411, + 0x1c0c: 0x2419, 0x1c0d: 0x2421, 0x1c0e: 0x2429, 0x1c0f: 0x2431, 0x1c10: 0x2439, 0x1c11: 0x0799, + 0x1c12: 0x03c1, 0x1c13: 0x2441, 0x1c14: 0x2441, 0x1c15: 0x2449, 0x1c16: 0x2451, 0x1c17: 0x03b1, + 0x1c18: 0x03b9, 0x1c19: 0x2459, 0x1c1a: 0x0769, 0x1c1b: 0x2469, 0x1c1c: 0x23f9, 0x1c1d: 0x04d1, + 0x1c1e: 0x2411, 0x1c1f: 0x03b1, 0x1c20: 0x03c1, 0x1c21: 0x0799, 0x1c22: 0x23f1, 0x1c23: 0x0399, + 0x1c24: 0x03a1, 0x1c25: 0x03a9, 0x1c26: 0x23f9, 0x1c27: 0x2401, 0x1c28: 0x2409, 0x1c29: 0x04d1, + 0x1c2a: 0x05f9, 0x1c2b: 0x2411, 0x1c2c: 0x2419, 0x1c2d: 0x2421, 0x1c2e: 0x2429, 0x1c2f: 0x2431, + 0x1c30: 0x2439, 0x1c31: 0x0799, 0x1c32: 0x03c1, 0x1c33: 0x04d1, 0x1c34: 0x2441, 0x1c35: 0x2449, + 0x1c36: 0x2451, 0x1c37: 0x03b1, 0x1c38: 0x03b9, 0x1c39: 0x2459, 0x1c3a: 0x0769, 0x1c3b: 0x2461, + 0x1c3c: 0x23f1, 0x1c3d: 0x0399, 0x1c3e: 0x03a1, 0x1c3f: 0x03a9, + // Block 0x71, offset 0x1c40 + 0x1c40: 0x23f9, 0x1c41: 0x2401, 0x1c42: 0x2409, 0x1c43: 0x04d1, 0x1c44: 0x05f9, 0x1c45: 0x2411, + 0x1c46: 0x2419, 0x1c47: 0x2421, 0x1c48: 0x2429, 0x1c49: 0x2431, 0x1c4a: 0x2439, 0x1c4b: 0x0799, + 0x1c4c: 0x03c1, 0x1c4d: 0x2441, 0x1c4e: 0x2441, 0x1c4f: 0x2449, 0x1c50: 0x2451, 0x1c51: 0x03b1, + 0x1c52: 0x03b9, 0x1c53: 0x2459, 0x1c54: 0x0769, 0x1c55: 0x2469, 0x1c56: 0x23f9, 0x1c57: 0x04d1, + 0x1c58: 0x2411, 0x1c59: 0x03b1, 0x1c5a: 0x03c1, 0x1c5b: 0x0799, 0x1c5c: 0x23f1, 0x1c5d: 0x0399, + 0x1c5e: 0x03a1, 0x1c5f: 0x03a9, 0x1c60: 0x23f9, 0x1c61: 0x2401, 0x1c62: 0x2409, 0x1c63: 0x04d1, + 0x1c64: 0x05f9, 0x1c65: 0x2411, 0x1c66: 0x2419, 0x1c67: 0x2421, 0x1c68: 0x2429, 0x1c69: 0x2431, + 0x1c6a: 0x2439, 0x1c6b: 0x0799, 0x1c6c: 0x03c1, 0x1c6d: 0x04d1, 0x1c6e: 0x2441, 0x1c6f: 0x2449, + 0x1c70: 0x2451, 0x1c71: 0x03b1, 0x1c72: 0x03b9, 0x1c73: 0x2459, 0x1c74: 0x0769, 0x1c75: 0x2461, + 0x1c76: 0x23f1, 0x1c77: 0x0399, 0x1c78: 0x03a1, 0x1c79: 0x03a9, 0x1c7a: 0x23f9, 0x1c7b: 0x2401, + 0x1c7c: 0x2409, 0x1c7d: 0x04d1, 0x1c7e: 0x05f9, 0x1c7f: 0x2411, + // Block 0x72, offset 0x1c80 + 0x1c80: 0x2419, 0x1c81: 0x2421, 0x1c82: 0x2429, 0x1c83: 0x2431, 0x1c84: 0x2439, 0x1c85: 0x0799, + 0x1c86: 0x03c1, 0x1c87: 0x2441, 0x1c88: 0x2441, 0x1c89: 0x2449, 0x1c8a: 0x2451, 0x1c8b: 0x03b1, + 0x1c8c: 0x03b9, 0x1c8d: 0x2459, 0x1c8e: 0x0769, 0x1c8f: 0x2469, 0x1c90: 0x23f9, 0x1c91: 0x04d1, + 0x1c92: 0x2411, 0x1c93: 0x03b1, 0x1c94: 0x03c1, 0x1c95: 0x0799, 0x1c96: 0x23f1, 0x1c97: 0x0399, + 0x1c98: 0x03a1, 0x1c99: 0x03a9, 0x1c9a: 0x23f9, 0x1c9b: 0x2401, 0x1c9c: 0x2409, 0x1c9d: 0x04d1, + 0x1c9e: 0x05f9, 0x1c9f: 0x2411, 0x1ca0: 0x2419, 0x1ca1: 0x2421, 0x1ca2: 0x2429, 0x1ca3: 0x2431, + 0x1ca4: 0x2439, 0x1ca5: 0x0799, 0x1ca6: 0x03c1, 0x1ca7: 0x04d1, 0x1ca8: 0x2441, 0x1ca9: 0x2449, + 0x1caa: 0x2451, 0x1cab: 0x03b1, 0x1cac: 0x03b9, 0x1cad: 0x2459, 0x1cae: 0x0769, 0x1caf: 0x2461, + 0x1cb0: 0x23f1, 0x1cb1: 0x0399, 0x1cb2: 0x03a1, 0x1cb3: 0x03a9, 0x1cb4: 0x23f9, 0x1cb5: 0x2401, + 0x1cb6: 0x2409, 0x1cb7: 0x04d1, 0x1cb8: 0x05f9, 0x1cb9: 0x2411, 0x1cba: 0x2419, 0x1cbb: 0x2421, + 0x1cbc: 0x2429, 0x1cbd: 0x2431, 0x1cbe: 0x2439, 0x1cbf: 0x0799, + // Block 0x73, offset 0x1cc0 + 0x1cc0: 0x03c1, 0x1cc1: 0x2441, 0x1cc2: 0x2441, 0x1cc3: 0x2449, 0x1cc4: 0x2451, 0x1cc5: 0x03b1, + 0x1cc6: 0x03b9, 0x1cc7: 0x2459, 0x1cc8: 0x0769, 0x1cc9: 0x2469, 0x1cca: 0x23f9, 0x1ccb: 0x04d1, + 0x1ccc: 0x2411, 0x1ccd: 0x03b1, 0x1cce: 0x03c1, 0x1ccf: 0x0799, 0x1cd0: 0x23f1, 0x1cd1: 0x0399, + 0x1cd2: 0x03a1, 0x1cd3: 0x03a9, 0x1cd4: 0x23f9, 0x1cd5: 0x2401, 0x1cd6: 0x2409, 0x1cd7: 0x04d1, + 0x1cd8: 0x05f9, 0x1cd9: 0x2411, 0x1cda: 0x2419, 0x1cdb: 0x2421, 0x1cdc: 0x2429, 0x1cdd: 0x2431, + 0x1cde: 0x2439, 0x1cdf: 0x0799, 0x1ce0: 0x03c1, 0x1ce1: 0x04d1, 0x1ce2: 0x2441, 0x1ce3: 0x2449, + 0x1ce4: 0x2451, 0x1ce5: 0x03b1, 0x1ce6: 0x03b9, 0x1ce7: 0x2459, 0x1ce8: 0x0769, 0x1ce9: 0x2461, + 0x1cea: 0x23f1, 0x1ceb: 0x0399, 0x1cec: 0x03a1, 0x1ced: 0x03a9, 0x1cee: 0x23f9, 0x1cef: 0x2401, + 0x1cf0: 0x2409, 0x1cf1: 0x04d1, 0x1cf2: 0x05f9, 0x1cf3: 0x2411, 0x1cf4: 0x2419, 0x1cf5: 0x2421, + 0x1cf6: 0x2429, 0x1cf7: 0x2431, 0x1cf8: 0x2439, 0x1cf9: 0x0799, 0x1cfa: 0x03c1, 0x1cfb: 0x2441, + 0x1cfc: 0x2441, 0x1cfd: 0x2449, 0x1cfe: 0x2451, 0x1cff: 0x03b1, + // Block 0x74, offset 0x1d00 + 0x1d00: 0x03b9, 0x1d01: 0x2459, 0x1d02: 0x0769, 0x1d03: 0x2469, 0x1d04: 0x23f9, 0x1d05: 0x04d1, + 0x1d06: 0x2411, 0x1d07: 0x03b1, 0x1d08: 0x03c1, 0x1d09: 0x0799, 0x1d0a: 0x2471, 0x1d0b: 0x2471, + 0x1d0c: 0x0040, 0x1d0d: 0x0040, 0x1d0e: 0x06e1, 0x1d0f: 0x0049, 0x1d10: 0x0029, 0x1d11: 0x0031, + 0x1d12: 0x06e9, 0x1d13: 0x06f1, 0x1d14: 0x06f9, 0x1d15: 0x0701, 0x1d16: 0x0709, 0x1d17: 0x0711, + 0x1d18: 0x06e1, 0x1d19: 0x0049, 0x1d1a: 0x0029, 0x1d1b: 0x0031, 0x1d1c: 0x06e9, 0x1d1d: 0x06f1, + 0x1d1e: 0x06f9, 0x1d1f: 0x0701, 0x1d20: 0x0709, 0x1d21: 0x0711, 0x1d22: 0x06e1, 0x1d23: 0x0049, + 0x1d24: 0x0029, 0x1d25: 0x0031, 0x1d26: 0x06e9, 0x1d27: 0x06f1, 0x1d28: 0x06f9, 0x1d29: 0x0701, + 0x1d2a: 0x0709, 0x1d2b: 0x0711, 0x1d2c: 0x06e1, 0x1d2d: 0x0049, 0x1d2e: 0x0029, 0x1d2f: 0x0031, + 0x1d30: 0x06e9, 0x1d31: 0x06f1, 0x1d32: 0x06f9, 0x1d33: 0x0701, 0x1d34: 0x0709, 0x1d35: 0x0711, + 0x1d36: 0x06e1, 0x1d37: 0x0049, 0x1d38: 0x0029, 0x1d39: 0x0031, 0x1d3a: 0x06e9, 0x1d3b: 0x06f1, + 0x1d3c: 0x06f9, 0x1d3d: 0x0701, 0x1d3e: 0x0709, 0x1d3f: 0x0711, + // Block 0x75, offset 0x1d40 + 0x1d40: 0x3308, 0x1d41: 0x3308, 0x1d42: 0x3308, 0x1d43: 0x3308, 0x1d44: 0x3308, 0x1d45: 0x3308, + 0x1d46: 0x3308, 0x1d47: 0x0040, 0x1d48: 0x3308, 0x1d49: 0x3308, 0x1d4a: 0x3308, 0x1d4b: 0x3308, + 0x1d4c: 0x3308, 0x1d4d: 0x3308, 0x1d4e: 0x3308, 0x1d4f: 0x3308, 0x1d50: 0x3308, 0x1d51: 0x3308, + 0x1d52: 0x3308, 0x1d53: 0x3308, 0x1d54: 0x3308, 0x1d55: 0x3308, 0x1d56: 0x3308, 0x1d57: 0x3308, + 0x1d58: 0x3308, 0x1d59: 0x0040, 0x1d5a: 0x0040, 0x1d5b: 0x3308, 0x1d5c: 0x3308, 0x1d5d: 0x3308, + 0x1d5e: 0x3308, 0x1d5f: 0x3308, 0x1d60: 0x3308, 0x1d61: 0x3308, 0x1d62: 0x0040, 0x1d63: 0x3308, + 0x1d64: 0x3308, 0x1d65: 0x0040, 0x1d66: 0x3308, 0x1d67: 0x3308, 0x1d68: 0x3308, 0x1d69: 0x3308, + 0x1d6a: 0x3308, 0x1d6b: 0x0040, 0x1d6c: 0x0040, 0x1d6d: 0x0040, 0x1d6e: 0x0040, 0x1d6f: 0x0040, + 0x1d70: 0x2479, 0x1d71: 0x2481, 0x1d72: 0x02a9, 0x1d73: 0x2489, 0x1d74: 0x02b1, 0x1d75: 0x2491, + 0x1d76: 0x2499, 0x1d77: 0x24a1, 0x1d78: 0x24a9, 0x1d79: 0x24b1, 0x1d7a: 0x24b9, 0x1d7b: 0x24c1, + 0x1d7c: 0x02b9, 0x1d7d: 0x24c9, 0x1d7e: 0x24d1, 0x1d7f: 0x02c1, + // Block 0x76, offset 0x1d80 + 0x1d80: 0x02c9, 0x1d81: 0x24d9, 0x1d82: 0x24e1, 0x1d83: 0x24e9, 0x1d84: 0x24f1, 0x1d85: 0x24f9, + 0x1d86: 0x2501, 0x1d87: 0x2509, 0x1d88: 0x2511, 0x1d89: 0x2519, 0x1d8a: 0x2521, 0x1d8b: 0x2529, + 0x1d8c: 0x2531, 0x1d8d: 0x2539, 0x1d8e: 0x2541, 0x1d8f: 0x2549, 0x1d90: 0x2551, 0x1d91: 0x2479, + 0x1d92: 0x2481, 0x1d93: 0x02a9, 0x1d94: 0x2489, 0x1d95: 0x02b1, 0x1d96: 0x2491, 0x1d97: 0x2499, + 0x1d98: 0x24a1, 0x1d99: 0x24a9, 0x1d9a: 0x24b1, 0x1d9b: 0x24b9, 0x1d9c: 0x02b9, 0x1d9d: 0x24c9, + 0x1d9e: 0x02c1, 0x1d9f: 0x24d9, 0x1da0: 0x24e1, 0x1da1: 0x24e9, 0x1da2: 0x24f1, 0x1da3: 0x24f9, + 0x1da4: 0x2501, 0x1da5: 0x02d1, 0x1da6: 0x2509, 0x1da7: 0x2559, 0x1da8: 0x2531, 0x1da9: 0x2561, + 0x1daa: 0x2569, 0x1dab: 0x2571, 0x1dac: 0x2579, 0x1dad: 0x2581, 0x1dae: 0x0040, 0x1daf: 0x0040, + 0x1db0: 0x0040, 0x1db1: 0x0040, 0x1db2: 0x0040, 0x1db3: 0x0040, 0x1db4: 0x0040, 0x1db5: 0x0040, + 0x1db6: 0x0040, 0x1db7: 0x0040, 0x1db8: 0x0040, 0x1db9: 0x0040, 0x1dba: 0x0040, 0x1dbb: 0x0040, + 0x1dbc: 0x0040, 0x1dbd: 0x0040, 0x1dbe: 0x0040, 0x1dbf: 0x0040, + // Block 0x77, offset 0x1dc0 + 0x1dc0: 0xe115, 0x1dc1: 0xe115, 0x1dc2: 0xe135, 0x1dc3: 0xe135, 0x1dc4: 0xe115, 0x1dc5: 0xe115, + 0x1dc6: 0xe175, 0x1dc7: 0xe175, 0x1dc8: 0xe115, 0x1dc9: 0xe115, 0x1dca: 0xe135, 0x1dcb: 0xe135, + 0x1dcc: 0xe115, 0x1dcd: 0xe115, 0x1dce: 0xe1f5, 0x1dcf: 0xe1f5, 0x1dd0: 0xe115, 0x1dd1: 0xe115, + 0x1dd2: 0xe135, 0x1dd3: 0xe135, 0x1dd4: 0xe115, 0x1dd5: 0xe115, 0x1dd6: 0xe175, 0x1dd7: 0xe175, + 0x1dd8: 0xe115, 0x1dd9: 0xe115, 0x1dda: 0xe135, 0x1ddb: 0xe135, 0x1ddc: 0xe115, 0x1ddd: 0xe115, + 0x1dde: 0x8ca5, 0x1ddf: 0x8ca5, 0x1de0: 0x04b5, 0x1de1: 0x04b5, 0x1de2: 0x0a08, 0x1de3: 0x0a08, + 0x1de4: 0x0a08, 0x1de5: 0x0a08, 0x1de6: 0x0a08, 0x1de7: 0x0a08, 0x1de8: 0x0a08, 0x1de9: 0x0a08, + 0x1dea: 0x0a08, 0x1deb: 0x0a08, 0x1dec: 0x0a08, 0x1ded: 0x0a08, 0x1dee: 0x0a08, 0x1def: 0x0a08, + 0x1df0: 0x0a08, 0x1df1: 0x0a08, 0x1df2: 0x0a08, 0x1df3: 0x0a08, 0x1df4: 0x0a08, 0x1df5: 0x0a08, + 0x1df6: 0x0a08, 0x1df7: 0x0a08, 0x1df8: 0x0a08, 0x1df9: 0x0a08, 0x1dfa: 0x0a08, 0x1dfb: 0x0a08, + 0x1dfc: 0x0a08, 0x1dfd: 0x0a08, 0x1dfe: 0x0a08, 0x1dff: 0x0a08, + // Block 0x78, offset 0x1e00 + 0x1e00: 0x20b1, 0x1e01: 0x20b9, 0x1e02: 0x20d9, 0x1e03: 0x20f1, 0x1e04: 0x0040, 0x1e05: 0x2189, + 0x1e06: 0x2109, 0x1e07: 0x20e1, 0x1e08: 0x2131, 0x1e09: 0x2191, 0x1e0a: 0x2161, 0x1e0b: 0x2169, + 0x1e0c: 0x2171, 0x1e0d: 0x2179, 0x1e0e: 0x2111, 0x1e0f: 0x2141, 0x1e10: 0x2151, 0x1e11: 0x2121, + 0x1e12: 0x2159, 0x1e13: 0x2101, 0x1e14: 0x2119, 0x1e15: 0x20c9, 0x1e16: 0x20d1, 0x1e17: 0x20e9, + 0x1e18: 0x20f9, 0x1e19: 0x2129, 0x1e1a: 0x2139, 0x1e1b: 0x2149, 0x1e1c: 0x2589, 0x1e1d: 0x1689, + 0x1e1e: 0x2591, 0x1e1f: 0x2599, 0x1e20: 0x0040, 0x1e21: 0x20b9, 0x1e22: 0x20d9, 0x1e23: 0x0040, + 0x1e24: 0x2181, 0x1e25: 0x0040, 0x1e26: 0x0040, 0x1e27: 0x20e1, 0x1e28: 0x0040, 0x1e29: 0x2191, + 0x1e2a: 0x2161, 0x1e2b: 0x2169, 0x1e2c: 0x2171, 0x1e2d: 0x2179, 0x1e2e: 0x2111, 0x1e2f: 0x2141, + 0x1e30: 0x2151, 0x1e31: 0x2121, 0x1e32: 0x2159, 0x1e33: 0x0040, 0x1e34: 0x2119, 0x1e35: 0x20c9, + 0x1e36: 0x20d1, 0x1e37: 0x20e9, 0x1e38: 0x0040, 0x1e39: 0x2129, 0x1e3a: 0x0040, 0x1e3b: 0x2149, + 0x1e3c: 0x0040, 0x1e3d: 0x0040, 0x1e3e: 0x0040, 0x1e3f: 0x0040, + // Block 0x79, offset 0x1e40 + 0x1e40: 0x0040, 0x1e41: 0x0040, 0x1e42: 0x20d9, 0x1e43: 0x0040, 0x1e44: 0x0040, 0x1e45: 0x0040, + 0x1e46: 0x0040, 0x1e47: 0x20e1, 0x1e48: 0x0040, 0x1e49: 0x2191, 0x1e4a: 0x0040, 0x1e4b: 0x2169, + 0x1e4c: 0x0040, 0x1e4d: 0x2179, 0x1e4e: 0x2111, 0x1e4f: 0x2141, 0x1e50: 0x0040, 0x1e51: 0x2121, + 0x1e52: 0x2159, 0x1e53: 0x0040, 0x1e54: 0x2119, 0x1e55: 0x0040, 0x1e56: 0x0040, 0x1e57: 0x20e9, + 0x1e58: 0x0040, 0x1e59: 0x2129, 0x1e5a: 0x0040, 0x1e5b: 0x2149, 0x1e5c: 0x0040, 0x1e5d: 0x1689, + 0x1e5e: 0x0040, 0x1e5f: 0x2599, 0x1e60: 0x0040, 0x1e61: 0x20b9, 0x1e62: 0x20d9, 0x1e63: 0x0040, + 0x1e64: 0x2181, 0x1e65: 0x0040, 0x1e66: 0x0040, 0x1e67: 0x20e1, 0x1e68: 0x2131, 0x1e69: 0x2191, + 0x1e6a: 0x2161, 0x1e6b: 0x0040, 0x1e6c: 0x2171, 0x1e6d: 0x2179, 0x1e6e: 0x2111, 0x1e6f: 0x2141, + 0x1e70: 0x2151, 0x1e71: 0x2121, 0x1e72: 0x2159, 0x1e73: 0x0040, 0x1e74: 0x2119, 0x1e75: 0x20c9, + 0x1e76: 0x20d1, 0x1e77: 0x20e9, 0x1e78: 0x0040, 0x1e79: 0x2129, 0x1e7a: 0x2139, 0x1e7b: 0x2149, + 0x1e7c: 0x2589, 0x1e7d: 0x0040, 0x1e7e: 0x2591, 0x1e7f: 0x0040, + // Block 0x7a, offset 0x1e80 + 0x1e80: 0x20b1, 0x1e81: 0x20b9, 0x1e82: 0x20d9, 0x1e83: 0x20f1, 0x1e84: 0x2181, 0x1e85: 0x2189, + 0x1e86: 0x2109, 0x1e87: 0x20e1, 0x1e88: 0x2131, 0x1e89: 0x2191, 0x1e8a: 0x0040, 0x1e8b: 0x2169, + 0x1e8c: 0x2171, 0x1e8d: 0x2179, 0x1e8e: 0x2111, 0x1e8f: 0x2141, 0x1e90: 0x2151, 0x1e91: 0x2121, + 0x1e92: 0x2159, 0x1e93: 0x2101, 0x1e94: 0x2119, 0x1e95: 0x20c9, 0x1e96: 0x20d1, 0x1e97: 0x20e9, + 0x1e98: 0x20f9, 0x1e99: 0x2129, 0x1e9a: 0x2139, 0x1e9b: 0x2149, 0x1e9c: 0x0040, 0x1e9d: 0x0040, + 0x1e9e: 0x0040, 0x1e9f: 0x0040, 0x1ea0: 0x0040, 0x1ea1: 0x20b9, 0x1ea2: 0x20d9, 0x1ea3: 0x20f1, + 0x1ea4: 0x0040, 0x1ea5: 0x2189, 0x1ea6: 0x2109, 0x1ea7: 0x20e1, 0x1ea8: 0x2131, 0x1ea9: 0x2191, + 0x1eaa: 0x0040, 0x1eab: 0x2169, 0x1eac: 0x2171, 0x1ead: 0x2179, 0x1eae: 0x2111, 0x1eaf: 0x2141, + 0x1eb0: 0x2151, 0x1eb1: 0x2121, 0x1eb2: 0x2159, 0x1eb3: 0x2101, 0x1eb4: 0x2119, 0x1eb5: 0x20c9, + 0x1eb6: 0x20d1, 0x1eb7: 0x20e9, 0x1eb8: 0x20f9, 0x1eb9: 0x2129, 0x1eba: 0x2139, 0x1ebb: 0x2149, + 0x1ebc: 0x0040, 0x1ebd: 0x0040, 0x1ebe: 0x0040, 0x1ebf: 0x0040, + // Block 0x7b, offset 0x1ec0 + 0x1ec0: 0x0040, 0x1ec1: 0x25a2, 0x1ec2: 0x25aa, 0x1ec3: 0x25b2, 0x1ec4: 0x25ba, 0x1ec5: 0x25c2, + 0x1ec6: 0x25ca, 0x1ec7: 0x25d2, 0x1ec8: 0x25da, 0x1ec9: 0x25e2, 0x1eca: 0x25ea, 0x1ecb: 0x0018, + 0x1ecc: 0x0018, 0x1ecd: 0x0018, 0x1ece: 0x0018, 0x1ecf: 0x0018, 0x1ed0: 0x25f2, 0x1ed1: 0x25fa, + 0x1ed2: 0x2602, 0x1ed3: 0x260a, 0x1ed4: 0x2612, 0x1ed5: 0x261a, 0x1ed6: 0x2622, 0x1ed7: 0x262a, + 0x1ed8: 0x2632, 0x1ed9: 0x263a, 0x1eda: 0x2642, 0x1edb: 0x264a, 0x1edc: 0x2652, 0x1edd: 0x265a, + 0x1ede: 0x2662, 0x1edf: 0x266a, 0x1ee0: 0x2672, 0x1ee1: 0x267a, 0x1ee2: 0x2682, 0x1ee3: 0x268a, + 0x1ee4: 0x2692, 0x1ee5: 0x269a, 0x1ee6: 0x26a2, 0x1ee7: 0x26aa, 0x1ee8: 0x26b2, 0x1ee9: 0x26ba, + 0x1eea: 0x26c1, 0x1eeb: 0x03d9, 0x1eec: 0x00b9, 0x1eed: 0x1239, 0x1eee: 0x26c9, 0x1eef: 0x0018, + 0x1ef0: 0x0019, 0x1ef1: 0x02e9, 0x1ef2: 0x03d9, 0x1ef3: 0x02f1, 0x1ef4: 0x02f9, 0x1ef5: 0x03f1, + 0x1ef6: 0x0309, 0x1ef7: 0x00a9, 0x1ef8: 0x0311, 0x1ef9: 0x00b1, 0x1efa: 0x0319, 0x1efb: 0x0101, + 0x1efc: 0x0321, 0x1efd: 0x0329, 0x1efe: 0x0051, 0x1eff: 0x0339, + // Block 0x7c, offset 0x1f00 + 0x1f00: 0x0751, 0x1f01: 0x00b9, 0x1f02: 0x0089, 0x1f03: 0x0341, 0x1f04: 0x0349, 0x1f05: 0x0391, + 0x1f06: 0x00c1, 0x1f07: 0x0109, 0x1f08: 0x00c9, 0x1f09: 0x04b1, 0x1f0a: 0x26d1, 0x1f0b: 0x11f9, + 0x1f0c: 0x26d9, 0x1f0d: 0x04d9, 0x1f0e: 0x26e1, 0x1f0f: 0x26e9, 0x1f10: 0x0018, 0x1f11: 0x0018, + 0x1f12: 0x0018, 0x1f13: 0x0018, 0x1f14: 0x0018, 0x1f15: 0x0018, 0x1f16: 0x0018, 0x1f17: 0x0018, + 0x1f18: 0x0018, 0x1f19: 0x0018, 0x1f1a: 0x0018, 0x1f1b: 0x0018, 0x1f1c: 0x0018, 0x1f1d: 0x0018, + 0x1f1e: 0x0018, 0x1f1f: 0x0018, 0x1f20: 0x0018, 0x1f21: 0x0018, 0x1f22: 0x0018, 0x1f23: 0x0018, + 0x1f24: 0x0018, 0x1f25: 0x0018, 0x1f26: 0x0018, 0x1f27: 0x0018, 0x1f28: 0x0018, 0x1f29: 0x0018, + 0x1f2a: 0x26f1, 0x1f2b: 0x26f9, 0x1f2c: 0x2701, 0x1f2d: 0x0018, 0x1f2e: 0x0018, 0x1f2f: 0x0018, + 0x1f30: 0x0018, 0x1f31: 0x0018, 0x1f32: 0x0018, 0x1f33: 0x0018, 0x1f34: 0x0018, 0x1f35: 0x0018, + 0x1f36: 0x0018, 0x1f37: 0x0018, 0x1f38: 0x0018, 0x1f39: 0x0018, 0x1f3a: 0x0018, 0x1f3b: 0x0018, + 0x1f3c: 0x0018, 0x1f3d: 0x0018, 0x1f3e: 0x0018, 0x1f3f: 0x0018, + // Block 0x7d, offset 0x1f40 + 0x1f40: 0x2711, 0x1f41: 0x2719, 0x1f42: 0x2721, 0x1f43: 0x0040, 0x1f44: 0x0040, 0x1f45: 0x0040, + 0x1f46: 0x0040, 0x1f47: 0x0040, 0x1f48: 0x0040, 0x1f49: 0x0040, 0x1f4a: 0x0040, 0x1f4b: 0x0040, + 0x1f4c: 0x0040, 0x1f4d: 0x0040, 0x1f4e: 0x0040, 0x1f4f: 0x0040, 0x1f50: 0x2729, 0x1f51: 0x2731, + 0x1f52: 0x2739, 0x1f53: 0x2741, 0x1f54: 0x2749, 0x1f55: 0x2751, 0x1f56: 0x2759, 0x1f57: 0x2761, + 0x1f58: 0x2769, 0x1f59: 0x2771, 0x1f5a: 0x2779, 0x1f5b: 0x2781, 0x1f5c: 0x2789, 0x1f5d: 0x2791, + 0x1f5e: 0x2799, 0x1f5f: 0x27a1, 0x1f60: 0x27a9, 0x1f61: 0x27b1, 0x1f62: 0x27b9, 0x1f63: 0x27c1, + 0x1f64: 0x27c9, 0x1f65: 0x27d1, 0x1f66: 0x27d9, 0x1f67: 0x27e1, 0x1f68: 0x27e9, 0x1f69: 0x27f1, + 0x1f6a: 0x27f9, 0x1f6b: 0x2801, 0x1f6c: 0x2809, 0x1f6d: 0x2811, 0x1f6e: 0x2819, 0x1f6f: 0x2821, + 0x1f70: 0x2829, 0x1f71: 0x2831, 0x1f72: 0x2839, 0x1f73: 0x2841, 0x1f74: 0x2849, 0x1f75: 0x2851, + 0x1f76: 0x2859, 0x1f77: 0x2861, 0x1f78: 0x2869, 0x1f79: 0x2871, 0x1f7a: 0x2879, 0x1f7b: 0x2881, + 0x1f7c: 0x0040, 0x1f7d: 0x0040, 0x1f7e: 0x0040, 0x1f7f: 0x0040, + // Block 0x7e, offset 0x1f80 + 0x1f80: 0x28e1, 0x1f81: 0x28e9, 0x1f82: 0x28f1, 0x1f83: 0x8cbd, 0x1f84: 0x28f9, 0x1f85: 0x2901, + 0x1f86: 0x2909, 0x1f87: 0x2911, 0x1f88: 0x2919, 0x1f89: 0x2921, 0x1f8a: 0x2929, 0x1f8b: 0x2931, + 0x1f8c: 0x2939, 0x1f8d: 0x8cdd, 0x1f8e: 0x2941, 0x1f8f: 0x2949, 0x1f90: 0x2951, 0x1f91: 0x2959, + 0x1f92: 0x8cfd, 0x1f93: 0x2961, 0x1f94: 0x2969, 0x1f95: 0x2799, 0x1f96: 0x8d1d, 0x1f97: 0x2971, + 0x1f98: 0x2979, 0x1f99: 0x2981, 0x1f9a: 0x2989, 0x1f9b: 0x2991, 0x1f9c: 0x8d3d, 0x1f9d: 0x2999, + 0x1f9e: 0x29a1, 0x1f9f: 0x29a9, 0x1fa0: 0x29b1, 0x1fa1: 0x29b9, 0x1fa2: 0x2871, 0x1fa3: 0x29c1, + 0x1fa4: 0x29c9, 0x1fa5: 0x29d1, 0x1fa6: 0x29d9, 0x1fa7: 0x29e1, 0x1fa8: 0x29e9, 0x1fa9: 0x29f1, + 0x1faa: 0x29f9, 0x1fab: 0x2a01, 0x1fac: 0x2a09, 0x1fad: 0x2a11, 0x1fae: 0x2a19, 0x1faf: 0x2a21, + 0x1fb0: 0x2a29, 0x1fb1: 0x2a31, 0x1fb2: 0x2a31, 0x1fb3: 0x2a31, 0x1fb4: 0x8d5d, 0x1fb5: 0x2a39, + 0x1fb6: 0x2a41, 0x1fb7: 0x2a49, 0x1fb8: 0x8d7d, 0x1fb9: 0x2a51, 0x1fba: 0x2a59, 0x1fbb: 0x2a61, + 0x1fbc: 0x2a69, 0x1fbd: 0x2a71, 0x1fbe: 0x2a79, 0x1fbf: 0x2a81, + // Block 0x7f, offset 0x1fc0 + 0x1fc0: 0x2a89, 0x1fc1: 0x2a91, 0x1fc2: 0x2a99, 0x1fc3: 0x2aa1, 0x1fc4: 0x2aa9, 0x1fc5: 0x2ab1, + 0x1fc6: 0x2ab1, 0x1fc7: 0x2ab9, 0x1fc8: 0x2ac1, 0x1fc9: 0x2ac9, 0x1fca: 0x2ad1, 0x1fcb: 0x2ad9, + 0x1fcc: 0x2ae1, 0x1fcd: 0x2ae9, 0x1fce: 0x2af1, 0x1fcf: 0x2af9, 0x1fd0: 0x2b01, 0x1fd1: 0x2b09, + 0x1fd2: 0x2b11, 0x1fd3: 0x2b19, 0x1fd4: 0x2b21, 0x1fd5: 0x2b29, 0x1fd6: 0x2b31, 0x1fd7: 0x2b39, + 0x1fd8: 0x2b41, 0x1fd9: 0x8d9d, 0x1fda: 0x2b49, 0x1fdb: 0x2b51, 0x1fdc: 0x2b59, 0x1fdd: 0x2751, + 0x1fde: 0x2b61, 0x1fdf: 0x2b69, 0x1fe0: 0x8dbd, 0x1fe1: 0x8ddd, 0x1fe2: 0x2b71, 0x1fe3: 0x2b79, + 0x1fe4: 0x2b81, 0x1fe5: 0x2b89, 0x1fe6: 0x2b91, 0x1fe7: 0x2b99, 0x1fe8: 0x2040, 0x1fe9: 0x2ba1, + 0x1fea: 0x2ba9, 0x1feb: 0x2ba9, 0x1fec: 0x8dfd, 0x1fed: 0x2bb1, 0x1fee: 0x2bb9, 0x1fef: 0x2bc1, + 0x1ff0: 0x2bc9, 0x1ff1: 0x8e1d, 0x1ff2: 0x2bd1, 0x1ff3: 0x2bd9, 0x1ff4: 0x2040, 0x1ff5: 0x2be1, + 0x1ff6: 0x2be9, 0x1ff7: 0x2bf1, 0x1ff8: 0x2bf9, 0x1ff9: 0x2c01, 0x1ffa: 0x2c09, 0x1ffb: 0x8e3d, + 0x1ffc: 0x2c11, 0x1ffd: 0x8e5d, 0x1ffe: 0x2c19, 0x1fff: 0x2c21, + // Block 0x80, offset 0x2000 + 0x2000: 0x2c29, 0x2001: 0x2c31, 0x2002: 0x2c39, 0x2003: 0x2c41, 0x2004: 0x2c49, 0x2005: 0x2c51, + 0x2006: 0x2c59, 0x2007: 0x2c61, 0x2008: 0x2c69, 0x2009: 0x8e7d, 0x200a: 0x2c71, 0x200b: 0x2c79, + 0x200c: 0x2c81, 0x200d: 0x2c89, 0x200e: 0x2c91, 0x200f: 0x8e9d, 0x2010: 0x2c99, 0x2011: 0x8ebd, + 0x2012: 0x8edd, 0x2013: 0x2ca1, 0x2014: 0x2ca9, 0x2015: 0x2ca9, 0x2016: 0x2cb1, 0x2017: 0x8efd, + 0x2018: 0x8f1d, 0x2019: 0x2cb9, 0x201a: 0x2cc1, 0x201b: 0x2cc9, 0x201c: 0x2cd1, 0x201d: 0x2cd9, + 0x201e: 0x2ce1, 0x201f: 0x2ce9, 0x2020: 0x2cf1, 0x2021: 0x2cf9, 0x2022: 0x2d01, 0x2023: 0x2d09, + 0x2024: 0x8f3d, 0x2025: 0x2d11, 0x2026: 0x2d19, 0x2027: 0x2d21, 0x2028: 0x2d29, 0x2029: 0x2d21, + 0x202a: 0x2d31, 0x202b: 0x2d39, 0x202c: 0x2d41, 0x202d: 0x2d49, 0x202e: 0x2d51, 0x202f: 0x2d59, + 0x2030: 0x2d61, 0x2031: 0x2d69, 0x2032: 0x2d71, 0x2033: 0x2d79, 0x2034: 0x2d81, 0x2035: 0x2d89, + 0x2036: 0x2d91, 0x2037: 0x2d99, 0x2038: 0x8f5d, 0x2039: 0x2da1, 0x203a: 0x2da9, 0x203b: 0x2db1, + 0x203c: 0x2db9, 0x203d: 0x2dc1, 0x203e: 0x8f7d, 0x203f: 0x2dc9, + // Block 0x81, offset 0x2040 + 0x2040: 0x2dd1, 0x2041: 0x2dd9, 0x2042: 0x2de1, 0x2043: 0x2de9, 0x2044: 0x2df1, 0x2045: 0x2df9, + 0x2046: 0x2e01, 0x2047: 0x2e09, 0x2048: 0x2e11, 0x2049: 0x2e19, 0x204a: 0x8f9d, 0x204b: 0x2e21, + 0x204c: 0x2e29, 0x204d: 0x2e31, 0x204e: 0x2e39, 0x204f: 0x2e41, 0x2050: 0x2e49, 0x2051: 0x2e51, + 0x2052: 0x2e59, 0x2053: 0x2e61, 0x2054: 0x2e69, 0x2055: 0x2e71, 0x2056: 0x2e79, 0x2057: 0x2e81, + 0x2058: 0x2e89, 0x2059: 0x2e91, 0x205a: 0x2e99, 0x205b: 0x2ea1, 0x205c: 0x2ea9, 0x205d: 0x8fbd, + 0x205e: 0x2eb1, 0x205f: 0x2eb9, 0x2060: 0x2ec1, 0x2061: 0x2ec9, 0x2062: 0x2ed1, 0x2063: 0x8fdd, + 0x2064: 0x2ed9, 0x2065: 0x2ee1, 0x2066: 0x2ee9, 0x2067: 0x2ef1, 0x2068: 0x2ef9, 0x2069: 0x2f01, + 0x206a: 0x2f09, 0x206b: 0x2f11, 0x206c: 0x7f0d, 0x206d: 0x2f19, 0x206e: 0x2f21, 0x206f: 0x2f29, + 0x2070: 0x8ffd, 0x2071: 0x2f31, 0x2072: 0x2f39, 0x2073: 0x2f41, 0x2074: 0x2f49, 0x2075: 0x2f51, + 0x2076: 0x2f59, 0x2077: 0x901d, 0x2078: 0x903d, 0x2079: 0x905d, 0x207a: 0x2f61, 0x207b: 0x907d, + 0x207c: 0x2f69, 0x207d: 0x2f71, 0x207e: 0x2f79, 0x207f: 0x2f81, + // Block 0x82, offset 0x2080 + 0x2080: 0x2f89, 0x2081: 0x2f91, 0x2082: 0x2f99, 0x2083: 0x2fa1, 0x2084: 0x2fa9, 0x2085: 0x2fb1, + 0x2086: 0x909d, 0x2087: 0x2fb9, 0x2088: 0x2fc1, 0x2089: 0x2fc9, 0x208a: 0x2fd1, 0x208b: 0x2fd9, + 0x208c: 0x2fe1, 0x208d: 0x90bd, 0x208e: 0x2fe9, 0x208f: 0x2ff1, 0x2090: 0x90dd, 0x2091: 0x90fd, + 0x2092: 0x2ff9, 0x2093: 0x3001, 0x2094: 0x3009, 0x2095: 0x3011, 0x2096: 0x3019, 0x2097: 0x3021, + 0x2098: 0x3029, 0x2099: 0x3031, 0x209a: 0x3039, 0x209b: 0x911d, 0x209c: 0x3041, 0x209d: 0x913d, + 0x209e: 0x3049, 0x209f: 0x2040, 0x20a0: 0x3051, 0x20a1: 0x3059, 0x20a2: 0x3061, 0x20a3: 0x915d, + 0x20a4: 0x3069, 0x20a5: 0x3071, 0x20a6: 0x917d, 0x20a7: 0x919d, 0x20a8: 0x3079, 0x20a9: 0x3081, + 0x20aa: 0x3089, 0x20ab: 0x3091, 0x20ac: 0x3099, 0x20ad: 0x3099, 0x20ae: 0x30a1, 0x20af: 0x30a9, + 0x20b0: 0x30b1, 0x20b1: 0x30b9, 0x20b2: 0x30c1, 0x20b3: 0x30c9, 0x20b4: 0x30d1, 0x20b5: 0x91bd, + 0x20b6: 0x30d9, 0x20b7: 0x91dd, 0x20b8: 0x30e1, 0x20b9: 0x91fd, 0x20ba: 0x30e9, 0x20bb: 0x921d, + 0x20bc: 0x923d, 0x20bd: 0x925d, 0x20be: 0x30f1, 0x20bf: 0x30f9, + // Block 0x83, offset 0x20c0 + 0x20c0: 0x3101, 0x20c1: 0x927d, 0x20c2: 0x929d, 0x20c3: 0x92bd, 0x20c4: 0x92dd, 0x20c5: 0x3109, + 0x20c6: 0x3111, 0x20c7: 0x3111, 0x20c8: 0x3119, 0x20c9: 0x3121, 0x20ca: 0x3129, 0x20cb: 0x3131, + 0x20cc: 0x3139, 0x20cd: 0x92fd, 0x20ce: 0x3141, 0x20cf: 0x3149, 0x20d0: 0x3151, 0x20d1: 0x3159, + 0x20d2: 0x931d, 0x20d3: 0x3161, 0x20d4: 0x933d, 0x20d5: 0x935d, 0x20d6: 0x3169, 0x20d7: 0x3171, + 0x20d8: 0x3179, 0x20d9: 0x3181, 0x20da: 0x3189, 0x20db: 0x3191, 0x20dc: 0x937d, 0x20dd: 0x939d, + 0x20de: 0x93bd, 0x20df: 0x2040, 0x20e0: 0x3199, 0x20e1: 0x93dd, 0x20e2: 0x31a1, 0x20e3: 0x31a9, + 0x20e4: 0x31b1, 0x20e5: 0x93fd, 0x20e6: 0x31b9, 0x20e7: 0x31c1, 0x20e8: 0x31c9, 0x20e9: 0x31d1, + 0x20ea: 0x31d9, 0x20eb: 0x941d, 0x20ec: 0x31e1, 0x20ed: 0x31e9, 0x20ee: 0x31f1, 0x20ef: 0x31f9, + 0x20f0: 0x3201, 0x20f1: 0x3209, 0x20f2: 0x943d, 0x20f3: 0x945d, 0x20f4: 0x3211, 0x20f5: 0x947d, + 0x20f6: 0x3219, 0x20f7: 0x949d, 0x20f8: 0x3221, 0x20f9: 0x3229, 0x20fa: 0x3231, 0x20fb: 0x94bd, + 0x20fc: 0x94dd, 0x20fd: 0x3239, 0x20fe: 0x94fd, 0x20ff: 0x3241, + // Block 0x84, offset 0x2100 + 0x2100: 0x951d, 0x2101: 0x3249, 0x2102: 0x3251, 0x2103: 0x3259, 0x2104: 0x3261, 0x2105: 0x3269, + 0x2106: 0x3271, 0x2107: 0x953d, 0x2108: 0x955d, 0x2109: 0x957d, 0x210a: 0x959d, 0x210b: 0x2ca1, + 0x210c: 0x3279, 0x210d: 0x3281, 0x210e: 0x3289, 0x210f: 0x3291, 0x2110: 0x3299, 0x2111: 0x32a1, + 0x2112: 0x32a9, 0x2113: 0x32b1, 0x2114: 0x32b9, 0x2115: 0x32c1, 0x2116: 0x32c9, 0x2117: 0x95bd, + 0x2118: 0x32d1, 0x2119: 0x32d9, 0x211a: 0x32e1, 0x211b: 0x32e9, 0x211c: 0x32f1, 0x211d: 0x32f9, + 0x211e: 0x3301, 0x211f: 0x3309, 0x2120: 0x3311, 0x2121: 0x3319, 0x2122: 0x3321, 0x2123: 0x3329, + 0x2124: 0x95dd, 0x2125: 0x95fd, 0x2126: 0x961d, 0x2127: 0x3331, 0x2128: 0x3339, 0x2129: 0x3341, + 0x212a: 0x3349, 0x212b: 0x963d, 0x212c: 0x3351, 0x212d: 0x965d, 0x212e: 0x3359, 0x212f: 0x3361, + 0x2130: 0x967d, 0x2131: 0x969d, 0x2132: 0x3369, 0x2133: 0x3371, 0x2134: 0x3379, 0x2135: 0x3381, + 0x2136: 0x3389, 0x2137: 0x3391, 0x2138: 0x3399, 0x2139: 0x33a1, 0x213a: 0x33a9, 0x213b: 0x33b1, + 0x213c: 0x33b9, 0x213d: 0x33c1, 0x213e: 0x33c9, 0x213f: 0x2040, + // Block 0x85, offset 0x2140 + 0x2140: 0x33d1, 0x2141: 0x33d9, 0x2142: 0x33e1, 0x2143: 0x33e9, 0x2144: 0x33f1, 0x2145: 0x96bd, + 0x2146: 0x33f9, 0x2147: 0x3401, 0x2148: 0x3409, 0x2149: 0x3411, 0x214a: 0x3419, 0x214b: 0x96dd, + 0x214c: 0x96fd, 0x214d: 0x3421, 0x214e: 0x3429, 0x214f: 0x3431, 0x2150: 0x3439, 0x2151: 0x3441, + 0x2152: 0x3449, 0x2153: 0x971d, 0x2154: 0x3451, 0x2155: 0x3459, 0x2156: 0x3461, 0x2157: 0x3469, + 0x2158: 0x973d, 0x2159: 0x975d, 0x215a: 0x3471, 0x215b: 0x3479, 0x215c: 0x3481, 0x215d: 0x977d, + 0x215e: 0x3489, 0x215f: 0x3491, 0x2160: 0x684d, 0x2161: 0x979d, 0x2162: 0x3499, 0x2163: 0x34a1, + 0x2164: 0x34a9, 0x2165: 0x97bd, 0x2166: 0x34b1, 0x2167: 0x34b9, 0x2168: 0x34c1, 0x2169: 0x34c9, + 0x216a: 0x34d1, 0x216b: 0x34d9, 0x216c: 0x34e1, 0x216d: 0x97dd, 0x216e: 0x34e9, 0x216f: 0x34f1, + 0x2170: 0x34f9, 0x2171: 0x97fd, 0x2172: 0x3501, 0x2173: 0x3509, 0x2174: 0x3511, 0x2175: 0x3519, + 0x2176: 0x7b6d, 0x2177: 0x981d, 0x2178: 0x3521, 0x2179: 0x3529, 0x217a: 0x3531, 0x217b: 0x983d, + 0x217c: 0x3539, 0x217d: 0x985d, 0x217e: 0x3541, 0x217f: 0x3541, + // Block 0x86, offset 0x2180 + 0x2180: 0x3549, 0x2181: 0x987d, 0x2182: 0x3551, 0x2183: 0x3559, 0x2184: 0x3561, 0x2185: 0x3569, + 0x2186: 0x3571, 0x2187: 0x3579, 0x2188: 0x3581, 0x2189: 0x989d, 0x218a: 0x3589, 0x218b: 0x3591, + 0x218c: 0x3599, 0x218d: 0x35a1, 0x218e: 0x35a9, 0x218f: 0x35b1, 0x2190: 0x98bd, 0x2191: 0x35b9, + 0x2192: 0x98dd, 0x2193: 0x98fd, 0x2194: 0x991d, 0x2195: 0x35c1, 0x2196: 0x35c9, 0x2197: 0x35d1, + 0x2198: 0x35d9, 0x2199: 0x35e1, 0x219a: 0x35e9, 0x219b: 0x35f1, 0x219c: 0x35f9, 0x219d: 0x993d, + 0x219e: 0x0040, 0x219f: 0x0040, 0x21a0: 0x0040, 0x21a1: 0x0040, 0x21a2: 0x0040, 0x21a3: 0x0040, + 0x21a4: 0x0040, 0x21a5: 0x0040, 0x21a6: 0x0040, 0x21a7: 0x0040, 0x21a8: 0x0040, 0x21a9: 0x0040, + 0x21aa: 0x0040, 0x21ab: 0x0040, 0x21ac: 0x0040, 0x21ad: 0x0040, 0x21ae: 0x0040, 0x21af: 0x0040, + 0x21b0: 0x0040, 0x21b1: 0x0040, 0x21b2: 0x0040, 0x21b3: 0x0040, 0x21b4: 0x0040, 0x21b5: 0x0040, + 0x21b6: 0x0040, 0x21b7: 0x0040, 0x21b8: 0x0040, 0x21b9: 0x0040, 0x21ba: 0x0040, 0x21bb: 0x0040, + 0x21bc: 0x0040, 0x21bd: 0x0040, 0x21be: 0x0040, 0x21bf: 0x0040, +} + +// idnaIndex: 39 blocks, 2496 entries, 4992 bytes +// Block 0 is the zero block. +var idnaIndex = [2496]uint16{ + // Block 0x0, offset 0x0 + // Block 0x1, offset 0x40 + // Block 0x2, offset 0x80 + // Block 0x3, offset 0xc0 + 0xc2: 0x01, 0xc3: 0x85, 0xc4: 0x02, 0xc5: 0x03, 0xc6: 0x04, 0xc7: 0x05, + 0xc8: 0x06, 0xc9: 0x86, 0xca: 0x87, 0xcb: 0x07, 0xcc: 0x88, 0xcd: 0x08, 0xce: 0x09, 0xcf: 0x0a, + 0xd0: 0x89, 0xd1: 0x0b, 0xd2: 0x0c, 0xd3: 0x0d, 0xd4: 0x0e, 0xd5: 0x8a, 0xd6: 0x8b, 0xd7: 0x8c, + 0xd8: 0x0f, 0xd9: 0x10, 0xda: 0x8d, 0xdb: 0x11, 0xdc: 0x12, 0xdd: 0x8e, 0xde: 0x8f, 0xdf: 0x90, + 0xe0: 0x02, 0xe1: 0x03, 0xe2: 0x04, 0xe3: 0x05, 0xe4: 0x06, 0xe5: 0x07, 0xe6: 0x07, 0xe7: 0x07, + 0xe8: 0x07, 0xe9: 0x07, 0xea: 0x08, 0xeb: 0x07, 0xec: 0x07, 0xed: 0x09, 0xee: 0x0a, 0xef: 0x0b, + 0xf0: 0x20, 0xf1: 0x21, 0xf2: 0x21, 0xf3: 0x23, 0xf4: 0x24, + // Block 0x4, offset 0x100 + 0x120: 0x91, 0x121: 0x13, 0x122: 0x14, 0x123: 0x92, 0x124: 0x93, 0x125: 0x15, 0x126: 0x16, 0x127: 0x17, + 0x128: 0x18, 0x129: 0x19, 0x12a: 0x1a, 0x12b: 0x1b, 0x12c: 0x1c, 0x12d: 0x1d, 0x12e: 0x1e, 0x12f: 0x94, + 0x130: 0x95, 0x131: 0x1f, 0x132: 0x20, 0x133: 0x21, 0x134: 0x96, 0x135: 0x22, 0x136: 0x97, 0x137: 0x98, + 0x138: 0x99, 0x139: 0x9a, 0x13a: 0x23, 0x13b: 0x9b, 0x13c: 0x9c, 0x13d: 0x24, 0x13e: 0x25, 0x13f: 0x9d, + // Block 0x5, offset 0x140 + 0x140: 0x9e, 0x141: 0x9f, 0x142: 0xa0, 0x143: 0xa1, 0x144: 0xa2, 0x145: 0xa3, 0x146: 0xa4, 0x147: 0xa5, + 0x148: 0xa6, 0x149: 0xa7, 0x14a: 0xa8, 0x14b: 0xa9, 0x14c: 0xaa, 0x14d: 0xab, 0x14e: 0xac, 0x14f: 0xad, + 0x150: 0xae, 0x151: 0xa6, 0x152: 0xa6, 0x153: 0xa6, 0x154: 0xa6, 0x155: 0xa6, 0x156: 0xa6, 0x157: 0xa6, + 0x158: 0xa6, 0x159: 0xaf, 0x15a: 0xb0, 0x15b: 0xb1, 0x15c: 0xb2, 0x15d: 0xb3, 0x15e: 0xb4, 0x15f: 0xb5, + 0x160: 0xb6, 0x161: 0xb7, 0x162: 0xb8, 0x163: 0xb9, 0x164: 0xba, 0x165: 0xbb, 0x166: 0xbc, 0x167: 0xbd, + 0x168: 0xbe, 0x169: 0xbf, 0x16a: 0xc0, 0x16b: 0xc1, 0x16c: 0xc2, 0x16d: 0xc3, 0x16e: 0xc4, 0x16f: 0xc5, + 0x170: 0xc6, 0x171: 0xc7, 0x172: 0xc8, 0x173: 0xc9, 0x174: 0x26, 0x175: 0x27, 0x176: 0x28, 0x177: 0x88, + 0x178: 0x29, 0x179: 0x29, 0x17a: 0x2a, 0x17b: 0x29, 0x17c: 0xca, 0x17d: 0x2b, 0x17e: 0x2c, 0x17f: 0x2d, + // Block 0x6, offset 0x180 + 0x180: 0x2e, 0x181: 0x2f, 0x182: 0x30, 0x183: 0xcb, 0x184: 0x31, 0x185: 0x32, 0x186: 0xcc, 0x187: 0xa2, + 0x188: 0xcd, 0x189: 0xce, 0x18a: 0xa2, 0x18b: 0xa2, 0x18c: 0xcf, 0x18d: 0xa2, 0x18e: 0xa2, 0x18f: 0xa2, + 0x190: 0xd0, 0x191: 0x33, 0x192: 0x34, 0x193: 0x35, 0x194: 0xa2, 0x195: 0xa2, 0x196: 0xa2, 0x197: 0xa2, + 0x198: 0xa2, 0x199: 0xa2, 0x19a: 0xa2, 0x19b: 0xa2, 0x19c: 0xa2, 0x19d: 0xa2, 0x19e: 0xa2, 0x19f: 0xa2, + 0x1a0: 0xa2, 0x1a1: 0xa2, 0x1a2: 0xa2, 0x1a3: 0xa2, 0x1a4: 0xa2, 0x1a5: 0xa2, 0x1a6: 0xa2, 0x1a7: 0xa2, + 0x1a8: 0xd1, 0x1a9: 0xd2, 0x1aa: 0xa2, 0x1ab: 0xd3, 0x1ac: 0xa2, 0x1ad: 0xd4, 0x1ae: 0xd5, 0x1af: 0xa2, + 0x1b0: 0xd6, 0x1b1: 0x36, 0x1b2: 0x29, 0x1b3: 0x37, 0x1b4: 0xd7, 0x1b5: 0xd8, 0x1b6: 0xd9, 0x1b7: 0xda, + 0x1b8: 0xdb, 0x1b9: 0xdc, 0x1ba: 0xdd, 0x1bb: 0xde, 0x1bc: 0xdf, 0x1bd: 0xe0, 0x1be: 0xe1, 0x1bf: 0x38, + // Block 0x7, offset 0x1c0 + 0x1c0: 0x39, 0x1c1: 0xe2, 0x1c2: 0xe3, 0x1c3: 0xe4, 0x1c4: 0xe5, 0x1c5: 0x3a, 0x1c6: 0x3b, 0x1c7: 0xe6, + 0x1c8: 0xe7, 0x1c9: 0x3c, 0x1ca: 0x3d, 0x1cb: 0x3e, 0x1cc: 0xe8, 0x1cd: 0xe9, 0x1ce: 0x3f, 0x1cf: 0x40, + 0x1d0: 0xa6, 0x1d1: 0xa6, 0x1d2: 0xa6, 0x1d3: 0xa6, 0x1d4: 0xa6, 0x1d5: 0xa6, 0x1d6: 0xa6, 0x1d7: 0xa6, + 0x1d8: 0xa6, 0x1d9: 0xa6, 0x1da: 0xa6, 0x1db: 0xa6, 0x1dc: 0xa6, 0x1dd: 0xa6, 0x1de: 0xa6, 0x1df: 0xa6, + 0x1e0: 0xa6, 0x1e1: 0xa6, 0x1e2: 0xa6, 0x1e3: 0xa6, 0x1e4: 0xa6, 0x1e5: 0xa6, 0x1e6: 0xa6, 0x1e7: 0xa6, + 0x1e8: 0xa6, 0x1e9: 0xa6, 0x1ea: 0xa6, 0x1eb: 0xa6, 0x1ec: 0xa6, 0x1ed: 0xa6, 0x1ee: 0xa6, 0x1ef: 0xa6, + 0x1f0: 0xa6, 0x1f1: 0xa6, 0x1f2: 0xa6, 0x1f3: 0xa6, 0x1f4: 0xa6, 0x1f5: 0xa6, 0x1f6: 0xa6, 0x1f7: 0xa6, + 0x1f8: 0xa6, 0x1f9: 0xa6, 0x1fa: 0xa6, 0x1fb: 0xa6, 0x1fc: 0xa6, 0x1fd: 0xa6, 0x1fe: 0xa6, 0x1ff: 0xa6, + // Block 0x8, offset 0x200 + 0x200: 0xa6, 0x201: 0xa6, 0x202: 0xa6, 0x203: 0xa6, 0x204: 0xa6, 0x205: 0xa6, 0x206: 0xa6, 0x207: 0xa6, + 0x208: 0xa6, 0x209: 0xa6, 0x20a: 0xa6, 0x20b: 0xa6, 0x20c: 0xa6, 0x20d: 0xa6, 0x20e: 0xa6, 0x20f: 0xa6, + 0x210: 0xa6, 0x211: 0xa6, 0x212: 0xa6, 0x213: 0xa6, 0x214: 0xa6, 0x215: 0xa6, 0x216: 0xa6, 0x217: 0xa6, + 0x218: 0xa6, 0x219: 0xa6, 0x21a: 0xa6, 0x21b: 0xa6, 0x21c: 0xa6, 0x21d: 0xa6, 0x21e: 0xa6, 0x21f: 0xa6, + 0x220: 0xa6, 0x221: 0xa6, 0x222: 0xa6, 0x223: 0xa6, 0x224: 0xa6, 0x225: 0xa6, 0x226: 0xa6, 0x227: 0xa6, + 0x228: 0xa6, 0x229: 0xa6, 0x22a: 0xa6, 0x22b: 0xa6, 0x22c: 0xa6, 0x22d: 0xa6, 0x22e: 0xa6, 0x22f: 0xa6, + 0x230: 0xa6, 0x231: 0xa6, 0x232: 0xa6, 0x233: 0xa6, 0x234: 0xa6, 0x235: 0xa6, 0x236: 0xa6, 0x237: 0xa2, + 0x238: 0xa6, 0x239: 0xa6, 0x23a: 0xa6, 0x23b: 0xa6, 0x23c: 0xa6, 0x23d: 0xa6, 0x23e: 0xa6, 0x23f: 0xa6, + // Block 0x9, offset 0x240 + 0x240: 0xa6, 0x241: 0xa6, 0x242: 0xa6, 0x243: 0xa6, 0x244: 0xa6, 0x245: 0xa6, 0x246: 0xa6, 0x247: 0xa6, + 0x248: 0xa6, 0x249: 0xa6, 0x24a: 0xa6, 0x24b: 0xa6, 0x24c: 0xa6, 0x24d: 0xa6, 0x24e: 0xa6, 0x24f: 0xa6, + 0x250: 0xa6, 0x251: 0xa6, 0x252: 0xa6, 0x253: 0xa6, 0x254: 0xa6, 0x255: 0xa6, 0x256: 0xa6, 0x257: 0xa6, + 0x258: 0xa6, 0x259: 0xa6, 0x25a: 0xa6, 0x25b: 0xa6, 0x25c: 0xa6, 0x25d: 0xa6, 0x25e: 0xa6, 0x25f: 0xa6, + 0x260: 0xa6, 0x261: 0xa6, 0x262: 0xa6, 0x263: 0xa6, 0x264: 0xa6, 0x265: 0xa6, 0x266: 0xa6, 0x267: 0xa6, + 0x268: 0xa6, 0x269: 0xa6, 0x26a: 0xa6, 0x26b: 0xa6, 0x26c: 0xa6, 0x26d: 0xa6, 0x26e: 0xa6, 0x26f: 0xa6, + 0x270: 0xa6, 0x271: 0xa6, 0x272: 0xa6, 0x273: 0xa6, 0x274: 0xa6, 0x275: 0xa6, 0x276: 0xa6, 0x277: 0xa6, + 0x278: 0xa6, 0x279: 0xa6, 0x27a: 0xa6, 0x27b: 0xa6, 0x27c: 0xa6, 0x27d: 0xa6, 0x27e: 0xa6, 0x27f: 0xa6, + // Block 0xa, offset 0x280 + 0x280: 0xa6, 0x281: 0xa6, 0x282: 0xa6, 0x283: 0xa6, 0x284: 0xa6, 0x285: 0xa6, 0x286: 0xa6, 0x287: 0xa6, + 0x288: 0xa6, 0x289: 0xa6, 0x28a: 0xa6, 0x28b: 0xa6, 0x28c: 0xa6, 0x28d: 0xa6, 0x28e: 0xa6, 0x28f: 0xa6, + 0x290: 0xa6, 0x291: 0xa6, 0x292: 0xea, 0x293: 0xeb, 0x294: 0xa6, 0x295: 0xa6, 0x296: 0xa6, 0x297: 0xa6, + 0x298: 0xec, 0x299: 0x41, 0x29a: 0x42, 0x29b: 0xed, 0x29c: 0x43, 0x29d: 0x44, 0x29e: 0x45, 0x29f: 0x46, + 0x2a0: 0xee, 0x2a1: 0xef, 0x2a2: 0xf0, 0x2a3: 0xf1, 0x2a4: 0xf2, 0x2a5: 0xf3, 0x2a6: 0xf4, 0x2a7: 0xf5, + 0x2a8: 0xf6, 0x2a9: 0xf7, 0x2aa: 0xf8, 0x2ab: 0xf9, 0x2ac: 0xfa, 0x2ad: 0xfb, 0x2ae: 0xfc, 0x2af: 0xfd, + 0x2b0: 0xa6, 0x2b1: 0xa6, 0x2b2: 0xa6, 0x2b3: 0xa6, 0x2b4: 0xa6, 0x2b5: 0xa6, 0x2b6: 0xa6, 0x2b7: 0xa6, + 0x2b8: 0xa6, 0x2b9: 0xa6, 0x2ba: 0xa6, 0x2bb: 0xa6, 0x2bc: 0xa6, 0x2bd: 0xa6, 0x2be: 0xa6, 0x2bf: 0xa6, + // Block 0xb, offset 0x2c0 + 0x2c0: 0xa6, 0x2c1: 0xa6, 0x2c2: 0xa6, 0x2c3: 0xa6, 0x2c4: 0xa6, 0x2c5: 0xa6, 0x2c6: 0xa6, 0x2c7: 0xa6, + 0x2c8: 0xa6, 0x2c9: 0xa6, 0x2ca: 0xa6, 0x2cb: 0xa6, 0x2cc: 0xa6, 0x2cd: 0xa6, 0x2ce: 0xa6, 0x2cf: 0xa6, + 0x2d0: 0xa6, 0x2d1: 0xa6, 0x2d2: 0xa6, 0x2d3: 0xa6, 0x2d4: 0xa6, 0x2d5: 0xa6, 0x2d6: 0xa6, 0x2d7: 0xa6, + 0x2d8: 0xa6, 0x2d9: 0xa6, 0x2da: 0xa6, 0x2db: 0xa6, 0x2dc: 0xa6, 0x2dd: 0xa6, 0x2de: 0xfe, 0x2df: 0xff, + // Block 0xc, offset 0x300 + 0x300: 0x100, 0x301: 0x100, 0x302: 0x100, 0x303: 0x100, 0x304: 0x100, 0x305: 0x100, 0x306: 0x100, 0x307: 0x100, + 0x308: 0x100, 0x309: 0x100, 0x30a: 0x100, 0x30b: 0x100, 0x30c: 0x100, 0x30d: 0x100, 0x30e: 0x100, 0x30f: 0x100, + 0x310: 0x100, 0x311: 0x100, 0x312: 0x100, 0x313: 0x100, 0x314: 0x100, 0x315: 0x100, 0x316: 0x100, 0x317: 0x100, + 0x318: 0x100, 0x319: 0x100, 0x31a: 0x100, 0x31b: 0x100, 0x31c: 0x100, 0x31d: 0x100, 0x31e: 0x100, 0x31f: 0x100, + 0x320: 0x100, 0x321: 0x100, 0x322: 0x100, 0x323: 0x100, 0x324: 0x100, 0x325: 0x100, 0x326: 0x100, 0x327: 0x100, + 0x328: 0x100, 0x329: 0x100, 0x32a: 0x100, 0x32b: 0x100, 0x32c: 0x100, 0x32d: 0x100, 0x32e: 0x100, 0x32f: 0x100, + 0x330: 0x100, 0x331: 0x100, 0x332: 0x100, 0x333: 0x100, 0x334: 0x100, 0x335: 0x100, 0x336: 0x100, 0x337: 0x100, + 0x338: 0x100, 0x339: 0x100, 0x33a: 0x100, 0x33b: 0x100, 0x33c: 0x100, 0x33d: 0x100, 0x33e: 0x100, 0x33f: 0x100, + // Block 0xd, offset 0x340 + 0x340: 0x100, 0x341: 0x100, 0x342: 0x100, 0x343: 0x100, 0x344: 0x100, 0x345: 0x100, 0x346: 0x100, 0x347: 0x100, + 0x348: 0x100, 0x349: 0x100, 0x34a: 0x100, 0x34b: 0x100, 0x34c: 0x100, 0x34d: 0x100, 0x34e: 0x100, 0x34f: 0x100, + 0x350: 0x100, 0x351: 0x100, 0x352: 0x100, 0x353: 0x100, 0x354: 0x100, 0x355: 0x100, 0x356: 0x100, 0x357: 0x100, + 0x358: 0x100, 0x359: 0x100, 0x35a: 0x100, 0x35b: 0x100, 0x35c: 0x100, 0x35d: 0x100, 0x35e: 0x100, 0x35f: 0x100, + 0x360: 0x100, 0x361: 0x100, 0x362: 0x100, 0x363: 0x100, 0x364: 0x101, 0x365: 0x102, 0x366: 0x103, 0x367: 0x104, + 0x368: 0x47, 0x369: 0x105, 0x36a: 0x106, 0x36b: 0x48, 0x36c: 0x49, 0x36d: 0x4a, 0x36e: 0x4b, 0x36f: 0x4c, + 0x370: 0x107, 0x371: 0x4d, 0x372: 0x4e, 0x373: 0x4f, 0x374: 0x50, 0x375: 0x51, 0x376: 0x108, 0x377: 0x52, + 0x378: 0x53, 0x379: 0x54, 0x37a: 0x55, 0x37b: 0x56, 0x37c: 0x57, 0x37d: 0x58, 0x37e: 0x59, 0x37f: 0x5a, + // Block 0xe, offset 0x380 + 0x380: 0x109, 0x381: 0x10a, 0x382: 0xa6, 0x383: 0x10b, 0x384: 0x10c, 0x385: 0xa2, 0x386: 0x10d, 0x387: 0x10e, + 0x388: 0x100, 0x389: 0x100, 0x38a: 0x10f, 0x38b: 0x110, 0x38c: 0x111, 0x38d: 0x112, 0x38e: 0x113, 0x38f: 0x114, + 0x390: 0x115, 0x391: 0xa6, 0x392: 0x116, 0x393: 0x117, 0x394: 0x118, 0x395: 0x5b, 0x396: 0x5c, 0x397: 0x100, + 0x398: 0xa6, 0x399: 0xa6, 0x39a: 0xa6, 0x39b: 0xa6, 0x39c: 0x119, 0x39d: 0x11a, 0x39e: 0x5d, 0x39f: 0x100, + 0x3a0: 0x11b, 0x3a1: 0x11c, 0x3a2: 0x11d, 0x3a3: 0x11e, 0x3a4: 0x11f, 0x3a5: 0x100, 0x3a6: 0x120, 0x3a7: 0x121, + 0x3a8: 0x122, 0x3a9: 0x123, 0x3aa: 0x124, 0x3ab: 0x5e, 0x3ac: 0x125, 0x3ad: 0x126, 0x3ae: 0x5f, 0x3af: 0x100, + 0x3b0: 0x127, 0x3b1: 0x128, 0x3b2: 0x129, 0x3b3: 0x12a, 0x3b4: 0x12b, 0x3b5: 0x100, 0x3b6: 0x100, 0x3b7: 0x100, + 0x3b8: 0x100, 0x3b9: 0x12c, 0x3ba: 0x12d, 0x3bb: 0x12e, 0x3bc: 0x12f, 0x3bd: 0x130, 0x3be: 0x131, 0x3bf: 0x132, + // Block 0xf, offset 0x3c0 + 0x3c0: 0x133, 0x3c1: 0x134, 0x3c2: 0x135, 0x3c3: 0x136, 0x3c4: 0x137, 0x3c5: 0x138, 0x3c6: 0x139, 0x3c7: 0x13a, + 0x3c8: 0x13b, 0x3c9: 0x13c, 0x3ca: 0x13d, 0x3cb: 0x13e, 0x3cc: 0x60, 0x3cd: 0x61, 0x3ce: 0x100, 0x3cf: 0x100, + 0x3d0: 0x13f, 0x3d1: 0x140, 0x3d2: 0x141, 0x3d3: 0x142, 0x3d4: 0x100, 0x3d5: 0x100, 0x3d6: 0x143, 0x3d7: 0x144, + 0x3d8: 0x145, 0x3d9: 0x146, 0x3da: 0x147, 0x3db: 0x148, 0x3dc: 0x149, 0x3dd: 0x14a, 0x3de: 0x100, 0x3df: 0x100, + 0x3e0: 0x14b, 0x3e1: 0x100, 0x3e2: 0x14c, 0x3e3: 0x14d, 0x3e4: 0x62, 0x3e5: 0x14e, 0x3e6: 0x14f, 0x3e7: 0x150, + 0x3e8: 0x151, 0x3e9: 0x152, 0x3ea: 0x153, 0x3eb: 0x154, 0x3ec: 0x155, 0x3ed: 0x100, 0x3ee: 0x100, 0x3ef: 0x100, + 0x3f0: 0x156, 0x3f1: 0x157, 0x3f2: 0x158, 0x3f3: 0x100, 0x3f4: 0x159, 0x3f5: 0x15a, 0x3f6: 0x15b, 0x3f7: 0x100, + 0x3f8: 0x100, 0x3f9: 0x100, 0x3fa: 0x100, 0x3fb: 0x15c, 0x3fc: 0x15d, 0x3fd: 0x15e, 0x3fe: 0x15f, 0x3ff: 0x160, + // Block 0x10, offset 0x400 + 0x400: 0xa6, 0x401: 0xa6, 0x402: 0xa6, 0x403: 0xa6, 0x404: 0xa6, 0x405: 0xa6, 0x406: 0xa6, 0x407: 0xa6, + 0x408: 0xa6, 0x409: 0xa6, 0x40a: 0xa6, 0x40b: 0xa6, 0x40c: 0xa6, 0x40d: 0xa6, 0x40e: 0x161, 0x40f: 0x100, + 0x410: 0xa2, 0x411: 0x162, 0x412: 0xa6, 0x413: 0xa6, 0x414: 0xa6, 0x415: 0x163, 0x416: 0x100, 0x417: 0x100, + 0x418: 0x100, 0x419: 0x100, 0x41a: 0x100, 0x41b: 0x100, 0x41c: 0x100, 0x41d: 0x100, 0x41e: 0x100, 0x41f: 0x100, + 0x420: 0x100, 0x421: 0x100, 0x422: 0x100, 0x423: 0x100, 0x424: 0x100, 0x425: 0x100, 0x426: 0x100, 0x427: 0x100, + 0x428: 0x100, 0x429: 0x100, 0x42a: 0x100, 0x42b: 0x100, 0x42c: 0x100, 0x42d: 0x100, 0x42e: 0x100, 0x42f: 0x100, + 0x430: 0x100, 0x431: 0x100, 0x432: 0x100, 0x433: 0x100, 0x434: 0x100, 0x435: 0x100, 0x436: 0x100, 0x437: 0x100, + 0x438: 0x100, 0x439: 0x100, 0x43a: 0x100, 0x43b: 0x100, 0x43c: 0x100, 0x43d: 0x100, 0x43e: 0x164, 0x43f: 0x165, + // Block 0x11, offset 0x440 + 0x440: 0xa6, 0x441: 0xa6, 0x442: 0xa6, 0x443: 0xa6, 0x444: 0xa6, 0x445: 0xa6, 0x446: 0xa6, 0x447: 0xa6, + 0x448: 0xa6, 0x449: 0xa6, 0x44a: 0xa6, 0x44b: 0xa6, 0x44c: 0xa6, 0x44d: 0xa6, 0x44e: 0xa6, 0x44f: 0xa6, + 0x450: 0x166, 0x451: 0x167, 0x452: 0x100, 0x453: 0x100, 0x454: 0x100, 0x455: 0x100, 0x456: 0x100, 0x457: 0x100, + 0x458: 0x100, 0x459: 0x100, 0x45a: 0x100, 0x45b: 0x100, 0x45c: 0x100, 0x45d: 0x100, 0x45e: 0x100, 0x45f: 0x100, + 0x460: 0x100, 0x461: 0x100, 0x462: 0x100, 0x463: 0x100, 0x464: 0x100, 0x465: 0x100, 0x466: 0x100, 0x467: 0x100, + 0x468: 0x100, 0x469: 0x100, 0x46a: 0x100, 0x46b: 0x100, 0x46c: 0x100, 0x46d: 0x100, 0x46e: 0x100, 0x46f: 0x100, + 0x470: 0x100, 0x471: 0x100, 0x472: 0x100, 0x473: 0x100, 0x474: 0x100, 0x475: 0x100, 0x476: 0x100, 0x477: 0x100, + 0x478: 0x100, 0x479: 0x100, 0x47a: 0x100, 0x47b: 0x100, 0x47c: 0x100, 0x47d: 0x100, 0x47e: 0x100, 0x47f: 0x100, + // Block 0x12, offset 0x480 + 0x480: 0x100, 0x481: 0x100, 0x482: 0x100, 0x483: 0x100, 0x484: 0x100, 0x485: 0x100, 0x486: 0x100, 0x487: 0x100, + 0x488: 0x100, 0x489: 0x100, 0x48a: 0x100, 0x48b: 0x100, 0x48c: 0x100, 0x48d: 0x100, 0x48e: 0x100, 0x48f: 0x100, + 0x490: 0xa6, 0x491: 0xa6, 0x492: 0xa6, 0x493: 0xa6, 0x494: 0xa6, 0x495: 0xa6, 0x496: 0xa6, 0x497: 0xa6, + 0x498: 0xa6, 0x499: 0x14a, 0x49a: 0x100, 0x49b: 0x100, 0x49c: 0x100, 0x49d: 0x100, 0x49e: 0x100, 0x49f: 0x100, + 0x4a0: 0x100, 0x4a1: 0x100, 0x4a2: 0x100, 0x4a3: 0x100, 0x4a4: 0x100, 0x4a5: 0x100, 0x4a6: 0x100, 0x4a7: 0x100, + 0x4a8: 0x100, 0x4a9: 0x100, 0x4aa: 0x100, 0x4ab: 0x100, 0x4ac: 0x100, 0x4ad: 0x100, 0x4ae: 0x100, 0x4af: 0x100, + 0x4b0: 0x100, 0x4b1: 0x100, 0x4b2: 0x100, 0x4b3: 0x100, 0x4b4: 0x100, 0x4b5: 0x100, 0x4b6: 0x100, 0x4b7: 0x100, + 0x4b8: 0x100, 0x4b9: 0x100, 0x4ba: 0x100, 0x4bb: 0x100, 0x4bc: 0x100, 0x4bd: 0x100, 0x4be: 0x100, 0x4bf: 0x100, + // Block 0x13, offset 0x4c0 + 0x4c0: 0x100, 0x4c1: 0x100, 0x4c2: 0x100, 0x4c3: 0x100, 0x4c4: 0x100, 0x4c5: 0x100, 0x4c6: 0x100, 0x4c7: 0x100, + 0x4c8: 0x100, 0x4c9: 0x100, 0x4ca: 0x100, 0x4cb: 0x100, 0x4cc: 0x100, 0x4cd: 0x100, 0x4ce: 0x100, 0x4cf: 0x100, + 0x4d0: 0x100, 0x4d1: 0x100, 0x4d2: 0x100, 0x4d3: 0x100, 0x4d4: 0x100, 0x4d5: 0x100, 0x4d6: 0x100, 0x4d7: 0x100, + 0x4d8: 0x100, 0x4d9: 0x100, 0x4da: 0x100, 0x4db: 0x100, 0x4dc: 0x100, 0x4dd: 0x100, 0x4de: 0x100, 0x4df: 0x100, + 0x4e0: 0xa6, 0x4e1: 0xa6, 0x4e2: 0xa6, 0x4e3: 0xa6, 0x4e4: 0xa6, 0x4e5: 0xa6, 0x4e6: 0xa6, 0x4e7: 0xa6, + 0x4e8: 0x154, 0x4e9: 0x168, 0x4ea: 0x169, 0x4eb: 0x16a, 0x4ec: 0x16b, 0x4ed: 0x16c, 0x4ee: 0x16d, 0x4ef: 0x100, + 0x4f0: 0x100, 0x4f1: 0x100, 0x4f2: 0x100, 0x4f3: 0x100, 0x4f4: 0x100, 0x4f5: 0x100, 0x4f6: 0x100, 0x4f7: 0x100, + 0x4f8: 0x100, 0x4f9: 0x16e, 0x4fa: 0x16f, 0x4fb: 0x100, 0x4fc: 0xa6, 0x4fd: 0x170, 0x4fe: 0x171, 0x4ff: 0x172, + // Block 0x14, offset 0x500 + 0x500: 0xa6, 0x501: 0xa6, 0x502: 0xa6, 0x503: 0xa6, 0x504: 0xa6, 0x505: 0xa6, 0x506: 0xa6, 0x507: 0xa6, + 0x508: 0xa6, 0x509: 0xa6, 0x50a: 0xa6, 0x50b: 0xa6, 0x50c: 0xa6, 0x50d: 0xa6, 0x50e: 0xa6, 0x50f: 0xa6, + 0x510: 0xa6, 0x511: 0xa6, 0x512: 0xa6, 0x513: 0xa6, 0x514: 0xa6, 0x515: 0xa6, 0x516: 0xa6, 0x517: 0xa6, + 0x518: 0xa6, 0x519: 0xa6, 0x51a: 0xa6, 0x51b: 0xa6, 0x51c: 0xa6, 0x51d: 0xa6, 0x51e: 0xa6, 0x51f: 0x173, + 0x520: 0xa6, 0x521: 0xa6, 0x522: 0xa6, 0x523: 0xa6, 0x524: 0xa6, 0x525: 0xa6, 0x526: 0xa6, 0x527: 0xa6, + 0x528: 0xa6, 0x529: 0xa6, 0x52a: 0xa6, 0x52b: 0xa6, 0x52c: 0xa6, 0x52d: 0xa6, 0x52e: 0xa6, 0x52f: 0xa6, + 0x530: 0xa6, 0x531: 0xa6, 0x532: 0xa6, 0x533: 0x174, 0x534: 0x175, 0x535: 0x100, 0x536: 0x100, 0x537: 0x100, + 0x538: 0x100, 0x539: 0x100, 0x53a: 0x100, 0x53b: 0x100, 0x53c: 0x100, 0x53d: 0x100, 0x53e: 0x100, 0x53f: 0x100, + // Block 0x15, offset 0x540 + 0x540: 0x100, 0x541: 0x100, 0x542: 0x100, 0x543: 0x100, 0x544: 0x100, 0x545: 0x100, 0x546: 0x100, 0x547: 0x100, + 0x548: 0x100, 0x549: 0x100, 0x54a: 0x100, 0x54b: 0x100, 0x54c: 0x100, 0x54d: 0x100, 0x54e: 0x100, 0x54f: 0x100, + 0x550: 0x100, 0x551: 0x100, 0x552: 0x100, 0x553: 0x100, 0x554: 0x100, 0x555: 0x100, 0x556: 0x100, 0x557: 0x100, + 0x558: 0x100, 0x559: 0x100, 0x55a: 0x100, 0x55b: 0x100, 0x55c: 0x100, 0x55d: 0x100, 0x55e: 0x100, 0x55f: 0x100, + 0x560: 0x100, 0x561: 0x100, 0x562: 0x100, 0x563: 0x100, 0x564: 0x100, 0x565: 0x100, 0x566: 0x100, 0x567: 0x100, + 0x568: 0x100, 0x569: 0x100, 0x56a: 0x100, 0x56b: 0x100, 0x56c: 0x100, 0x56d: 0x100, 0x56e: 0x100, 0x56f: 0x100, + 0x570: 0x100, 0x571: 0x100, 0x572: 0x100, 0x573: 0x100, 0x574: 0x100, 0x575: 0x100, 0x576: 0x100, 0x577: 0x100, + 0x578: 0x100, 0x579: 0x100, 0x57a: 0x100, 0x57b: 0x100, 0x57c: 0x100, 0x57d: 0x100, 0x57e: 0x100, 0x57f: 0x176, + // Block 0x16, offset 0x580 + 0x580: 0xa6, 0x581: 0xa6, 0x582: 0xa6, 0x583: 0xa6, 0x584: 0x177, 0x585: 0x178, 0x586: 0xa6, 0x587: 0xa6, + 0x588: 0xa6, 0x589: 0xa6, 0x58a: 0xa6, 0x58b: 0x179, 0x58c: 0x100, 0x58d: 0x100, 0x58e: 0x100, 0x58f: 0x100, + 0x590: 0x100, 0x591: 0x100, 0x592: 0x100, 0x593: 0x100, 0x594: 0x100, 0x595: 0x100, 0x596: 0x100, 0x597: 0x100, + 0x598: 0x100, 0x599: 0x100, 0x59a: 0x100, 0x59b: 0x100, 0x59c: 0x100, 0x59d: 0x100, 0x59e: 0x100, 0x59f: 0x100, + 0x5a0: 0x100, 0x5a1: 0x100, 0x5a2: 0x100, 0x5a3: 0x100, 0x5a4: 0x100, 0x5a5: 0x100, 0x5a6: 0x100, 0x5a7: 0x100, + 0x5a8: 0x100, 0x5a9: 0x100, 0x5aa: 0x100, 0x5ab: 0x100, 0x5ac: 0x100, 0x5ad: 0x100, 0x5ae: 0x100, 0x5af: 0x100, + 0x5b0: 0xa6, 0x5b1: 0x17a, 0x5b2: 0x17b, 0x5b3: 0x100, 0x5b4: 0x100, 0x5b5: 0x100, 0x5b6: 0x100, 0x5b7: 0x100, + 0x5b8: 0x100, 0x5b9: 0x100, 0x5ba: 0x100, 0x5bb: 0x100, 0x5bc: 0x100, 0x5bd: 0x100, 0x5be: 0x100, 0x5bf: 0x100, + // Block 0x17, offset 0x5c0 + 0x5c0: 0x100, 0x5c1: 0x100, 0x5c2: 0x100, 0x5c3: 0x100, 0x5c4: 0x100, 0x5c5: 0x100, 0x5c6: 0x100, 0x5c7: 0x100, + 0x5c8: 0x100, 0x5c9: 0x100, 0x5ca: 0x100, 0x5cb: 0x100, 0x5cc: 0x100, 0x5cd: 0x100, 0x5ce: 0x100, 0x5cf: 0x100, + 0x5d0: 0x100, 0x5d1: 0x100, 0x5d2: 0x100, 0x5d3: 0x100, 0x5d4: 0x100, 0x5d5: 0x100, 0x5d6: 0x100, 0x5d7: 0x100, + 0x5d8: 0x100, 0x5d9: 0x100, 0x5da: 0x100, 0x5db: 0x100, 0x5dc: 0x100, 0x5dd: 0x100, 0x5de: 0x100, 0x5df: 0x100, + 0x5e0: 0x100, 0x5e1: 0x100, 0x5e2: 0x100, 0x5e3: 0x100, 0x5e4: 0x100, 0x5e5: 0x100, 0x5e6: 0x100, 0x5e7: 0x100, + 0x5e8: 0x100, 0x5e9: 0x100, 0x5ea: 0x100, 0x5eb: 0x100, 0x5ec: 0x100, 0x5ed: 0x100, 0x5ee: 0x100, 0x5ef: 0x100, + 0x5f0: 0x100, 0x5f1: 0x100, 0x5f2: 0x100, 0x5f3: 0x100, 0x5f4: 0x100, 0x5f5: 0x100, 0x5f6: 0x100, 0x5f7: 0x100, + 0x5f8: 0x100, 0x5f9: 0x100, 0x5fa: 0x100, 0x5fb: 0x100, 0x5fc: 0x17c, 0x5fd: 0x17d, 0x5fe: 0xa2, 0x5ff: 0x17e, + // Block 0x18, offset 0x600 + 0x600: 0xa2, 0x601: 0xa2, 0x602: 0xa2, 0x603: 0x17f, 0x604: 0x180, 0x605: 0x181, 0x606: 0x182, 0x607: 0x183, + 0x608: 0xa2, 0x609: 0x184, 0x60a: 0x100, 0x60b: 0x185, 0x60c: 0xa2, 0x60d: 0x186, 0x60e: 0x100, 0x60f: 0x100, + 0x610: 0x63, 0x611: 0x64, 0x612: 0x65, 0x613: 0x66, 0x614: 0x67, 0x615: 0x68, 0x616: 0x69, 0x617: 0x6a, + 0x618: 0x6b, 0x619: 0x6c, 0x61a: 0x6d, 0x61b: 0x6e, 0x61c: 0x6f, 0x61d: 0x70, 0x61e: 0x71, 0x61f: 0x72, + 0x620: 0xa2, 0x621: 0xa2, 0x622: 0xa2, 0x623: 0xa2, 0x624: 0xa2, 0x625: 0xa2, 0x626: 0xa2, 0x627: 0xa2, + 0x628: 0x187, 0x629: 0x188, 0x62a: 0x189, 0x62b: 0x100, 0x62c: 0x100, 0x62d: 0x100, 0x62e: 0x100, 0x62f: 0x100, + 0x630: 0x100, 0x631: 0x100, 0x632: 0x100, 0x633: 0x100, 0x634: 0x100, 0x635: 0x100, 0x636: 0x100, 0x637: 0x100, + 0x638: 0x100, 0x639: 0x100, 0x63a: 0x100, 0x63b: 0x100, 0x63c: 0x18a, 0x63d: 0x100, 0x63e: 0x100, 0x63f: 0x100, + // Block 0x19, offset 0x640 + 0x640: 0x73, 0x641: 0x74, 0x642: 0x18b, 0x643: 0x100, 0x644: 0x18c, 0x645: 0x18d, 0x646: 0x100, 0x647: 0x100, + 0x648: 0x100, 0x649: 0x100, 0x64a: 0x18e, 0x64b: 0x18f, 0x64c: 0x100, 0x64d: 0x100, 0x64e: 0x100, 0x64f: 0x100, + 0x650: 0x100, 0x651: 0x100, 0x652: 0x100, 0x653: 0x190, 0x654: 0x100, 0x655: 0x100, 0x656: 0x100, 0x657: 0x100, + 0x658: 0x100, 0x659: 0x100, 0x65a: 0x100, 0x65b: 0x100, 0x65c: 0x100, 0x65d: 0x100, 0x65e: 0x100, 0x65f: 0x191, + 0x660: 0x127, 0x661: 0x127, 0x662: 0x127, 0x663: 0x192, 0x664: 0x75, 0x665: 0x193, 0x666: 0x100, 0x667: 0x100, + 0x668: 0x100, 0x669: 0x100, 0x66a: 0x100, 0x66b: 0x100, 0x66c: 0x100, 0x66d: 0x100, 0x66e: 0x100, 0x66f: 0x100, + 0x670: 0x100, 0x671: 0x194, 0x672: 0x195, 0x673: 0x100, 0x674: 0x196, 0x675: 0x100, 0x676: 0x100, 0x677: 0x100, + 0x678: 0x76, 0x679: 0x77, 0x67a: 0x78, 0x67b: 0x197, 0x67c: 0x100, 0x67d: 0x100, 0x67e: 0x100, 0x67f: 0x100, + // Block 0x1a, offset 0x680 + 0x680: 0x198, 0x681: 0xa2, 0x682: 0x199, 0x683: 0x19a, 0x684: 0x79, 0x685: 0x7a, 0x686: 0x19b, 0x687: 0x19c, + 0x688: 0x7b, 0x689: 0x19d, 0x68a: 0x100, 0x68b: 0x100, 0x68c: 0xa2, 0x68d: 0xa2, 0x68e: 0xa2, 0x68f: 0xa2, + 0x690: 0xa2, 0x691: 0xa2, 0x692: 0xa2, 0x693: 0xa2, 0x694: 0xa2, 0x695: 0xa2, 0x696: 0xa2, 0x697: 0xa2, + 0x698: 0xa2, 0x699: 0xa2, 0x69a: 0xa2, 0x69b: 0x19e, 0x69c: 0xa2, 0x69d: 0x19f, 0x69e: 0xa2, 0x69f: 0x1a0, + 0x6a0: 0x1a1, 0x6a1: 0x1a2, 0x6a2: 0x1a3, 0x6a3: 0x100, 0x6a4: 0xa2, 0x6a5: 0xa2, 0x6a6: 0xa2, 0x6a7: 0xa2, + 0x6a8: 0xa2, 0x6a9: 0x1a4, 0x6aa: 0x1a5, 0x6ab: 0x1a6, 0x6ac: 0xa2, 0x6ad: 0xa2, 0x6ae: 0x1a7, 0x6af: 0x1a8, + 0x6b0: 0x100, 0x6b1: 0x100, 0x6b2: 0x100, 0x6b3: 0x100, 0x6b4: 0x100, 0x6b5: 0x100, 0x6b6: 0x100, 0x6b7: 0x100, + 0x6b8: 0x100, 0x6b9: 0x100, 0x6ba: 0x100, 0x6bb: 0x100, 0x6bc: 0x100, 0x6bd: 0x100, 0x6be: 0x100, 0x6bf: 0x100, + // Block 0x1b, offset 0x6c0 + 0x6c0: 0xa6, 0x6c1: 0xa6, 0x6c2: 0xa6, 0x6c3: 0xa6, 0x6c4: 0xa6, 0x6c5: 0xa6, 0x6c6: 0xa6, 0x6c7: 0xa6, + 0x6c8: 0xa6, 0x6c9: 0xa6, 0x6ca: 0xa6, 0x6cb: 0xa6, 0x6cc: 0xa6, 0x6cd: 0xa6, 0x6ce: 0xa6, 0x6cf: 0xa6, + 0x6d0: 0xa6, 0x6d1: 0xa6, 0x6d2: 0xa6, 0x6d3: 0xa6, 0x6d4: 0xa6, 0x6d5: 0xa6, 0x6d6: 0xa6, 0x6d7: 0xa6, + 0x6d8: 0xa6, 0x6d9: 0xa6, 0x6da: 0xa6, 0x6db: 0x1a9, 0x6dc: 0xa6, 0x6dd: 0xa6, 0x6de: 0xa6, 0x6df: 0xa6, + 0x6e0: 0xa6, 0x6e1: 0xa6, 0x6e2: 0xa6, 0x6e3: 0xa6, 0x6e4: 0xa6, 0x6e5: 0xa6, 0x6e6: 0xa6, 0x6e7: 0xa6, + 0x6e8: 0xa6, 0x6e9: 0xa6, 0x6ea: 0xa6, 0x6eb: 0xa6, 0x6ec: 0xa6, 0x6ed: 0xa6, 0x6ee: 0xa6, 0x6ef: 0xa6, + 0x6f0: 0xa6, 0x6f1: 0xa6, 0x6f2: 0xa6, 0x6f3: 0xa6, 0x6f4: 0xa6, 0x6f5: 0xa6, 0x6f6: 0xa6, 0x6f7: 0xa6, + 0x6f8: 0xa6, 0x6f9: 0xa6, 0x6fa: 0xa6, 0x6fb: 0xa6, 0x6fc: 0xa6, 0x6fd: 0xa6, 0x6fe: 0xa6, 0x6ff: 0xa6, + // Block 0x1c, offset 0x700 + 0x700: 0xa6, 0x701: 0xa6, 0x702: 0xa6, 0x703: 0xa6, 0x704: 0xa6, 0x705: 0xa6, 0x706: 0xa6, 0x707: 0xa6, + 0x708: 0xa6, 0x709: 0xa6, 0x70a: 0xa6, 0x70b: 0xa6, 0x70c: 0xa6, 0x70d: 0xa6, 0x70e: 0xa6, 0x70f: 0xa6, + 0x710: 0xa6, 0x711: 0xa6, 0x712: 0xa6, 0x713: 0xa6, 0x714: 0xa6, 0x715: 0xa6, 0x716: 0xa6, 0x717: 0xa6, + 0x718: 0xa6, 0x719: 0xa6, 0x71a: 0xa6, 0x71b: 0xa6, 0x71c: 0x1aa, 0x71d: 0xa6, 0x71e: 0xa6, 0x71f: 0xa6, + 0x720: 0x1ab, 0x721: 0xa6, 0x722: 0xa6, 0x723: 0xa6, 0x724: 0xa6, 0x725: 0xa6, 0x726: 0xa6, 0x727: 0xa6, + 0x728: 0xa6, 0x729: 0xa6, 0x72a: 0xa6, 0x72b: 0xa6, 0x72c: 0xa6, 0x72d: 0xa6, 0x72e: 0xa6, 0x72f: 0xa6, + 0x730: 0xa6, 0x731: 0xa6, 0x732: 0xa6, 0x733: 0xa6, 0x734: 0xa6, 0x735: 0xa6, 0x736: 0xa6, 0x737: 0xa6, + 0x738: 0xa6, 0x739: 0xa6, 0x73a: 0xa6, 0x73b: 0xa6, 0x73c: 0xa6, 0x73d: 0xa6, 0x73e: 0xa6, 0x73f: 0xa6, + // Block 0x1d, offset 0x740 + 0x740: 0xa6, 0x741: 0xa6, 0x742: 0xa6, 0x743: 0xa6, 0x744: 0xa6, 0x745: 0xa6, 0x746: 0xa6, 0x747: 0xa6, + 0x748: 0xa6, 0x749: 0xa6, 0x74a: 0xa6, 0x74b: 0xa6, 0x74c: 0xa6, 0x74d: 0xa6, 0x74e: 0xa6, 0x74f: 0xa6, + 0x750: 0xa6, 0x751: 0xa6, 0x752: 0xa6, 0x753: 0xa6, 0x754: 0xa6, 0x755: 0xa6, 0x756: 0xa6, 0x757: 0xa6, + 0x758: 0xa6, 0x759: 0xa6, 0x75a: 0xa6, 0x75b: 0xa6, 0x75c: 0xa6, 0x75d: 0xa6, 0x75e: 0xa6, 0x75f: 0xa6, + 0x760: 0xa6, 0x761: 0xa6, 0x762: 0xa6, 0x763: 0xa6, 0x764: 0xa6, 0x765: 0xa6, 0x766: 0xa6, 0x767: 0xa6, + 0x768: 0xa6, 0x769: 0xa6, 0x76a: 0xa6, 0x76b: 0xa6, 0x76c: 0xa6, 0x76d: 0xa6, 0x76e: 0xa6, 0x76f: 0xa6, + 0x770: 0xa6, 0x771: 0xa6, 0x772: 0xa6, 0x773: 0xa6, 0x774: 0xa6, 0x775: 0xa6, 0x776: 0xa6, 0x777: 0xa6, + 0x778: 0xa6, 0x779: 0xa6, 0x77a: 0x1ac, 0x77b: 0xa6, 0x77c: 0xa6, 0x77d: 0xa6, 0x77e: 0xa6, 0x77f: 0xa6, + // Block 0x1e, offset 0x780 + 0x780: 0xa6, 0x781: 0xa6, 0x782: 0xa6, 0x783: 0xa6, 0x784: 0xa6, 0x785: 0xa6, 0x786: 0xa6, 0x787: 0xa6, + 0x788: 0xa6, 0x789: 0xa6, 0x78a: 0xa6, 0x78b: 0xa6, 0x78c: 0xa6, 0x78d: 0xa6, 0x78e: 0xa6, 0x78f: 0xa6, + 0x790: 0xa6, 0x791: 0xa6, 0x792: 0xa6, 0x793: 0xa6, 0x794: 0xa6, 0x795: 0xa6, 0x796: 0xa6, 0x797: 0xa6, + 0x798: 0xa6, 0x799: 0xa6, 0x79a: 0xa6, 0x79b: 0xa6, 0x79c: 0xa6, 0x79d: 0xa6, 0x79e: 0xa6, 0x79f: 0xa6, + 0x7a0: 0xa6, 0x7a1: 0xa6, 0x7a2: 0xa6, 0x7a3: 0xa6, 0x7a4: 0xa6, 0x7a5: 0xa6, 0x7a6: 0xa6, 0x7a7: 0xa6, + 0x7a8: 0xa6, 0x7a9: 0xa6, 0x7aa: 0xa6, 0x7ab: 0xa6, 0x7ac: 0xa6, 0x7ad: 0xa6, 0x7ae: 0xa6, 0x7af: 0x1ad, + 0x7b0: 0x100, 0x7b1: 0x100, 0x7b2: 0x100, 0x7b3: 0x100, 0x7b4: 0x100, 0x7b5: 0x100, 0x7b6: 0x100, 0x7b7: 0x100, + 0x7b8: 0x100, 0x7b9: 0x100, 0x7ba: 0x100, 0x7bb: 0x100, 0x7bc: 0x100, 0x7bd: 0x100, 0x7be: 0x100, 0x7bf: 0x100, + // Block 0x1f, offset 0x7c0 + 0x7c0: 0x100, 0x7c1: 0x100, 0x7c2: 0x100, 0x7c3: 0x100, 0x7c4: 0x100, 0x7c5: 0x100, 0x7c6: 0x100, 0x7c7: 0x100, + 0x7c8: 0x100, 0x7c9: 0x100, 0x7ca: 0x100, 0x7cb: 0x100, 0x7cc: 0x100, 0x7cd: 0x100, 0x7ce: 0x100, 0x7cf: 0x100, + 0x7d0: 0x100, 0x7d1: 0x100, 0x7d2: 0x100, 0x7d3: 0x100, 0x7d4: 0x100, 0x7d5: 0x100, 0x7d6: 0x100, 0x7d7: 0x100, + 0x7d8: 0x100, 0x7d9: 0x100, 0x7da: 0x100, 0x7db: 0x100, 0x7dc: 0x100, 0x7dd: 0x100, 0x7de: 0x100, 0x7df: 0x100, + 0x7e0: 0x7c, 0x7e1: 0x7d, 0x7e2: 0x7e, 0x7e3: 0x7f, 0x7e4: 0x80, 0x7e5: 0x81, 0x7e6: 0x82, 0x7e7: 0x83, + 0x7e8: 0x84, 0x7e9: 0x100, 0x7ea: 0x100, 0x7eb: 0x100, 0x7ec: 0x100, 0x7ed: 0x100, 0x7ee: 0x100, 0x7ef: 0x100, + 0x7f0: 0x100, 0x7f1: 0x100, 0x7f2: 0x100, 0x7f3: 0x100, 0x7f4: 0x100, 0x7f5: 0x100, 0x7f6: 0x100, 0x7f7: 0x100, + 0x7f8: 0x100, 0x7f9: 0x100, 0x7fa: 0x100, 0x7fb: 0x100, 0x7fc: 0x100, 0x7fd: 0x100, 0x7fe: 0x100, 0x7ff: 0x100, + // Block 0x20, offset 0x800 + 0x800: 0xa6, 0x801: 0xa6, 0x802: 0xa6, 0x803: 0xa6, 0x804: 0xa6, 0x805: 0xa6, 0x806: 0xa6, 0x807: 0xa6, + 0x808: 0xa6, 0x809: 0xa6, 0x80a: 0xa6, 0x80b: 0xa6, 0x80c: 0xa6, 0x80d: 0x1ae, 0x80e: 0xa6, 0x80f: 0xa6, + 0x810: 0xa6, 0x811: 0xa6, 0x812: 0xa6, 0x813: 0xa6, 0x814: 0xa6, 0x815: 0xa6, 0x816: 0xa6, 0x817: 0xa6, + 0x818: 0xa6, 0x819: 0xa6, 0x81a: 0xa6, 0x81b: 0xa6, 0x81c: 0xa6, 0x81d: 0xa6, 0x81e: 0xa6, 0x81f: 0xa6, + 0x820: 0xa6, 0x821: 0xa6, 0x822: 0xa6, 0x823: 0xa6, 0x824: 0xa6, 0x825: 0xa6, 0x826: 0xa6, 0x827: 0xa6, + 0x828: 0xa6, 0x829: 0xa6, 0x82a: 0xa6, 0x82b: 0xa6, 0x82c: 0xa6, 0x82d: 0xa6, 0x82e: 0xa6, 0x82f: 0xa6, + 0x830: 0xa6, 0x831: 0xa6, 0x832: 0xa6, 0x833: 0xa6, 0x834: 0xa6, 0x835: 0xa6, 0x836: 0xa6, 0x837: 0xa6, + 0x838: 0xa6, 0x839: 0xa6, 0x83a: 0xa6, 0x83b: 0xa6, 0x83c: 0xa6, 0x83d: 0xa6, 0x83e: 0xa6, 0x83f: 0xa6, + // Block 0x21, offset 0x840 + 0x840: 0xa6, 0x841: 0xa6, 0x842: 0xa6, 0x843: 0xa6, 0x844: 0xa6, 0x845: 0xa6, 0x846: 0xa6, 0x847: 0xa6, + 0x848: 0xa6, 0x849: 0xa6, 0x84a: 0xa6, 0x84b: 0xa6, 0x84c: 0xa6, 0x84d: 0xa6, 0x84e: 0x1af, 0x84f: 0x100, + 0x850: 0x100, 0x851: 0x100, 0x852: 0x100, 0x853: 0x100, 0x854: 0x100, 0x855: 0x100, 0x856: 0x100, 0x857: 0x100, + 0x858: 0x100, 0x859: 0x100, 0x85a: 0x100, 0x85b: 0x100, 0x85c: 0x100, 0x85d: 0x100, 0x85e: 0x100, 0x85f: 0x100, + 0x860: 0x100, 0x861: 0x100, 0x862: 0x100, 0x863: 0x100, 0x864: 0x100, 0x865: 0x100, 0x866: 0x100, 0x867: 0x100, + 0x868: 0x100, 0x869: 0x100, 0x86a: 0x100, 0x86b: 0x100, 0x86c: 0x100, 0x86d: 0x100, 0x86e: 0x100, 0x86f: 0x100, + 0x870: 0x100, 0x871: 0x100, 0x872: 0x100, 0x873: 0x100, 0x874: 0x100, 0x875: 0x100, 0x876: 0x100, 0x877: 0x100, + 0x878: 0x100, 0x879: 0x100, 0x87a: 0x100, 0x87b: 0x100, 0x87c: 0x100, 0x87d: 0x100, 0x87e: 0x100, 0x87f: 0x100, + // Block 0x22, offset 0x880 + 0x890: 0x0c, 0x891: 0x0d, 0x892: 0x0e, 0x893: 0x0f, 0x894: 0x10, 0x895: 0x0a, 0x896: 0x11, 0x897: 0x07, + 0x898: 0x12, 0x899: 0x0a, 0x89a: 0x13, 0x89b: 0x14, 0x89c: 0x15, 0x89d: 0x16, 0x89e: 0x17, 0x89f: 0x18, + 0x8a0: 0x07, 0x8a1: 0x07, 0x8a2: 0x07, 0x8a3: 0x07, 0x8a4: 0x07, 0x8a5: 0x07, 0x8a6: 0x07, 0x8a7: 0x07, + 0x8a8: 0x07, 0x8a9: 0x07, 0x8aa: 0x19, 0x8ab: 0x1a, 0x8ac: 0x1b, 0x8ad: 0x07, 0x8ae: 0x1c, 0x8af: 0x1d, + 0x8b0: 0x07, 0x8b1: 0x1e, 0x8b2: 0x1f, 0x8b3: 0x0a, 0x8b4: 0x0a, 0x8b5: 0x0a, 0x8b6: 0x0a, 0x8b7: 0x0a, + 0x8b8: 0x0a, 0x8b9: 0x0a, 0x8ba: 0x0a, 0x8bb: 0x0a, 0x8bc: 0x0a, 0x8bd: 0x0a, 0x8be: 0x0a, 0x8bf: 0x0a, + // Block 0x23, offset 0x8c0 + 0x8c0: 0x0a, 0x8c1: 0x0a, 0x8c2: 0x0a, 0x8c3: 0x0a, 0x8c4: 0x0a, 0x8c5: 0x0a, 0x8c6: 0x0a, 0x8c7: 0x0a, + 0x8c8: 0x0a, 0x8c9: 0x0a, 0x8ca: 0x0a, 0x8cb: 0x0a, 0x8cc: 0x0a, 0x8cd: 0x0a, 0x8ce: 0x0a, 0x8cf: 0x0a, + 0x8d0: 0x0a, 0x8d1: 0x0a, 0x8d2: 0x0a, 0x8d3: 0x0a, 0x8d4: 0x0a, 0x8d5: 0x0a, 0x8d6: 0x0a, 0x8d7: 0x0a, + 0x8d8: 0x0a, 0x8d9: 0x0a, 0x8da: 0x0a, 0x8db: 0x0a, 0x8dc: 0x0a, 0x8dd: 0x0a, 0x8de: 0x0a, 0x8df: 0x0a, + 0x8e0: 0x0a, 0x8e1: 0x0a, 0x8e2: 0x0a, 0x8e3: 0x0a, 0x8e4: 0x0a, 0x8e5: 0x0a, 0x8e6: 0x0a, 0x8e7: 0x0a, + 0x8e8: 0x0a, 0x8e9: 0x0a, 0x8ea: 0x0a, 0x8eb: 0x0a, 0x8ec: 0x0a, 0x8ed: 0x0a, 0x8ee: 0x0a, 0x8ef: 0x0a, + 0x8f0: 0x0a, 0x8f1: 0x0a, 0x8f2: 0x0a, 0x8f3: 0x0a, 0x8f4: 0x0a, 0x8f5: 0x0a, 0x8f6: 0x0a, 0x8f7: 0x0a, + 0x8f8: 0x0a, 0x8f9: 0x0a, 0x8fa: 0x0a, 0x8fb: 0x0a, 0x8fc: 0x0a, 0x8fd: 0x0a, 0x8fe: 0x0a, 0x8ff: 0x0a, + // Block 0x24, offset 0x900 + 0x900: 0x1b0, 0x901: 0x1b1, 0x902: 0x100, 0x903: 0x100, 0x904: 0x1b2, 0x905: 0x1b2, 0x906: 0x1b2, 0x907: 0x1b3, + 0x908: 0x100, 0x909: 0x100, 0x90a: 0x100, 0x90b: 0x100, 0x90c: 0x100, 0x90d: 0x100, 0x90e: 0x100, 0x90f: 0x100, + 0x910: 0x100, 0x911: 0x100, 0x912: 0x100, 0x913: 0x100, 0x914: 0x100, 0x915: 0x100, 0x916: 0x100, 0x917: 0x100, + 0x918: 0x100, 0x919: 0x100, 0x91a: 0x100, 0x91b: 0x100, 0x91c: 0x100, 0x91d: 0x100, 0x91e: 0x100, 0x91f: 0x100, + 0x920: 0x100, 0x921: 0x100, 0x922: 0x100, 0x923: 0x100, 0x924: 0x100, 0x925: 0x100, 0x926: 0x100, 0x927: 0x100, + 0x928: 0x100, 0x929: 0x100, 0x92a: 0x100, 0x92b: 0x100, 0x92c: 0x100, 0x92d: 0x100, 0x92e: 0x100, 0x92f: 0x100, + 0x930: 0x100, 0x931: 0x100, 0x932: 0x100, 0x933: 0x100, 0x934: 0x100, 0x935: 0x100, 0x936: 0x100, 0x937: 0x100, + 0x938: 0x100, 0x939: 0x100, 0x93a: 0x100, 0x93b: 0x100, 0x93c: 0x100, 0x93d: 0x100, 0x93e: 0x100, 0x93f: 0x100, + // Block 0x25, offset 0x940 + 0x940: 0x0a, 0x941: 0x0a, 0x942: 0x0a, 0x943: 0x0a, 0x944: 0x0a, 0x945: 0x0a, 0x946: 0x0a, 0x947: 0x0a, + 0x948: 0x0a, 0x949: 0x0a, 0x94a: 0x0a, 0x94b: 0x0a, 0x94c: 0x0a, 0x94d: 0x0a, 0x94e: 0x0a, 0x94f: 0x0a, + 0x950: 0x0a, 0x951: 0x0a, 0x952: 0x0a, 0x953: 0x0a, 0x954: 0x0a, 0x955: 0x0a, 0x956: 0x0a, 0x957: 0x0a, + 0x958: 0x0a, 0x959: 0x0a, 0x95a: 0x0a, 0x95b: 0x0a, 0x95c: 0x0a, 0x95d: 0x0a, 0x95e: 0x0a, 0x95f: 0x0a, + 0x960: 0x22, 0x961: 0x0a, 0x962: 0x0a, 0x963: 0x0a, 0x964: 0x0a, 0x965: 0x0a, 0x966: 0x0a, 0x967: 0x0a, + 0x968: 0x0a, 0x969: 0x0a, 0x96a: 0x0a, 0x96b: 0x0a, 0x96c: 0x0a, 0x96d: 0x0a, 0x96e: 0x0a, 0x96f: 0x0a, + 0x970: 0x0a, 0x971: 0x0a, 0x972: 0x0a, 0x973: 0x0a, 0x974: 0x0a, 0x975: 0x0a, 0x976: 0x0a, 0x977: 0x0a, + 0x978: 0x0a, 0x979: 0x0a, 0x97a: 0x0a, 0x97b: 0x0a, 0x97c: 0x0a, 0x97d: 0x0a, 0x97e: 0x0a, 0x97f: 0x0a, + // Block 0x26, offset 0x980 + 0x980: 0x0a, 0x981: 0x0a, 0x982: 0x0a, 0x983: 0x0a, 0x984: 0x0a, 0x985: 0x0a, 0x986: 0x0a, 0x987: 0x0a, + 0x988: 0x0a, 0x989: 0x0a, 0x98a: 0x0a, 0x98b: 0x0a, 0x98c: 0x0a, 0x98d: 0x0a, 0x98e: 0x0a, 0x98f: 0x0a, +} + +// idnaSparseOffset: 303 entries, 606 bytes +var idnaSparseOffset = []uint16{0x0, 0x8, 0x19, 0x25, 0x27, 0x2c, 0x33, 0x3e, 0x4a, 0x4e, 0x5d, 0x62, 0x6c, 0x78, 0x7e, 0x87, 0x97, 0xa6, 0xb1, 0xbe, 0xcf, 0xd9, 0xe0, 0xed, 0xfe, 0x105, 0x110, 0x11f, 0x12d, 0x137, 0x139, 0x13e, 0x141, 0x144, 0x146, 0x152, 0x15d, 0x165, 0x16b, 0x171, 0x176, 0x17b, 0x17e, 0x182, 0x188, 0x18d, 0x198, 0x1a2, 0x1a8, 0x1b9, 0x1c4, 0x1c7, 0x1cf, 0x1d2, 0x1df, 0x1e7, 0x1eb, 0x1f2, 0x1fa, 0x20a, 0x216, 0x219, 0x223, 0x22f, 0x23b, 0x247, 0x24f, 0x254, 0x261, 0x272, 0x27d, 0x282, 0x28b, 0x293, 0x299, 0x29e, 0x2a1, 0x2a5, 0x2ab, 0x2af, 0x2b3, 0x2b7, 0x2bc, 0x2c4, 0x2cb, 0x2d6, 0x2e0, 0x2e4, 0x2e7, 0x2ed, 0x2f1, 0x2f3, 0x2f6, 0x2f8, 0x2fb, 0x305, 0x308, 0x317, 0x31b, 0x31f, 0x321, 0x32a, 0x32e, 0x333, 0x338, 0x33e, 0x34e, 0x354, 0x358, 0x367, 0x36c, 0x374, 0x37e, 0x389, 0x391, 0x3a2, 0x3ab, 0x3bb, 0x3c8, 0x3d4, 0x3d9, 0x3e6, 0x3ea, 0x3ef, 0x3f1, 0x3f3, 0x3f7, 0x3f9, 0x3fd, 0x406, 0x40c, 0x410, 0x420, 0x42a, 0x42f, 0x432, 0x438, 0x43f, 0x444, 0x448, 0x44e, 0x453, 0x45c, 0x461, 0x467, 0x46e, 0x475, 0x47c, 0x480, 0x483, 0x488, 0x494, 0x49a, 0x49f, 0x4a6, 0x4ae, 0x4b3, 0x4b7, 0x4c7, 0x4ce, 0x4d2, 0x4d6, 0x4dd, 0x4df, 0x4e2, 0x4e5, 0x4e9, 0x4f2, 0x4f6, 0x4fe, 0x501, 0x509, 0x514, 0x523, 0x52f, 0x535, 0x542, 0x54e, 0x556, 0x55f, 0x56a, 0x571, 0x580, 0x58d, 0x591, 0x59e, 0x5a7, 0x5ab, 0x5ba, 0x5c2, 0x5cd, 0x5d6, 0x5dc, 0x5e4, 0x5ed, 0x5f9, 0x5fc, 0x608, 0x60b, 0x614, 0x617, 0x61c, 0x625, 0x62a, 0x637, 0x642, 0x64b, 0x656, 0x659, 0x65c, 0x666, 0x66f, 0x67b, 0x688, 0x695, 0x6a3, 0x6aa, 0x6b5, 0x6bc, 0x6c0, 0x6c4, 0x6c7, 0x6cc, 0x6cf, 0x6d2, 0x6d6, 0x6d9, 0x6de, 0x6e5, 0x6e8, 0x6f0, 0x6f4, 0x6ff, 0x702, 0x705, 0x708, 0x70e, 0x714, 0x71d, 0x720, 0x723, 0x726, 0x72e, 0x733, 0x73c, 0x73f, 0x744, 0x74e, 0x752, 0x756, 0x759, 0x75c, 0x760, 0x76f, 0x77b, 0x77f, 0x784, 0x789, 0x78e, 0x792, 0x797, 0x7a0, 0x7a5, 0x7a9, 0x7af, 0x7b5, 0x7ba, 0x7c0, 0x7c6, 0x7d0, 0x7d6, 0x7df, 0x7e2, 0x7e5, 0x7e9, 0x7ed, 0x7f1, 0x7f7, 0x7fd, 0x802, 0x805, 0x815, 0x81c, 0x820, 0x827, 0x82b, 0x831, 0x838, 0x83f, 0x845, 0x84e, 0x852, 0x860, 0x863, 0x866, 0x86a, 0x86e, 0x871, 0x875, 0x878, 0x87d, 0x87f, 0x881} + +// idnaSparseValues: 2180 entries, 8720 bytes +var idnaSparseValues = [2180]valueRange{ + // Block 0x0, offset 0x0 + {value: 0x0000, lo: 0x07}, + {value: 0xe105, lo: 0x80, hi: 0x96}, + {value: 0x0018, lo: 0x97, hi: 0x97}, + {value: 0xe105, lo: 0x98, hi: 0x9e}, + {value: 0x001f, lo: 0x9f, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xb6}, + {value: 0x0018, lo: 0xb7, hi: 0xb7}, + {value: 0x0008, lo: 0xb8, hi: 0xbf}, + // Block 0x1, offset 0x8 + {value: 0x0000, lo: 0x10}, + {value: 0x0008, lo: 0x80, hi: 0x80}, + {value: 0xe01d, lo: 0x81, hi: 0x81}, + {value: 0x0008, lo: 0x82, hi: 0x82}, + {value: 0x0335, lo: 0x83, hi: 0x83}, + {value: 0x034d, lo: 0x84, hi: 0x84}, + {value: 0x0365, lo: 0x85, hi: 0x85}, + {value: 0xe00d, lo: 0x86, hi: 0x86}, + {value: 0x0008, lo: 0x87, hi: 0x87}, + {value: 0xe00d, lo: 0x88, hi: 0x88}, + {value: 0x0008, lo: 0x89, hi: 0x89}, + {value: 0xe00d, lo: 0x8a, hi: 0x8a}, + {value: 0x0008, lo: 0x8b, hi: 0x8b}, + {value: 0xe00d, lo: 0x8c, hi: 0x8c}, + {value: 0x0008, lo: 0x8d, hi: 0x8d}, + {value: 0xe00d, lo: 0x8e, hi: 0x8e}, + {value: 0x0008, lo: 0x8f, hi: 0xbf}, + // Block 0x2, offset 0x19 + {value: 0x0000, lo: 0x0b}, + {value: 0x0008, lo: 0x80, hi: 0xaf}, + {value: 0x00a9, lo: 0xb0, hi: 0xb0}, + {value: 0x037d, lo: 0xb1, hi: 0xb1}, + {value: 0x00b1, lo: 0xb2, hi: 0xb2}, + {value: 0x00b9, lo: 0xb3, hi: 0xb3}, + {value: 0x034d, lo: 0xb4, hi: 0xb4}, + {value: 0x0395, lo: 0xb5, hi: 0xb5}, + {value: 0xe1bd, lo: 0xb6, hi: 0xb6}, + {value: 0x00c1, lo: 0xb7, hi: 0xb7}, + {value: 0x00c9, lo: 0xb8, hi: 0xb8}, + {value: 0x0008, lo: 0xb9, hi: 0xbf}, + // Block 0x3, offset 0x25 + {value: 0x0000, lo: 0x01}, + {value: 0x3308, lo: 0x80, hi: 0xbf}, + // Block 0x4, offset 0x27 + {value: 0x0000, lo: 0x04}, + {value: 0x03f5, lo: 0x80, hi: 0x8f}, + {value: 0xe105, lo: 0x90, hi: 0x9f}, + {value: 0x049d, lo: 0xa0, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0x5, offset 0x2c + {value: 0x0000, lo: 0x06}, + {value: 0xe185, lo: 0x80, hi: 0x8f}, + {value: 0x0545, lo: 0x90, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0x98}, + {value: 0x0008, lo: 0x99, hi: 0x99}, + {value: 0x0018, lo: 0x9a, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xbf}, + // Block 0x6, offset 0x33 + {value: 0x0000, lo: 0x0a}, + {value: 0x0008, lo: 0x80, hi: 0x86}, + {value: 0x0131, lo: 0x87, hi: 0x87}, + {value: 0x0008, lo: 0x88, hi: 0x88}, + {value: 0x0018, lo: 0x89, hi: 0x8a}, + {value: 0x0040, lo: 0x8b, hi: 0x8c}, + {value: 0x0018, lo: 0x8d, hi: 0x8f}, + {value: 0x0040, lo: 0x90, hi: 0x90}, + {value: 0x3308, lo: 0x91, hi: 0xbd}, + {value: 0x0818, lo: 0xbe, hi: 0xbe}, + {value: 0x3308, lo: 0xbf, hi: 0xbf}, + // Block 0x7, offset 0x3e + {value: 0x0000, lo: 0x0b}, + {value: 0x0818, lo: 0x80, hi: 0x80}, + {value: 0x3308, lo: 0x81, hi: 0x82}, + {value: 0x0818, lo: 0x83, hi: 0x83}, + {value: 0x3308, lo: 0x84, hi: 0x85}, + {value: 0x0818, lo: 0x86, hi: 0x86}, + {value: 0x3308, lo: 0x87, hi: 0x87}, + {value: 0x0040, lo: 0x88, hi: 0x8f}, + {value: 0x0808, lo: 0x90, hi: 0xaa}, + {value: 0x0040, lo: 0xab, hi: 0xae}, + {value: 0x0808, lo: 0xaf, hi: 0xb4}, + {value: 0x0040, lo: 0xb5, hi: 0xbf}, + // Block 0x8, offset 0x4a + {value: 0x0000, lo: 0x03}, + {value: 0x0a08, lo: 0x80, hi: 0x87}, + {value: 0x0c08, lo: 0x88, hi: 0x99}, + {value: 0x0a08, lo: 0x9a, hi: 0xbf}, + // Block 0x9, offset 0x4e + {value: 0x0000, lo: 0x0e}, + {value: 0x3308, lo: 0x80, hi: 0x8a}, + {value: 0x0040, lo: 0x8b, hi: 0x8c}, + {value: 0x0c08, lo: 0x8d, hi: 0x8d}, + {value: 0x0a08, lo: 0x8e, hi: 0x98}, + {value: 0x0c08, lo: 0x99, hi: 0x9b}, + {value: 0x0a08, lo: 0x9c, hi: 0xaa}, + {value: 0x0c08, lo: 0xab, hi: 0xac}, + {value: 0x0a08, lo: 0xad, hi: 0xb0}, + {value: 0x0c08, lo: 0xb1, hi: 0xb1}, + {value: 0x0a08, lo: 0xb2, hi: 0xb2}, + {value: 0x0c08, lo: 0xb3, hi: 0xb4}, + {value: 0x0a08, lo: 0xb5, hi: 0xb7}, + {value: 0x0c08, lo: 0xb8, hi: 0xb9}, + {value: 0x0a08, lo: 0xba, hi: 0xbf}, + // Block 0xa, offset 0x5d + {value: 0x0000, lo: 0x04}, + {value: 0x0808, lo: 0x80, hi: 0xa5}, + {value: 0x3308, lo: 0xa6, hi: 0xb0}, + {value: 0x0808, lo: 0xb1, hi: 0xb1}, + {value: 0x0040, lo: 0xb2, hi: 0xbf}, + // Block 0xb, offset 0x62 + {value: 0x0000, lo: 0x09}, + {value: 0x0808, lo: 0x80, hi: 0x89}, + {value: 0x0a08, lo: 0x8a, hi: 0xaa}, + {value: 0x3308, lo: 0xab, hi: 0xb3}, + {value: 0x0808, lo: 0xb4, hi: 0xb5}, + {value: 0x0018, lo: 0xb6, hi: 0xb9}, + {value: 0x0818, lo: 0xba, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbc}, + {value: 0x3308, lo: 0xbd, hi: 0xbd}, + {value: 0x0818, lo: 0xbe, hi: 0xbf}, + // Block 0xc, offset 0x6c + {value: 0x0000, lo: 0x0b}, + {value: 0x0808, lo: 0x80, hi: 0x95}, + {value: 0x3308, lo: 0x96, hi: 0x99}, + {value: 0x0808, lo: 0x9a, hi: 0x9a}, + {value: 0x3308, lo: 0x9b, hi: 0xa3}, + {value: 0x0808, lo: 0xa4, hi: 0xa4}, + {value: 0x3308, lo: 0xa5, hi: 0xa7}, + {value: 0x0808, lo: 0xa8, hi: 0xa8}, + {value: 0x3308, lo: 0xa9, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xaf}, + {value: 0x0818, lo: 0xb0, hi: 0xbe}, + {value: 0x0040, lo: 0xbf, hi: 0xbf}, + // Block 0xd, offset 0x78 + {value: 0x0000, lo: 0x05}, + {value: 0x0a08, lo: 0x80, hi: 0x88}, + {value: 0x0808, lo: 0x89, hi: 0x89}, + {value: 0x3308, lo: 0x8a, hi: 0xa1}, + {value: 0x0840, lo: 0xa2, hi: 0xa2}, + {value: 0x3308, lo: 0xa3, hi: 0xbf}, + // Block 0xe, offset 0x7e + {value: 0x0000, lo: 0x08}, + {value: 0x3308, lo: 0x80, hi: 0x82}, + {value: 0x3008, lo: 0x83, hi: 0x83}, + {value: 0x0008, lo: 0x84, hi: 0xb9}, + {value: 0x3308, lo: 0xba, hi: 0xba}, + {value: 0x3008, lo: 0xbb, hi: 0xbb}, + {value: 0x3308, lo: 0xbc, hi: 0xbc}, + {value: 0x0008, lo: 0xbd, hi: 0xbd}, + {value: 0x3008, lo: 0xbe, hi: 0xbf}, + // Block 0xf, offset 0x87 + {value: 0x0000, lo: 0x0f}, + {value: 0x3308, lo: 0x80, hi: 0x80}, + {value: 0x3008, lo: 0x81, hi: 0x82}, + {value: 0x0040, lo: 0x83, hi: 0x85}, + {value: 0x3008, lo: 0x86, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x89}, + {value: 0x3008, lo: 0x8a, hi: 0x8c}, + {value: 0x3b08, lo: 0x8d, hi: 0x8d}, + {value: 0x0040, lo: 0x8e, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x90}, + {value: 0x0040, lo: 0x91, hi: 0x96}, + {value: 0x3008, lo: 0x97, hi: 0x97}, + {value: 0x0040, lo: 0x98, hi: 0xa5}, + {value: 0x0008, lo: 0xa6, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbf}, + // Block 0x10, offset 0x97 + {value: 0x0000, lo: 0x0e}, + {value: 0x3308, lo: 0x80, hi: 0x80}, + {value: 0x3008, lo: 0x81, hi: 0x83}, + {value: 0x3308, lo: 0x84, hi: 0x84}, + {value: 0x0008, lo: 0x85, hi: 0x8c}, + {value: 0x0040, lo: 0x8d, hi: 0x8d}, + {value: 0x0008, lo: 0x8e, hi: 0x90}, + {value: 0x0040, lo: 0x91, hi: 0x91}, + {value: 0x0008, lo: 0x92, hi: 0xa8}, + {value: 0x0040, lo: 0xa9, hi: 0xa9}, + {value: 0x0008, lo: 0xaa, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbb}, + {value: 0x3308, lo: 0xbc, hi: 0xbc}, + {value: 0x0008, lo: 0xbd, hi: 0xbd}, + {value: 0x3308, lo: 0xbe, hi: 0xbf}, + // Block 0x11, offset 0xa6 + {value: 0x0000, lo: 0x0a}, + {value: 0x3308, lo: 0x80, hi: 0x81}, + {value: 0x3008, lo: 0x82, hi: 0x83}, + {value: 0x0008, lo: 0x84, hi: 0x8c}, + {value: 0x0040, lo: 0x8d, hi: 0x8d}, + {value: 0x0008, lo: 0x8e, hi: 0x90}, + {value: 0x0040, lo: 0x91, hi: 0x91}, + {value: 0x0008, lo: 0x92, hi: 0xba}, + {value: 0x3b08, lo: 0xbb, hi: 0xbc}, + {value: 0x0008, lo: 0xbd, hi: 0xbd}, + {value: 0x3008, lo: 0xbe, hi: 0xbf}, + // Block 0x12, offset 0xb1 + {value: 0x0000, lo: 0x0c}, + {value: 0x0040, lo: 0x80, hi: 0x80}, + {value: 0x3308, lo: 0x81, hi: 0x81}, + {value: 0x3008, lo: 0x82, hi: 0x83}, + {value: 0x0040, lo: 0x84, hi: 0x84}, + {value: 0x0008, lo: 0x85, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0x99}, + {value: 0x0008, lo: 0x9a, hi: 0xb1}, + {value: 0x0040, lo: 0xb2, hi: 0xb2}, + {value: 0x0008, lo: 0xb3, hi: 0xbb}, + {value: 0x0040, lo: 0xbc, hi: 0xbc}, + {value: 0x0008, lo: 0xbd, hi: 0xbd}, + {value: 0x0040, lo: 0xbe, hi: 0xbf}, + // Block 0x13, offset 0xbe + {value: 0x0000, lo: 0x10}, + {value: 0x0008, lo: 0x80, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x89}, + {value: 0x3b08, lo: 0x8a, hi: 0x8a}, + {value: 0x0040, lo: 0x8b, hi: 0x8e}, + {value: 0x3008, lo: 0x8f, hi: 0x91}, + {value: 0x3308, lo: 0x92, hi: 0x94}, + {value: 0x0040, lo: 0x95, hi: 0x95}, + {value: 0x3308, lo: 0x96, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0x97}, + {value: 0x3008, lo: 0x98, hi: 0x9f}, + {value: 0x0040, lo: 0xa0, hi: 0xa5}, + {value: 0x0008, lo: 0xa6, hi: 0xaf}, + {value: 0x0040, lo: 0xb0, hi: 0xb1}, + {value: 0x3008, lo: 0xb2, hi: 0xb3}, + {value: 0x0018, lo: 0xb4, hi: 0xb4}, + {value: 0x0040, lo: 0xb5, hi: 0xbf}, + // Block 0x14, offset 0xcf + {value: 0x0000, lo: 0x09}, + {value: 0x0040, lo: 0x80, hi: 0x80}, + {value: 0x0008, lo: 0x81, hi: 0xb0}, + {value: 0x3308, lo: 0xb1, hi: 0xb1}, + {value: 0x0008, lo: 0xb2, hi: 0xb2}, + {value: 0x01f1, lo: 0xb3, hi: 0xb3}, + {value: 0x3308, lo: 0xb4, hi: 0xb9}, + {value: 0x3b08, lo: 0xba, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbe}, + {value: 0x0018, lo: 0xbf, hi: 0xbf}, + // Block 0x15, offset 0xd9 + {value: 0x0000, lo: 0x06}, + {value: 0x0008, lo: 0x80, hi: 0x86}, + {value: 0x3308, lo: 0x87, hi: 0x8e}, + {value: 0x0018, lo: 0x8f, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0018, lo: 0x9a, hi: 0x9b}, + {value: 0x0040, lo: 0x9c, hi: 0xbf}, + // Block 0x16, offset 0xe0 + {value: 0x0000, lo: 0x0c}, + {value: 0x0008, lo: 0x80, hi: 0x84}, + {value: 0x0040, lo: 0x85, hi: 0x85}, + {value: 0x0008, lo: 0x86, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x87}, + {value: 0x3308, lo: 0x88, hi: 0x8e}, + {value: 0x0040, lo: 0x8f, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9b}, + {value: 0x0201, lo: 0x9c, hi: 0x9c}, + {value: 0x0209, lo: 0x9d, hi: 0x9d}, + {value: 0x0008, lo: 0x9e, hi: 0x9f}, + {value: 0x0040, lo: 0xa0, hi: 0xbf}, + // Block 0x17, offset 0xed + {value: 0x0000, lo: 0x10}, + {value: 0x0008, lo: 0x80, hi: 0x80}, + {value: 0x0018, lo: 0x81, hi: 0x8a}, + {value: 0x0008, lo: 0x8b, hi: 0x8b}, + {value: 0xe03d, lo: 0x8c, hi: 0x8c}, + {value: 0x0018, lo: 0x8d, hi: 0x97}, + {value: 0x3308, lo: 0x98, hi: 0x99}, + {value: 0x0018, lo: 0x9a, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa9}, + {value: 0x0018, lo: 0xaa, hi: 0xb4}, + {value: 0x3308, lo: 0xb5, hi: 0xb5}, + {value: 0x0018, lo: 0xb6, hi: 0xb6}, + {value: 0x3308, lo: 0xb7, hi: 0xb7}, + {value: 0x0018, lo: 0xb8, hi: 0xb8}, + {value: 0x3308, lo: 0xb9, hi: 0xb9}, + {value: 0x0018, lo: 0xba, hi: 0xbd}, + {value: 0x3008, lo: 0xbe, hi: 0xbf}, + // Block 0x18, offset 0xfe + {value: 0x0000, lo: 0x06}, + {value: 0x0018, lo: 0x80, hi: 0x85}, + {value: 0x3308, lo: 0x86, hi: 0x86}, + {value: 0x0018, lo: 0x87, hi: 0x8c}, + {value: 0x0040, lo: 0x8d, hi: 0x8d}, + {value: 0x0018, lo: 0x8e, hi: 0x9a}, + {value: 0x0040, lo: 0x9b, hi: 0xbf}, + // Block 0x19, offset 0x105 + {value: 0x0000, lo: 0x0a}, + {value: 0x0008, lo: 0x80, hi: 0xaa}, + {value: 0x3008, lo: 0xab, hi: 0xac}, + {value: 0x3308, lo: 0xad, hi: 0xb0}, + {value: 0x3008, lo: 0xb1, hi: 0xb1}, + {value: 0x3308, lo: 0xb2, hi: 0xb7}, + {value: 0x3008, lo: 0xb8, hi: 0xb8}, + {value: 0x3b08, lo: 0xb9, hi: 0xba}, + {value: 0x3008, lo: 0xbb, hi: 0xbc}, + {value: 0x3308, lo: 0xbd, hi: 0xbe}, + {value: 0x0008, lo: 0xbf, hi: 0xbf}, + // Block 0x1a, offset 0x110 + {value: 0x0000, lo: 0x0e}, + {value: 0x0008, lo: 0x80, hi: 0x89}, + {value: 0x0018, lo: 0x8a, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x95}, + {value: 0x3008, lo: 0x96, hi: 0x97}, + {value: 0x3308, lo: 0x98, hi: 0x99}, + {value: 0x0008, lo: 0x9a, hi: 0x9d}, + {value: 0x3308, lo: 0x9e, hi: 0xa0}, + {value: 0x0008, lo: 0xa1, hi: 0xa1}, + {value: 0x3008, lo: 0xa2, hi: 0xa4}, + {value: 0x0008, lo: 0xa5, hi: 0xa6}, + {value: 0x3008, lo: 0xa7, hi: 0xad}, + {value: 0x0008, lo: 0xae, hi: 0xb0}, + {value: 0x3308, lo: 0xb1, hi: 0xb4}, + {value: 0x0008, lo: 0xb5, hi: 0xbf}, + // Block 0x1b, offset 0x11f + {value: 0x0000, lo: 0x0d}, + {value: 0x0008, lo: 0x80, hi: 0x81}, + {value: 0x3308, lo: 0x82, hi: 0x82}, + {value: 0x3008, lo: 0x83, hi: 0x84}, + {value: 0x3308, lo: 0x85, hi: 0x86}, + {value: 0x3008, lo: 0x87, hi: 0x8c}, + {value: 0x3308, lo: 0x8d, hi: 0x8d}, + {value: 0x0008, lo: 0x8e, hi: 0x8e}, + {value: 0x3008, lo: 0x8f, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x3008, lo: 0x9a, hi: 0x9c}, + {value: 0x3308, lo: 0x9d, hi: 0x9d}, + {value: 0x0018, lo: 0x9e, hi: 0x9f}, + {value: 0x0040, lo: 0xa0, hi: 0xbf}, + // Block 0x1c, offset 0x12d + {value: 0x0000, lo: 0x09}, + {value: 0x0040, lo: 0x80, hi: 0x86}, + {value: 0x055d, lo: 0x87, hi: 0x87}, + {value: 0x0040, lo: 0x88, hi: 0x8c}, + {value: 0x055d, lo: 0x8d, hi: 0x8d}, + {value: 0x0040, lo: 0x8e, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xba}, + {value: 0x0018, lo: 0xbb, hi: 0xbb}, + {value: 0xe105, lo: 0xbc, hi: 0xbc}, + {value: 0x0008, lo: 0xbd, hi: 0xbf}, + // Block 0x1d, offset 0x137 + {value: 0x0000, lo: 0x01}, + {value: 0x0018, lo: 0x80, hi: 0xbf}, + // Block 0x1e, offset 0x139 + {value: 0x0000, lo: 0x04}, + {value: 0x0018, lo: 0x80, hi: 0x9e}, + {value: 0x0040, lo: 0x9f, hi: 0xa0}, + {value: 0x2018, lo: 0xa1, hi: 0xb5}, + {value: 0x0018, lo: 0xb6, hi: 0xbf}, + // Block 0x1f, offset 0x13e + {value: 0x0000, lo: 0x02}, + {value: 0x0018, lo: 0x80, hi: 0xa7}, + {value: 0x2018, lo: 0xa8, hi: 0xbf}, + // Block 0x20, offset 0x141 + {value: 0x0000, lo: 0x02}, + {value: 0x2018, lo: 0x80, hi: 0x82}, + {value: 0x0018, lo: 0x83, hi: 0xbf}, + // Block 0x21, offset 0x144 + {value: 0x0000, lo: 0x01}, + {value: 0x0008, lo: 0x80, hi: 0xbf}, + // Block 0x22, offset 0x146 + {value: 0x0000, lo: 0x0b}, + {value: 0x0008, lo: 0x80, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x89}, + {value: 0x0008, lo: 0x8a, hi: 0x8d}, + {value: 0x0040, lo: 0x8e, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0x97}, + {value: 0x0008, lo: 0x98, hi: 0x98}, + {value: 0x0040, lo: 0x99, hi: 0x99}, + {value: 0x0008, lo: 0x9a, hi: 0x9d}, + {value: 0x0040, lo: 0x9e, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xbf}, + // Block 0x23, offset 0x152 + {value: 0x0000, lo: 0x0a}, + {value: 0x0008, lo: 0x80, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x89}, + {value: 0x0008, lo: 0x8a, hi: 0x8d}, + {value: 0x0040, lo: 0x8e, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xb0}, + {value: 0x0040, lo: 0xb1, hi: 0xb1}, + {value: 0x0008, lo: 0xb2, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xb7}, + {value: 0x0008, lo: 0xb8, hi: 0xbe}, + {value: 0x0040, lo: 0xbf, hi: 0xbf}, + // Block 0x24, offset 0x15d + {value: 0x0000, lo: 0x07}, + {value: 0x0008, lo: 0x80, hi: 0x80}, + {value: 0x0040, lo: 0x81, hi: 0x81}, + {value: 0x0008, lo: 0x82, hi: 0x85}, + {value: 0x0040, lo: 0x86, hi: 0x87}, + {value: 0x0008, lo: 0x88, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0x97}, + {value: 0x0008, lo: 0x98, hi: 0xbf}, + // Block 0x25, offset 0x165 + {value: 0x0000, lo: 0x05}, + {value: 0x0008, lo: 0x80, hi: 0x90}, + {value: 0x0040, lo: 0x91, hi: 0x91}, + {value: 0x0008, lo: 0x92, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0x97}, + {value: 0x0008, lo: 0x98, hi: 0xbf}, + // Block 0x26, offset 0x16b + {value: 0x0000, lo: 0x05}, + {value: 0x0008, lo: 0x80, hi: 0x9a}, + {value: 0x0040, lo: 0x9b, hi: 0x9c}, + {value: 0x3308, lo: 0x9d, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xbc}, + {value: 0x0040, lo: 0xbd, hi: 0xbf}, + // Block 0x27, offset 0x171 + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xbf}, + // Block 0x28, offset 0x176 + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xb7}, + {value: 0xe045, lo: 0xb8, hi: 0xbd}, + {value: 0x0040, lo: 0xbe, hi: 0xbf}, + // Block 0x29, offset 0x17b + {value: 0x0000, lo: 0x02}, + {value: 0x0018, lo: 0x80, hi: 0x80}, + {value: 0x0008, lo: 0x81, hi: 0xbf}, + // Block 0x2a, offset 0x17e + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0xac}, + {value: 0x0018, lo: 0xad, hi: 0xae}, + {value: 0x0008, lo: 0xaf, hi: 0xbf}, + // Block 0x2b, offset 0x182 + {value: 0x0000, lo: 0x05}, + {value: 0x0040, lo: 0x80, hi: 0x80}, + {value: 0x0008, lo: 0x81, hi: 0x9a}, + {value: 0x0018, lo: 0x9b, hi: 0x9c}, + {value: 0x0040, lo: 0x9d, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xbf}, + // Block 0x2c, offset 0x188 + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0xaa}, + {value: 0x0018, lo: 0xab, hi: 0xb0}, + {value: 0x0008, lo: 0xb1, hi: 0xb8}, + {value: 0x0040, lo: 0xb9, hi: 0xbf}, + // Block 0x2d, offset 0x18d + {value: 0x0000, lo: 0x0a}, + {value: 0x0008, lo: 0x80, hi: 0x91}, + {value: 0x3308, lo: 0x92, hi: 0x93}, + {value: 0x3b08, lo: 0x94, hi: 0x94}, + {value: 0x3808, lo: 0x95, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0x9e}, + {value: 0x0008, lo: 0x9f, hi: 0xb1}, + {value: 0x3308, lo: 0xb2, hi: 0xb3}, + {value: 0x3808, lo: 0xb4, hi: 0xb4}, + {value: 0x0018, lo: 0xb5, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xbf}, + // Block 0x2e, offset 0x198 + {value: 0x0000, lo: 0x09}, + {value: 0x0008, lo: 0x80, hi: 0x91}, + {value: 0x3308, lo: 0x92, hi: 0x93}, + {value: 0x0040, lo: 0x94, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xac}, + {value: 0x0040, lo: 0xad, hi: 0xad}, + {value: 0x0008, lo: 0xae, hi: 0xb0}, + {value: 0x0040, lo: 0xb1, hi: 0xb1}, + {value: 0x3308, lo: 0xb2, hi: 0xb3}, + {value: 0x0040, lo: 0xb4, hi: 0xbf}, + // Block 0x2f, offset 0x1a2 + {value: 0x0000, lo: 0x05}, + {value: 0x0008, lo: 0x80, hi: 0xb3}, + {value: 0x3340, lo: 0xb4, hi: 0xb5}, + {value: 0x3008, lo: 0xb6, hi: 0xb6}, + {value: 0x3308, lo: 0xb7, hi: 0xbd}, + {value: 0x3008, lo: 0xbe, hi: 0xbf}, + // Block 0x30, offset 0x1a8 + {value: 0x0000, lo: 0x10}, + {value: 0x3008, lo: 0x80, hi: 0x85}, + {value: 0x3308, lo: 0x86, hi: 0x86}, + {value: 0x3008, lo: 0x87, hi: 0x88}, + {value: 0x3308, lo: 0x89, hi: 0x91}, + {value: 0x3b08, lo: 0x92, hi: 0x92}, + {value: 0x3308, lo: 0x93, hi: 0x93}, + {value: 0x0018, lo: 0x94, hi: 0x96}, + {value: 0x0008, lo: 0x97, hi: 0x97}, + {value: 0x0018, lo: 0x98, hi: 0x9b}, + {value: 0x0008, lo: 0x9c, hi: 0x9c}, + {value: 0x3308, lo: 0x9d, hi: 0x9d}, + {value: 0x0040, lo: 0x9e, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa9}, + {value: 0x0040, lo: 0xaa, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbf}, + // Block 0x31, offset 0x1b9 + {value: 0x0000, lo: 0x0a}, + {value: 0x0018, lo: 0x80, hi: 0x85}, + {value: 0x0040, lo: 0x86, hi: 0x86}, + {value: 0x0218, lo: 0x87, hi: 0x87}, + {value: 0x0018, lo: 0x88, hi: 0x8a}, + {value: 0x33c0, lo: 0x8b, hi: 0x8d}, + {value: 0x0040, lo: 0x8e, hi: 0x8e}, + {value: 0x33c0, lo: 0x8f, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9f}, + {value: 0x0208, lo: 0xa0, hi: 0xbf}, + // Block 0x32, offset 0x1c4 + {value: 0x0000, lo: 0x02}, + {value: 0x0208, lo: 0x80, hi: 0xb8}, + {value: 0x0040, lo: 0xb9, hi: 0xbf}, + // Block 0x33, offset 0x1c7 + {value: 0x0000, lo: 0x07}, + {value: 0x0008, lo: 0x80, hi: 0x84}, + {value: 0x3308, lo: 0x85, hi: 0x86}, + {value: 0x0208, lo: 0x87, hi: 0xa8}, + {value: 0x3308, lo: 0xa9, hi: 0xa9}, + {value: 0x0208, lo: 0xaa, hi: 0xaa}, + {value: 0x0040, lo: 0xab, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0x34, offset 0x1cf + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xbf}, + // Block 0x35, offset 0x1d2 + {value: 0x0000, lo: 0x0c}, + {value: 0x0008, lo: 0x80, hi: 0x9e}, + {value: 0x0040, lo: 0x9f, hi: 0x9f}, + {value: 0x3308, lo: 0xa0, hi: 0xa2}, + {value: 0x3008, lo: 0xa3, hi: 0xa6}, + {value: 0x3308, lo: 0xa7, hi: 0xa8}, + {value: 0x3008, lo: 0xa9, hi: 0xab}, + {value: 0x0040, lo: 0xac, hi: 0xaf}, + {value: 0x3008, lo: 0xb0, hi: 0xb1}, + {value: 0x3308, lo: 0xb2, hi: 0xb2}, + {value: 0x3008, lo: 0xb3, hi: 0xb8}, + {value: 0x3308, lo: 0xb9, hi: 0xbb}, + {value: 0x0040, lo: 0xbc, hi: 0xbf}, + // Block 0x36, offset 0x1df + {value: 0x0000, lo: 0x07}, + {value: 0x0018, lo: 0x80, hi: 0x80}, + {value: 0x0040, lo: 0x81, hi: 0x83}, + {value: 0x0018, lo: 0x84, hi: 0x85}, + {value: 0x0008, lo: 0x86, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb4}, + {value: 0x0040, lo: 0xb5, hi: 0xbf}, + // Block 0x37, offset 0x1e7 + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0xab}, + {value: 0x0040, lo: 0xac, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0x38, offset 0x1eb + {value: 0x0000, lo: 0x06}, + {value: 0x0008, lo: 0x80, hi: 0x89}, + {value: 0x0040, lo: 0x8a, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0028, lo: 0x9a, hi: 0x9a}, + {value: 0x0040, lo: 0x9b, hi: 0x9d}, + {value: 0x0018, lo: 0x9e, hi: 0xbf}, + // Block 0x39, offset 0x1f2 + {value: 0x0000, lo: 0x07}, + {value: 0x0008, lo: 0x80, hi: 0x96}, + {value: 0x3308, lo: 0x97, hi: 0x98}, + {value: 0x3008, lo: 0x99, hi: 0x9a}, + {value: 0x3308, lo: 0x9b, hi: 0x9b}, + {value: 0x0040, lo: 0x9c, hi: 0x9d}, + {value: 0x0018, lo: 0x9e, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xbf}, + // Block 0x3a, offset 0x1fa + {value: 0x0000, lo: 0x0f}, + {value: 0x0008, lo: 0x80, hi: 0x94}, + {value: 0x3008, lo: 0x95, hi: 0x95}, + {value: 0x3308, lo: 0x96, hi: 0x96}, + {value: 0x3008, lo: 0x97, hi: 0x97}, + {value: 0x3308, lo: 0x98, hi: 0x9e}, + {value: 0x0040, lo: 0x9f, hi: 0x9f}, + {value: 0x3b08, lo: 0xa0, hi: 0xa0}, + {value: 0x3008, lo: 0xa1, hi: 0xa1}, + {value: 0x3308, lo: 0xa2, hi: 0xa2}, + {value: 0x3008, lo: 0xa3, hi: 0xa4}, + {value: 0x3308, lo: 0xa5, hi: 0xac}, + {value: 0x3008, lo: 0xad, hi: 0xb2}, + {value: 0x3308, lo: 0xb3, hi: 0xbc}, + {value: 0x0040, lo: 0xbd, hi: 0xbe}, + {value: 0x3308, lo: 0xbf, hi: 0xbf}, + // Block 0x3b, offset 0x20a + {value: 0x0000, lo: 0x0b}, + {value: 0x0008, lo: 0x80, hi: 0x89}, + {value: 0x0040, lo: 0x8a, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xa6}, + {value: 0x0008, lo: 0xa7, hi: 0xa7}, + {value: 0x0018, lo: 0xa8, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xaf}, + {value: 0x3308, lo: 0xb0, hi: 0xbd}, + {value: 0x3318, lo: 0xbe, hi: 0xbe}, + {value: 0x3308, lo: 0xbf, hi: 0xbf}, + // Block 0x3c, offset 0x216 + {value: 0x0000, lo: 0x02}, + {value: 0x3308, lo: 0x80, hi: 0x8e}, + {value: 0x0040, lo: 0x8f, hi: 0xbf}, + // Block 0x3d, offset 0x219 + {value: 0x0000, lo: 0x09}, + {value: 0x3308, lo: 0x80, hi: 0x83}, + {value: 0x3008, lo: 0x84, hi: 0x84}, + {value: 0x0008, lo: 0x85, hi: 0xb3}, + {value: 0x3308, lo: 0xb4, hi: 0xb4}, + {value: 0x3008, lo: 0xb5, hi: 0xb5}, + {value: 0x3308, lo: 0xb6, hi: 0xba}, + {value: 0x3008, lo: 0xbb, hi: 0xbb}, + {value: 0x3308, lo: 0xbc, hi: 0xbc}, + {value: 0x3008, lo: 0xbd, hi: 0xbf}, + // Block 0x3e, offset 0x223 + {value: 0x0000, lo: 0x0b}, + {value: 0x3008, lo: 0x80, hi: 0x81}, + {value: 0x3308, lo: 0x82, hi: 0x82}, + {value: 0x3008, lo: 0x83, hi: 0x83}, + {value: 0x3808, lo: 0x84, hi: 0x84}, + {value: 0x0008, lo: 0x85, hi: 0x8c}, + {value: 0x0040, lo: 0x8d, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0018, lo: 0x9a, hi: 0xaa}, + {value: 0x3308, lo: 0xab, hi: 0xb3}, + {value: 0x0018, lo: 0xb4, hi: 0xbe}, + {value: 0x0040, lo: 0xbf, hi: 0xbf}, + // Block 0x3f, offset 0x22f + {value: 0x0000, lo: 0x0b}, + {value: 0x3308, lo: 0x80, hi: 0x81}, + {value: 0x3008, lo: 0x82, hi: 0x82}, + {value: 0x0008, lo: 0x83, hi: 0xa0}, + {value: 0x3008, lo: 0xa1, hi: 0xa1}, + {value: 0x3308, lo: 0xa2, hi: 0xa5}, + {value: 0x3008, lo: 0xa6, hi: 0xa7}, + {value: 0x3308, lo: 0xa8, hi: 0xa9}, + {value: 0x3808, lo: 0xaa, hi: 0xaa}, + {value: 0x3b08, lo: 0xab, hi: 0xab}, + {value: 0x3308, lo: 0xac, hi: 0xad}, + {value: 0x0008, lo: 0xae, hi: 0xbf}, + // Block 0x40, offset 0x23b + {value: 0x0000, lo: 0x0b}, + {value: 0x0008, lo: 0x80, hi: 0xa5}, + {value: 0x3308, lo: 0xa6, hi: 0xa6}, + {value: 0x3008, lo: 0xa7, hi: 0xa7}, + {value: 0x3308, lo: 0xa8, hi: 0xa9}, + {value: 0x3008, lo: 0xaa, hi: 0xac}, + {value: 0x3308, lo: 0xad, hi: 0xad}, + {value: 0x3008, lo: 0xae, hi: 0xae}, + {value: 0x3308, lo: 0xaf, hi: 0xb1}, + {value: 0x3808, lo: 0xb2, hi: 0xb3}, + {value: 0x0040, lo: 0xb4, hi: 0xbb}, + {value: 0x0018, lo: 0xbc, hi: 0xbf}, + // Block 0x41, offset 0x247 + {value: 0x0000, lo: 0x07}, + {value: 0x0008, lo: 0x80, hi: 0xa3}, + {value: 0x3008, lo: 0xa4, hi: 0xab}, + {value: 0x3308, lo: 0xac, hi: 0xb3}, + {value: 0x3008, lo: 0xb4, hi: 0xb5}, + {value: 0x3308, lo: 0xb6, hi: 0xb7}, + {value: 0x0040, lo: 0xb8, hi: 0xba}, + {value: 0x0018, lo: 0xbb, hi: 0xbf}, + // Block 0x42, offset 0x24f + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0x89}, + {value: 0x0040, lo: 0x8a, hi: 0x8c}, + {value: 0x0008, lo: 0x8d, hi: 0xbd}, + {value: 0x0018, lo: 0xbe, hi: 0xbf}, + // Block 0x43, offset 0x254 + {value: 0x0000, lo: 0x0c}, + {value: 0x02a9, lo: 0x80, hi: 0x80}, + {value: 0x02b1, lo: 0x81, hi: 0x81}, + {value: 0x02b9, lo: 0x82, hi: 0x82}, + {value: 0x02c1, lo: 0x83, hi: 0x83}, + {value: 0x02c9, lo: 0x84, hi: 0x85}, + {value: 0x02d1, lo: 0x86, hi: 0x86}, + {value: 0x02d9, lo: 0x87, hi: 0x87}, + {value: 0x057d, lo: 0x88, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x8f}, + {value: 0x059d, lo: 0x90, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbc}, + {value: 0x059d, lo: 0xbd, hi: 0xbf}, + // Block 0x44, offset 0x261 + {value: 0x0000, lo: 0x10}, + {value: 0x0018, lo: 0x80, hi: 0x87}, + {value: 0x0040, lo: 0x88, hi: 0x8f}, + {value: 0x3308, lo: 0x90, hi: 0x92}, + {value: 0x0018, lo: 0x93, hi: 0x93}, + {value: 0x3308, lo: 0x94, hi: 0xa0}, + {value: 0x3008, lo: 0xa1, hi: 0xa1}, + {value: 0x3308, lo: 0xa2, hi: 0xa8}, + {value: 0x0008, lo: 0xa9, hi: 0xac}, + {value: 0x3308, lo: 0xad, hi: 0xad}, + {value: 0x0008, lo: 0xae, hi: 0xb3}, + {value: 0x3308, lo: 0xb4, hi: 0xb4}, + {value: 0x0008, lo: 0xb5, hi: 0xb6}, + {value: 0x3008, lo: 0xb7, hi: 0xb7}, + {value: 0x3308, lo: 0xb8, hi: 0xb9}, + {value: 0x0008, lo: 0xba, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbf}, + // Block 0x45, offset 0x272 + {value: 0x0000, lo: 0x0a}, + {value: 0x0008, lo: 0x80, hi: 0x87}, + {value: 0xe045, lo: 0x88, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0x97}, + {value: 0xe045, lo: 0x98, hi: 0x9d}, + {value: 0x0040, lo: 0x9e, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa7}, + {value: 0xe045, lo: 0xa8, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb7}, + {value: 0xe045, lo: 0xb8, hi: 0xbf}, + // Block 0x46, offset 0x27d + {value: 0x0000, lo: 0x04}, + {value: 0x0018, lo: 0x80, hi: 0x80}, + {value: 0x0040, lo: 0x81, hi: 0x8f}, + {value: 0x3318, lo: 0x90, hi: 0xb0}, + {value: 0x0040, lo: 0xb1, hi: 0xbf}, + // Block 0x47, offset 0x282 + {value: 0x0000, lo: 0x08}, + {value: 0x0018, lo: 0x80, hi: 0x82}, + {value: 0x0040, lo: 0x83, hi: 0x83}, + {value: 0x0008, lo: 0x84, hi: 0x84}, + {value: 0x0018, lo: 0x85, hi: 0x88}, + {value: 0x0851, lo: 0x89, hi: 0x89}, + {value: 0x0018, lo: 0x8a, hi: 0x8b}, + {value: 0x0040, lo: 0x8c, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0xbf}, + // Block 0x48, offset 0x28b + {value: 0x0000, lo: 0x07}, + {value: 0x0018, lo: 0x80, hi: 0xab}, + {value: 0x0859, lo: 0xac, hi: 0xac}, + {value: 0x0861, lo: 0xad, hi: 0xad}, + {value: 0x0018, lo: 0xae, hi: 0xae}, + {value: 0x0869, lo: 0xaf, hi: 0xaf}, + {value: 0x0871, lo: 0xb0, hi: 0xb0}, + {value: 0x0018, lo: 0xb1, hi: 0xbf}, + // Block 0x49, offset 0x293 + {value: 0x0000, lo: 0x05}, + {value: 0x0018, lo: 0x80, hi: 0x9f}, + {value: 0x0080, lo: 0xa0, hi: 0xa0}, + {value: 0x0018, lo: 0xa1, hi: 0xad}, + {value: 0x0080, lo: 0xae, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xbf}, + // Block 0x4a, offset 0x299 + {value: 0x0000, lo: 0x04}, + {value: 0x0018, lo: 0x80, hi: 0xa8}, + {value: 0x09dd, lo: 0xa9, hi: 0xa9}, + {value: 0x09fd, lo: 0xaa, hi: 0xaa}, + {value: 0x0018, lo: 0xab, hi: 0xbf}, + // Block 0x4b, offset 0x29e + {value: 0x0000, lo: 0x02}, + {value: 0x0018, lo: 0x80, hi: 0xa6}, + {value: 0x0040, lo: 0xa7, hi: 0xbf}, + // Block 0x4c, offset 0x2a1 + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0x8b}, + {value: 0x0929, lo: 0x8c, hi: 0x8c}, + {value: 0x0018, lo: 0x8d, hi: 0xbf}, + // Block 0x4d, offset 0x2a5 + {value: 0x0000, lo: 0x05}, + {value: 0x0018, lo: 0x80, hi: 0xb3}, + {value: 0x0e7e, lo: 0xb4, hi: 0xb4}, + {value: 0x0932, lo: 0xb5, hi: 0xb5}, + {value: 0x0e9e, lo: 0xb6, hi: 0xb6}, + {value: 0x0018, lo: 0xb7, hi: 0xbf}, + // Block 0x4e, offset 0x2ab + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0x9b}, + {value: 0x0939, lo: 0x9c, hi: 0x9c}, + {value: 0x0018, lo: 0x9d, hi: 0xbf}, + // Block 0x4f, offset 0x2af + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0xb3}, + {value: 0x0040, lo: 0xb4, hi: 0xb5}, + {value: 0x0018, lo: 0xb6, hi: 0xbf}, + // Block 0x50, offset 0x2b3 + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0x96}, + {value: 0x0018, lo: 0x97, hi: 0xbf}, + // Block 0x51, offset 0x2b7 + {value: 0x0000, lo: 0x04}, + {value: 0xe185, lo: 0x80, hi: 0x8f}, + {value: 0x03f5, lo: 0x90, hi: 0x9f}, + {value: 0x0ebd, lo: 0xa0, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0x52, offset 0x2bc + {value: 0x0000, lo: 0x07}, + {value: 0x0008, lo: 0x80, hi: 0xa5}, + {value: 0x0040, lo: 0xa6, hi: 0xa6}, + {value: 0x0008, lo: 0xa7, hi: 0xa7}, + {value: 0x0040, lo: 0xa8, hi: 0xac}, + {value: 0x0008, lo: 0xad, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0x53, offset 0x2c4 + {value: 0x0000, lo: 0x06}, + {value: 0x0008, lo: 0x80, hi: 0xa7}, + {value: 0x0040, lo: 0xa8, hi: 0xae}, + {value: 0xe075, lo: 0xaf, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xb0}, + {value: 0x0040, lo: 0xb1, hi: 0xbe}, + {value: 0x3b08, lo: 0xbf, hi: 0xbf}, + // Block 0x54, offset 0x2cb + {value: 0x0000, lo: 0x0a}, + {value: 0x0008, lo: 0x80, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa6}, + {value: 0x0040, lo: 0xa7, hi: 0xa7}, + {value: 0x0008, lo: 0xa8, hi: 0xae}, + {value: 0x0040, lo: 0xaf, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xb7}, + {value: 0x0008, lo: 0xb8, hi: 0xbe}, + {value: 0x0040, lo: 0xbf, hi: 0xbf}, + // Block 0x55, offset 0x2d6 + {value: 0x0000, lo: 0x09}, + {value: 0x0008, lo: 0x80, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x87}, + {value: 0x0008, lo: 0x88, hi: 0x8e}, + {value: 0x0040, lo: 0x8f, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0x97}, + {value: 0x0008, lo: 0x98, hi: 0x9e}, + {value: 0x0040, lo: 0x9f, hi: 0x9f}, + {value: 0x3308, lo: 0xa0, hi: 0xbf}, + // Block 0x56, offset 0x2e0 + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0xae}, + {value: 0x0008, lo: 0xaf, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xbf}, + // Block 0x57, offset 0x2e4 + {value: 0x0000, lo: 0x02}, + {value: 0x0018, lo: 0x80, hi: 0x9d}, + {value: 0x0040, lo: 0x9e, hi: 0xbf}, + // Block 0x58, offset 0x2e7 + {value: 0x0000, lo: 0x05}, + {value: 0x0018, lo: 0x80, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9a}, + {value: 0x0018, lo: 0x9b, hi: 0x9e}, + {value: 0x0ef5, lo: 0x9f, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xbf}, + // Block 0x59, offset 0x2ed + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0xb2}, + {value: 0x0f15, lo: 0xb3, hi: 0xb3}, + {value: 0x0040, lo: 0xb4, hi: 0xbf}, + // Block 0x5a, offset 0x2f1 + {value: 0x0020, lo: 0x01}, + {value: 0x0f35, lo: 0x80, hi: 0xbf}, + // Block 0x5b, offset 0x2f3 + {value: 0x0020, lo: 0x02}, + {value: 0x1735, lo: 0x80, hi: 0x8f}, + {value: 0x1915, lo: 0x90, hi: 0xbf}, + // Block 0x5c, offset 0x2f6 + {value: 0x0020, lo: 0x01}, + {value: 0x1f15, lo: 0x80, hi: 0xbf}, + // Block 0x5d, offset 0x2f8 + {value: 0x0000, lo: 0x02}, + {value: 0x0040, lo: 0x80, hi: 0x80}, + {value: 0x0008, lo: 0x81, hi: 0xbf}, + // Block 0x5e, offset 0x2fb + {value: 0x0000, lo: 0x09}, + {value: 0x0008, lo: 0x80, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0x98}, + {value: 0x3308, lo: 0x99, hi: 0x9a}, + {value: 0x096a, lo: 0x9b, hi: 0x9b}, + {value: 0x0972, lo: 0x9c, hi: 0x9c}, + {value: 0x0008, lo: 0x9d, hi: 0x9e}, + {value: 0x0979, lo: 0x9f, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xa0}, + {value: 0x0008, lo: 0xa1, hi: 0xbf}, + // Block 0x5f, offset 0x305 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xbe}, + {value: 0x0981, lo: 0xbf, hi: 0xbf}, + // Block 0x60, offset 0x308 + {value: 0x0000, lo: 0x0e}, + {value: 0x0040, lo: 0x80, hi: 0x84}, + {value: 0x0008, lo: 0x85, hi: 0xaf}, + {value: 0x0040, lo: 0xb0, hi: 0xb0}, + {value: 0x2a35, lo: 0xb1, hi: 0xb1}, + {value: 0x2a55, lo: 0xb2, hi: 0xb2}, + {value: 0x2a75, lo: 0xb3, hi: 0xb3}, + {value: 0x2a95, lo: 0xb4, hi: 0xb4}, + {value: 0x2a75, lo: 0xb5, hi: 0xb5}, + {value: 0x2ab5, lo: 0xb6, hi: 0xb6}, + {value: 0x2ad5, lo: 0xb7, hi: 0xb7}, + {value: 0x2af5, lo: 0xb8, hi: 0xb9}, + {value: 0x2b15, lo: 0xba, hi: 0xbb}, + {value: 0x2b35, lo: 0xbc, hi: 0xbd}, + {value: 0x2b15, lo: 0xbe, hi: 0xbf}, + // Block 0x61, offset 0x317 + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0xa3}, + {value: 0x0040, lo: 0xa4, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0x62, offset 0x31b + {value: 0x0008, lo: 0x03}, + {value: 0x098a, lo: 0x80, hi: 0x9e}, + {value: 0x0040, lo: 0x9f, hi: 0x9f}, + {value: 0x0a82, lo: 0xa0, hi: 0xbf}, + // Block 0x63, offset 0x31f + {value: 0x0008, lo: 0x01}, + {value: 0x0d19, lo: 0x80, hi: 0xbf}, + // Block 0x64, offset 0x321 + {value: 0x0008, lo: 0x08}, + {value: 0x0f19, lo: 0x80, hi: 0xb0}, + {value: 0x4045, lo: 0xb1, hi: 0xb1}, + {value: 0x10a1, lo: 0xb2, hi: 0xb3}, + {value: 0x4065, lo: 0xb4, hi: 0xb4}, + {value: 0x10b1, lo: 0xb5, hi: 0xb7}, + {value: 0x4085, lo: 0xb8, hi: 0xb8}, + {value: 0x4085, lo: 0xb9, hi: 0xb9}, + {value: 0x10c9, lo: 0xba, hi: 0xbf}, + // Block 0x65, offset 0x32a + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0x8c}, + {value: 0x0040, lo: 0x8d, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0xbf}, + // Block 0x66, offset 0x32e + {value: 0x0000, lo: 0x04}, + {value: 0x0018, lo: 0x80, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xbd}, + {value: 0x0018, lo: 0xbe, hi: 0xbf}, + // Block 0x67, offset 0x333 + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0x8c}, + {value: 0x0018, lo: 0x8d, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xab}, + {value: 0x0040, lo: 0xac, hi: 0xbf}, + // Block 0x68, offset 0x338 + {value: 0x0000, lo: 0x05}, + {value: 0x0008, lo: 0x80, hi: 0xa5}, + {value: 0x0018, lo: 0xa6, hi: 0xaf}, + {value: 0x3308, lo: 0xb0, hi: 0xb1}, + {value: 0x0018, lo: 0xb2, hi: 0xb7}, + {value: 0x0040, lo: 0xb8, hi: 0xbf}, + // Block 0x69, offset 0x33e + {value: 0x0000, lo: 0x0f}, + {value: 0x0008, lo: 0x80, hi: 0x81}, + {value: 0x3308, lo: 0x82, hi: 0x82}, + {value: 0x0008, lo: 0x83, hi: 0x85}, + {value: 0x3b08, lo: 0x86, hi: 0x86}, + {value: 0x0008, lo: 0x87, hi: 0x8a}, + {value: 0x3308, lo: 0x8b, hi: 0x8b}, + {value: 0x0008, lo: 0x8c, hi: 0xa2}, + {value: 0x3008, lo: 0xa3, hi: 0xa4}, + {value: 0x3308, lo: 0xa5, hi: 0xa6}, + {value: 0x3008, lo: 0xa7, hi: 0xa7}, + {value: 0x0018, lo: 0xa8, hi: 0xab}, + {value: 0x3b08, lo: 0xac, hi: 0xac}, + {value: 0x0040, lo: 0xad, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbf}, + // Block 0x6a, offset 0x34e + {value: 0x0000, lo: 0x05}, + {value: 0x0208, lo: 0x80, hi: 0xb1}, + {value: 0x0108, lo: 0xb2, hi: 0xb2}, + {value: 0x0008, lo: 0xb3, hi: 0xb3}, + {value: 0x0018, lo: 0xb4, hi: 0xb7}, + {value: 0x0040, lo: 0xb8, hi: 0xbf}, + // Block 0x6b, offset 0x354 + {value: 0x0000, lo: 0x03}, + {value: 0x3008, lo: 0x80, hi: 0x81}, + {value: 0x0008, lo: 0x82, hi: 0xb3}, + {value: 0x3008, lo: 0xb4, hi: 0xbf}, + // Block 0x6c, offset 0x358 + {value: 0x0000, lo: 0x0e}, + {value: 0x3008, lo: 0x80, hi: 0x83}, + {value: 0x3b08, lo: 0x84, hi: 0x84}, + {value: 0x3308, lo: 0x85, hi: 0x85}, + {value: 0x0040, lo: 0x86, hi: 0x8d}, + {value: 0x0018, lo: 0x8e, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9f}, + {value: 0x3308, lo: 0xa0, hi: 0xb1}, + {value: 0x0008, lo: 0xb2, hi: 0xb7}, + {value: 0x0018, lo: 0xb8, hi: 0xba}, + {value: 0x0008, lo: 0xbb, hi: 0xbb}, + {value: 0x0018, lo: 0xbc, hi: 0xbc}, + {value: 0x0008, lo: 0xbd, hi: 0xbe}, + {value: 0x3308, lo: 0xbf, hi: 0xbf}, + // Block 0x6d, offset 0x367 + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0xa5}, + {value: 0x3308, lo: 0xa6, hi: 0xad}, + {value: 0x0018, lo: 0xae, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0x6e, offset 0x36c + {value: 0x0000, lo: 0x07}, + {value: 0x0008, lo: 0x80, hi: 0x86}, + {value: 0x3308, lo: 0x87, hi: 0x91}, + {value: 0x3008, lo: 0x92, hi: 0x92}, + {value: 0x3808, lo: 0x93, hi: 0x93}, + {value: 0x0040, lo: 0x94, hi: 0x9e}, + {value: 0x0018, lo: 0x9f, hi: 0xbc}, + {value: 0x0040, lo: 0xbd, hi: 0xbf}, + // Block 0x6f, offset 0x374 + {value: 0x0000, lo: 0x09}, + {value: 0x3308, lo: 0x80, hi: 0x82}, + {value: 0x3008, lo: 0x83, hi: 0x83}, + {value: 0x0008, lo: 0x84, hi: 0xb2}, + {value: 0x3308, lo: 0xb3, hi: 0xb3}, + {value: 0x3008, lo: 0xb4, hi: 0xb5}, + {value: 0x3308, lo: 0xb6, hi: 0xb9}, + {value: 0x3008, lo: 0xba, hi: 0xbb}, + {value: 0x3308, lo: 0xbc, hi: 0xbd}, + {value: 0x3008, lo: 0xbe, hi: 0xbf}, + // Block 0x70, offset 0x37e + {value: 0x0000, lo: 0x0a}, + {value: 0x3808, lo: 0x80, hi: 0x80}, + {value: 0x0018, lo: 0x81, hi: 0x8d}, + {value: 0x0040, lo: 0x8e, hi: 0x8e}, + {value: 0x0008, lo: 0x8f, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9d}, + {value: 0x0018, lo: 0x9e, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa4}, + {value: 0x3308, lo: 0xa5, hi: 0xa5}, + {value: 0x0008, lo: 0xa6, hi: 0xbe}, + {value: 0x0040, lo: 0xbf, hi: 0xbf}, + // Block 0x71, offset 0x389 + {value: 0x0000, lo: 0x07}, + {value: 0x0008, lo: 0x80, hi: 0xa8}, + {value: 0x3308, lo: 0xa9, hi: 0xae}, + {value: 0x3008, lo: 0xaf, hi: 0xb0}, + {value: 0x3308, lo: 0xb1, hi: 0xb2}, + {value: 0x3008, lo: 0xb3, hi: 0xb4}, + {value: 0x3308, lo: 0xb5, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xbf}, + // Block 0x72, offset 0x391 + {value: 0x0000, lo: 0x10}, + {value: 0x0008, lo: 0x80, hi: 0x82}, + {value: 0x3308, lo: 0x83, hi: 0x83}, + {value: 0x0008, lo: 0x84, hi: 0x8b}, + {value: 0x3308, lo: 0x8c, hi: 0x8c}, + {value: 0x3008, lo: 0x8d, hi: 0x8d}, + {value: 0x0040, lo: 0x8e, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9b}, + {value: 0x0018, lo: 0x9c, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xb6}, + {value: 0x0018, lo: 0xb7, hi: 0xb9}, + {value: 0x0008, lo: 0xba, hi: 0xba}, + {value: 0x3008, lo: 0xbb, hi: 0xbb}, + {value: 0x3308, lo: 0xbc, hi: 0xbc}, + {value: 0x3008, lo: 0xbd, hi: 0xbd}, + {value: 0x0008, lo: 0xbe, hi: 0xbf}, + // Block 0x73, offset 0x3a2 + {value: 0x0000, lo: 0x08}, + {value: 0x0008, lo: 0x80, hi: 0xaf}, + {value: 0x3308, lo: 0xb0, hi: 0xb0}, + {value: 0x0008, lo: 0xb1, hi: 0xb1}, + {value: 0x3308, lo: 0xb2, hi: 0xb4}, + {value: 0x0008, lo: 0xb5, hi: 0xb6}, + {value: 0x3308, lo: 0xb7, hi: 0xb8}, + {value: 0x0008, lo: 0xb9, hi: 0xbd}, + {value: 0x3308, lo: 0xbe, hi: 0xbf}, + // Block 0x74, offset 0x3ab + {value: 0x0000, lo: 0x0f}, + {value: 0x0008, lo: 0x80, hi: 0x80}, + {value: 0x3308, lo: 0x81, hi: 0x81}, + {value: 0x0008, lo: 0x82, hi: 0x82}, + {value: 0x0040, lo: 0x83, hi: 0x9a}, + {value: 0x0008, lo: 0x9b, hi: 0x9d}, + {value: 0x0018, lo: 0x9e, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xaa}, + {value: 0x3008, lo: 0xab, hi: 0xab}, + {value: 0x3308, lo: 0xac, hi: 0xad}, + {value: 0x3008, lo: 0xae, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xb1}, + {value: 0x0008, lo: 0xb2, hi: 0xb4}, + {value: 0x3008, lo: 0xb5, hi: 0xb5}, + {value: 0x3b08, lo: 0xb6, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xbf}, + // Block 0x75, offset 0x3bb + {value: 0x0000, lo: 0x0c}, + {value: 0x0040, lo: 0x80, hi: 0x80}, + {value: 0x0008, lo: 0x81, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x88}, + {value: 0x0008, lo: 0x89, hi: 0x8e}, + {value: 0x0040, lo: 0x8f, hi: 0x90}, + {value: 0x0008, lo: 0x91, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa6}, + {value: 0x0040, lo: 0xa7, hi: 0xa7}, + {value: 0x0008, lo: 0xa8, hi: 0xae}, + {value: 0x0040, lo: 0xaf, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0x76, offset 0x3c8 + {value: 0x0000, lo: 0x0b}, + {value: 0x0008, lo: 0x80, hi: 0x9a}, + {value: 0x0018, lo: 0x9b, hi: 0x9b}, + {value: 0x449d, lo: 0x9c, hi: 0x9c}, + {value: 0x44b5, lo: 0x9d, hi: 0x9d}, + {value: 0x0941, lo: 0x9e, hi: 0x9e}, + {value: 0xe06d, lo: 0x9f, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa8}, + {value: 0x13f9, lo: 0xa9, hi: 0xa9}, + {value: 0x0018, lo: 0xaa, hi: 0xab}, + {value: 0x0040, lo: 0xac, hi: 0xaf}, + {value: 0x44cd, lo: 0xb0, hi: 0xbf}, + // Block 0x77, offset 0x3d4 + {value: 0x0000, lo: 0x04}, + {value: 0x44ed, lo: 0x80, hi: 0x8f}, + {value: 0x450d, lo: 0x90, hi: 0x9f}, + {value: 0x452d, lo: 0xa0, hi: 0xaf}, + {value: 0x450d, lo: 0xb0, hi: 0xbf}, + // Block 0x78, offset 0x3d9 + {value: 0x0000, lo: 0x0c}, + {value: 0x0008, lo: 0x80, hi: 0xa2}, + {value: 0x3008, lo: 0xa3, hi: 0xa4}, + {value: 0x3308, lo: 0xa5, hi: 0xa5}, + {value: 0x3008, lo: 0xa6, hi: 0xa7}, + {value: 0x3308, lo: 0xa8, hi: 0xa8}, + {value: 0x3008, lo: 0xa9, hi: 0xaa}, + {value: 0x0018, lo: 0xab, hi: 0xab}, + {value: 0x3008, lo: 0xac, hi: 0xac}, + {value: 0x3b08, lo: 0xad, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbf}, + // Block 0x79, offset 0x3e6 + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0xa3}, + {value: 0x0040, lo: 0xa4, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xbf}, + // Block 0x7a, offset 0x3ea + {value: 0x0000, lo: 0x04}, + {value: 0x0018, lo: 0x80, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x8a}, + {value: 0x0018, lo: 0x8b, hi: 0xbb}, + {value: 0x0040, lo: 0xbc, hi: 0xbf}, + // Block 0x7b, offset 0x3ef + {value: 0x0000, lo: 0x01}, + {value: 0x0040, lo: 0x80, hi: 0xbf}, + // Block 0x7c, offset 0x3f1 + {value: 0x0020, lo: 0x01}, + {value: 0x454d, lo: 0x80, hi: 0xbf}, + // Block 0x7d, offset 0x3f3 + {value: 0x0020, lo: 0x03}, + {value: 0x4d4d, lo: 0x80, hi: 0x94}, + {value: 0x4b0d, lo: 0x95, hi: 0x95}, + {value: 0x4fed, lo: 0x96, hi: 0xbf}, + // Block 0x7e, offset 0x3f7 + {value: 0x0020, lo: 0x01}, + {value: 0x552d, lo: 0x80, hi: 0xbf}, + // Block 0x7f, offset 0x3f9 + {value: 0x0020, lo: 0x03}, + {value: 0x5d2d, lo: 0x80, hi: 0x84}, + {value: 0x568d, lo: 0x85, hi: 0x85}, + {value: 0x5dcd, lo: 0x86, hi: 0xbf}, + // Block 0x80, offset 0x3fd + {value: 0x0020, lo: 0x08}, + {value: 0x6b8d, lo: 0x80, hi: 0x8f}, + {value: 0x6d4d, lo: 0x90, hi: 0x90}, + {value: 0x6d8d, lo: 0x91, hi: 0xab}, + {value: 0x1401, lo: 0xac, hi: 0xac}, + {value: 0x70ed, lo: 0xad, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xae}, + {value: 0x0040, lo: 0xaf, hi: 0xaf}, + {value: 0x710d, lo: 0xb0, hi: 0xbf}, + // Block 0x81, offset 0x406 + {value: 0x0020, lo: 0x05}, + {value: 0x730d, lo: 0x80, hi: 0xad}, + {value: 0x656d, lo: 0xae, hi: 0xae}, + {value: 0x78cd, lo: 0xaf, hi: 0xb5}, + {value: 0x6f8d, lo: 0xb6, hi: 0xb6}, + {value: 0x79ad, lo: 0xb7, hi: 0xbf}, + // Block 0x82, offset 0x40c + {value: 0x0008, lo: 0x03}, + {value: 0x1751, lo: 0x80, hi: 0x82}, + {value: 0x1741, lo: 0x83, hi: 0x83}, + {value: 0x1769, lo: 0x84, hi: 0xbf}, + // Block 0x83, offset 0x410 + {value: 0x0008, lo: 0x0f}, + {value: 0x1d81, lo: 0x80, hi: 0x83}, + {value: 0x1d99, lo: 0x84, hi: 0x85}, + {value: 0x1da1, lo: 0x86, hi: 0x87}, + {value: 0x1da9, lo: 0x88, hi: 0x8f}, + {value: 0x0040, lo: 0x90, hi: 0x90}, + {value: 0x0040, lo: 0x91, hi: 0x91}, + {value: 0x1de9, lo: 0x92, hi: 0x97}, + {value: 0x1e11, lo: 0x98, hi: 0x9c}, + {value: 0x1e31, lo: 0x9d, hi: 0xb3}, + {value: 0x1d71, lo: 0xb4, hi: 0xb4}, + {value: 0x1d81, lo: 0xb5, hi: 0xb5}, + {value: 0x1ee9, lo: 0xb6, hi: 0xbb}, + {value: 0x1f09, lo: 0xbc, hi: 0xbc}, + {value: 0x1ef9, lo: 0xbd, hi: 0xbd}, + {value: 0x1f19, lo: 0xbe, hi: 0xbf}, + // Block 0x84, offset 0x420 + {value: 0x0000, lo: 0x09}, + {value: 0x0008, lo: 0x80, hi: 0x8b}, + {value: 0x0040, lo: 0x8c, hi: 0x8c}, + {value: 0x0008, lo: 0x8d, hi: 0xa6}, + {value: 0x0040, lo: 0xa7, hi: 0xa7}, + {value: 0x0008, lo: 0xa8, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbb}, + {value: 0x0008, lo: 0xbc, hi: 0xbd}, + {value: 0x0040, lo: 0xbe, hi: 0xbe}, + {value: 0x0008, lo: 0xbf, hi: 0xbf}, + // Block 0x85, offset 0x42a + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0x8d}, + {value: 0x0040, lo: 0x8e, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x9d}, + {value: 0x0040, lo: 0x9e, hi: 0xbf}, + // Block 0x86, offset 0x42f + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbf}, + // Block 0x87, offset 0x432 + {value: 0x0000, lo: 0x05}, + {value: 0x0018, lo: 0x80, hi: 0x82}, + {value: 0x0040, lo: 0x83, hi: 0x86}, + {value: 0x0018, lo: 0x87, hi: 0xb3}, + {value: 0x0040, lo: 0xb4, hi: 0xb6}, + {value: 0x0018, lo: 0xb7, hi: 0xbf}, + // Block 0x88, offset 0x438 + {value: 0x0000, lo: 0x06}, + {value: 0x0018, lo: 0x80, hi: 0x8e}, + {value: 0x0040, lo: 0x8f, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0x9c}, + {value: 0x0040, lo: 0x9d, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xa0}, + {value: 0x0040, lo: 0xa1, hi: 0xbf}, + // Block 0x89, offset 0x43f + {value: 0x0000, lo: 0x04}, + {value: 0x0040, lo: 0x80, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0xbc}, + {value: 0x3308, lo: 0xbd, hi: 0xbd}, + {value: 0x0040, lo: 0xbe, hi: 0xbf}, + // Block 0x8a, offset 0x444 + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0x9c}, + {value: 0x0040, lo: 0x9d, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xbf}, + // Block 0x8b, offset 0x448 + {value: 0x0000, lo: 0x05}, + {value: 0x0008, lo: 0x80, hi: 0x90}, + {value: 0x0040, lo: 0x91, hi: 0x9f}, + {value: 0x3308, lo: 0xa0, hi: 0xa0}, + {value: 0x0018, lo: 0xa1, hi: 0xbb}, + {value: 0x0040, lo: 0xbc, hi: 0xbf}, + // Block 0x8c, offset 0x44e + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xa3}, + {value: 0x0040, lo: 0xa4, hi: 0xac}, + {value: 0x0008, lo: 0xad, hi: 0xbf}, + // Block 0x8d, offset 0x453 + {value: 0x0000, lo: 0x08}, + {value: 0x0008, lo: 0x80, hi: 0x80}, + {value: 0x0018, lo: 0x81, hi: 0x81}, + {value: 0x0008, lo: 0x82, hi: 0x89}, + {value: 0x0018, lo: 0x8a, hi: 0x8a}, + {value: 0x0040, lo: 0x8b, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xb5}, + {value: 0x3308, lo: 0xb6, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbf}, + // Block 0x8e, offset 0x45c + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0x9d}, + {value: 0x0040, lo: 0x9e, hi: 0x9e}, + {value: 0x0018, lo: 0x9f, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xbf}, + // Block 0x8f, offset 0x461 + {value: 0x0000, lo: 0x05}, + {value: 0x0008, lo: 0x80, hi: 0x83}, + {value: 0x0040, lo: 0x84, hi: 0x87}, + {value: 0x0008, lo: 0x88, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0xbf}, + // Block 0x90, offset 0x467 + {value: 0x0000, lo: 0x06}, + {value: 0xe145, lo: 0x80, hi: 0x87}, + {value: 0xe1c5, lo: 0x88, hi: 0x8f}, + {value: 0xe145, lo: 0x90, hi: 0x97}, + {value: 0x8b0d, lo: 0x98, hi: 0x9f}, + {value: 0x8b25, lo: 0xa0, hi: 0xa7}, + {value: 0x0008, lo: 0xa8, hi: 0xbf}, + // Block 0x91, offset 0x46e + {value: 0x0000, lo: 0x06}, + {value: 0x0008, lo: 0x80, hi: 0x9d}, + {value: 0x0040, lo: 0x9e, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa9}, + {value: 0x0040, lo: 0xaa, hi: 0xaf}, + {value: 0x8b25, lo: 0xb0, hi: 0xb7}, + {value: 0x8b0d, lo: 0xb8, hi: 0xbf}, + // Block 0x92, offset 0x475 + {value: 0x0000, lo: 0x06}, + {value: 0xe145, lo: 0x80, hi: 0x87}, + {value: 0xe1c5, lo: 0x88, hi: 0x8f}, + {value: 0xe145, lo: 0x90, hi: 0x93}, + {value: 0x0040, lo: 0x94, hi: 0x97}, + {value: 0x0008, lo: 0x98, hi: 0xbb}, + {value: 0x0040, lo: 0xbc, hi: 0xbf}, + // Block 0x93, offset 0x47c + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0xa7}, + {value: 0x0040, lo: 0xa8, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0x94, offset 0x480 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xbf}, + // Block 0x95, offset 0x483 + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa7}, + {value: 0x0040, lo: 0xa8, hi: 0xbf}, + // Block 0x96, offset 0x488 + {value: 0x0000, lo: 0x0b}, + {value: 0x0808, lo: 0x80, hi: 0x85}, + {value: 0x0040, lo: 0x86, hi: 0x87}, + {value: 0x0808, lo: 0x88, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x89}, + {value: 0x0808, lo: 0x8a, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xb6}, + {value: 0x0808, lo: 0xb7, hi: 0xb8}, + {value: 0x0040, lo: 0xb9, hi: 0xbb}, + {value: 0x0808, lo: 0xbc, hi: 0xbc}, + {value: 0x0040, lo: 0xbd, hi: 0xbe}, + {value: 0x0808, lo: 0xbf, hi: 0xbf}, + // Block 0x97, offset 0x494 + {value: 0x0000, lo: 0x05}, + {value: 0x0808, lo: 0x80, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0x96}, + {value: 0x0818, lo: 0x97, hi: 0x9f}, + {value: 0x0808, lo: 0xa0, hi: 0xb6}, + {value: 0x0818, lo: 0xb7, hi: 0xbf}, + // Block 0x98, offset 0x49a + {value: 0x0000, lo: 0x04}, + {value: 0x0808, lo: 0x80, hi: 0x9e}, + {value: 0x0040, lo: 0x9f, hi: 0xa6}, + {value: 0x0818, lo: 0xa7, hi: 0xaf}, + {value: 0x0040, lo: 0xb0, hi: 0xbf}, + // Block 0x99, offset 0x49f + {value: 0x0000, lo: 0x06}, + {value: 0x0040, lo: 0x80, hi: 0x9f}, + {value: 0x0808, lo: 0xa0, hi: 0xb2}, + {value: 0x0040, lo: 0xb3, hi: 0xb3}, + {value: 0x0808, lo: 0xb4, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xba}, + {value: 0x0818, lo: 0xbb, hi: 0xbf}, + // Block 0x9a, offset 0x4a6 + {value: 0x0000, lo: 0x07}, + {value: 0x0808, lo: 0x80, hi: 0x95}, + {value: 0x0818, lo: 0x96, hi: 0x9b}, + {value: 0x0040, lo: 0x9c, hi: 0x9e}, + {value: 0x0018, lo: 0x9f, hi: 0x9f}, + {value: 0x0808, lo: 0xa0, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbe}, + {value: 0x0818, lo: 0xbf, hi: 0xbf}, + // Block 0x9b, offset 0x4ae + {value: 0x0000, lo: 0x04}, + {value: 0x0808, lo: 0x80, hi: 0xb7}, + {value: 0x0040, lo: 0xb8, hi: 0xbb}, + {value: 0x0818, lo: 0xbc, hi: 0xbd}, + {value: 0x0808, lo: 0xbe, hi: 0xbf}, + // Block 0x9c, offset 0x4b3 + {value: 0x0000, lo: 0x03}, + {value: 0x0818, lo: 0x80, hi: 0x8f}, + {value: 0x0040, lo: 0x90, hi: 0x91}, + {value: 0x0818, lo: 0x92, hi: 0xbf}, + // Block 0x9d, offset 0x4b7 + {value: 0x0000, lo: 0x0f}, + {value: 0x0808, lo: 0x80, hi: 0x80}, + {value: 0x3308, lo: 0x81, hi: 0x83}, + {value: 0x0040, lo: 0x84, hi: 0x84}, + {value: 0x3308, lo: 0x85, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x8b}, + {value: 0x3308, lo: 0x8c, hi: 0x8f}, + {value: 0x0808, lo: 0x90, hi: 0x93}, + {value: 0x0040, lo: 0x94, hi: 0x94}, + {value: 0x0808, lo: 0x95, hi: 0x97}, + {value: 0x0040, lo: 0x98, hi: 0x98}, + {value: 0x0808, lo: 0x99, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xb7}, + {value: 0x3308, lo: 0xb8, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbe}, + {value: 0x3b08, lo: 0xbf, hi: 0xbf}, + // Block 0x9e, offset 0x4c7 + {value: 0x0000, lo: 0x06}, + {value: 0x0818, lo: 0x80, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x8f}, + {value: 0x0818, lo: 0x90, hi: 0x98}, + {value: 0x0040, lo: 0x99, hi: 0x9f}, + {value: 0x0808, lo: 0xa0, hi: 0xbc}, + {value: 0x0818, lo: 0xbd, hi: 0xbf}, + // Block 0x9f, offset 0x4ce + {value: 0x0000, lo: 0x03}, + {value: 0x0808, lo: 0x80, hi: 0x9c}, + {value: 0x0818, lo: 0x9d, hi: 0x9f}, + {value: 0x0040, lo: 0xa0, hi: 0xbf}, + // Block 0xa0, offset 0x4d2 + {value: 0x0000, lo: 0x03}, + {value: 0x0808, lo: 0x80, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xb8}, + {value: 0x0018, lo: 0xb9, hi: 0xbf}, + // Block 0xa1, offset 0x4d6 + {value: 0x0000, lo: 0x06}, + {value: 0x0808, lo: 0x80, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0x97}, + {value: 0x0818, lo: 0x98, hi: 0x9f}, + {value: 0x0808, lo: 0xa0, hi: 0xb2}, + {value: 0x0040, lo: 0xb3, hi: 0xb7}, + {value: 0x0818, lo: 0xb8, hi: 0xbf}, + // Block 0xa2, offset 0x4dd + {value: 0x0000, lo: 0x01}, + {value: 0x0808, lo: 0x80, hi: 0xbf}, + // Block 0xa3, offset 0x4df + {value: 0x0000, lo: 0x02}, + {value: 0x0808, lo: 0x80, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0xbf}, + // Block 0xa4, offset 0x4e2 + {value: 0x0000, lo: 0x02}, + {value: 0x03dd, lo: 0x80, hi: 0xb2}, + {value: 0x0040, lo: 0xb3, hi: 0xbf}, + // Block 0xa5, offset 0x4e5 + {value: 0x0000, lo: 0x03}, + {value: 0x0808, lo: 0x80, hi: 0xb2}, + {value: 0x0040, lo: 0xb3, hi: 0xb9}, + {value: 0x0818, lo: 0xba, hi: 0xbf}, + // Block 0xa6, offset 0x4e9 + {value: 0x0000, lo: 0x08}, + {value: 0x0908, lo: 0x80, hi: 0x80}, + {value: 0x0a08, lo: 0x81, hi: 0xa1}, + {value: 0x0c08, lo: 0xa2, hi: 0xa2}, + {value: 0x0a08, lo: 0xa3, hi: 0xa3}, + {value: 0x3308, lo: 0xa4, hi: 0xa7}, + {value: 0x0040, lo: 0xa8, hi: 0xaf}, + {value: 0x0808, lo: 0xb0, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbf}, + // Block 0xa7, offset 0x4f2 + {value: 0x0000, lo: 0x03}, + {value: 0x0040, lo: 0x80, hi: 0x9f}, + {value: 0x0818, lo: 0xa0, hi: 0xbe}, + {value: 0x0040, lo: 0xbf, hi: 0xbf}, + // Block 0xa8, offset 0x4f6 + {value: 0x0000, lo: 0x07}, + {value: 0x0808, lo: 0x80, hi: 0xa9}, + {value: 0x0040, lo: 0xaa, hi: 0xaa}, + {value: 0x3308, lo: 0xab, hi: 0xac}, + {value: 0x0818, lo: 0xad, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xaf}, + {value: 0x0808, lo: 0xb0, hi: 0xb1}, + {value: 0x0040, lo: 0xb2, hi: 0xbf}, + // Block 0xa9, offset 0x4fe + {value: 0x0000, lo: 0x02}, + {value: 0x0040, lo: 0x80, hi: 0xbc}, + {value: 0x3308, lo: 0xbd, hi: 0xbf}, + // Block 0xaa, offset 0x501 + {value: 0x0000, lo: 0x07}, + {value: 0x0808, lo: 0x80, hi: 0x9c}, + {value: 0x0818, lo: 0x9d, hi: 0xa6}, + {value: 0x0808, lo: 0xa7, hi: 0xa7}, + {value: 0x0040, lo: 0xa8, hi: 0xaf}, + {value: 0x0a08, lo: 0xb0, hi: 0xb2}, + {value: 0x0c08, lo: 0xb3, hi: 0xb3}, + {value: 0x0a08, lo: 0xb4, hi: 0xbf}, + // Block 0xab, offset 0x509 + {value: 0x0000, lo: 0x0a}, + {value: 0x0a08, lo: 0x80, hi: 0x84}, + {value: 0x0808, lo: 0x85, hi: 0x85}, + {value: 0x3308, lo: 0x86, hi: 0x90}, + {value: 0x0a18, lo: 0x91, hi: 0x93}, + {value: 0x0c18, lo: 0x94, hi: 0x94}, + {value: 0x0818, lo: 0x95, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0xaf}, + {value: 0x0a08, lo: 0xb0, hi: 0xb3}, + {value: 0x0c08, lo: 0xb4, hi: 0xb5}, + {value: 0x0a08, lo: 0xb6, hi: 0xbf}, + // Block 0xac, offset 0x514 + {value: 0x0000, lo: 0x0e}, + {value: 0x0a08, lo: 0x80, hi: 0x81}, + {value: 0x3308, lo: 0x82, hi: 0x85}, + {value: 0x0818, lo: 0x86, hi: 0x89}, + {value: 0x0040, lo: 0x8a, hi: 0xaf}, + {value: 0x0a08, lo: 0xb0, hi: 0xb0}, + {value: 0x0808, lo: 0xb1, hi: 0xb1}, + {value: 0x0a08, lo: 0xb2, hi: 0xb3}, + {value: 0x0c08, lo: 0xb4, hi: 0xb6}, + {value: 0x0808, lo: 0xb7, hi: 0xb7}, + {value: 0x0a08, lo: 0xb8, hi: 0xb8}, + {value: 0x0c08, lo: 0xb9, hi: 0xba}, + {value: 0x0a08, lo: 0xbb, hi: 0xbc}, + {value: 0x0c08, lo: 0xbd, hi: 0xbd}, + {value: 0x0a08, lo: 0xbe, hi: 0xbf}, + // Block 0xad, offset 0x523 + {value: 0x0000, lo: 0x0b}, + {value: 0x0808, lo: 0x80, hi: 0x80}, + {value: 0x0a08, lo: 0x81, hi: 0x81}, + {value: 0x0c08, lo: 0x82, hi: 0x83}, + {value: 0x0a08, lo: 0x84, hi: 0x84}, + {value: 0x0818, lo: 0x85, hi: 0x88}, + {value: 0x0c18, lo: 0x89, hi: 0x89}, + {value: 0x0a18, lo: 0x8a, hi: 0x8a}, + {value: 0x0918, lo: 0x8b, hi: 0x8b}, + {value: 0x0040, lo: 0x8c, hi: 0x9f}, + {value: 0x0808, lo: 0xa0, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xbf}, + // Block 0xae, offset 0x52f + {value: 0x0000, lo: 0x05}, + {value: 0x3008, lo: 0x80, hi: 0x80}, + {value: 0x3308, lo: 0x81, hi: 0x81}, + {value: 0x3008, lo: 0x82, hi: 0x82}, + {value: 0x0008, lo: 0x83, hi: 0xb7}, + {value: 0x3308, lo: 0xb8, hi: 0xbf}, + // Block 0xaf, offset 0x535 + {value: 0x0000, lo: 0x0c}, + {value: 0x3308, lo: 0x80, hi: 0x85}, + {value: 0x3b08, lo: 0x86, hi: 0x86}, + {value: 0x0018, lo: 0x87, hi: 0x8d}, + {value: 0x0040, lo: 0x8e, hi: 0x91}, + {value: 0x0018, lo: 0x92, hi: 0xa5}, + {value: 0x0008, lo: 0xa6, hi: 0xaf}, + {value: 0x3b08, lo: 0xb0, hi: 0xb0}, + {value: 0x0008, lo: 0xb1, hi: 0xb2}, + {value: 0x3308, lo: 0xb3, hi: 0xb4}, + {value: 0x0008, lo: 0xb5, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xbe}, + {value: 0x3b08, lo: 0xbf, hi: 0xbf}, + // Block 0xb0, offset 0x542 + {value: 0x0000, lo: 0x0b}, + {value: 0x3308, lo: 0x80, hi: 0x81}, + {value: 0x3008, lo: 0x82, hi: 0x82}, + {value: 0x0008, lo: 0x83, hi: 0xaf}, + {value: 0x3008, lo: 0xb0, hi: 0xb2}, + {value: 0x3308, lo: 0xb3, hi: 0xb6}, + {value: 0x3008, lo: 0xb7, hi: 0xb8}, + {value: 0x3b08, lo: 0xb9, hi: 0xb9}, + {value: 0x3308, lo: 0xba, hi: 0xba}, + {value: 0x0018, lo: 0xbb, hi: 0xbc}, + {value: 0x0040, lo: 0xbd, hi: 0xbd}, + {value: 0x0018, lo: 0xbe, hi: 0xbf}, + // Block 0xb1, offset 0x54e + {value: 0x0000, lo: 0x07}, + {value: 0x0018, lo: 0x80, hi: 0x81}, + {value: 0x3308, lo: 0x82, hi: 0x82}, + {value: 0x0040, lo: 0x83, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xa8}, + {value: 0x0040, lo: 0xa9, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbf}, + // Block 0xb2, offset 0x556 + {value: 0x0000, lo: 0x08}, + {value: 0x3308, lo: 0x80, hi: 0x82}, + {value: 0x0008, lo: 0x83, hi: 0xa6}, + {value: 0x3308, lo: 0xa7, hi: 0xab}, + {value: 0x3008, lo: 0xac, hi: 0xac}, + {value: 0x3308, lo: 0xad, hi: 0xb2}, + {value: 0x3b08, lo: 0xb3, hi: 0xb4}, + {value: 0x0040, lo: 0xb5, hi: 0xb5}, + {value: 0x0008, lo: 0xb6, hi: 0xbf}, + // Block 0xb3, offset 0x55f + {value: 0x0000, lo: 0x0a}, + {value: 0x0018, lo: 0x80, hi: 0x83}, + {value: 0x0008, lo: 0x84, hi: 0x84}, + {value: 0x3008, lo: 0x85, hi: 0x86}, + {value: 0x0008, lo: 0x87, hi: 0x87}, + {value: 0x0040, lo: 0x88, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xb2}, + {value: 0x3308, lo: 0xb3, hi: 0xb3}, + {value: 0x0018, lo: 0xb4, hi: 0xb5}, + {value: 0x0008, lo: 0xb6, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xbf}, + // Block 0xb4, offset 0x56a + {value: 0x0000, lo: 0x06}, + {value: 0x3308, lo: 0x80, hi: 0x81}, + {value: 0x3008, lo: 0x82, hi: 0x82}, + {value: 0x0008, lo: 0x83, hi: 0xb2}, + {value: 0x3008, lo: 0xb3, hi: 0xb5}, + {value: 0x3308, lo: 0xb6, hi: 0xbe}, + {value: 0x3008, lo: 0xbf, hi: 0xbf}, + // Block 0xb5, offset 0x571 + {value: 0x0000, lo: 0x0e}, + {value: 0x3808, lo: 0x80, hi: 0x80}, + {value: 0x0008, lo: 0x81, hi: 0x84}, + {value: 0x0018, lo: 0x85, hi: 0x88}, + {value: 0x3308, lo: 0x89, hi: 0x8c}, + {value: 0x0018, lo: 0x8d, hi: 0x8d}, + {value: 0x3008, lo: 0x8e, hi: 0x8e}, + {value: 0x3308, lo: 0x8f, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x9a}, + {value: 0x0018, lo: 0x9b, hi: 0x9b}, + {value: 0x0008, lo: 0x9c, hi: 0x9c}, + {value: 0x0018, lo: 0x9d, hi: 0x9f}, + {value: 0x0040, lo: 0xa0, hi: 0xa0}, + {value: 0x0018, lo: 0xa1, hi: 0xb4}, + {value: 0x0040, lo: 0xb5, hi: 0xbf}, + // Block 0xb6, offset 0x580 + {value: 0x0000, lo: 0x0c}, + {value: 0x0008, lo: 0x80, hi: 0x91}, + {value: 0x0040, lo: 0x92, hi: 0x92}, + {value: 0x0008, lo: 0x93, hi: 0xab}, + {value: 0x3008, lo: 0xac, hi: 0xae}, + {value: 0x3308, lo: 0xaf, hi: 0xb1}, + {value: 0x3008, lo: 0xb2, hi: 0xb3}, + {value: 0x3308, lo: 0xb4, hi: 0xb4}, + {value: 0x3808, lo: 0xb5, hi: 0xb5}, + {value: 0x3308, lo: 0xb6, hi: 0xb7}, + {value: 0x0018, lo: 0xb8, hi: 0xbd}, + {value: 0x3308, lo: 0xbe, hi: 0xbe}, + {value: 0x0008, lo: 0xbf, hi: 0xbf}, + // Block 0xb7, offset 0x58d + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0x80}, + {value: 0x3308, lo: 0x81, hi: 0x81}, + {value: 0x0040, lo: 0x82, hi: 0xbf}, + // Block 0xb8, offset 0x591 + {value: 0x0000, lo: 0x0c}, + {value: 0x0008, lo: 0x80, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x87}, + {value: 0x0008, lo: 0x88, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x89}, + {value: 0x0008, lo: 0x8a, hi: 0x8d}, + {value: 0x0040, lo: 0x8e, hi: 0x8e}, + {value: 0x0008, lo: 0x8f, hi: 0x9d}, + {value: 0x0040, lo: 0x9e, hi: 0x9e}, + {value: 0x0008, lo: 0x9f, hi: 0xa8}, + {value: 0x0018, lo: 0xa9, hi: 0xa9}, + {value: 0x0040, lo: 0xaa, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0xb9, offset 0x59e + {value: 0x0000, lo: 0x08}, + {value: 0x0008, lo: 0x80, hi: 0x9e}, + {value: 0x3308, lo: 0x9f, hi: 0x9f}, + {value: 0x3008, lo: 0xa0, hi: 0xa2}, + {value: 0x3308, lo: 0xa3, hi: 0xa9}, + {value: 0x3b08, lo: 0xaa, hi: 0xaa}, + {value: 0x0040, lo: 0xab, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbf}, + // Block 0xba, offset 0x5a7 + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0xb4}, + {value: 0x3008, lo: 0xb5, hi: 0xb7}, + {value: 0x3308, lo: 0xb8, hi: 0xbf}, + // Block 0xbb, offset 0x5ab + {value: 0x0000, lo: 0x0e}, + {value: 0x3008, lo: 0x80, hi: 0x81}, + {value: 0x3b08, lo: 0x82, hi: 0x82}, + {value: 0x3308, lo: 0x83, hi: 0x84}, + {value: 0x3008, lo: 0x85, hi: 0x85}, + {value: 0x3308, lo: 0x86, hi: 0x86}, + {value: 0x0008, lo: 0x87, hi: 0x8a}, + {value: 0x0018, lo: 0x8b, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0018, lo: 0x9a, hi: 0x9b}, + {value: 0x0040, lo: 0x9c, hi: 0x9c}, + {value: 0x0018, lo: 0x9d, hi: 0x9d}, + {value: 0x3308, lo: 0x9e, hi: 0x9e}, + {value: 0x0008, lo: 0x9f, hi: 0xa1}, + {value: 0x0040, lo: 0xa2, hi: 0xbf}, + // Block 0xbc, offset 0x5ba + {value: 0x0000, lo: 0x07}, + {value: 0x0008, lo: 0x80, hi: 0xaf}, + {value: 0x3008, lo: 0xb0, hi: 0xb2}, + {value: 0x3308, lo: 0xb3, hi: 0xb8}, + {value: 0x3008, lo: 0xb9, hi: 0xb9}, + {value: 0x3308, lo: 0xba, hi: 0xba}, + {value: 0x3008, lo: 0xbb, hi: 0xbe}, + {value: 0x3308, lo: 0xbf, hi: 0xbf}, + // Block 0xbd, offset 0x5c2 + {value: 0x0000, lo: 0x0a}, + {value: 0x3308, lo: 0x80, hi: 0x80}, + {value: 0x3008, lo: 0x81, hi: 0x81}, + {value: 0x3b08, lo: 0x82, hi: 0x82}, + {value: 0x3308, lo: 0x83, hi: 0x83}, + {value: 0x0008, lo: 0x84, hi: 0x85}, + {value: 0x0018, lo: 0x86, hi: 0x86}, + {value: 0x0008, lo: 0x87, hi: 0x87}, + {value: 0x0040, lo: 0x88, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0xbf}, + // Block 0xbe, offset 0x5cd + {value: 0x0000, lo: 0x08}, + {value: 0x0008, lo: 0x80, hi: 0xae}, + {value: 0x3008, lo: 0xaf, hi: 0xb1}, + {value: 0x3308, lo: 0xb2, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xb7}, + {value: 0x3008, lo: 0xb8, hi: 0xbb}, + {value: 0x3308, lo: 0xbc, hi: 0xbd}, + {value: 0x3008, lo: 0xbe, hi: 0xbe}, + {value: 0x3b08, lo: 0xbf, hi: 0xbf}, + // Block 0xbf, offset 0x5d6 + {value: 0x0000, lo: 0x05}, + {value: 0x3308, lo: 0x80, hi: 0x80}, + {value: 0x0018, lo: 0x81, hi: 0x97}, + {value: 0x0008, lo: 0x98, hi: 0x9b}, + {value: 0x3308, lo: 0x9c, hi: 0x9d}, + {value: 0x0040, lo: 0x9e, hi: 0xbf}, + // Block 0xc0, offset 0x5dc + {value: 0x0000, lo: 0x07}, + {value: 0x0008, lo: 0x80, hi: 0xaf}, + {value: 0x3008, lo: 0xb0, hi: 0xb2}, + {value: 0x3308, lo: 0xb3, hi: 0xba}, + {value: 0x3008, lo: 0xbb, hi: 0xbc}, + {value: 0x3308, lo: 0xbd, hi: 0xbd}, + {value: 0x3008, lo: 0xbe, hi: 0xbe}, + {value: 0x3b08, lo: 0xbf, hi: 0xbf}, + // Block 0xc1, offset 0x5e4 + {value: 0x0000, lo: 0x08}, + {value: 0x3308, lo: 0x80, hi: 0x80}, + {value: 0x0018, lo: 0x81, hi: 0x83}, + {value: 0x0008, lo: 0x84, hi: 0x84}, + {value: 0x0040, lo: 0x85, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xac}, + {value: 0x0040, lo: 0xad, hi: 0xbf}, + // Block 0xc2, offset 0x5ed + {value: 0x0000, lo: 0x0b}, + {value: 0x0008, lo: 0x80, hi: 0xaa}, + {value: 0x3308, lo: 0xab, hi: 0xab}, + {value: 0x3008, lo: 0xac, hi: 0xac}, + {value: 0x3308, lo: 0xad, hi: 0xad}, + {value: 0x3008, lo: 0xae, hi: 0xaf}, + {value: 0x3308, lo: 0xb0, hi: 0xb5}, + {value: 0x3808, lo: 0xb6, hi: 0xb6}, + {value: 0x3308, lo: 0xb7, hi: 0xb7}, + {value: 0x0008, lo: 0xb8, hi: 0xb8}, + {value: 0x0018, lo: 0xb9, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbf}, + // Block 0xc3, offset 0x5f9 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0x89}, + {value: 0x0040, lo: 0x8a, hi: 0xbf}, + // Block 0xc4, offset 0x5fc + {value: 0x0000, lo: 0x0b}, + {value: 0x0008, lo: 0x80, hi: 0x9a}, + {value: 0x0040, lo: 0x9b, hi: 0x9c}, + {value: 0x3308, lo: 0x9d, hi: 0x9f}, + {value: 0x3008, lo: 0xa0, hi: 0xa1}, + {value: 0x3308, lo: 0xa2, hi: 0xa5}, + {value: 0x3008, lo: 0xa6, hi: 0xa6}, + {value: 0x3308, lo: 0xa7, hi: 0xaa}, + {value: 0x3b08, lo: 0xab, hi: 0xab}, + {value: 0x0040, lo: 0xac, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb9}, + {value: 0x0018, lo: 0xba, hi: 0xbf}, + // Block 0xc5, offset 0x608 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0xbf}, + // Block 0xc6, offset 0x60b + {value: 0x0000, lo: 0x08}, + {value: 0x0008, lo: 0x80, hi: 0xab}, + {value: 0x3008, lo: 0xac, hi: 0xae}, + {value: 0x3308, lo: 0xaf, hi: 0xb7}, + {value: 0x3008, lo: 0xb8, hi: 0xb8}, + {value: 0x3b08, lo: 0xb9, hi: 0xb9}, + {value: 0x3308, lo: 0xba, hi: 0xba}, + {value: 0x0018, lo: 0xbb, hi: 0xbb}, + {value: 0x0040, lo: 0xbc, hi: 0xbf}, + // Block 0xc7, offset 0x614 + {value: 0x0000, lo: 0x02}, + {value: 0x0040, lo: 0x80, hi: 0x9f}, + {value: 0x049d, lo: 0xa0, hi: 0xbf}, + // Block 0xc8, offset 0x617 + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0xa9}, + {value: 0x0018, lo: 0xaa, hi: 0xb2}, + {value: 0x0040, lo: 0xb3, hi: 0xbe}, + {value: 0x0008, lo: 0xbf, hi: 0xbf}, + // Block 0xc9, offset 0x61c + {value: 0x0000, lo: 0x08}, + {value: 0x3008, lo: 0x80, hi: 0x80}, + {value: 0x0008, lo: 0x81, hi: 0x81}, + {value: 0x3008, lo: 0x82, hi: 0x82}, + {value: 0x3308, lo: 0x83, hi: 0x83}, + {value: 0x0018, lo: 0x84, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0xbf}, + // Block 0xca, offset 0x625 + {value: 0x0000, lo: 0x04}, + {value: 0x0040, lo: 0x80, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa7}, + {value: 0x0040, lo: 0xa8, hi: 0xa9}, + {value: 0x0008, lo: 0xaa, hi: 0xbf}, + // Block 0xcb, offset 0x62a + {value: 0x0000, lo: 0x0c}, + {value: 0x0008, lo: 0x80, hi: 0x90}, + {value: 0x3008, lo: 0x91, hi: 0x93}, + {value: 0x3308, lo: 0x94, hi: 0x97}, + {value: 0x0040, lo: 0x98, hi: 0x99}, + {value: 0x3308, lo: 0x9a, hi: 0x9b}, + {value: 0x3008, lo: 0x9c, hi: 0x9f}, + {value: 0x3b08, lo: 0xa0, hi: 0xa0}, + {value: 0x0008, lo: 0xa1, hi: 0xa1}, + {value: 0x0018, lo: 0xa2, hi: 0xa2}, + {value: 0x0008, lo: 0xa3, hi: 0xa3}, + {value: 0x3008, lo: 0xa4, hi: 0xa4}, + {value: 0x0040, lo: 0xa5, hi: 0xbf}, + // Block 0xcc, offset 0x637 + {value: 0x0000, lo: 0x0a}, + {value: 0x0008, lo: 0x80, hi: 0x80}, + {value: 0x3308, lo: 0x81, hi: 0x8a}, + {value: 0x0008, lo: 0x8b, hi: 0xb2}, + {value: 0x3308, lo: 0xb3, hi: 0xb3}, + {value: 0x3b08, lo: 0xb4, hi: 0xb4}, + {value: 0x3308, lo: 0xb5, hi: 0xb8}, + {value: 0x3008, lo: 0xb9, hi: 0xb9}, + {value: 0x0008, lo: 0xba, hi: 0xba}, + {value: 0x3308, lo: 0xbb, hi: 0xbe}, + {value: 0x0018, lo: 0xbf, hi: 0xbf}, + // Block 0xcd, offset 0x642 + {value: 0x0000, lo: 0x08}, + {value: 0x0018, lo: 0x80, hi: 0x86}, + {value: 0x3b08, lo: 0x87, hi: 0x87}, + {value: 0x0040, lo: 0x88, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x90}, + {value: 0x3308, lo: 0x91, hi: 0x96}, + {value: 0x3008, lo: 0x97, hi: 0x98}, + {value: 0x3308, lo: 0x99, hi: 0x9b}, + {value: 0x0008, lo: 0x9c, hi: 0xbf}, + // Block 0xce, offset 0x64b + {value: 0x0000, lo: 0x0a}, + {value: 0x0008, lo: 0x80, hi: 0x89}, + {value: 0x3308, lo: 0x8a, hi: 0x96}, + {value: 0x3008, lo: 0x97, hi: 0x97}, + {value: 0x3308, lo: 0x98, hi: 0x98}, + {value: 0x3b08, lo: 0x99, hi: 0x99}, + {value: 0x0018, lo: 0x9a, hi: 0x9c}, + {value: 0x0008, lo: 0x9d, hi: 0x9d}, + {value: 0x0018, lo: 0x9e, hi: 0xa2}, + {value: 0x0040, lo: 0xa3, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0xcf, offset 0x656 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xb8}, + {value: 0x0040, lo: 0xb9, hi: 0xbf}, + // Block 0xd0, offset 0x659 + {value: 0x0000, lo: 0x02}, + {value: 0x0018, lo: 0x80, hi: 0x89}, + {value: 0x0040, lo: 0x8a, hi: 0xbf}, + // Block 0xd1, offset 0x65c + {value: 0x0000, lo: 0x09}, + {value: 0x0008, lo: 0x80, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x89}, + {value: 0x0008, lo: 0x8a, hi: 0xae}, + {value: 0x3008, lo: 0xaf, hi: 0xaf}, + {value: 0x3308, lo: 0xb0, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xb7}, + {value: 0x3308, lo: 0xb8, hi: 0xbd}, + {value: 0x3008, lo: 0xbe, hi: 0xbe}, + {value: 0x3b08, lo: 0xbf, hi: 0xbf}, + // Block 0xd2, offset 0x666 + {value: 0x0000, lo: 0x08}, + {value: 0x0008, lo: 0x80, hi: 0x80}, + {value: 0x0018, lo: 0x81, hi: 0x85}, + {value: 0x0040, lo: 0x86, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0018, lo: 0x9a, hi: 0xac}, + {value: 0x0040, lo: 0xad, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xb1}, + {value: 0x0008, lo: 0xb2, hi: 0xbf}, + // Block 0xd3, offset 0x66f + {value: 0x0000, lo: 0x0b}, + {value: 0x0008, lo: 0x80, hi: 0x8f}, + {value: 0x0040, lo: 0x90, hi: 0x91}, + {value: 0x3308, lo: 0x92, hi: 0xa7}, + {value: 0x0040, lo: 0xa8, hi: 0xa8}, + {value: 0x3008, lo: 0xa9, hi: 0xa9}, + {value: 0x3308, lo: 0xaa, hi: 0xb0}, + {value: 0x3008, lo: 0xb1, hi: 0xb1}, + {value: 0x3308, lo: 0xb2, hi: 0xb3}, + {value: 0x3008, lo: 0xb4, hi: 0xb4}, + {value: 0x3308, lo: 0xb5, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xbf}, + // Block 0xd4, offset 0x67b + {value: 0x0000, lo: 0x0c}, + {value: 0x0008, lo: 0x80, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x87}, + {value: 0x0008, lo: 0x88, hi: 0x89}, + {value: 0x0040, lo: 0x8a, hi: 0x8a}, + {value: 0x0008, lo: 0x8b, hi: 0xb0}, + {value: 0x3308, lo: 0xb1, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xb9}, + {value: 0x3308, lo: 0xba, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbb}, + {value: 0x3308, lo: 0xbc, hi: 0xbd}, + {value: 0x0040, lo: 0xbe, hi: 0xbe}, + {value: 0x3308, lo: 0xbf, hi: 0xbf}, + // Block 0xd5, offset 0x688 + {value: 0x0000, lo: 0x0c}, + {value: 0x3308, lo: 0x80, hi: 0x83}, + {value: 0x3b08, lo: 0x84, hi: 0x85}, + {value: 0x0008, lo: 0x86, hi: 0x86}, + {value: 0x3308, lo: 0x87, hi: 0x87}, + {value: 0x0040, lo: 0x88, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa5}, + {value: 0x0040, lo: 0xa6, hi: 0xa6}, + {value: 0x0008, lo: 0xa7, hi: 0xa8}, + {value: 0x0040, lo: 0xa9, hi: 0xa9}, + {value: 0x0008, lo: 0xaa, hi: 0xbf}, + // Block 0xd6, offset 0x695 + {value: 0x0000, lo: 0x0d}, + {value: 0x0008, lo: 0x80, hi: 0x89}, + {value: 0x3008, lo: 0x8a, hi: 0x8e}, + {value: 0x0040, lo: 0x8f, hi: 0x8f}, + {value: 0x3308, lo: 0x90, hi: 0x91}, + {value: 0x0040, lo: 0x92, hi: 0x92}, + {value: 0x3008, lo: 0x93, hi: 0x94}, + {value: 0x3308, lo: 0x95, hi: 0x95}, + {value: 0x3008, lo: 0x96, hi: 0x96}, + {value: 0x3b08, lo: 0x97, hi: 0x97}, + {value: 0x0008, lo: 0x98, hi: 0x98}, + {value: 0x0040, lo: 0x99, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa9}, + {value: 0x0040, lo: 0xaa, hi: 0xbf}, + // Block 0xd7, offset 0x6a3 + {value: 0x0000, lo: 0x06}, + {value: 0x0040, lo: 0x80, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xb2}, + {value: 0x3308, lo: 0xb3, hi: 0xb4}, + {value: 0x3008, lo: 0xb5, hi: 0xb6}, + {value: 0x0018, lo: 0xb7, hi: 0xb8}, + {value: 0x0040, lo: 0xb9, hi: 0xbf}, + // Block 0xd8, offset 0x6aa + {value: 0x0000, lo: 0x0a}, + {value: 0x3308, lo: 0x80, hi: 0x81}, + {value: 0x0008, lo: 0x82, hi: 0x82}, + {value: 0x3008, lo: 0x83, hi: 0x83}, + {value: 0x0008, lo: 0x84, hi: 0x90}, + {value: 0x0040, lo: 0x91, hi: 0x91}, + {value: 0x0008, lo: 0x92, hi: 0xb3}, + {value: 0x3008, lo: 0xb4, hi: 0xb5}, + {value: 0x3308, lo: 0xb6, hi: 0xba}, + {value: 0x0040, lo: 0xbb, hi: 0xbd}, + {value: 0x3008, lo: 0xbe, hi: 0xbf}, + // Block 0xd9, offset 0x6b5 + {value: 0x0000, lo: 0x06}, + {value: 0x3308, lo: 0x80, hi: 0x80}, + {value: 0x3808, lo: 0x81, hi: 0x81}, + {value: 0x3b08, lo: 0x82, hi: 0x82}, + {value: 0x0018, lo: 0x83, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0xbf}, + // Block 0xda, offset 0x6bc + {value: 0x0000, lo: 0x03}, + {value: 0x0040, lo: 0x80, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb0}, + {value: 0x0040, lo: 0xb1, hi: 0xbf}, + // Block 0xdb, offset 0x6c0 + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0xb1}, + {value: 0x0040, lo: 0xb2, hi: 0xbe}, + {value: 0x0018, lo: 0xbf, hi: 0xbf}, + // Block 0xdc, offset 0x6c4 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0xbf}, + // Block 0xdd, offset 0x6c7 + {value: 0x0000, lo: 0x04}, + {value: 0x0018, lo: 0x80, hi: 0xae}, + {value: 0x0040, lo: 0xaf, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xb4}, + {value: 0x0040, lo: 0xb5, hi: 0xbf}, + // Block 0xde, offset 0x6cc + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0x83}, + {value: 0x0040, lo: 0x84, hi: 0xbf}, + // Block 0xdf, offset 0x6cf + {value: 0x0000, lo: 0x02}, + {value: 0x0040, lo: 0x80, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xbf}, + // Block 0xe0, offset 0x6d2 + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0xb0}, + {value: 0x0018, lo: 0xb1, hi: 0xb2}, + {value: 0x0040, lo: 0xb3, hi: 0xbf}, + // Block 0xe1, offset 0x6d6 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xaf}, + {value: 0x0340, lo: 0xb0, hi: 0xbf}, + // Block 0xe2, offset 0x6d9 + {value: 0x0000, lo: 0x04}, + {value: 0x3308, lo: 0x80, hi: 0x80}, + {value: 0x0008, lo: 0x81, hi: 0x86}, + {value: 0x3308, lo: 0x87, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0xbf}, + // Block 0xe3, offset 0x6de + {value: 0x0000, lo: 0x06}, + {value: 0x0008, lo: 0x80, hi: 0x9e}, + {value: 0x0040, lo: 0x9f, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa9}, + {value: 0x0040, lo: 0xaa, hi: 0xad}, + {value: 0x0018, lo: 0xae, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0xe4, offset 0x6e5 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xbe}, + {value: 0x0040, lo: 0xbf, hi: 0xbf}, + // Block 0xe5, offset 0x6e8 + {value: 0x0000, lo: 0x07}, + {value: 0x0008, lo: 0x80, hi: 0x89}, + {value: 0x0040, lo: 0x8a, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xaf}, + {value: 0x3308, lo: 0xb0, hi: 0xb4}, + {value: 0x0018, lo: 0xb5, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xbf}, + // Block 0xe6, offset 0x6f0 + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0xaf}, + {value: 0x3308, lo: 0xb0, hi: 0xb6}, + {value: 0x0018, lo: 0xb7, hi: 0xbf}, + // Block 0xe7, offset 0x6f4 + {value: 0x0000, lo: 0x0a}, + {value: 0x0008, lo: 0x80, hi: 0x83}, + {value: 0x0018, lo: 0x84, hi: 0x85}, + {value: 0x0040, lo: 0x86, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9a}, + {value: 0x0018, lo: 0x9b, hi: 0xa1}, + {value: 0x0040, lo: 0xa2, hi: 0xa2}, + {value: 0x0008, lo: 0xa3, hi: 0xb7}, + {value: 0x0040, lo: 0xb8, hi: 0xbc}, + {value: 0x0008, lo: 0xbd, hi: 0xbf}, + // Block 0xe8, offset 0x6ff + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0x8f}, + {value: 0x0040, lo: 0x90, hi: 0xbf}, + // Block 0xe9, offset 0x702 + {value: 0x0000, lo: 0x02}, + {value: 0xe105, lo: 0x80, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xbf}, + // Block 0xea, offset 0x705 + {value: 0x0000, lo: 0x02}, + {value: 0x0018, lo: 0x80, hi: 0x9a}, + {value: 0x0040, lo: 0x9b, hi: 0xbf}, + // Block 0xeb, offset 0x708 + {value: 0x0000, lo: 0x05}, + {value: 0x0008, lo: 0x80, hi: 0x8a}, + {value: 0x0040, lo: 0x8b, hi: 0x8e}, + {value: 0x3308, lo: 0x8f, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x90}, + {value: 0x3008, lo: 0x91, hi: 0xbf}, + // Block 0xec, offset 0x70e + {value: 0x0000, lo: 0x05}, + {value: 0x3008, lo: 0x80, hi: 0x87}, + {value: 0x0040, lo: 0x88, hi: 0x8e}, + {value: 0x3308, lo: 0x8f, hi: 0x92}, + {value: 0x0008, lo: 0x93, hi: 0x9f}, + {value: 0x0040, lo: 0xa0, hi: 0xbf}, + // Block 0xed, offset 0x714 + {value: 0x0000, lo: 0x08}, + {value: 0x0040, lo: 0x80, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa1}, + {value: 0x0018, lo: 0xa2, hi: 0xa2}, + {value: 0x0008, lo: 0xa3, hi: 0xa3}, + {value: 0x3308, lo: 0xa4, hi: 0xa4}, + {value: 0x0040, lo: 0xa5, hi: 0xaf}, + {value: 0x3008, lo: 0xb0, hi: 0xb1}, + {value: 0x0040, lo: 0xb2, hi: 0xbf}, + // Block 0xee, offset 0x71d + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xb7}, + {value: 0x0040, lo: 0xb8, hi: 0xbf}, + // Block 0xef, offset 0x720 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0xbf}, + // Block 0xf0, offset 0x723 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0xbf}, + // Block 0xf1, offset 0x726 + {value: 0x0000, lo: 0x07}, + {value: 0x0040, lo: 0x80, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb3}, + {value: 0x0040, lo: 0xb4, hi: 0xb4}, + {value: 0x0008, lo: 0xb5, hi: 0xbb}, + {value: 0x0040, lo: 0xbc, hi: 0xbc}, + {value: 0x0008, lo: 0xbd, hi: 0xbe}, + {value: 0x0040, lo: 0xbf, hi: 0xbf}, + // Block 0xf2, offset 0x72e + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0xa2}, + {value: 0x0040, lo: 0xa3, hi: 0xb1}, + {value: 0x0008, lo: 0xb2, hi: 0xb2}, + {value: 0x0040, lo: 0xb3, hi: 0xbf}, + // Block 0xf3, offset 0x733 + {value: 0x0000, lo: 0x08}, + {value: 0x0040, lo: 0x80, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x92}, + {value: 0x0040, lo: 0x93, hi: 0x94}, + {value: 0x0008, lo: 0x95, hi: 0x95}, + {value: 0x0040, lo: 0x96, hi: 0xa3}, + {value: 0x0008, lo: 0xa4, hi: 0xa7}, + {value: 0x0040, lo: 0xa8, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0xf4, offset 0x73c + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xbb}, + {value: 0x0040, lo: 0xbc, hi: 0xbf}, + // Block 0xf5, offset 0x73f + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0xaa}, + {value: 0x0040, lo: 0xab, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbc}, + {value: 0x0040, lo: 0xbd, hi: 0xbf}, + // Block 0xf6, offset 0x744 + {value: 0x0000, lo: 0x09}, + {value: 0x0008, lo: 0x80, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9b}, + {value: 0x0018, lo: 0x9c, hi: 0x9c}, + {value: 0x3308, lo: 0x9d, hi: 0x9e}, + {value: 0x0018, lo: 0x9f, hi: 0x9f}, + {value: 0x03c0, lo: 0xa0, hi: 0xa3}, + {value: 0x0040, lo: 0xa4, hi: 0xbf}, + // Block 0xf7, offset 0x74e + {value: 0x0000, lo: 0x03}, + {value: 0x3308, lo: 0x80, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xaf}, + {value: 0x3308, lo: 0xb0, hi: 0xbf}, + // Block 0xf8, offset 0x752 + {value: 0x0000, lo: 0x03}, + {value: 0x3308, lo: 0x80, hi: 0x86}, + {value: 0x0040, lo: 0x87, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0xbf}, + // Block 0xf9, offset 0x756 + {value: 0x0000, lo: 0x02}, + {value: 0x0018, lo: 0x80, hi: 0x83}, + {value: 0x0040, lo: 0x84, hi: 0xbf}, + // Block 0xfa, offset 0x759 + {value: 0x0000, lo: 0x02}, + {value: 0x0018, lo: 0x80, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xbf}, + // Block 0xfb, offset 0x75c + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0xa6}, + {value: 0x0040, lo: 0xa7, hi: 0xa8}, + {value: 0x0018, lo: 0xa9, hi: 0xbf}, + // Block 0xfc, offset 0x760 + {value: 0x0000, lo: 0x0e}, + {value: 0x0018, lo: 0x80, hi: 0x9d}, + {value: 0x2379, lo: 0x9e, hi: 0x9e}, + {value: 0x2381, lo: 0x9f, hi: 0x9f}, + {value: 0x2389, lo: 0xa0, hi: 0xa0}, + {value: 0x2391, lo: 0xa1, hi: 0xa1}, + {value: 0x2399, lo: 0xa2, hi: 0xa2}, + {value: 0x23a1, lo: 0xa3, hi: 0xa3}, + {value: 0x23a9, lo: 0xa4, hi: 0xa4}, + {value: 0x3018, lo: 0xa5, hi: 0xa6}, + {value: 0x3318, lo: 0xa7, hi: 0xa9}, + {value: 0x0018, lo: 0xaa, hi: 0xac}, + {value: 0x3018, lo: 0xad, hi: 0xb2}, + {value: 0x0340, lo: 0xb3, hi: 0xba}, + {value: 0x3318, lo: 0xbb, hi: 0xbf}, + // Block 0xfd, offset 0x76f + {value: 0x0000, lo: 0x0b}, + {value: 0x3318, lo: 0x80, hi: 0x82}, + {value: 0x0018, lo: 0x83, hi: 0x84}, + {value: 0x3318, lo: 0x85, hi: 0x8b}, + {value: 0x0018, lo: 0x8c, hi: 0xa9}, + {value: 0x3318, lo: 0xaa, hi: 0xad}, + {value: 0x0018, lo: 0xae, hi: 0xba}, + {value: 0x23b1, lo: 0xbb, hi: 0xbb}, + {value: 0x23b9, lo: 0xbc, hi: 0xbc}, + {value: 0x23c1, lo: 0xbd, hi: 0xbd}, + {value: 0x23c9, lo: 0xbe, hi: 0xbe}, + {value: 0x23d1, lo: 0xbf, hi: 0xbf}, + // Block 0xfe, offset 0x77b + {value: 0x0000, lo: 0x03}, + {value: 0x23d9, lo: 0x80, hi: 0x80}, + {value: 0x0018, lo: 0x81, hi: 0xaa}, + {value: 0x0040, lo: 0xab, hi: 0xbf}, + // Block 0xff, offset 0x77f + {value: 0x0000, lo: 0x04}, + {value: 0x0018, lo: 0x80, hi: 0x81}, + {value: 0x3318, lo: 0x82, hi: 0x84}, + {value: 0x0018, lo: 0x85, hi: 0x85}, + {value: 0x0040, lo: 0x86, hi: 0xbf}, + // Block 0x100, offset 0x784 + {value: 0x0000, lo: 0x04}, + {value: 0x0018, lo: 0x80, hi: 0x93}, + {value: 0x0040, lo: 0x94, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xb3}, + {value: 0x0040, lo: 0xb4, hi: 0xbf}, + // Block 0x101, offset 0x789 + {value: 0x0000, lo: 0x04}, + {value: 0x0018, lo: 0x80, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xb8}, + {value: 0x0040, lo: 0xb9, hi: 0xbf}, + // Block 0x102, offset 0x78e + {value: 0x0000, lo: 0x03}, + {value: 0x3308, lo: 0x80, hi: 0xb6}, + {value: 0x0018, lo: 0xb7, hi: 0xba}, + {value: 0x3308, lo: 0xbb, hi: 0xbf}, + // Block 0x103, offset 0x792 + {value: 0x0000, lo: 0x04}, + {value: 0x3308, lo: 0x80, hi: 0xac}, + {value: 0x0018, lo: 0xad, hi: 0xb4}, + {value: 0x3308, lo: 0xb5, hi: 0xb5}, + {value: 0x0018, lo: 0xb6, hi: 0xbf}, + // Block 0x104, offset 0x797 + {value: 0x0000, lo: 0x08}, + {value: 0x0018, lo: 0x80, hi: 0x83}, + {value: 0x3308, lo: 0x84, hi: 0x84}, + {value: 0x0018, lo: 0x85, hi: 0x8b}, + {value: 0x0040, lo: 0x8c, hi: 0x9a}, + {value: 0x3308, lo: 0x9b, hi: 0x9f}, + {value: 0x0040, lo: 0xa0, hi: 0xa0}, + {value: 0x3308, lo: 0xa1, hi: 0xaf}, + {value: 0x0040, lo: 0xb0, hi: 0xbf}, + // Block 0x105, offset 0x7a0 + {value: 0x0000, lo: 0x04}, + {value: 0x0008, lo: 0x80, hi: 0x9e}, + {value: 0x0040, lo: 0x9f, hi: 0xa4}, + {value: 0x0008, lo: 0xa5, hi: 0xaa}, + {value: 0x0040, lo: 0xab, hi: 0xbf}, + // Block 0x106, offset 0x7a5 + {value: 0x0000, lo: 0x03}, + {value: 0x0040, lo: 0x80, hi: 0x8e}, + {value: 0x3308, lo: 0x8f, hi: 0x8f}, + {value: 0x0040, lo: 0x90, hi: 0xbf}, + // Block 0x107, offset 0x7a9 + {value: 0x0000, lo: 0x05}, + {value: 0x0008, lo: 0x80, hi: 0xac}, + {value: 0x0040, lo: 0xad, hi: 0xaf}, + {value: 0x3308, lo: 0xb0, hi: 0xb6}, + {value: 0x0008, lo: 0xb7, hi: 0xbd}, + {value: 0x0040, lo: 0xbe, hi: 0xbf}, + // Block 0x108, offset 0x7af + {value: 0x0000, lo: 0x05}, + {value: 0x0008, lo: 0x80, hi: 0x89}, + {value: 0x0040, lo: 0x8a, hi: 0x8d}, + {value: 0x0008, lo: 0x8e, hi: 0x8e}, + {value: 0x0018, lo: 0x8f, hi: 0x8f}, + {value: 0x0040, lo: 0x90, hi: 0xbf}, + // Block 0x109, offset 0x7b5 + {value: 0x0000, lo: 0x04}, + {value: 0x0040, lo: 0x80, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xad}, + {value: 0x3308, lo: 0xae, hi: 0xae}, + {value: 0x0040, lo: 0xaf, hi: 0xbf}, + // Block 0x10a, offset 0x7ba + {value: 0x0000, lo: 0x05}, + {value: 0x0008, lo: 0x80, hi: 0xab}, + {value: 0x3308, lo: 0xac, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbe}, + {value: 0x0018, lo: 0xbf, hi: 0xbf}, + // Block 0x10b, offset 0x7c0 + {value: 0x0000, lo: 0x05}, + {value: 0x0040, lo: 0x80, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xab}, + {value: 0x3308, lo: 0xac, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbf}, + // Block 0x10c, offset 0x7c6 + {value: 0x0000, lo: 0x09}, + {value: 0x0040, lo: 0x80, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xa6}, + {value: 0x0040, lo: 0xa7, hi: 0xa7}, + {value: 0x0008, lo: 0xa8, hi: 0xab}, + {value: 0x0040, lo: 0xac, hi: 0xac}, + {value: 0x0008, lo: 0xad, hi: 0xae}, + {value: 0x0040, lo: 0xaf, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbe}, + {value: 0x0040, lo: 0xbf, hi: 0xbf}, + // Block 0x10d, offset 0x7d0 + {value: 0x0000, lo: 0x05}, + {value: 0x0808, lo: 0x80, hi: 0x84}, + {value: 0x0040, lo: 0x85, hi: 0x86}, + {value: 0x0818, lo: 0x87, hi: 0x8f}, + {value: 0x3308, lo: 0x90, hi: 0x96}, + {value: 0x0040, lo: 0x97, hi: 0xbf}, + // Block 0x10e, offset 0x7d6 + {value: 0x0000, lo: 0x08}, + {value: 0x0a08, lo: 0x80, hi: 0x83}, + {value: 0x3308, lo: 0x84, hi: 0x8a}, + {value: 0x0b08, lo: 0x8b, hi: 0x8b}, + {value: 0x0040, lo: 0x8c, hi: 0x8f}, + {value: 0x0808, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9d}, + {value: 0x0818, lo: 0x9e, hi: 0x9f}, + {value: 0x0040, lo: 0xa0, hi: 0xbf}, + // Block 0x10f, offset 0x7df + {value: 0x0000, lo: 0x02}, + {value: 0x0040, lo: 0x80, hi: 0xb0}, + {value: 0x0818, lo: 0xb1, hi: 0xbf}, + // Block 0x110, offset 0x7e2 + {value: 0x0000, lo: 0x02}, + {value: 0x0818, lo: 0x80, hi: 0xb4}, + {value: 0x0040, lo: 0xb5, hi: 0xbf}, + // Block 0x111, offset 0x7e5 + {value: 0x0000, lo: 0x03}, + {value: 0x0040, lo: 0x80, hi: 0x80}, + {value: 0x0818, lo: 0x81, hi: 0xbd}, + {value: 0x0040, lo: 0xbe, hi: 0xbf}, + // Block 0x112, offset 0x7e9 + {value: 0x0000, lo: 0x03}, + {value: 0x0040, lo: 0x80, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xb1}, + {value: 0x0040, lo: 0xb2, hi: 0xbf}, + // Block 0x113, offset 0x7ed + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0xab}, + {value: 0x0040, lo: 0xac, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xbf}, + // Block 0x114, offset 0x7f1 + {value: 0x0000, lo: 0x05}, + {value: 0x0018, lo: 0x80, hi: 0x93}, + {value: 0x0040, lo: 0x94, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xae}, + {value: 0x0040, lo: 0xaf, hi: 0xb0}, + {value: 0x0018, lo: 0xb1, hi: 0xbf}, + // Block 0x115, offset 0x7f7 + {value: 0x0000, lo: 0x05}, + {value: 0x0040, lo: 0x80, hi: 0x80}, + {value: 0x0018, lo: 0x81, hi: 0x8f}, + {value: 0x0040, lo: 0x90, hi: 0x90}, + {value: 0x0018, lo: 0x91, hi: 0xb5}, + {value: 0x0040, lo: 0xb6, hi: 0xbf}, + // Block 0x116, offset 0x7fd + {value: 0x0000, lo: 0x04}, + {value: 0x0018, lo: 0x80, hi: 0x8f}, + {value: 0x2709, lo: 0x90, hi: 0x90}, + {value: 0x0018, lo: 0x91, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xbf}, + // Block 0x117, offset 0x802 + {value: 0x0000, lo: 0x02}, + {value: 0x0040, lo: 0x80, hi: 0xa5}, + {value: 0x0018, lo: 0xa6, hi: 0xbf}, + // Block 0x118, offset 0x805 + {value: 0x0000, lo: 0x0f}, + {value: 0x2889, lo: 0x80, hi: 0x80}, + {value: 0x2891, lo: 0x81, hi: 0x81}, + {value: 0x2899, lo: 0x82, hi: 0x82}, + {value: 0x28a1, lo: 0x83, hi: 0x83}, + {value: 0x28a9, lo: 0x84, hi: 0x84}, + {value: 0x28b1, lo: 0x85, hi: 0x85}, + {value: 0x28b9, lo: 0x86, hi: 0x86}, + {value: 0x28c1, lo: 0x87, hi: 0x87}, + {value: 0x28c9, lo: 0x88, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x8f}, + {value: 0x28d1, lo: 0x90, hi: 0x90}, + {value: 0x28d9, lo: 0x91, hi: 0x91}, + {value: 0x0040, lo: 0x92, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xa5}, + {value: 0x0040, lo: 0xa6, hi: 0xbf}, + // Block 0x119, offset 0x815 + {value: 0x0000, lo: 0x06}, + {value: 0x0018, lo: 0x80, hi: 0x97}, + {value: 0x0040, lo: 0x98, hi: 0x9b}, + {value: 0x0018, lo: 0x9c, hi: 0xac}, + {value: 0x0040, lo: 0xad, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xbc}, + {value: 0x0040, lo: 0xbd, hi: 0xbf}, + // Block 0x11a, offset 0x81c + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0xb6}, + {value: 0x0040, lo: 0xb7, hi: 0xba}, + {value: 0x0018, lo: 0xbb, hi: 0xbf}, + // Block 0x11b, offset 0x820 + {value: 0x0000, lo: 0x06}, + {value: 0x0018, lo: 0x80, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xab}, + {value: 0x0040, lo: 0xac, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xb0}, + {value: 0x0040, lo: 0xb1, hi: 0xbf}, + // Block 0x11c, offset 0x827 + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0x8b}, + {value: 0x0040, lo: 0x8c, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0xbf}, + // Block 0x11d, offset 0x82b + {value: 0x0000, lo: 0x05}, + {value: 0x0018, lo: 0x80, hi: 0x87}, + {value: 0x0040, lo: 0x88, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0x99}, + {value: 0x0040, lo: 0x9a, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xbf}, + // Block 0x11e, offset 0x831 + {value: 0x0000, lo: 0x06}, + {value: 0x0018, lo: 0x80, hi: 0x87}, + {value: 0x0040, lo: 0x88, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xb1}, + {value: 0x0040, lo: 0xb2, hi: 0xbf}, + // Block 0x11f, offset 0x838 + {value: 0x0000, lo: 0x06}, + {value: 0x0018, lo: 0x80, hi: 0x93}, + {value: 0x0040, lo: 0x94, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xad}, + {value: 0x0040, lo: 0xae, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xbc}, + {value: 0x0040, lo: 0xbd, hi: 0xbf}, + // Block 0x120, offset 0x83f + {value: 0x0000, lo: 0x05}, + {value: 0x0018, lo: 0x80, hi: 0x88}, + {value: 0x0040, lo: 0x89, hi: 0x8f}, + {value: 0x0018, lo: 0x90, hi: 0xbd}, + {value: 0x0040, lo: 0xbe, hi: 0xbe}, + {value: 0x0018, lo: 0xbf, hi: 0xbf}, + // Block 0x121, offset 0x845 + {value: 0x0000, lo: 0x08}, + {value: 0x0018, lo: 0x80, hi: 0x85}, + {value: 0x0040, lo: 0x86, hi: 0x8d}, + {value: 0x0018, lo: 0x8e, hi: 0x9b}, + {value: 0x0040, lo: 0x9c, hi: 0x9f}, + {value: 0x0018, lo: 0xa0, hi: 0xa8}, + {value: 0x0040, lo: 0xa9, hi: 0xaf}, + {value: 0x0018, lo: 0xb0, hi: 0xb8}, + {value: 0x0040, lo: 0xb9, hi: 0xbf}, + // Block 0x122, offset 0x84e + {value: 0x0000, lo: 0x03}, + {value: 0x0018, lo: 0x80, hi: 0x92}, + {value: 0x0040, lo: 0x93, hi: 0x93}, + {value: 0x0018, lo: 0x94, hi: 0xbf}, + // Block 0x123, offset 0x852 + {value: 0x0000, lo: 0x0d}, + {value: 0x0018, lo: 0x80, hi: 0x8a}, + {value: 0x0040, lo: 0x8b, hi: 0xaf}, + {value: 0x06e1, lo: 0xb0, hi: 0xb0}, + {value: 0x0049, lo: 0xb1, hi: 0xb1}, + {value: 0x0029, lo: 0xb2, hi: 0xb2}, + {value: 0x0031, lo: 0xb3, hi: 0xb3}, + {value: 0x06e9, lo: 0xb4, hi: 0xb4}, + {value: 0x06f1, lo: 0xb5, hi: 0xb5}, + {value: 0x06f9, lo: 0xb6, hi: 0xb6}, + {value: 0x0701, lo: 0xb7, hi: 0xb7}, + {value: 0x0709, lo: 0xb8, hi: 0xb8}, + {value: 0x0711, lo: 0xb9, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbf}, + // Block 0x124, offset 0x860 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0x9f}, + {value: 0x0040, lo: 0xa0, hi: 0xbf}, + // Block 0x125, offset 0x863 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xb9}, + {value: 0x0040, lo: 0xba, hi: 0xbf}, + // Block 0x126, offset 0x866 + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0x9d}, + {value: 0x0040, lo: 0x9e, hi: 0x9f}, + {value: 0x0008, lo: 0xa0, hi: 0xbf}, + // Block 0x127, offset 0x86a + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0xa1}, + {value: 0x0040, lo: 0xa2, hi: 0xaf}, + {value: 0x0008, lo: 0xb0, hi: 0xbf}, + // Block 0x128, offset 0x86e + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xa0}, + {value: 0x0040, lo: 0xa1, hi: 0xbf}, + // Block 0x129, offset 0x871 + {value: 0x0000, lo: 0x03}, + {value: 0x0008, lo: 0x80, hi: 0x8a}, + {value: 0x0040, lo: 0x8b, hi: 0x8f}, + {value: 0x0008, lo: 0x90, hi: 0xbf}, + // Block 0x12a, offset 0x875 + {value: 0x0000, lo: 0x02}, + {value: 0x0008, lo: 0x80, hi: 0xaf}, + {value: 0x0040, lo: 0xb0, hi: 0xbf}, + // Block 0x12b, offset 0x878 + {value: 0x0000, lo: 0x04}, + {value: 0x0040, lo: 0x80, hi: 0x80}, + {value: 0x0340, lo: 0x81, hi: 0x81}, + {value: 0x0040, lo: 0x82, hi: 0x9f}, + {value: 0x0340, lo: 0xa0, hi: 0xbf}, + // Block 0x12c, offset 0x87d + {value: 0x0000, lo: 0x01}, + {value: 0x0340, lo: 0x80, hi: 0xbf}, + // Block 0x12d, offset 0x87f + {value: 0x0000, lo: 0x01}, + {value: 0x33c0, lo: 0x80, hi: 0xbf}, + // Block 0x12e, offset 0x881 + {value: 0x0000, lo: 0x02}, + {value: 0x33c0, lo: 0x80, hi: 0xaf}, + {value: 0x0040, lo: 0xb0, hi: 0xbf}, +} + +// Total table size 46723 bytes (45KiB); checksum: 4CF3143A diff --git a/idna/trie.go b/idna/trie.go index c4ef847e7..421274172 100644 --- a/idna/trie.go +++ b/idna/trie.go @@ -6,27 +6,6 @@ package idna -// appendMapping appends the mapping for the respective rune. isMapped must be -// true. A mapping is a categorization of a rune as defined in UTS #46. -func (c info) appendMapping(b []byte, s string) []byte { - index := int(c >> indexShift) - if c&xorBit == 0 { - s := mappings[index:] - return append(b, s[1:s[0]+1]...) - } - b = append(b, s...) - if c&inlineXOR == inlineXOR { - // TODO: support and handle two-byte inline masks - b[len(b)-1] ^= byte(index) - } else { - for p := len(b) - int(xorData[index]); p < len(b); p++ { - index++ - b[p] ^= xorData[index] - } - } - return b -} - // Sparse block handling code. type valueRange struct { diff --git a/idna/trie12.0.0.go b/idna/trie12.0.0.go new file mode 100644 index 000000000..bb63f904b --- /dev/null +++ b/idna/trie12.0.0.go @@ -0,0 +1,31 @@ +// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. + +// Copyright 2016 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. + +//go:build !go1.16 +// +build !go1.16 + +package idna + +// appendMapping appends the mapping for the respective rune. isMapped must be +// true. A mapping is a categorization of a rune as defined in UTS #46. +func (c info) appendMapping(b []byte, s string) []byte { + index := int(c >> indexShift) + if c&xorBit == 0 { + s := mappings[index:] + return append(b, s[1:s[0]+1]...) + } + b = append(b, s...) + if c&inlineXOR == inlineXOR { + // TODO: support and handle two-byte inline masks + b[len(b)-1] ^= byte(index) + } else { + for p := len(b) - int(xorData[index]); p < len(b); p++ { + index++ + b[p] ^= xorData[index] + } + } + return b +} diff --git a/idna/trie13.0.0.go b/idna/trie13.0.0.go new file mode 100644 index 000000000..7d68a8dc1 --- /dev/null +++ b/idna/trie13.0.0.go @@ -0,0 +1,31 @@ +// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT. + +// Copyright 2016 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. + +//go:build go1.16 +// +build go1.16 + +package idna + +// appendMapping appends the mapping for the respective rune. isMapped must be +// true. A mapping is a categorization of a rune as defined in UTS #46. +func (c info) appendMapping(b []byte, s string) []byte { + index := int(c >> indexShift) + if c&xorBit == 0 { + p := index + return append(b, mappings[mappingIndex[p]:mappingIndex[p+1]]...) + } + b = append(b, s...) + if c&inlineXOR == inlineXOR { + // TODO: support and handle two-byte inline masks + b[len(b)-1] ^= byte(index) + } else { + for p := len(b) - int(xorData[index]); p < len(b); p++ { + index++ + b[p] ^= xorData[index] + } + } + return b +} diff --git a/internal/quic/ack_delay.go b/internal/quic/ack_delay.go new file mode 100644 index 000000000..66bdf3cbc --- /dev/null +++ b/internal/quic/ack_delay.go @@ -0,0 +1,28 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "math" + "time" +) + +// An unscaledAckDelay is an ACK Delay field value from an ACK packet, +// without the ack_delay_exponent scaling applied. +type unscaledAckDelay int64 + +func unscaledAckDelayFromDuration(d time.Duration, ackDelayExponent uint8) unscaledAckDelay { + return unscaledAckDelay(d.Microseconds() >> ackDelayExponent) +} + +func (d unscaledAckDelay) Duration(ackDelayExponent uint8) time.Duration { + if int64(d) > (math.MaxInt64>>ackDelayExponent)/int64(time.Microsecond) { + // If scaling the delay would overflow, ignore the delay. + return 0 + } + return time.Duration(d< num { + // We've discarded the state for this range of packet numbers. + // Discard the packet rather than potentially processing a duplicate. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.2.3-5 + return false + } + if acks.seen.contains(num) { + // Discard duplicate packets. + return false + } + return true +} + +// receive records receipt of a packet. +func (acks *ackState) receive(now time.Time, space numberSpace, num packetNumber, ackEliciting bool) { + if ackEliciting { + acks.unackedAckEliciting++ + if acks.mustAckImmediately(space, num) { + acks.nextAck = now + } else if acks.nextAck.IsZero() { + // This packet does not need to be acknowledged immediately, + // but the ack must not be intentionally delayed by more than + // the max_ack_delay transport parameter we sent to the peer. + // + // We always delay acks by the maximum allowed, less the timer + // granularity. ("[max_ack_delay] SHOULD include the receiver's + // expected delays in alarms firing.") + // + // https://www.rfc-editor.org/rfc/rfc9000#section-18.2-4.28.1 + acks.nextAck = now.Add(maxAckDelay - timerGranularity) + } + if num > acks.maxAckEliciting { + acks.maxAckEliciting = num + } + } + + acks.seen.add(num, num+1) + if num == acks.seen.max() { + acks.maxRecvTime = now + } + + // Limit the total number of ACK ranges by dropping older ranges. + // + // Remembering more ranges results in larger ACK frames. + // + // Remembering a large number of ranges could result in ACK frames becoming + // too large to fit in a packet, in which case we will silently drop older + // ranges during packet construction. + // + // Remembering fewer ranges can result in unnecessary retransmissions, + // since we cannot accept packets older than the oldest remembered range. + // + // The limit here is completely arbitrary. If it seems wrong, it probably is. + // + // https://www.rfc-editor.org/rfc/rfc9000#section-13.2.3 + const maxAckRanges = 8 + if overflow := acks.seen.numRanges() - maxAckRanges; overflow > 0 { + acks.seen.removeranges(0, overflow) + } +} + +// mustAckImmediately reports whether an ack-eliciting packet must be acknowledged immediately, +// or whether the ack may be deferred. +func (acks *ackState) mustAckImmediately(space numberSpace, num packetNumber) bool { + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.2.1 + if space != appDataSpace { + // "[...] all ack-eliciting Initial and Handshake packets [...]" + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.2.1-2 + return true + } + if num < acks.maxAckEliciting { + // "[...] when the received packet has a packet number less than another + // ack-eliciting packet that has been received [...]" + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.2.1-8.1 + return true + } + if acks.seen.rangeContaining(acks.maxAckEliciting).end != num { + // "[...] when the packet has a packet number larger than the highest-numbered + // ack-eliciting packet that has been received and there are missing packets + // between that packet and this packet." + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.2.1-8.2 + // + // This case is a bit tricky. Let's say we've received: + // 0, ack-eliciting + // 1, ack-eliciting + // 3, NOT ack eliciting + // + // We have sent ACKs for 0 and 1. If we receive ack-eliciting packet 2, + // we do not need to send an immediate ACK, because there are no missing + // packets between it and the highest-numbered ack-eliciting packet (1). + // If we receive ack-eliciting packet 4, we do need to send an immediate ACK, + // because there's a gap (the missing packet 2). + // + // We check for this by looking up the ACK range which contains the + // highest-numbered ack-eliciting packet: [0, 1) in the above example. + // If the range ends just before the packet we are now processing, + // there are no gaps. If it does not, there must be a gap. + return true + } + if acks.unackedAckEliciting >= 2 { + // "[...] after receiving at least two ack-eliciting packets." + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.2.2 + return true + } + return false +} + +// shouldSendAck reports whether the connection should send an ACK frame at this time, +// in an ACK-only packet if necessary. +func (acks *ackState) shouldSendAck(now time.Time) bool { + return !acks.nextAck.IsZero() && !acks.nextAck.After(now) +} + +// acksToSend returns the set of packet numbers to ACK at this time, and the current ack delay. +// It may return acks even if shouldSendAck returns false, when there are unacked +// ack-eliciting packets whose ack is being delayed. +func (acks *ackState) acksToSend(now time.Time) (nums rangeset[packetNumber], ackDelay time.Duration) { + if acks.nextAck.IsZero() && acks.unackedAckEliciting == 0 { + return nil, 0 + } + // "[...] the delays intentionally introduced between the time the packet with the + // largest packet number is received and the time an acknowledgement is sent." + // https://www.rfc-editor.org/rfc/rfc9000#section-13.2.5-1 + delay := now.Sub(acks.maxRecvTime) + if delay < 0 { + delay = 0 + } + return acks.seen, delay +} + +// sentAck records that an ACK frame has been sent. +func (acks *ackState) sentAck() { + acks.nextAck = time.Time{} + acks.unackedAckEliciting = 0 +} + +// handleAck records that an ack has been received for a ACK frame we sent +// containing the given Largest Acknowledged field. +func (acks *ackState) handleAck(largestAcked packetNumber) { + // We can stop acking packets less or equal to largestAcked. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.2.4-1 + // + // We rely on acks.seen containing the largest packet number that has been successfully + // processed, so we retain the range containing largestAcked and discard previous ones. + acks.seen.sub(0, acks.seen.rangeContaining(largestAcked).start) +} + +// largestSeen reports the largest seen packet. +func (acks *ackState) largestSeen() packetNumber { + return acks.seen.max() +} diff --git a/internal/quic/acks_test.go b/internal/quic/acks_test.go new file mode 100644 index 000000000..4f1032910 --- /dev/null +++ b/internal/quic/acks_test.go @@ -0,0 +1,248 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "testing" + "time" +) + +func TestAcksDisallowDuplicate(t *testing.T) { + // Don't process a packet that we've seen before. + acks := ackState{} + now := time.Now() + receive := []packetNumber{0, 1, 2, 4, 7, 6, 9} + seen := map[packetNumber]bool{} + for i, pnum := range receive { + acks.receive(now, appDataSpace, pnum, true) + seen[pnum] = true + for ppnum := packetNumber(0); ppnum < 11; ppnum++ { + if got, want := acks.shouldProcess(ppnum), !seen[ppnum]; got != want { + t.Fatalf("after receiving %v: acks.shouldProcess(%v) = %v, want %v", receive[:i+1], ppnum, got, want) + } + } + } +} + +func TestAcksDisallowDiscardedAckRanges(t *testing.T) { + // Don't process a packet with a number in a discarded range. + acks := ackState{} + now := time.Now() + for pnum := packetNumber(0); ; pnum += 2 { + acks.receive(now, appDataSpace, pnum, true) + send, _ := acks.acksToSend(now) + for ppnum := packetNumber(0); ppnum < packetNumber(send.min()); ppnum++ { + if acks.shouldProcess(ppnum) { + t.Fatalf("after limiting ack ranges to %v: acks.shouldProcess(%v) (in discarded range) = true, want false", send, ppnum) + } + } + if send.min() > 10 { + break + } + } +} + +func TestAcksSent(t *testing.T) { + type packet struct { + pnum packetNumber + ackEliciting bool + } + for _, test := range []struct { + name string + space numberSpace + + // ackedPackets and packets are packets that we receive. + // After receiving all packets in ackedPackets, we send an ack. + // Then we receive the subsequent packets in packets. + ackedPackets []packet + packets []packet + + wantDelay time.Duration + wantAcks rangeset[packetNumber] + }{{ + name: "no packets to ack", + space: initialSpace, + }, { + name: "non-ack-eliciting packets are not acked", + space: initialSpace, + packets: []packet{{ + pnum: 0, + ackEliciting: false, + }}, + }, { + name: "ack-eliciting Initial packets are acked immediately", + space: initialSpace, + packets: []packet{{ + pnum: 0, + ackEliciting: true, + }}, + wantAcks: rangeset[packetNumber]{{0, 1}}, + wantDelay: 0, + }, { + name: "ack-eliciting Handshake packets are acked immediately", + space: handshakeSpace, + packets: []packet{{ + pnum: 0, + ackEliciting: true, + }}, + wantAcks: rangeset[packetNumber]{{0, 1}}, + wantDelay: 0, + }, { + name: "ack-eliciting AppData packets are acked after max_ack_delay", + space: appDataSpace, + packets: []packet{{ + pnum: 0, + ackEliciting: true, + }}, + wantAcks: rangeset[packetNumber]{{0, 1}}, + wantDelay: maxAckDelay - timerGranularity, + }, { + name: "reordered ack-eliciting packets are acked immediately", + space: appDataSpace, + ackedPackets: []packet{{ + pnum: 1, + ackEliciting: true, + }}, + packets: []packet{{ + pnum: 0, + ackEliciting: true, + }}, + wantAcks: rangeset[packetNumber]{{0, 2}}, + wantDelay: 0, + }, { + name: "gaps in ack-eliciting packets are acked immediately", + space: appDataSpace, + packets: []packet{{ + pnum: 1, + ackEliciting: true, + }}, + wantAcks: rangeset[packetNumber]{{1, 2}}, + wantDelay: 0, + }, { + name: "reordered non-ack-eliciting packets are not acked immediately", + space: appDataSpace, + ackedPackets: []packet{{ + pnum: 1, + ackEliciting: true, + }}, + packets: []packet{{ + pnum: 2, + ackEliciting: true, + }, { + pnum: 0, + ackEliciting: false, + }, { + pnum: 4, + ackEliciting: false, + }}, + wantAcks: rangeset[packetNumber]{{0, 3}, {4, 5}}, + wantDelay: maxAckDelay - timerGranularity, + }, { + name: "immediate ack after two ack-eliciting packets are received", + space: appDataSpace, + packets: []packet{{ + pnum: 0, + ackEliciting: true, + }, { + pnum: 1, + ackEliciting: true, + }}, + wantAcks: rangeset[packetNumber]{{0, 2}}, + wantDelay: 0, + }} { + t.Run(test.name, func(t *testing.T) { + acks := ackState{} + start := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + for _, p := range test.ackedPackets { + t.Logf("receive %v.%v, ack-eliciting=%v", test.space, p.pnum, p.ackEliciting) + acks.receive(start, test.space, p.pnum, p.ackEliciting) + } + t.Logf("send an ACK frame") + acks.sentAck() + for _, p := range test.packets { + t.Logf("receive %v.%v, ack-eliciting=%v", test.space, p.pnum, p.ackEliciting) + acks.receive(start, test.space, p.pnum, p.ackEliciting) + } + switch { + case len(test.wantAcks) == 0: + // No ACK should be sent, even well after max_ack_delay. + if acks.shouldSendAck(start.Add(10 * maxAckDelay)) { + t.Errorf("acks.shouldSendAck(T+10*max_ack_delay) = true, want false") + } + case test.wantDelay > 0: + // No ACK should be sent before a delay. + if acks.shouldSendAck(start.Add(test.wantDelay - 1)) { + t.Errorf("acks.shouldSendAck(T+%v-1ns) = true, want false", test.wantDelay) + } + fallthrough + default: + // ACK should be sent after a delay. + if !acks.shouldSendAck(start.Add(test.wantDelay)) { + t.Errorf("acks.shouldSendAck(T+%v) = false, want true", test.wantDelay) + } + } + // acksToSend always reports the available packets that can be acked, + // and the amount of time that has passed since the most recent acked + // packet was received. + for _, delay := range []time.Duration{ + 0, + test.wantDelay, + test.wantDelay + 1, + } { + gotNums, gotDelay := acks.acksToSend(start.Add(delay)) + wantDelay := delay + if len(gotNums) == 0 { + wantDelay = 0 + } + if !slicesEqual(gotNums, test.wantAcks) || gotDelay != wantDelay { + t.Errorf("acks.acksToSend(T+%v) = %v, %v; want %v, %v", delay, gotNums, gotDelay, test.wantAcks, wantDelay) + } + } + }) + } +} + +// slicesEqual reports whether two slices are equal. +// Replace this with slices.Equal once the module go.mod is go1.17 or newer. +func slicesEqual[E comparable](s1, s2 []E) bool { + if len(s1) != len(s2) { + return false + } + for i := range s1 { + if s1[i] != s2[i] { + return false + } + } + return true +} + +func TestAcksDiscardAfterAck(t *testing.T) { + acks := ackState{} + now := time.Now() + acks.receive(now, appDataSpace, 0, true) + acks.receive(now, appDataSpace, 2, true) + acks.receive(now, appDataSpace, 4, true) + acks.receive(now, appDataSpace, 5, true) + acks.receive(now, appDataSpace, 6, true) + acks.handleAck(6) // discards all ranges prior to the one containing packet 6 + acks.receive(now, appDataSpace, 7, true) + got, _ := acks.acksToSend(now) + if len(got) != 1 { + t.Errorf("acks.acksToSend contains ranges prior to last acknowledged ack; got %v, want 1 range", got) + } +} + +func TestAcksLargestSeen(t *testing.T) { + acks := ackState{} + now := time.Now() + acks.receive(now, appDataSpace, 0, true) + acks.receive(now, appDataSpace, 4, true) + acks.receive(now, appDataSpace, 1, true) + if got, want := acks.largestSeen(), packetNumber(4); got != want { + t.Errorf("acks.largestSeen() = %v, want %v", got, want) + } +} diff --git a/internal/quic/atomic_bits.go b/internal/quic/atomic_bits.go new file mode 100644 index 000000000..e1e2594d1 --- /dev/null +++ b/internal/quic/atomic_bits.go @@ -0,0 +1,33 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "sync/atomic" + +// atomicBits is an atomic uint32 that supports setting individual bits. +type atomicBits[T ~uint32] struct { + bits atomic.Uint32 +} + +// set sets the bits in mask to the corresponding bits in v. +// It returns the new value. +func (a *atomicBits[T]) set(v, mask T) T { + if v&^mask != 0 { + panic("BUG: bits in v are not in mask") + } + for { + o := a.bits.Load() + n := (o &^ uint32(mask)) | uint32(v) + if a.bits.CompareAndSwap(o, n) { + return T(n) + } + } +} + +func (a *atomicBits[T]) load() T { + return T(a.bits.Load()) +} diff --git a/internal/quic/config.go b/internal/quic/config.go new file mode 100644 index 000000000..b390d6911 --- /dev/null +++ b/internal/quic/config.go @@ -0,0 +1,81 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "crypto/tls" +) + +// A Config structure configures a QUIC endpoint. +// A Config must not be modified after it has been passed to a QUIC function. +// A Config may be reused; the quic package will also not modify it. +type Config struct { + // TLSConfig is the endpoint's TLS configuration. + // It must be non-nil and include at least one certificate or else set GetCertificate. + TLSConfig *tls.Config + + // MaxBidiRemoteStreams limits the number of simultaneous bidirectional streams + // a peer may open. + // If zero, the default value of 100 is used. + // If negative, the limit is zero. + MaxBidiRemoteStreams int64 + + // MaxUniRemoteStreams limits the number of simultaneous unidirectional streams + // a peer may open. + // If zero, the default value of 100 is used. + // If negative, the limit is zero. + MaxUniRemoteStreams int64 + + // MaxStreamReadBufferSize is the maximum amount of data sent by the peer that a + // stream will buffer for reading. + // If zero, the default value of 1MiB is used. + // If negative, the limit is zero. + MaxStreamReadBufferSize int64 + + // MaxStreamWriteBufferSize is the maximum amount of data a stream will buffer for + // sending to the peer. + // If zero, the default value of 1MiB is used. + // If negative, the limit is zero. + MaxStreamWriteBufferSize int64 + + // MaxConnReadBufferSize is the maximum amount of data sent by the peer that a + // connection will buffer for reading, across all streams. + // If zero, the default value of 1MiB is used. + // If negative, the limit is zero. + MaxConnReadBufferSize int64 +} + +func configDefault(v, def, limit int64) int64 { + switch { + case v == 0: + return def + case v < 0: + return 0 + default: + return min(v, limit) + } +} + +func (c *Config) maxBidiRemoteStreams() int64 { + return configDefault(c.MaxBidiRemoteStreams, 100, maxStreamsLimit) +} + +func (c *Config) maxUniRemoteStreams() int64 { + return configDefault(c.MaxUniRemoteStreams, 100, maxStreamsLimit) +} + +func (c *Config) maxStreamReadBufferSize() int64 { + return configDefault(c.MaxStreamReadBufferSize, 1<<20, maxVarint) +} + +func (c *Config) maxStreamWriteBufferSize() int64 { + return configDefault(c.MaxStreamWriteBufferSize, 1<<20, maxVarint) +} + +func (c *Config) maxConnReadBufferSize() int64 { + return configDefault(c.MaxConnReadBufferSize, 1<<20, maxVarint) +} diff --git a/internal/quic/config_test.go b/internal/quic/config_test.go new file mode 100644 index 000000000..d292854f5 --- /dev/null +++ b/internal/quic/config_test.go @@ -0,0 +1,47 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "testing" + +func TestConfigTransportParameters(t *testing.T) { + const ( + wantInitialMaxData = int64(1) + wantInitialMaxStreamData = int64(2) + wantInitialMaxStreamsBidi = int64(3) + wantInitialMaxStreamsUni = int64(4) + ) + tc := newTestConn(t, clientSide, func(c *Config) { + c.MaxBidiRemoteStreams = wantInitialMaxStreamsBidi + c.MaxUniRemoteStreams = wantInitialMaxStreamsUni + c.MaxStreamReadBufferSize = wantInitialMaxStreamData + c.MaxConnReadBufferSize = wantInitialMaxData + }) + tc.handshake() + if tc.sentTransportParameters == nil { + t.Fatalf("conn didn't send transport parameters during handshake") + } + p := tc.sentTransportParameters + if got, want := p.initialMaxData, wantInitialMaxData; got != want { + t.Errorf("initial_max_data = %v, want %v", got, want) + } + if got, want := p.initialMaxStreamDataBidiLocal, wantInitialMaxStreamData; got != want { + t.Errorf("initial_max_stream_data_bidi_local = %v, want %v", got, want) + } + if got, want := p.initialMaxStreamDataBidiRemote, wantInitialMaxStreamData; got != want { + t.Errorf("initial_max_stream_data_bidi_remote = %v, want %v", got, want) + } + if got, want := p.initialMaxStreamDataUni, wantInitialMaxStreamData; got != want { + t.Errorf("initial_max_stream_data_uni = %v, want %v", got, want) + } + if got, want := p.initialMaxStreamsBidi, wantInitialMaxStreamsBidi; got != want { + t.Errorf("initial_max_stream_data_uni = %v, want %v", got, want) + } + if got, want := p.initialMaxStreamsUni, wantInitialMaxStreamsUni; got != want { + t.Errorf("initial_max_stream_data_uni = %v, want %v", got, want) + } +} diff --git a/internal/quic/congestion_reno.go b/internal/quic/congestion_reno.go new file mode 100644 index 000000000..982cbf4bb --- /dev/null +++ b/internal/quic/congestion_reno.go @@ -0,0 +1,255 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "math" + "time" +) + +// ccReno is the NewReno-based congestion controller defined in RFC 9002. +// https://www.rfc-editor.org/rfc/rfc9002.html#section-7 +type ccReno struct { + maxDatagramSize int + + // Maximum number of bytes allowed to be in flight. + congestionWindow int + + // Sum of size of all packets that contain at least one ack-eliciting + // or PADDING frame (i.e., any non-ACK frame), and have neither been + // acknowledged nor declared lost. + bytesInFlight int + + // When the congestion window is below the slow start threshold, + // the controller is in slow start. + slowStartThreshold int + + // The time the current recovery period started, or zero when not + // in a recovery period. + recoveryStartTime time.Time + + // Accumulated count of bytes acknowledged in congestion avoidance. + congestionPendingAcks int + + // When entering a recovery period, we are allowed to send one packet + // before reducing the congestion window. sendOnePacketInRecovery is + // true if we haven't sent that packet yet. + sendOnePacketInRecovery bool + + // underutilized is set if the congestion window is underutilized + // due to insufficient application data, flow control limits, or + // anti-amplification limits. + underutilized bool + + // ackLastLoss is the sent time of the newest lost packet processed + // in the current batch. + ackLastLoss time.Time + + // Data tracking the duration of the most recently handled sequence of + // contiguous lost packets. If this exceeds the persistent congestion duration, + // persistent congestion is declared. + // + // https://www.rfc-editor.org/rfc/rfc9002#section-7.6 + persistentCongestion [numberSpaceCount]struct { + start time.Time // send time of first lost packet + end time.Time // send time of last lost packet + next packetNumber // one plus the number of the last lost packet + } +} + +func newReno(maxDatagramSize int) *ccReno { + c := &ccReno{ + maxDatagramSize: maxDatagramSize, + } + + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.2-1 + c.congestionWindow = min(10*maxDatagramSize, max(14720, c.minimumCongestionWindow())) + + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.1-1 + c.slowStartThreshold = math.MaxInt + + for space := range c.persistentCongestion { + c.persistentCongestion[space].next = -1 + } + return c +} + +// canSend reports whether the congestion controller permits sending +// a maximum-size datagram at this time. +// +// "An endpoint MUST NOT send a packet if it would cause bytes_in_flight [...] +// to be larger than the congestion window [...]" +// https://www.rfc-editor.org/rfc/rfc9002#section-7-7 +// +// For simplicity and efficiency, we don't permit sending undersized datagrams. +func (c *ccReno) canSend() bool { + if c.sendOnePacketInRecovery { + return true + } + return c.bytesInFlight+c.maxDatagramSize <= c.congestionWindow +} + +// setUnderutilized indicates that the congestion window is underutilized. +// +// The congestion window is underutilized if bytes in flight is smaller than +// the congestion window and sending is not pacing limited; that is, the +// congestion controller permits sending data, but no data is sent. +// +// https://www.rfc-editor.org/rfc/rfc9002#section-7.8 +func (c *ccReno) setUnderutilized(v bool) { + c.underutilized = v +} + +// packetSent indicates that a packet has been sent. +func (c *ccReno) packetSent(now time.Time, space numberSpace, sent *sentPacket) { + if !sent.inFlight { + return + } + c.bytesInFlight += sent.size + if c.sendOnePacketInRecovery { + c.sendOnePacketInRecovery = false + } +} + +// Acked and lost packets are processed in batches +// resulting from either a received ACK frame or +// the loss detection timer expiring. +// +// A batch consists of zero or more calls to packetAcked and packetLost, +// followed by a single call to packetBatchEnd. +// +// Acks may be reported in any order, but lost packets must +// be reported in strictly increasing order. + +// packetAcked indicates that a packet has been newly acknowledged. +func (c *ccReno) packetAcked(now time.Time, sent *sentPacket) { + if !sent.inFlight { + return + } + c.bytesInFlight -= sent.size + + if c.underutilized { + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.8 + return + } + if sent.time.Before(c.recoveryStartTime) { + // In recovery, and this packet was sent before we entered recovery. + // (If this packet was sent after we entered recovery, receiving an ack + // for it moves us out of recovery into congestion avoidance.) + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.2 + return + } + c.congestionPendingAcks += sent.size +} + +// packetLost indicates that a packet has been newly marked as lost. +// Lost packets must be reported in increasing order. +func (c *ccReno) packetLost(now time.Time, space numberSpace, sent *sentPacket, rtt *rttState) { + // Record state to check for persistent congestion. + // https://www.rfc-editor.org/rfc/rfc9002#section-7.6 + // + // Note that this relies on always receiving loss events in increasing order: + // All packets prior to the one we're examining now have either been + // acknowledged or declared lost. + isValidPersistentCongestionSample := (sent.ackEliciting && + !rtt.firstSampleTime.IsZero() && + !sent.time.Before(rtt.firstSampleTime)) + if isValidPersistentCongestionSample { + // This packet either extends an existing range of lost packets, + // or starts a new one. + if sent.num != c.persistentCongestion[space].next { + c.persistentCongestion[space].start = sent.time + } + c.persistentCongestion[space].end = sent.time + c.persistentCongestion[space].next = sent.num + 1 + } else { + // This packet cannot establish persistent congestion on its own. + // However, if we have an existing range of lost packets, + // this does not break it. + if sent.num == c.persistentCongestion[space].next { + c.persistentCongestion[space].next = sent.num + 1 + } + } + + if !sent.inFlight { + return + } + c.bytesInFlight -= sent.size + if sent.time.After(c.ackLastLoss) { + c.ackLastLoss = sent.time + } +} + +// packetBatchEnd is called at the end of processing a batch of acked or lost packets. +func (c *ccReno) packetBatchEnd(now time.Time, space numberSpace, rtt *rttState, maxAckDelay time.Duration) { + if !c.ackLastLoss.IsZero() && !c.ackLastLoss.Before(c.recoveryStartTime) { + // Enter the recovery state. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.2 + c.recoveryStartTime = now + c.slowStartThreshold = c.congestionWindow / 2 + c.congestionWindow = max(c.slowStartThreshold, c.minimumCongestionWindow()) + c.sendOnePacketInRecovery = true + // Clear congestionPendingAcks to avoid increasing the congestion + // window based on acks in a frame that sends us into recovery. + c.congestionPendingAcks = 0 + } else if c.congestionPendingAcks > 0 { + // We are in slow start or congestion avoidance. + if c.congestionWindow < c.slowStartThreshold { + // When the congestion window is less than the slow start threshold, + // we are in slow start and increase the window by the number of + // bytes acknowledged. + d := min(c.slowStartThreshold-c.congestionWindow, c.congestionPendingAcks) + c.congestionWindow += d + c.congestionPendingAcks -= d + } + // When the congestion window is at or above the slow start threshold, + // we are in congestion avoidance. + // + // RFC 9002 does not specify an algorithm here. The following is + // the recommended algorithm from RFC 5681, in which we increment + // the window by the maximum datagram size every time the number + // of bytes acknowledged reaches cwnd. + for c.congestionPendingAcks > c.congestionWindow { + c.congestionPendingAcks -= c.congestionWindow + c.congestionWindow += c.maxDatagramSize + } + } + if !c.ackLastLoss.IsZero() { + // Check for persistent congestion. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.6 + // + // "A sender [...] MAY use state for just the packet number space that + // was acknowledged." + // https://www.rfc-editor.org/rfc/rfc9002#section-7.6.2-5 + // + // For simplicity, we consider each number space independently. + const persistentCongestionThreshold = 3 + d := (rtt.smoothedRTT + max(4*rtt.rttvar, timerGranularity) + maxAckDelay) * + persistentCongestionThreshold + start := c.persistentCongestion[space].start + end := c.persistentCongestion[space].end + if end.Sub(start) >= d { + c.congestionWindow = c.minimumCongestionWindow() + c.recoveryStartTime = time.Time{} + rtt.establishPersistentCongestion() + } + } + c.ackLastLoss = time.Time{} +} + +// packetDiscarded indicates that the keys for a packet's space have been discarded. +func (c *ccReno) packetDiscarded(sent *sentPacket) { + // https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-3 + if sent.inFlight { + c.bytesInFlight -= sent.size + } +} + +func (c *ccReno) minimumCongestionWindow() int { + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.2-4 + return 2 * c.maxDatagramSize +} diff --git a/internal/quic/congestion_reno_test.go b/internal/quic/congestion_reno_test.go new file mode 100644 index 000000000..e9af6452c --- /dev/null +++ b/internal/quic/congestion_reno_test.go @@ -0,0 +1,550 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "testing" + "time" +) + +func TestRenoInitialCongestionWindow(t *testing.T) { + // https://www.rfc-editor.org/rfc/rfc9002#section-7.2-1 + for _, test := range []struct { + maxDatagramSize int + wantWindow int + }{{ + // "[...] ten times the maximum datagram size [...]" + maxDatagramSize: 1200, + wantWindow: 12000, + }, { + // [...] limiting the window to the larger of 14,720 bytes [...]" + maxDatagramSize: 1500, + wantWindow: 14720, + }, { + // [...] or twice the maximum datagram size." + maxDatagramSize: 15000, + wantWindow: 30000, + }} { + c := newReno(test.maxDatagramSize) + if got, want := c.congestionWindow, test.wantWindow; got != want { + t.Errorf("newReno(max_datagram_size=%v): congestion_window = %v, want %v", + test.maxDatagramSize, got, want) + } + } +} + +func TestRenoSlowStartWindowIncreases(t *testing.T) { + // "[...] the congestion window increases by the number of bytes acknowledged [...]" + // https://www.rfc-editor.org/rfc/rfc9002#section-7.3.1-2 + test := newRenoTest(t, 1200) + + p0 := test.packetSent(initialSpace, 1200) + test.wantVar("congestion_window", 12000) + test.packetAcked(initialSpace, p0) + test.packetBatchEnd(initialSpace) + test.wantVar("congestion_window", 12000+1200) + + p1 := test.packetSent(handshakeSpace, 600) + p2 := test.packetSent(handshakeSpace, 300) + test.packetAcked(handshakeSpace, p1) + test.packetAcked(handshakeSpace, p2) + test.packetBatchEnd(handshakeSpace) + test.wantVar("congestion_window", 12000+1200+600+300) +} + +func TestRenoSlowStartToRecovery(t *testing.T) { + // "The sender MUST exit slow start and enter a recovery period + // when a packet is lost [...]" + // https://www.rfc-editor.org/rfc/rfc9002#section-7.3.1-3 + test := newRenoTest(t, 1200) + + p0 := test.packetSent(initialSpace, 1200) + p1 := test.packetSent(initialSpace, 1200) + p2 := test.packetSent(initialSpace, 1200) + p3 := test.packetSent(initialSpace, 1200) + test.wantVar("congestion_window", 12000) + + t.Logf("# ACK triggers packet loss, sender enters recovery") + test.advance(1 * time.Millisecond) + test.packetAcked(initialSpace, p3) + test.packetLost(initialSpace, p0) + test.packetBatchEnd(initialSpace) + + // "[...] set the slow start threshold to half the value of + // the congestion window when loss is detected." + // https://www.rfc-editor.org/rfc/rfc9002#section-7.3.2-2 + test.wantVar("slow_start_threshold", 6000) + + t.Logf("# packet loss in recovery does not change congestion window") + test.packetLost(initialSpace, p1) + test.packetBatchEnd(initialSpace) + + t.Logf("# ack of packet from before recovery does not change congestion window") + test.packetAcked(initialSpace, p2) + test.packetBatchEnd(initialSpace) + + p4 := test.packetSent(initialSpace, 1200) + test.packetAcked(initialSpace, p4) + test.packetBatchEnd(initialSpace) + + // "The congestion window MUST be set to the reduced value of + // the slow start threshold before exiting the recovery period." + // https://www.rfc-editor.org/rfc/rfc9002#section-7.3.2-2 + test.wantVar("congestion_window", 6000) +} + +func TestRenoRecoveryToCongestionAvoidance(t *testing.T) { + // "A sender in congestion avoidance [limits] the increase + // to the congestion window to at most one maximum datagram size + // for each congestion window that is acknowledged." + // https://www.rfc-editor.org/rfc/rfc9002#section-7.3.3-2 + test := newRenoTest(t, 1200) + + p0 := test.packetSent(initialSpace, 1200) + p1 := test.packetSent(initialSpace, 1200) + p2 := test.packetSent(initialSpace, 1200) + test.advance(1 * time.Millisecond) + test.packetAcked(initialSpace, p1) + test.packetLost(initialSpace, p0) + test.packetBatchEnd(initialSpace) + + p3 := test.packetSent(initialSpace, 1000) + test.advance(1 * time.Millisecond) + test.packetAcked(initialSpace, p3) + test.packetBatchEnd(initialSpace) + + test.wantVar("congestion_window", 6000) + test.wantVar("slow_start_threshold", 6000) + test.wantVar("congestion_pending_acks", 1000) + + t.Logf("# ack of packet from before recovery does not change congestion window") + test.packetAcked(initialSpace, p2) + test.packetBatchEnd(initialSpace) + test.wantVar("congestion_pending_acks", 1000) + + for i := 0; i < 6; i++ { + p := test.packetSent(initialSpace, 1000) + test.packetAcked(initialSpace, p) + } + test.packetBatchEnd(initialSpace) + t.Logf("# congestion window increased by max_datagram_size") + test.wantVar("congestion_window", 6000+1200) + test.wantVar("congestion_pending_acks", 1000) +} + +func TestRenoMinimumCongestionWindow(t *testing.T) { + // "The RECOMMENDED [minimum congestion window] is 2 * max_datagram_size." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.2-4 + test := newRenoTest(t, 1200) + + p0 := test.packetSent(handshakeSpace, 1200) + p1 := test.packetSent(handshakeSpace, 1200) + test.advance(1 * time.Millisecond) + test.packetAcked(handshakeSpace, p1) + test.packetLost(handshakeSpace, p0) + test.packetBatchEnd(handshakeSpace) + test.wantVar("slow_start_threshold", 6000) + test.wantVar("congestion_window", 6000) + + test.advance(1 * time.Millisecond) + p2 := test.packetSent(handshakeSpace, 1200) + p3 := test.packetSent(handshakeSpace, 1200) + test.advance(1 * time.Millisecond) + test.packetAcked(handshakeSpace, p3) + test.packetLost(handshakeSpace, p2) + test.packetBatchEnd(handshakeSpace) + test.wantVar("slow_start_threshold", 3000) + test.wantVar("congestion_window", 3000) + + p4 := test.packetSent(handshakeSpace, 1200) + p5 := test.packetSent(handshakeSpace, 1200) + test.advance(1 * time.Millisecond) + test.packetAcked(handshakeSpace, p4) + test.packetLost(handshakeSpace, p5) + test.packetBatchEnd(handshakeSpace) + test.wantVar("slow_start_threshold", 1500) + test.wantVar("congestion_window", 2400) // minimum + + p6 := test.packetSent(handshakeSpace, 1200) + p7 := test.packetSent(handshakeSpace, 1200) + test.advance(1 * time.Millisecond) + test.packetAcked(handshakeSpace, p7) + test.packetLost(handshakeSpace, p6) + test.packetBatchEnd(handshakeSpace) + test.wantVar("slow_start_threshold", 1200) // half congestion window + test.wantVar("congestion_window", 2400) // minimum +} + +func TestRenoSlowStartToCongestionAvoidance(t *testing.T) { + test := newRenoTest(t, 1200) + test.setRTT(1*time.Millisecond, 0) + + t.Logf("# enter recovery with persistent congestion") + p0 := test.packetSent(handshakeSpace, 1200) + test.advance(1 * time.Second) // larger than persistent congestion duration + p1 := test.packetSent(handshakeSpace, 1200) + p2 := test.packetSent(handshakeSpace, 1200) + test.advance(1 * time.Millisecond) + test.packetAcked(handshakeSpace, p2) + test.packetLost(handshakeSpace, p0) + test.packetLost(handshakeSpace, p1) + test.packetBatchEnd(handshakeSpace) + test.wantVar("slow_start_threshold", 6000) + test.wantVar("congestion_window", 2400) // minimum in persistent congestion + test.wantVar("congestion_pending_acks", 0) + + t.Logf("# enter slow start on new ack") + p3 := test.packetSent(handshakeSpace, 1200) + test.packetAcked(handshakeSpace, p3) + test.packetBatchEnd(handshakeSpace) + test.wantVar("congestion_window", 3600) + test.wantVar("congestion_pending_acks", 0) + + t.Logf("# enter congestion avoidance after reaching slow_start_threshold") + p4 := test.packetSent(handshakeSpace, 1200) + p5 := test.packetSent(handshakeSpace, 1200) + p6 := test.packetSent(handshakeSpace, 1200) + test.packetAcked(handshakeSpace, p4) + test.packetAcked(handshakeSpace, p5) + test.packetAcked(handshakeSpace, p6) + test.packetBatchEnd(handshakeSpace) + test.wantVar("congestion_window", 6000) + test.wantVar("congestion_pending_acks", 1200) +} + +func TestRenoPersistentCongestionDurationExceeded(t *testing.T) { + // "When persistent congestion is declared, the sender's congestion + // window MUST be reduced to the minimum congestion window [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.6.2-6 + test := newRenoTest(t, 1200) + test.setRTT(10*time.Millisecond, 3*time.Millisecond) + test.maxAckDelay = 25 * time.Millisecond + + t.Logf("persistent congesion duration is 3 * (10ms + 4*3ms + 25ms) = 141ms") + p0 := test.packetSent(handshakeSpace, 1200) + test.advance(142 * time.Millisecond) // larger than persistent congestion duration + p1 := test.packetSent(handshakeSpace, 1200) + p2 := test.packetSent(handshakeSpace, 1200) + test.packetAcked(handshakeSpace, p2) + test.packetLost(handshakeSpace, p0) + test.packetLost(handshakeSpace, p1) + test.packetBatchEnd(handshakeSpace) + test.wantVar("slow_start_threshold", 6000) + test.wantVar("congestion_window", 2400) // minimum in persistent congestion +} + +func TestRenoPersistentCongestionDurationNotExceeded(t *testing.T) { + test := newRenoTest(t, 1200) + test.setRTT(10*time.Millisecond, 3*time.Millisecond) + test.maxAckDelay = 25 * time.Millisecond + + t.Logf("persistent congesion duration is 3 * (10ms + 4*3ms + 25ms) = 141ms") + p0 := test.packetSent(handshakeSpace, 1200) + test.advance(140 * time.Millisecond) // smaller than persistent congestion duration + p1 := test.packetSent(handshakeSpace, 1200) + p2 := test.packetSent(handshakeSpace, 1200) + test.packetAcked(handshakeSpace, p2) + test.packetLost(handshakeSpace, p0) + test.packetLost(handshakeSpace, p1) + test.packetBatchEnd(handshakeSpace) + test.wantVar("slow_start_threshold", 6000) + test.wantVar("congestion_window", 6000) // no persistent congestion +} + +func TestRenoPersistentCongestionInterveningAck(t *testing.T) { + // "[...] none of the packets sent between the send times + // of these two packets are acknowledged [...]" + // https://www.rfc-editor.org/rfc/rfc9002#section-7.6.2-2.1 + test := newRenoTest(t, 1200) + + test.setRTT(10*time.Millisecond, 3*time.Millisecond) + test.maxAckDelay = 25 * time.Millisecond + + t.Logf("persistent congesion duration is 3 * (10ms + 4*3ms + 25ms) = 141ms") + p0 := test.packetSent(handshakeSpace, 1200) + test.advance(100 * time.Millisecond) + p1 := test.packetSent(handshakeSpace, 1200) + test.advance(42 * time.Millisecond) + p2 := test.packetSent(handshakeSpace, 1200) + p3 := test.packetSent(handshakeSpace, 1200) + test.packetAcked(handshakeSpace, p1) + test.packetAcked(handshakeSpace, p3) + test.packetLost(handshakeSpace, p0) + test.packetLost(handshakeSpace, p2) + test.packetBatchEnd(handshakeSpace) + test.wantVar("slow_start_threshold", 6000) + test.wantVar("congestion_window", 6000) // no persistent congestion +} + +func TestRenoPersistentCongestionInterveningLosses(t *testing.T) { + test := newRenoTest(t, 1200) + + test.setRTT(10*time.Millisecond, 3*time.Millisecond) + test.maxAckDelay = 25 * time.Millisecond + + t.Logf("persistent congesion duration is 3 * (10ms + 4*3ms + 25ms) = 141ms") + p0 := test.packetSent(handshakeSpace, 1200) + test.advance(50 * time.Millisecond) + p1 := test.packetSent(handshakeSpace, 1200, func(p *sentPacket) { + p.inFlight = false + p.ackEliciting = false + }) + test.advance(50 * time.Millisecond) + p2 := test.packetSent(handshakeSpace, 1200, func(p *sentPacket) { + p.ackEliciting = false + }) + test.advance(42 * time.Millisecond) + p3 := test.packetSent(handshakeSpace, 1200) + p4 := test.packetSent(handshakeSpace, 1200) + test.packetAcked(handshakeSpace, p4) + test.packetLost(handshakeSpace, p0) + test.packetLost(handshakeSpace, p1) + test.packetLost(handshakeSpace, p2) + test.packetBatchEnd(handshakeSpace) + test.wantVar("congestion_window", 6000) // no persistent congestion yet + test.packetLost(handshakeSpace, p3) + test.packetBatchEnd(handshakeSpace) + test.wantVar("congestion_window", 2400) // persistent congestion +} + +func TestRenoPersistentCongestionNoRTTSample(t *testing.T) { + // "[...] a prior RTT sample existed when these two packets were sent." + // https://www.rfc-editor.org/rfc/rfc9002#section-7.6.2-2.3 + test := newRenoTest(t, 1200) + + t.Logf("first packet sent prior to first RTT sample") + p0 := test.packetSent(handshakeSpace, 1200) + + test.advance(1 * time.Millisecond) + test.setRTT(10*time.Millisecond, 3*time.Millisecond) + test.maxAckDelay = 25 * time.Millisecond + + t.Logf("persistent congesion duration is 3 * (10ms + 4*3ms + 25ms) = 141ms") + test.advance(142 * time.Millisecond) // larger than persistent congestion duration + p1 := test.packetSent(handshakeSpace, 1200) + p2 := test.packetSent(handshakeSpace, 1200) + test.packetAcked(handshakeSpace, p2) + test.packetLost(handshakeSpace, p0) + test.packetLost(handshakeSpace, p1) + test.packetBatchEnd(handshakeSpace) + test.wantVar("slow_start_threshold", 6000) + test.wantVar("congestion_window", 6000) // no persistent congestion +} + +func TestRenoPersistentCongestionPacketNotAckEliciting(t *testing.T) { + // "These two packets MUST be ack-eliciting [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.6.2-3 + test := newRenoTest(t, 1200) + + t.Logf("first packet set prior to first RTT sample") + p0 := test.packetSent(handshakeSpace, 1200) + + test.advance(1 * time.Millisecond) + test.setRTT(10*time.Millisecond, 3*time.Millisecond) + test.maxAckDelay = 25 * time.Millisecond + + t.Logf("persistent congesion duration is 3 * (10ms + 4*3ms + 25ms) = 141ms") + test.advance(142 * time.Millisecond) // larger than persistent congestion duration + p1 := test.packetSent(handshakeSpace, 1200) + p2 := test.packetSent(handshakeSpace, 1200) + test.packetAcked(handshakeSpace, p2) + test.packetLost(handshakeSpace, p0) + test.packetLost(handshakeSpace, p1) + test.packetBatchEnd(handshakeSpace) + test.wantVar("slow_start_threshold", 6000) + test.wantVar("congestion_window", 6000) // no persistent congestion +} + +func TestRenoCanSend(t *testing.T) { + test := newRenoTest(t, 1200) + test.wantVar("congestion_window", 12000) + + t.Logf("controller permits sending until congestion window is full") + var packets []*sentPacket + for i := 0; i < 10; i++ { + test.wantVar("bytes_in_flight", i*1200) + test.wantCanSend(true) + p := test.packetSent(initialSpace, 1200) + packets = append(packets, p) + } + test.wantVar("bytes_in_flight", 12000) + + t.Logf("controller blocks sending when congestion window is consumed") + test.wantCanSend(false) + + t.Logf("loss of packet moves to recovery, reduces window") + test.packetLost(initialSpace, packets[0]) + test.packetAcked(initialSpace, packets[1]) + test.packetBatchEnd(initialSpace) + test.wantVar("bytes_in_flight", 9600) // 12000 - 2*1200 + test.wantVar("congestion_window", 6000) // 12000 / 2 + + t.Logf("one packet permitted on entry to recovery") + test.wantCanSend(true) + test.packetSent(initialSpace, 1200) + test.wantVar("bytes_in_flight", 10800) + test.wantCanSend(false) +} + +func TestRenoNonAckEliciting(t *testing.T) { + test := newRenoTest(t, 1200) + test.wantVar("congestion_window", 12000) + + t.Logf("in-flight packet") + p0 := test.packetSent(initialSpace, 1200) + test.wantVar("bytes_in_flight", 1200) + test.packetAcked(initialSpace, p0) + test.packetBatchEnd(initialSpace) + test.wantVar("bytes_in_flight", 0) + test.wantVar("congestion_window", 12000+1200) + + t.Logf("non-in-flight packet") + p1 := test.packetSent(initialSpace, 1200, func(p *sentPacket) { + p.inFlight = false + p.ackEliciting = false + }) + test.wantVar("bytes_in_flight", 0) + test.packetAcked(initialSpace, p1) + test.packetBatchEnd(initialSpace) + test.wantVar("bytes_in_flight", 0) + test.wantVar("congestion_window", 12000+1200) +} + +func TestRenoUnderutilizedCongestionWindow(t *testing.T) { + test := newRenoTest(t, 1200) + test.setUnderutilized(true) + test.wantVar("congestion_window", 12000) + + t.Logf("congestion window does not increase when application limited") + p0 := test.packetSent(initialSpace, 1200) + test.packetAcked(initialSpace, p0) + test.wantVar("congestion_window", 12000) +} + +func TestRenoDiscardKeys(t *testing.T) { + test := newRenoTest(t, 1200) + + p0 := test.packetSent(initialSpace, 1200) + p1 := test.packetSent(handshakeSpace, 1200) + test.wantVar("bytes_in_flight", 2400) + + test.packetDiscarded(initialSpace, p0) + test.wantVar("bytes_in_flight", 1200) + + test.packetDiscarded(handshakeSpace, p1) + test.wantVar("bytes_in_flight", 0) +} + +type ccTest struct { + t *testing.T + cc *ccReno + rtt rttState + maxAckDelay time.Duration + now time.Time + nextNum [numberSpaceCount]packetNumber +} + +func newRenoTest(t *testing.T, maxDatagramSize int) *ccTest { + test := &ccTest{ + t: t, + now: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + } + test.cc = newReno(maxDatagramSize) + return test +} + +func (c *ccTest) setRTT(smoothedRTT, rttvar time.Duration) { + c.t.Helper() + c.t.Logf("set smoothed_rtt=%v rttvar=%v", smoothedRTT, rttvar) + c.rtt.smoothedRTT = smoothedRTT + c.rtt.rttvar = rttvar + if c.rtt.firstSampleTime.IsZero() { + c.rtt.firstSampleTime = c.now + } +} + +func (c *ccTest) setUnderutilized(v bool) { + c.t.Helper() + c.t.Logf("set underutilized = %v", v) + c.cc.setUnderutilized(v) +} + +func (c *ccTest) packetSent(space numberSpace, size int, fns ...func(*sentPacket)) *sentPacket { + c.t.Helper() + num := c.nextNum[space] + c.nextNum[space]++ + sent := &sentPacket{ + inFlight: true, + ackEliciting: true, + num: num, + size: size, + time: c.now, + } + for _, f := range fns { + f(sent) + } + c.t.Logf("packet sent: num=%v.%v, size=%v", space, sent.num, sent.size) + c.cc.packetSent(c.now, space, sent) + return sent +} + +func (c *ccTest) advance(d time.Duration) { + c.t.Helper() + c.t.Logf("advance time %v", d) + c.now = c.now.Add(d) +} + +func (c *ccTest) packetAcked(space numberSpace, sent *sentPacket) { + c.t.Helper() + c.t.Logf("packet acked: num=%v.%v, size=%v", space, sent.num, sent.size) + c.cc.packetAcked(c.now, sent) +} + +func (c *ccTest) packetLost(space numberSpace, sent *sentPacket) { + c.t.Helper() + c.t.Logf("packet lost: num=%v.%v, size=%v", space, sent.num, sent.size) + c.cc.packetLost(c.now, space, sent, &c.rtt) +} + +func (c *ccTest) packetDiscarded(space numberSpace, sent *sentPacket) { + c.t.Helper() + c.t.Logf("packet number space discarded: num=%v.%v, size=%v", space, sent.num, sent.size) + c.cc.packetDiscarded(sent) +} + +func (c *ccTest) packetBatchEnd(space numberSpace) { + c.t.Helper() + c.t.Logf("(end of batch)") + c.cc.packetBatchEnd(c.now, space, &c.rtt, c.maxAckDelay) +} + +func (c *ccTest) wantCanSend(want bool) { + if got := c.cc.canSend(); got != want { + c.t.Fatalf("canSend() = %v, want %v", got, want) + } +} + +func (c *ccTest) wantVar(name string, want int) { + c.t.Helper() + var got int + switch name { + case "bytes_in_flight": + got = c.cc.bytesInFlight + case "congestion_pending_acks": + got = c.cc.congestionPendingAcks + case "congestion_window": + got = c.cc.congestionWindow + case "slow_start_threshold": + got = c.cc.slowStartThreshold + default: + c.t.Fatalf("unknown var %q", name) + } + if got != want { + c.t.Fatalf("ERROR: %v = %v, want %v", name, got, want) + } + c.t.Logf("# %v = %v", name, got) +} diff --git a/internal/quic/conn.go b/internal/quic/conn.go new file mode 100644 index 000000000..9db00fe09 --- /dev/null +++ b/internal/quic/conn.go @@ -0,0 +1,376 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net/netip" + "time" +) + +// A Conn is a QUIC connection. +// +// Multiple goroutines may invoke methods on a Conn simultaneously. +type Conn struct { + side connSide + listener *Listener + config *Config + testHooks connTestHooks + peerAddr netip.AddrPort + + msgc chan any + donec chan struct{} // closed when conn loop exits + exited bool // set to make the conn loop exit immediately + + w packetWriter + acks [numberSpaceCount]ackState // indexed by number space + lifetime lifetimeState + connIDState connIDState + loss lossState + streams streamsState + + // idleTimeout is the time at which the connection will be closed due to inactivity. + // https://www.rfc-editor.org/rfc/rfc9000#section-10.1 + maxIdleTimeout time.Duration + idleTimeout time.Time + + // Packet protection keys, CRYPTO streams, and TLS state. + keysInitial fixedKeyPair + keysHandshake fixedKeyPair + keysAppData updatingKeyPair + crypto [numberSpaceCount]cryptoStream + tls *tls.QUICConn + + // handshakeConfirmed is set when the handshake is confirmed. + // For server connections, it tracks sending HANDSHAKE_DONE. + handshakeConfirmed sentVal + + peerAckDelayExponent int8 // -1 when unknown + + // Tests only: Send a PING in a specific number space. + testSendPingSpace numberSpace + testSendPing sentVal +} + +// connTestHooks override conn behavior in tests. +type connTestHooks interface { + nextMessage(msgc chan any, nextTimeout time.Time) (now time.Time, message any) + handleTLSEvent(tls.QUICEvent) + newConnID(seq int64) ([]byte, error) + waitUntil(ctx context.Context, until func() bool) error + timeNow() time.Time +} + +func newConn(now time.Time, side connSide, initialConnID []byte, peerAddr netip.AddrPort, config *Config, l *Listener, hooks connTestHooks) (*Conn, error) { + c := &Conn{ + side: side, + listener: l, + config: config, + peerAddr: peerAddr, + msgc: make(chan any, 1), + donec: make(chan struct{}), + testHooks: hooks, + maxIdleTimeout: defaultMaxIdleTimeout, + idleTimeout: now.Add(defaultMaxIdleTimeout), + peerAckDelayExponent: -1, + } + + // A one-element buffer allows us to wake a Conn's event loop as a + // non-blocking operation. + c.msgc = make(chan any, 1) + + var originalDstConnID []byte + if c.side == clientSide { + if err := c.connIDState.initClient(c); err != nil { + return nil, err + } + initialConnID, _ = c.connIDState.dstConnID() + } else { + if err := c.connIDState.initServer(c, initialConnID); err != nil { + return nil, err + } + originalDstConnID = initialConnID + } + + // The smallest allowed maximum QUIC datagram size is 1200 bytes. + // TODO: PMTU discovery. + const maxDatagramSize = 1200 + c.keysAppData.init() + c.loss.init(c.side, maxDatagramSize, now) + c.streamsInit() + c.lifetimeInit() + + // TODO: retry_source_connection_id + if err := c.startTLS(now, initialConnID, transportParameters{ + initialSrcConnID: c.connIDState.srcConnID(), + originalDstConnID: originalDstConnID, + ackDelayExponent: ackDelayExponent, + maxUDPPayloadSize: maxUDPPayloadSize, + maxAckDelay: maxAckDelay, + disableActiveMigration: true, + initialMaxData: config.maxConnReadBufferSize(), + initialMaxStreamDataBidiLocal: config.maxStreamReadBufferSize(), + initialMaxStreamDataBidiRemote: config.maxStreamReadBufferSize(), + initialMaxStreamDataUni: config.maxStreamReadBufferSize(), + initialMaxStreamsBidi: c.streams.remoteLimit[bidiStream].max, + initialMaxStreamsUni: c.streams.remoteLimit[uniStream].max, + activeConnIDLimit: activeConnIDLimit, + }); err != nil { + return nil, err + } + + go c.loop(now) + return c, nil +} + +func (c *Conn) String() string { + return fmt.Sprintf("quic.Conn(%v,->%v)", c.side, c.peerAddr) +} + +// confirmHandshake is called when the handshake is confirmed. +// https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2 +func (c *Conn) confirmHandshake(now time.Time) { + // If handshakeConfirmed is unset, the handshake is not confirmed. + // If it is unsent, the handshake is confirmed and we need to send a HANDSHAKE_DONE. + // If it is sent, we have sent a HANDSHAKE_DONE. + // If it is received, the handshake is confirmed and we do not need to send anything. + if c.handshakeConfirmed.isSet() { + return // already confirmed + } + if c.side == serverSide { + // When the server confirms the handshake, it sends a HANDSHAKE_DONE. + c.handshakeConfirmed.setUnsent() + c.listener.serverConnEstablished(c) + } else { + // The client never sends a HANDSHAKE_DONE, so we set handshakeConfirmed + // to the received state, indicating that the handshake is confirmed and we + // don't need to send anything. + c.handshakeConfirmed.setReceived() + } + c.loss.confirmHandshake() + // "An endpoint MUST discard its Handshake keys when the TLS handshake is confirmed" + // https://www.rfc-editor.org/rfc/rfc9001#section-4.9.2-1 + c.discardKeys(now, handshakeSpace) +} + +// discardKeys discards unused packet protection keys. +// https://www.rfc-editor.org/rfc/rfc9001#section-4.9 +func (c *Conn) discardKeys(now time.Time, space numberSpace) { + switch space { + case initialSpace: + c.keysInitial.discard() + case handshakeSpace: + c.keysHandshake.discard() + } + c.loss.discardKeys(now, space) +} + +// receiveTransportParameters applies transport parameters sent by the peer. +func (c *Conn) receiveTransportParameters(p transportParameters) error { + if err := c.connIDState.validateTransportParameters(c.side, p); err != nil { + return err + } + c.streams.outflow.setMaxData(p.initialMaxData) + c.streams.localLimit[bidiStream].setMax(p.initialMaxStreamsBidi) + c.streams.localLimit[uniStream].setMax(p.initialMaxStreamsUni) + c.streams.peerInitialMaxStreamDataBidiLocal = p.initialMaxStreamDataBidiLocal + c.streams.peerInitialMaxStreamDataRemote[bidiStream] = p.initialMaxStreamDataBidiRemote + c.streams.peerInitialMaxStreamDataRemote[uniStream] = p.initialMaxStreamDataUni + c.peerAckDelayExponent = p.ackDelayExponent + c.loss.setMaxAckDelay(p.maxAckDelay) + if err := c.connIDState.setPeerActiveConnIDLimit(c, p.activeConnIDLimit); err != nil { + return err + } + if p.preferredAddrConnID != nil { + var ( + seq int64 = 1 // sequence number of this conn id is 1 + retirePriorTo int64 = 0 // retire nothing + resetToken [16]byte + ) + copy(resetToken[:], p.preferredAddrResetToken) + if err := c.connIDState.handleNewConnID(seq, retirePriorTo, p.preferredAddrConnID, resetToken); err != nil { + return err + } + } + + // TODO: Many more transport parameters to come. + + return nil +} + +type ( + timerEvent struct{} + wakeEvent struct{} +) + +// loop is the connection main loop. +// +// Except where otherwise noted, all connection state is owned by the loop goroutine. +// +// The loop processes messages from c.msgc and timer events. +// Other goroutines may examine or modify conn state by sending the loop funcs to execute. +func (c *Conn) loop(now time.Time) { + defer close(c.donec) + defer c.tls.Close() + defer c.listener.connDrained(c) + + // The connection timer sends a message to the connection loop on expiry. + // We need to give it an expiry when creating it, so set the initial timeout to + // an arbitrary large value. The timer will be reset before this expires (and it + // isn't a problem if it does anyway). Skip creating the timer in tests which + // take control of the connection message loop. + var timer *time.Timer + var lastTimeout time.Time + hooks := c.testHooks + if hooks == nil { + timer = time.AfterFunc(1*time.Hour, func() { + c.sendMsg(timerEvent{}) + }) + defer timer.Stop() + } + + for !c.exited { + sendTimeout := c.maybeSend(now) // try sending + + // Note that we only need to consider the ack timer for the App Data space, + // since the Initial and Handshake spaces always ack immediately. + nextTimeout := sendTimeout + nextTimeout = firstTime(nextTimeout, c.idleTimeout) + if !c.isClosingOrDraining() { + nextTimeout = firstTime(nextTimeout, c.loss.timer) + nextTimeout = firstTime(nextTimeout, c.acks[appDataSpace].nextAck) + } else { + nextTimeout = firstTime(nextTimeout, c.lifetime.drainEndTime) + } + + var m any + if hooks != nil { + // Tests only: Wait for the test to tell us to continue. + now, m = hooks.nextMessage(c.msgc, nextTimeout) + } else if !nextTimeout.IsZero() && nextTimeout.Before(now) { + // A connection timer has expired. + now = time.Now() + m = timerEvent{} + } else { + // Reschedule the connection timer if necessary + // and wait for the next event. + if !nextTimeout.Equal(lastTimeout) && !nextTimeout.IsZero() { + // Resetting a timer created with time.AfterFunc guarantees + // that the timer will run again. We might generate a spurious + // timer event under some circumstances, but that's okay. + timer.Reset(nextTimeout.Sub(now)) + lastTimeout = nextTimeout + } + m = <-c.msgc + now = time.Now() + } + switch m := m.(type) { + case *datagram: + c.handleDatagram(now, m) + m.recycle() + case timerEvent: + // A connection timer has expired. + if !now.Before(c.idleTimeout) { + // "[...] the connection is silently closed and + // its state is discarded [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-10.1-1 + c.exited = true + return + } + c.loss.advance(now, c.handleAckOrLoss) + if c.lifetimeAdvance(now) { + // The connection has completed the draining period, + // and may be shut down. + return + } + case wakeEvent: + // We're being woken up to try sending some frames. + case func(time.Time, *Conn): + // Send a func to msgc to run it on the main Conn goroutine + m(now, c) + default: + panic(fmt.Sprintf("quic: unrecognized conn message %T", m)) + } + } +} + +// sendMsg sends a message to the conn's loop. +// It does not wait for the message to be processed. +// The conn may close before processing the message, in which case it is lost. +func (c *Conn) sendMsg(m any) { + select { + case c.msgc <- m: + case <-c.donec: + } +} + +// wake wakes up the conn's loop. +func (c *Conn) wake() { + select { + case c.msgc <- wakeEvent{}: + default: + } +} + +// runOnLoop executes a function within the conn's loop goroutine. +func (c *Conn) runOnLoop(f func(now time.Time, c *Conn)) error { + donec := make(chan struct{}) + c.sendMsg(func(now time.Time, c *Conn) { + defer close(donec) + f(now, c) + }) + select { + case <-donec: + case <-c.donec: + return errors.New("quic: connection closed") + } + return nil +} + +func (c *Conn) waitOnDone(ctx context.Context, ch <-chan struct{}) error { + if c.testHooks != nil { + return c.testHooks.waitUntil(ctx, func() bool { + select { + case <-ch: + return true + default: + } + return false + }) + } + // Check the channel before the context. + // We always prefer to return results when available, + // even when provided with an already-canceled context. + select { + case <-ch: + return nil + default: + } + select { + case <-ch: + case <-ctx.Done(): + return ctx.Err() + } + return nil +} + +// firstTime returns the earliest non-zero time, or zero if both times are zero. +func firstTime(a, b time.Time) time.Time { + switch { + case a.IsZero(): + return b + case b.IsZero(): + return a + case a.Before(b): + return a + default: + return b + } +} diff --git a/internal/quic/conn_async_test.go b/internal/quic/conn_async_test.go new file mode 100644 index 000000000..dc2a57f9d --- /dev/null +++ b/internal/quic/conn_async_test.go @@ -0,0 +1,185 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "errors" + "fmt" + "path/filepath" + "runtime" + "sync" +) + +// asyncTestState permits handling asynchronous operations in a synchronous test. +// +// For example, a test may want to write to a stream and observe that +// STREAM frames are sent with the contents of the write in response +// to MAX_STREAM_DATA frames received from the peer. +// The Stream.Write is an asynchronous operation, but the test is simpler +// if we can start the write, observe the first STREAM frame sent, +// send a MAX_STREAM_DATA frame, observe the next STREAM frame sent, etc. +// +// We do this by instrumenting points where operations can block. +// We start async operations like Write in a goroutine, +// and wait for the operation to either finish or hit a blocking point. +// When the connection event loop is idle, we check a list of +// blocked operations to see if any can be woken. +type asyncTestState struct { + mu sync.Mutex + notify chan struct{} + blocked map[*blockedAsync]struct{} +} + +// An asyncOp is an asynchronous operation that results in (T, error). +type asyncOp[T any] struct { + v T + err error + + caller string + state *asyncTestState + donec chan struct{} + cancelFunc context.CancelFunc +} + +// cancel cancels the async operation's context, and waits for +// the operation to complete. +func (a *asyncOp[T]) cancel() { + select { + case <-a.donec: + return // already done + default: + } + a.cancelFunc() + <-a.state.notify + select { + case <-a.donec: + default: + panic(fmt.Errorf("%v: async op failed to finish after being canceled", a.caller)) + } +} + +var errNotDone = errors.New("async op is not done") + +// result returns the result of the async operation. +// It returns errNotDone if the operation is still in progress. +// +// Note that unlike a traditional async/await, this doesn't block +// waiting for the operation to complete. Since tests have full +// control over the progress of operations, an asyncOp can only +// become done in reaction to the test taking some action. +func (a *asyncOp[T]) result() (v T, err error) { + select { + case <-a.donec: + return a.v, a.err + default: + return v, errNotDone + } +} + +// A blockedAsync is a blocked async operation. +type blockedAsync struct { + until func() bool // when this returns true, the operation is unblocked + donec chan struct{} // closed when the operation is unblocked +} + +type asyncContextKey struct{} + +// runAsync starts an asynchronous operation. +// +// The function f should call a blocking function such as +// Stream.Write or Conn.AcceptStream and return its result. +// It must use the provided context. +func runAsync[T any](ts *testConn, f func(context.Context) (T, error)) *asyncOp[T] { + as := &ts.asyncTestState + if as.notify == nil { + as.notify = make(chan struct{}) + as.mu.Lock() + as.blocked = make(map[*blockedAsync]struct{}) + as.mu.Unlock() + } + _, file, line, _ := runtime.Caller(1) + ctx := context.WithValue(context.Background(), asyncContextKey{}, true) + ctx, cancel := context.WithCancel(ctx) + a := &asyncOp[T]{ + state: as, + caller: fmt.Sprintf("%v:%v", filepath.Base(file), line), + donec: make(chan struct{}), + cancelFunc: cancel, + } + go func() { + a.v, a.err = f(ctx) + close(a.donec) + as.notify <- struct{}{} + }() + ts.t.Cleanup(func() { + if _, err := a.result(); err == errNotDone { + ts.t.Errorf("%v: async operation is still executing at end of test", a.caller) + a.cancel() + } + }) + // Wait for the operation to either finish or block. + <-as.notify + return a +} + +// waitUntil waits for a blocked async operation to complete. +// The operation is complete when the until func returns true. +func (as *asyncTestState) waitUntil(ctx context.Context, until func() bool) error { + if until() { + return nil + } + if err := ctx.Err(); err != nil { + // Context has already expired. + return err + } + if ctx.Value(asyncContextKey{}) == nil { + // Context is not one that we've created, and hasn't expired. + // This probably indicates that we've tried to perform a + // blocking operation without using the async test harness here, + // which may have unpredictable results. + panic("blocking async point with unexpected Context") + } + b := &blockedAsync{ + until: until, + donec: make(chan struct{}), + } + // Record this as a pending blocking operation. + as.mu.Lock() + as.blocked[b] = struct{}{} + as.mu.Unlock() + // Notify the creator of the operation that we're blocked, + // and wait to be woken up. + as.notify <- struct{}{} + select { + case <-b.donec: + case <-ctx.Done(): + return ctx.Err() + } + return nil +} + +// wakeAsync tries to wake up a blocked async operation. +// It returns true if one was woken, false otherwise. +func (as *asyncTestState) wakeAsync() bool { + as.mu.Lock() + var woken *blockedAsync + for w := range as.blocked { + if w.until() { + woken = w + delete(as.blocked, w) + break + } + } + as.mu.Unlock() + if woken == nil { + return false + } + close(woken.donec) + <-as.notify // must not hold as.mu while blocked here + return true +} diff --git a/internal/quic/conn_close.go b/internal/quic/conn_close.go new file mode 100644 index 000000000..b8b86fd6f --- /dev/null +++ b/internal/quic/conn_close.go @@ -0,0 +1,252 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "errors" + "time" +) + +// lifetimeState tracks the state of a connection. +// +// This is fairly coupled to the rest of a Conn, but putting it in a struct of its own helps +// reason about operations that cause state transitions. +type lifetimeState struct { + readyc chan struct{} // closed when TLS handshake completes + drainingc chan struct{} // closed when entering the draining state + + // Possible states for the connection: + // + // Alive: localErr and finalErr are both nil. + // + // Closing: localErr is non-nil and finalErr is nil. + // We have sent a CONNECTION_CLOSE to the peer or are about to + // (if connCloseSentTime is zero) and are waiting for the peer to respond. + // drainEndTime is set to the time the closing state ends. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.1 + // + // Draining: finalErr is non-nil. + // If localErr is nil, we're waiting for the user to provide us with a final status + // to send to the peer. + // Otherwise, we've either sent a CONNECTION_CLOSE to the peer or are about to + // (if connCloseSentTime is zero). + // drainEndTime is set to the time the draining state ends. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.2 + localErr error // error sent to the peer + finalErr error // error sent by the peer, or transport error; always set before draining + + connCloseSentTime time.Time // send time of last CONNECTION_CLOSE frame + connCloseDelay time.Duration // delay until next CONNECTION_CLOSE frame sent + drainEndTime time.Time // time the connection exits the draining state +} + +func (c *Conn) lifetimeInit() { + c.lifetime.readyc = make(chan struct{}) + c.lifetime.drainingc = make(chan struct{}) +} + +var errNoPeerResponse = errors.New("peer did not respond to CONNECTION_CLOSE") + +// advance is called when time passes. +func (c *Conn) lifetimeAdvance(now time.Time) (done bool) { + if c.lifetime.drainEndTime.IsZero() || c.lifetime.drainEndTime.After(now) { + return false + } + // The connection drain period has ended, and we can shut down. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2-7 + c.lifetime.drainEndTime = time.Time{} + if c.lifetime.finalErr == nil { + // The peer never responded to our CONNECTION_CLOSE. + c.enterDraining(errNoPeerResponse) + } + return true +} + +// confirmHandshake is called when the TLS handshake completes. +func (c *Conn) handshakeDone() { + close(c.lifetime.readyc) +} + +// isDraining reports whether the conn is in the draining state. +// +// The draining state is entered once an endpoint receives a CONNECTION_CLOSE frame. +// The endpoint will no longer send any packets, but we retain knowledge of the connection +// until the end of the drain period to ensure we discard packets for the connection +// rather than treating them as starting a new connection. +// +// https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.2 +func (c *Conn) isDraining() bool { + return c.lifetime.finalErr != nil +} + +// isClosingOrDraining reports whether the conn is in the closing or draining states. +func (c *Conn) isClosingOrDraining() bool { + return c.lifetime.localErr != nil || c.lifetime.finalErr != nil +} + +// sendOK reports whether the conn can send frames at this time. +func (c *Conn) sendOK(now time.Time) bool { + if !c.isClosingOrDraining() { + return true + } + // We are closing or draining. + if c.lifetime.localErr == nil { + // We're waiting for the user to close the connection, providing us with + // a final status to send to the peer. + return false + } + // Past this point, returning true will result in the conn sending a CONNECTION_CLOSE + // due to localErr being set. + if c.lifetime.drainEndTime.IsZero() { + // The closing and draining states should last for at least three times + // the current PTO interval. We currently use exactly that minimum. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2-5 + // + // The drain period begins when we send or receive a CONNECTION_CLOSE, + // whichever comes first. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-10.2.2-3 + c.lifetime.drainEndTime = now.Add(3 * c.loss.ptoBasePeriod()) + } + if c.lifetime.connCloseSentTime.IsZero() { + // We haven't sent a CONNECTION_CLOSE yet. Do so. + // Either we're initiating an immediate close + // (and will enter the closing state as soon as we send CONNECTION_CLOSE), + // or we've read a CONNECTION_CLOSE from our peer + // (and may send one CONNECTION_CLOSE before entering the draining state). + // + // Set the initial delay before we will send another CONNECTION_CLOSE. + // + // RFC 9000 states that we should rate limit CONNECTION_CLOSE frames, + // but leaves the implementation of the limit up to us. Here, we start + // with the same delay as the PTO timer (RFC 9002, Section 6.2.1), + // not including max_ack_delay, and double it on every CONNECTION_CLOSE sent. + c.lifetime.connCloseDelay = c.loss.rtt.smoothedRTT + max(4*c.loss.rtt.rttvar, timerGranularity) + c.lifetime.drainEndTime = now.Add(3 * c.loss.ptoBasePeriod()) + return true + } + if c.isDraining() { + // We are in the draining state, and will send no more packets. + return false + } + maxRecvTime := c.acks[initialSpace].maxRecvTime + if t := c.acks[handshakeSpace].maxRecvTime; t.After(maxRecvTime) { + maxRecvTime = t + } + if t := c.acks[appDataSpace].maxRecvTime; t.After(maxRecvTime) { + maxRecvTime = t + } + if maxRecvTime.Before(c.lifetime.connCloseSentTime.Add(c.lifetime.connCloseDelay)) { + // After sending CONNECTION_CLOSE, ignore packets from the peer for + // a delay. On the next packet received after the delay, send another + // CONNECTION_CLOSE. + return false + } + c.lifetime.connCloseSentTime = now + c.lifetime.connCloseDelay *= 2 + return true +} + +// enterDraining enters the draining state. +func (c *Conn) enterDraining(err error) { + if c.isDraining() { + return + } + if e, ok := c.lifetime.localErr.(localTransportError); ok && transportError(e) != errNo { + // If we've terminated the connection due to a peer protocol violation, + // record the final error on the connection as our reason for termination. + c.lifetime.finalErr = c.lifetime.localErr + } else { + c.lifetime.finalErr = err + } + close(c.lifetime.drainingc) + c.streams.queue.close(c.lifetime.finalErr) +} + +func (c *Conn) waitReady(ctx context.Context) error { + select { + case <-c.lifetime.readyc: + return nil + case <-c.lifetime.drainingc: + return c.lifetime.finalErr + default: + } + select { + case <-c.lifetime.readyc: + return nil + case <-c.lifetime.drainingc: + return c.lifetime.finalErr + case <-ctx.Done(): + return ctx.Err() + } +} + +// Close closes the connection. +// +// Close is equivalent to: +// +// conn.Abort(nil) +// err := conn.Wait(context.Background()) +func (c *Conn) Close() error { + c.Abort(nil) + <-c.lifetime.drainingc + return c.lifetime.finalErr +} + +// Wait waits for the peer to close the connection. +// +// If the connection is closed locally and the peer does not close its end of the connection, +// Wait will return with a non-nil error after the drain period expires. +// +// If the peer closes the connection with a NO_ERROR transport error, Wait returns nil. +// If the peer closes the connection with an application error, Wait returns an ApplicationError +// containing the peer's error code and reason. +// If the peer closes the connection with any other status, Wait returns a non-nil error. +func (c *Conn) Wait(ctx context.Context) error { + if err := c.waitOnDone(ctx, c.lifetime.drainingc); err != nil { + return err + } + return c.lifetime.finalErr +} + +// Abort closes the connection and returns immediately. +// +// If err is nil, Abort sends a transport error of NO_ERROR to the peer. +// If err is an ApplicationError, Abort sends its error code and text. +// Otherwise, Abort sends a transport error of APPLICATION_ERROR with the error's text. +func (c *Conn) Abort(err error) { + if err == nil { + err = localTransportError(errNo) + } + c.sendMsg(func(now time.Time, c *Conn) { + c.abort(now, err) + }) +} + +// abort terminates a connection with an error. +func (c *Conn) abort(now time.Time, err error) { + if c.lifetime.localErr != nil { + return // already closing + } + c.lifetime.localErr = err +} + +// abortImmediately terminates a connection. +// The connection does not send a CONNECTION_CLOSE, and skips the draining period. +func (c *Conn) abortImmediately(now time.Time, err error) { + c.abort(now, err) + c.enterDraining(err) + c.exited = true +} + +// exit fully terminates a connection immediately. +func (c *Conn) exit() { + c.sendMsg(func(now time.Time, c *Conn) { + c.enterDraining(errors.New("connection closed")) + c.exited = true + }) +} diff --git a/internal/quic/conn_close_test.go b/internal/quic/conn_close_test.go new file mode 100644 index 000000000..20c00e754 --- /dev/null +++ b/internal/quic/conn_close_test.go @@ -0,0 +1,186 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "crypto/tls" + "errors" + "testing" + "time" +) + +func TestConnCloseResponseBackoff(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.handshake() + + tc.conn.Abort(nil) + tc.wantFrame("aborting connection generates CONN_CLOSE", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errNo, + }) + + waiting := runAsync(tc, func(ctx context.Context) (struct{}, error) { + return struct{}{}, tc.conn.Wait(ctx) + }) + if _, err := waiting.result(); err != errNotDone { + t.Errorf("conn.Wait() = %v, want still waiting", err) + } + + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.wantIdle("packets received immediately after CONN_CLOSE receive no response") + + tc.advance(1100 * time.Microsecond) + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.wantFrame("receiving packet 1.1ms after CONN_CLOSE generates another CONN_CLOSE", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errNo, + }) + + tc.advance(1100 * time.Microsecond) + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.wantIdle("no response to packet, because CONN_CLOSE backoff is now 2ms") + + tc.advance(1000 * time.Microsecond) + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.wantFrame("2ms since last CONN_CLOSE, receiving a packet generates another CONN_CLOSE", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errNo, + }) + if _, err := waiting.result(); err != errNotDone { + t.Errorf("conn.Wait() = %v, want still waiting", err) + } + + tc.advance(100000 * time.Microsecond) + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.wantIdle("drain timer expired, no more responses") + + if _, err := waiting.result(); !errors.Is(err, errNoPeerResponse) { + t.Errorf("blocked conn.Wait() = %v, want errNoPeerResponse", err) + } + if err := tc.conn.Wait(canceledContext()); !errors.Is(err, errNoPeerResponse) { + t.Errorf("non-blocking conn.Wait() = %v, want errNoPeerResponse", err) + } +} + +func TestConnCloseWithPeerResponse(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.handshake() + + tc.conn.Abort(nil) + tc.wantFrame("aborting connection generates CONN_CLOSE", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errNo, + }) + + waiting := runAsync(tc, func(ctx context.Context) (struct{}, error) { + return struct{}{}, tc.conn.Wait(ctx) + }) + if _, err := waiting.result(); err != errNotDone { + t.Errorf("conn.Wait() = %v, want still waiting", err) + } + + tc.writeFrames(packetType1RTT, debugFrameConnectionCloseApplication{ + code: 20, + }) + + wantErr := &ApplicationError{ + Code: 20, + } + if _, err := waiting.result(); !errors.Is(err, wantErr) { + t.Errorf("blocked conn.Wait() = %v, want %v", err, wantErr) + } + if err := tc.conn.Wait(canceledContext()); !errors.Is(err, wantErr) { + t.Errorf("non-blocking conn.Wait() = %v, want %v", err, wantErr) + } +} + +func TestConnClosePeerCloses(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.handshake() + + wantErr := &ApplicationError{ + Code: 42, + Reason: "why?", + } + tc.writeFrames(packetType1RTT, debugFrameConnectionCloseApplication{ + code: wantErr.Code, + reason: wantErr.Reason, + }) + tc.wantIdle("CONN_CLOSE response not sent until user closes this side") + + if err := tc.conn.Wait(canceledContext()); !errors.Is(err, wantErr) { + t.Errorf("conn.Wait() = %v, want %v", err, wantErr) + } + + tc.conn.Abort(&ApplicationError{ + Code: 9, + Reason: "because", + }) + tc.wantFrame("CONN_CLOSE sent after user closes connection", + packetType1RTT, debugFrameConnectionCloseApplication{ + code: 9, + reason: "because", + }) +} + +func TestConnCloseReceiveInInitial(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.wantFrame("client sends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeInitial, debugFrameConnectionCloseTransport{ + code: errConnectionRefused, + }) + tc.wantIdle("CONN_CLOSE response not sent until user closes this side") + + wantErr := peerTransportError{code: errConnectionRefused} + if err := tc.conn.Wait(canceledContext()); !errors.Is(err, wantErr) { + t.Errorf("conn.Wait() = %v, want %v", err, wantErr) + } + + tc.conn.Abort(&ApplicationError{Code: 1}) + tc.wantFrame("CONN_CLOSE in Initial frame is APPLICATION_ERROR", + packetTypeInitial, debugFrameConnectionCloseTransport{ + code: errApplicationError, + }) + tc.wantIdle("no more frames to send") +} + +func TestConnCloseReceiveInHandshake(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.ignoreFrame(frameTypeAck) + tc.wantFrame("client sends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeHandshake, debugFrameConnectionCloseTransport{ + code: errConnectionRefused, + }) + tc.wantIdle("CONN_CLOSE response not sent until user closes this side") + + wantErr := peerTransportError{code: errConnectionRefused} + if err := tc.conn.Wait(canceledContext()); !errors.Is(err, wantErr) { + t.Errorf("conn.Wait() = %v, want %v", err, wantErr) + } + + // The conn has Initial and Handshake keys, so it will send CONN_CLOSE in both spaces. + tc.conn.Abort(&ApplicationError{Code: 1}) + tc.wantFrame("CONN_CLOSE in Initial frame is APPLICATION_ERROR", + packetTypeInitial, debugFrameConnectionCloseTransport{ + code: errApplicationError, + }) + tc.wantFrame("CONN_CLOSE in Handshake frame is APPLICATION_ERROR", + packetTypeHandshake, debugFrameConnectionCloseTransport{ + code: errApplicationError, + }) + tc.wantIdle("no more frames to send") +} diff --git a/internal/quic/conn_flow.go b/internal/quic/conn_flow.go new file mode 100644 index 000000000..4f1ab6eaf --- /dev/null +++ b/internal/quic/conn_flow.go @@ -0,0 +1,141 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "sync/atomic" + "time" +) + +// connInflow tracks connection-level flow control for data sent by the peer to us. +// +// There are four byte offsets of significance in the stream of data received from the peer, +// each >= to the previous: +// +// - bytes read by the user +// - bytes received from the peer +// - limit sent to the peer in a MAX_DATA frame +// - potential new limit to sent to the peer +// +// We maintain a flow control window, so as bytes are read by the user +// the potential limit is extended correspondingly. +// +// We keep an atomic counter of bytes read by the user and not yet applied to the +// potential limit (credit). When this count grows large enough, we update the +// new limit to send and mark that we need to send a new MAX_DATA frame. +type connInflow struct { + sent sentVal // set when we need to send a MAX_DATA update to the peer + usedLimit int64 // total bytes sent by the peer, must be less than sentLimit + sentLimit int64 // last MAX_DATA sent to the peer + newLimit int64 // new MAX_DATA to send + + credit atomic.Int64 // bytes read but not yet applied to extending the flow-control window +} + +func (c *Conn) inflowInit() { + // The initial MAX_DATA limit is sent as a transport parameter. + c.streams.inflow.sentLimit = c.config.maxConnReadBufferSize() + c.streams.inflow.newLimit = c.streams.inflow.sentLimit +} + +// handleStreamBytesReadOffLoop records that the user has consumed bytes from a stream. +// We may extend the peer's flow control window. +// +// This is called indirectly by the user, via Read or CloseRead. +func (c *Conn) handleStreamBytesReadOffLoop(n int64) { + if n == 0 { + return + } + if c.shouldUpdateFlowControl(c.streams.inflow.credit.Add(n)) { + // We should send a MAX_DATA update to the peer. + // Record this on the Conn's main loop. + c.sendMsg(func(now time.Time, c *Conn) { + // A MAX_DATA update may have already happened, so check again. + if c.shouldUpdateFlowControl(c.streams.inflow.credit.Load()) { + c.sendMaxDataUpdate() + } + }) + } +} + +// handleStreamBytesReadOnLoop extends the peer's flow control window after +// data has been discarded due to a RESET_STREAM frame. +// +// This is called on the conn's loop. +func (c *Conn) handleStreamBytesReadOnLoop(n int64) { + if c.shouldUpdateFlowControl(c.streams.inflow.credit.Add(n)) { + c.sendMaxDataUpdate() + } +} + +func (c *Conn) sendMaxDataUpdate() { + c.streams.inflow.sent.setUnsent() + // Apply current credit to the limit. + // We don't strictly need to do this here + // since appendMaxDataFrame will do so as well, + // but this avoids redundant trips down this path + // if the MAX_DATA frame doesn't go out right away. + c.streams.inflow.newLimit += c.streams.inflow.credit.Swap(0) +} + +func (c *Conn) shouldUpdateFlowControl(credit int64) bool { + return shouldUpdateFlowControl(c.config.maxConnReadBufferSize(), credit) +} + +// handleStreamBytesReceived records that the peer has sent us stream data. +func (c *Conn) handleStreamBytesReceived(n int64) error { + c.streams.inflow.usedLimit += n + if c.streams.inflow.usedLimit > c.streams.inflow.sentLimit { + return localTransportError(errFlowControl) + } + return nil +} + +// appendMaxDataFrame appends a MAX_DATA frame to the current packet. +// +// It returns true if no more frames need appending, +// false if it could not fit a frame in the current packet. +func (c *Conn) appendMaxDataFrame(w *packetWriter, pnum packetNumber, pto bool) bool { + if c.streams.inflow.sent.shouldSendPTO(pto) { + // Add any unapplied credit to the new limit now. + c.streams.inflow.newLimit += c.streams.inflow.credit.Swap(0) + if !w.appendMaxDataFrame(c.streams.inflow.newLimit) { + return false + } + c.streams.inflow.sentLimit += c.streams.inflow.newLimit + c.streams.inflow.sent.setSent(pnum) + } + return true +} + +// ackOrLossMaxData records the fate of a MAX_DATA frame. +func (c *Conn) ackOrLossMaxData(pnum packetNumber, fate packetFate) { + c.streams.inflow.sent.ackLatestOrLoss(pnum, fate) +} + +// connOutflow tracks connection-level flow control for data sent by us to the peer. +type connOutflow struct { + max int64 // largest MAX_DATA received from peer + used int64 // total bytes of STREAM data sent to peer +} + +// setMaxData updates the connection-level flow control limit +// with the initial limit conveyed in transport parameters +// or an update from a MAX_DATA frame. +func (f *connOutflow) setMaxData(maxData int64) { + f.max = max(f.max, maxData) +} + +// avail returns the number of connection-level flow control bytes available. +func (f *connOutflow) avail() int64 { + return f.max - f.used +} + +// consume records consumption of n bytes of flow. +func (f *connOutflow) consume(n int64) { + f.used += n +} diff --git a/internal/quic/conn_flow_test.go b/internal/quic/conn_flow_test.go new file mode 100644 index 000000000..03e0757a6 --- /dev/null +++ b/internal/quic/conn_flow_test.go @@ -0,0 +1,430 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "testing" +) + +func TestConnInflowReturnOnRead(t *testing.T) { + ctx := canceledContext() + tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) { + c.MaxConnReadBufferSize = 64 + }) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + data: make([]byte, 64), + }) + const readSize = 8 + if n, err := s.ReadContext(ctx, make([]byte, readSize)); n != readSize || err != nil { + t.Fatalf("s.Read() = %v, %v; want %v, nil", n, err, readSize) + } + tc.wantFrame("available window increases, send a MAX_DATA", + packetType1RTT, debugFrameMaxData{ + max: 64 + readSize, + }) + if n, err := s.ReadContext(ctx, make([]byte, 64)); n != 64-readSize || err != nil { + t.Fatalf("s.Read() = %v, %v; want %v, nil", n, err, 64-readSize) + } + tc.wantFrame("available window increases, send a MAX_DATA", + packetType1RTT, debugFrameMaxData{ + max: 128, + }) + // Peer can write up to the new limit. + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + off: 64, + data: make([]byte, 64), + }) + tc.wantIdle("connection is idle") + if n, err := s.ReadContext(ctx, make([]byte, 64)); n != 64 || err != nil { + t.Fatalf("offset 64: s.Read() = %v, %v; want %v, nil", n, err, 64) + } +} + +func TestConnInflowReturnOnRacingReads(t *testing.T) { + // Perform two reads at the same time, + // one for half of MaxConnReadBufferSize + // and one for one byte. + // + // We should observe a single MAX_DATA update. + // Depending on the ordering of events, + // this may include the credit from just the larger read + // or the credit from both. + ctx := canceledContext() + tc := newTestConn(t, serverSide, func(c *Config) { + c.MaxConnReadBufferSize = 64 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, uniStream, 0), + data: make([]byte, 32), + }) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, uniStream, 1), + data: make([]byte, 32), + }) + s1, err := tc.conn.AcceptStream(ctx) + if err != nil { + t.Fatalf("conn.AcceptStream() = %v", err) + } + s2, err := tc.conn.AcceptStream(ctx) + if err != nil { + t.Fatalf("conn.AcceptStream() = %v", err) + } + read1 := runAsync(tc, func(ctx context.Context) (int, error) { + return s1.ReadContext(ctx, make([]byte, 16)) + }) + read2 := runAsync(tc, func(ctx context.Context) (int, error) { + return s2.ReadContext(ctx, make([]byte, 1)) + }) + // This MAX_DATA might extend the window by 16 or 17, depending on + // whether the second write occurs before the update happens. + tc.wantFrameType("MAX_DATA update is sent", + packetType1RTT, debugFrameMaxData{}) + tc.wantIdle("redundant MAX_DATA is not sent") + if _, err := read1.result(); err != nil { + t.Errorf("ReadContext #1 = %v", err) + } + if _, err := read2.result(); err != nil { + t.Errorf("ReadContext #2 = %v", err) + } +} + +func TestConnInflowReturnOnClose(t *testing.T) { + tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) { + c.MaxConnReadBufferSize = 64 + }) + tc.ignoreFrame(frameTypeStopSending) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + data: make([]byte, 64), + }) + s.CloseRead() + tc.wantFrame("closing stream updates connection-level flow control", + packetType1RTT, debugFrameMaxData{ + max: 128, + }) +} + +func TestConnInflowReturnOnReset(t *testing.T) { + tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) { + c.MaxConnReadBufferSize = 64 + }) + tc.ignoreFrame(frameTypeStopSending) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + data: make([]byte, 32), + }) + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: s.id, + finalSize: 64, + }) + s.CloseRead() + tc.wantFrame("receiving stream reseet updates connection-level flow control", + packetType1RTT, debugFrameMaxData{ + max: 128, + }) +} + +func TestConnInflowStreamViolation(t *testing.T) { + tc := newTestConn(t, serverSide, func(c *Config) { + c.MaxConnReadBufferSize = 100 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + // Total MAX_DATA consumed: 50 + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, bidiStream, 0), + data: make([]byte, 50), + }) + // Total MAX_DATA consumed: 80 + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, uniStream, 0), + off: 20, + data: make([]byte, 10), + }) + // Total MAX_DATA consumed: 100 + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, bidiStream, 0), + off: 70, + fin: true, + }) + // This stream has already consumed quota for these bytes. + // Total MAX_DATA consumed: 100 + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, uniStream, 0), + data: make([]byte, 20), + }) + tc.wantIdle("peer has consumed all MAX_DATA quota") + + // Total MAX_DATA consumed: 101 + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, bidiStream, 2), + data: make([]byte, 1), + }) + tc.wantFrame("peer violates MAX_DATA limit", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errFlowControl, + }) +} + +func TestConnInflowResetViolation(t *testing.T) { + tc := newTestConn(t, serverSide, func(c *Config) { + c.MaxConnReadBufferSize = 100 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, bidiStream, 0), + data: make([]byte, 100), + }) + tc.wantIdle("peer has consumed all MAX_DATA quota") + + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: newStreamID(clientSide, uniStream, 0), + finalSize: 0, + }) + tc.wantIdle("stream reset does not consume MAX_DATA quota, no error") + + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: newStreamID(clientSide, uniStream, 1), + finalSize: 1, + }) + tc.wantFrame("RESET_STREAM final size violates MAX_DATA limit", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errFlowControl, + }) +} + +func TestConnInflowMultipleStreams(t *testing.T) { + ctx := canceledContext() + tc := newTestConn(t, serverSide, func(c *Config) { + c.MaxConnReadBufferSize = 128 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + var streams []*Stream + for _, id := range []streamID{ + newStreamID(clientSide, uniStream, 0), + newStreamID(clientSide, uniStream, 1), + newStreamID(clientSide, bidiStream, 0), + newStreamID(clientSide, bidiStream, 1), + } { + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: id, + data: make([]byte, 32), + }) + s, err := tc.conn.AcceptStream(ctx) + if err != nil { + t.Fatalf("AcceptStream() = %v", err) + } + streams = append(streams, s) + if n, err := s.ReadContext(ctx, make([]byte, 1)); err != nil || n != 1 { + t.Fatalf("s.Read() = %v, %v; want 1, nil", n, err) + } + } + tc.wantIdle("streams have read data, but not enough to update MAX_DATA") + + if n, err := streams[0].ReadContext(ctx, make([]byte, 32)); err != nil || n != 31 { + t.Fatalf("s.Read() = %v, %v; want 31, nil", n, err) + } + tc.wantFrame("read enough data to trigger a MAX_DATA update", + packetType1RTT, debugFrameMaxData{ + max: 128 + 32 + 1 + 1 + 1, + }) + + tc.ignoreFrame(frameTypeStopSending) + streams[2].CloseRead() + tc.wantFrame("closed stream triggers another MAX_DATA update", + packetType1RTT, debugFrameMaxData{ + max: 128 + 32 + 1 + 32 + 1, + }) +} + +func TestConnOutflowBlocked(t *testing.T) { + tc, s := newTestConnAndLocalStream(t, clientSide, uniStream, + permissiveTransportParameters, + func(p *transportParameters) { + p.initialMaxData = 10 + }) + tc.ignoreFrame(frameTypeAck) + + data := makeTestData(32) + n, err := s.Write(data) + if n != len(data) || err != nil { + t.Fatalf("s.Write() = %v, %v; want %v, nil", n, err, len(data)) + } + + tc.wantFrame("stream writes data up to MAX_DATA limit", + packetType1RTT, debugFrameStream{ + id: s.id, + data: data[:10], + }) + tc.wantIdle("stream is blocked by MAX_DATA limit") + + tc.writeFrames(packetType1RTT, debugFrameMaxData{ + max: 20, + }) + tc.wantFrame("stream writes data up to new MAX_DATA limit", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 10, + data: data[10:20], + }) + tc.wantIdle("stream is blocked by new MAX_DATA limit") + + tc.writeFrames(packetType1RTT, debugFrameMaxData{ + max: 100, + }) + tc.wantFrame("stream writes remaining data", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 20, + data: data[20:], + }) +} + +func TestConnOutflowMaxDataDecreases(t *testing.T) { + tc, s := newTestConnAndLocalStream(t, clientSide, uniStream, + permissiveTransportParameters, + func(p *transportParameters) { + p.initialMaxData = 10 + }) + tc.ignoreFrame(frameTypeAck) + + // Decrease in MAX_DATA is ignored. + tc.writeFrames(packetType1RTT, debugFrameMaxData{ + max: 5, + }) + + data := makeTestData(32) + n, err := s.Write(data) + if n != len(data) || err != nil { + t.Fatalf("s.Write() = %v, %v; want %v, nil", n, err, len(data)) + } + + tc.wantFrame("stream writes data up to MAX_DATA limit", + packetType1RTT, debugFrameStream{ + id: s.id, + data: data[:10], + }) +} + +func TestConnOutflowMaxDataRoundRobin(t *testing.T) { + ctx := canceledContext() + tc := newTestConn(t, clientSide, permissiveTransportParameters, + func(p *transportParameters) { + p.initialMaxData = 0 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + s1, err := tc.conn.newLocalStream(ctx, uniStream) + if err != nil { + t.Fatalf("conn.newLocalStream(%v) = %v", uniStream, err) + } + s2, err := tc.conn.newLocalStream(ctx, uniStream) + if err != nil { + t.Fatalf("conn.newLocalStream(%v) = %v", uniStream, err) + } + + s1.Write(make([]byte, 10)) + s2.Write(make([]byte, 10)) + + tc.writeFrames(packetType1RTT, debugFrameMaxData{ + max: 1, + }) + tc.wantFrame("stream 1 writes data up to MAX_DATA limit", + packetType1RTT, debugFrameStream{ + id: s1.id, + data: []byte{0}, + }) + + tc.writeFrames(packetType1RTT, debugFrameMaxData{ + max: 2, + }) + tc.wantFrame("stream 2 writes data up to MAX_DATA limit", + packetType1RTT, debugFrameStream{ + id: s2.id, + data: []byte{0}, + }) + + tc.writeFrames(packetType1RTT, debugFrameMaxData{ + max: 3, + }) + tc.wantFrame("stream 1 writes data up to MAX_DATA limit", + packetType1RTT, debugFrameStream{ + id: s1.id, + off: 1, + data: []byte{0}, + }) +} + +func TestConnOutflowMetaAndData(t *testing.T) { + tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream, + permissiveTransportParameters, + func(p *transportParameters) { + p.initialMaxData = 0 + }) + tc.ignoreFrame(frameTypeAck) + + data := makeTestData(32) + s.Write(data) + + s.CloseRead() + tc.wantFrame("CloseRead sends a STOP_SENDING, not flow controlled", + packetType1RTT, debugFrameStopSending{ + id: s.id, + }) + + tc.writeFrames(packetType1RTT, debugFrameMaxData{ + max: 100, + }) + tc.wantFrame("unblocked MAX_DATA", + packetType1RTT, debugFrameStream{ + id: s.id, + data: data, + }) +} + +func TestConnOutflowResentData(t *testing.T) { + tc, s := newTestConnAndLocalStream(t, clientSide, bidiStream, + permissiveTransportParameters, + func(p *transportParameters) { + p.initialMaxData = 10 + }) + tc.ignoreFrame(frameTypeAck) + + data := makeTestData(15) + s.Write(data[:8]) + tc.wantFrame("data is under MAX_DATA limit, all sent", + packetType1RTT, debugFrameStream{ + id: s.id, + data: data[:8], + }) + + // Lose the last STREAM packet. + const pto = false + tc.triggerLossOrPTO(packetType1RTT, false) + tc.wantFrame("lost STREAM data is retransmitted", + packetType1RTT, debugFrameStream{ + id: s.id, + data: data[:8], + }) + + s.Write(data[8:]) + tc.wantFrame("new data is sent up to the MAX_DATA limit", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 8, + data: data[8:10], + }) +} diff --git a/internal/quic/conn_id.go b/internal/quic/conn_id.go new file mode 100644 index 000000000..045e646ac --- /dev/null +++ b/internal/quic/conn_id.go @@ -0,0 +1,423 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "crypto/rand" +) + +// connIDState is a conn's connection IDs. +type connIDState struct { + // The destination connection IDs of packets we receive are local. + // The destination connection IDs of packets we send are remote. + // + // Local IDs are usually issued by us, and remote IDs by the peer. + // The exception is the transient destination connection ID sent in + // a client's Initial packets, which is chosen by the client. + // + // These are []connID rather than []*connID to minimize allocations. + local []connID + remote []connID + + nextLocalSeq int64 + retireRemotePriorTo int64 // largest Retire Prior To value sent by the peer + peerActiveConnIDLimit int64 // peer's active_connection_id_limit transport parameter + + needSend bool +} + +// A connID is a connection ID and associated metadata. +type connID struct { + // cid is the connection ID itself. + cid []byte + + // seq is the connection ID's sequence number: + // https://www.rfc-editor.org/rfc/rfc9000.html#section-5.1.1-1 + // + // For the transient destination ID in a client's Initial packet, this is -1. + seq int64 + + // retired is set when the connection ID is retired. + retired bool + + // send is set when the connection ID's state needs to be sent to the peer. + // + // For local IDs, this indicates a new ID that should be sent + // in a NEW_CONNECTION_ID frame. + // + // For remote IDs, this indicates a retired ID that should be sent + // in a RETIRE_CONNECTION_ID frame. + send sentVal +} + +func (s *connIDState) initClient(c *Conn) error { + // Client chooses its initial connection ID, and sends it + // in the Source Connection ID field of the first Initial packet. + locid, err := c.newConnID(0) + if err != nil { + return err + } + s.local = append(s.local, connID{ + seq: 0, + cid: locid, + }) + s.nextLocalSeq = 1 + + // Client chooses an initial, transient connection ID for the server, + // and sends it in the Destination Connection ID field of the first Initial packet. + remid, err := c.newConnID(-1) + if err != nil { + return err + } + s.remote = append(s.remote, connID{ + seq: -1, + cid: remid, + }) + const retired = false + c.listener.connIDsChanged(c, retired, s.local[:]) + return nil +} + +func (s *connIDState) initServer(c *Conn, dstConnID []byte) error { + // Client-chosen, transient connection ID received in the first Initial packet. + // The server will not use this as the Source Connection ID of packets it sends, + // but remembers it because it may receive packets sent to this destination. + s.local = append(s.local, connID{ + seq: -1, + cid: cloneBytes(dstConnID), + }) + + // Server chooses a connection ID, and sends it in the Source Connection ID of + // the response to the clent. + locid, err := c.newConnID(0) + if err != nil { + return err + } + s.local = append(s.local, connID{ + seq: 0, + cid: locid, + }) + s.nextLocalSeq = 1 + const retired = false + c.listener.connIDsChanged(c, retired, s.local[:]) + return nil +} + +// srcConnID is the Source Connection ID to use in a sent packet. +func (s *connIDState) srcConnID() []byte { + if s.local[0].seq == -1 && len(s.local) > 1 { + // Don't use the transient connection ID if another is available. + return s.local[1].cid + } + return s.local[0].cid +} + +// dstConnID is the Destination Connection ID to use in a sent packet. +func (s *connIDState) dstConnID() (cid []byte, ok bool) { + for i := range s.remote { + if !s.remote[i].retired { + return s.remote[i].cid, true + } + } + return nil, false +} + +// setPeerActiveConnIDLimit sets the active_connection_id_limit +// transport parameter received from the peer. +func (s *connIDState) setPeerActiveConnIDLimit(c *Conn, lim int64) error { + s.peerActiveConnIDLimit = lim + return s.issueLocalIDs(c) +} + +func (s *connIDState) issueLocalIDs(c *Conn) error { + toIssue := min(int(s.peerActiveConnIDLimit), maxPeerActiveConnIDLimit) + for i := range s.local { + if s.local[i].seq != -1 && !s.local[i].retired { + toIssue-- + } + } + prev := len(s.local) + for toIssue > 0 { + cid, err := c.newConnID(s.nextLocalSeq) + if err != nil { + return err + } + s.local = append(s.local, connID{ + seq: s.nextLocalSeq, + cid: cid, + }) + s.local[len(s.local)-1].send.setUnsent() + s.nextLocalSeq++ + s.needSend = true + toIssue-- + } + const retired = false + c.listener.connIDsChanged(c, retired, s.local[prev:]) + return nil +} + +// validateTransportParameters verifies the original_destination_connection_id and +// initial_source_connection_id transport parameters match the expected values. +func (s *connIDState) validateTransportParameters(side connSide, p transportParameters) error { + // TODO: Consider returning more detailed errors, for debugging. + switch side { + case clientSide: + // Verify original_destination_connection_id matches + // the transient remote connection ID we chose. + if len(s.remote) == 0 || s.remote[0].seq != -1 { + return localTransportError(errInternal) + } + if !bytes.Equal(s.remote[0].cid, p.originalDstConnID) { + return localTransportError(errTransportParameter) + } + // Remove the transient remote connection ID. + // We have no further need for it. + s.remote = append(s.remote[:0], s.remote[1:]...) + case serverSide: + if p.originalDstConnID != nil { + // Clients do not send original_destination_connection_id. + return localTransportError(errTransportParameter) + } + } + // Verify initial_source_connection_id matches the first remote connection ID. + if len(s.remote) == 0 || s.remote[0].seq != 0 { + return localTransportError(errInternal) + } + if !bytes.Equal(p.initialSrcConnID, s.remote[0].cid) { + return localTransportError(errTransportParameter) + } + return nil +} + +// handlePacket updates the connection ID state during the handshake +// (Initial and Handshake packets). +func (s *connIDState) handlePacket(c *Conn, ptype packetType, srcConnID []byte) { + switch { + case ptype == packetTypeInitial && c.side == clientSide: + if len(s.remote) == 1 && s.remote[0].seq == -1 { + // We're a client connection processing the first Initial packet + // from the server. Replace the transient remote connection ID + // with the Source Connection ID from the packet. + // Leave the transient ID the list for now, since we'll need it when + // processing the transport parameters. + s.remote[0].retired = true + s.remote = append(s.remote, connID{ + seq: 0, + cid: cloneBytes(srcConnID), + }) + } + case ptype == packetTypeInitial && c.side == serverSide: + if len(s.remote) == 0 { + // We're a server connection processing the first Initial packet + // from the client. Set the client's connection ID. + s.remote = append(s.remote, connID{ + seq: 0, + cid: cloneBytes(srcConnID), + }) + } + case ptype == packetTypeHandshake && c.side == serverSide: + if len(s.local) > 0 && s.local[0].seq == -1 && !s.local[0].retired { + // We're a server connection processing the first Handshake packet from + // the client. Discard the transient, client-chosen connection ID used + // for Initial packets; the client will never send it again. + const retired = true + c.listener.connIDsChanged(c, retired, s.local[0:1]) + s.local = append(s.local[:0], s.local[1:]...) + } + } +} + +func (s *connIDState) handleNewConnID(seq, retire int64, cid []byte, resetToken [16]byte) error { + if len(s.remote[0].cid) == 0 { + // "An endpoint that is sending packets with a zero-length + // Destination Connection ID MUST treat receipt of a NEW_CONNECTION_ID + // frame as a connection error of type PROTOCOL_VIOLATION." + // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.15-6 + return localTransportError(errProtocolViolation) + } + + if retire > s.retireRemotePriorTo { + s.retireRemotePriorTo = retire + } + + have := false // do we already have this connection ID? + active := 0 + for i := range s.remote { + rcid := &s.remote[i] + if !rcid.retired && rcid.seq >= 0 && rcid.seq < s.retireRemotePriorTo { + s.retireRemote(rcid) + } + if !rcid.retired { + active++ + } + if rcid.seq == seq { + if !bytes.Equal(rcid.cid, cid) { + return localTransportError(errProtocolViolation) + } + have = true // yes, we've seen this sequence number + } + } + + if !have { + // This is a new connection ID that we have not seen before. + // + // We could take steps to keep the list of remote connection IDs + // sorted by sequence number, but there's no particular need + // so we don't bother. + s.remote = append(s.remote, connID{ + seq: seq, + cid: cloneBytes(cid), + }) + if seq < s.retireRemotePriorTo { + // This ID was already retired by a previous NEW_CONNECTION_ID frame. + s.retireRemote(&s.remote[len(s.remote)-1]) + } else { + active++ + } + } + + if active > activeConnIDLimit { + // Retired connection IDs (including newly-retired ones) do not count + // against the limit. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-5.1.1-5 + return localTransportError(errConnectionIDLimit) + } + + // "An endpoint SHOULD limit the number of connection IDs it has retired locally + // for which RETIRE_CONNECTION_ID frames have not yet been acknowledged." + // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-6 + // + // Set a limit of four times the active_connection_id_limit for + // the total number of remote connection IDs we keep state for locally. + if len(s.remote) > 4*activeConnIDLimit { + return localTransportError(errConnectionIDLimit) + } + + return nil +} + +// retireRemote marks a remote connection ID as retired. +func (s *connIDState) retireRemote(rcid *connID) { + rcid.retired = true + rcid.send.setUnsent() + s.needSend = true +} + +func (s *connIDState) handleRetireConnID(c *Conn, seq int64) error { + if seq >= s.nextLocalSeq { + return localTransportError(errProtocolViolation) + } + for i := range s.local { + if s.local[i].seq == seq { + const retired = true + c.listener.connIDsChanged(c, retired, s.local[i:i+1]) + s.local = append(s.local[:i], s.local[i+1:]...) + break + } + } + s.issueLocalIDs(c) + return nil +} + +func (s *connIDState) ackOrLossNewConnectionID(pnum packetNumber, seq int64, fate packetFate) { + for i := range s.local { + if s.local[i].seq != seq { + continue + } + s.local[i].send.ackOrLoss(pnum, fate) + if fate != packetAcked { + s.needSend = true + } + return + } +} + +func (s *connIDState) ackOrLossRetireConnectionID(pnum packetNumber, seq int64, fate packetFate) { + for i := 0; i < len(s.remote); i++ { + if s.remote[i].seq != seq { + continue + } + if fate == packetAcked { + // We have retired this connection ID, and the peer has acked. + // Discard its state completely. + s.remote = append(s.remote[:i], s.remote[i+1:]...) + } else { + // RETIRE_CONNECTION_ID frame was lost, mark for retransmission. + s.needSend = true + s.remote[i].send.ackOrLoss(pnum, fate) + } + return + } +} + +// appendFrames appends NEW_CONNECTION_ID and RETIRE_CONNECTION_ID frames +// to the current packet. +// +// It returns true if no more frames need appending, +// false if not everything fit in the current packet. +func (s *connIDState) appendFrames(w *packetWriter, pnum packetNumber, pto bool) bool { + if !s.needSend && !pto { + // Fast path: We don't need to send anything. + return true + } + retireBefore := int64(0) + if s.local[0].seq != -1 { + retireBefore = s.local[0].seq + } + for i := range s.local { + if !s.local[i].send.shouldSendPTO(pto) { + continue + } + if !w.appendNewConnectionIDFrame( + s.local[i].seq, + retireBefore, + s.local[i].cid, + [16]byte{}, // TODO: stateless reset token + ) { + return false + } + s.local[i].send.setSent(pnum) + } + for i := range s.remote { + if !s.remote[i].send.shouldSendPTO(pto) { + continue + } + if !w.appendRetireConnectionIDFrame(s.remote[i].seq) { + return false + } + s.remote[i].send.setSent(pnum) + } + s.needSend = false + return true +} + +func cloneBytes(b []byte) []byte { + n := make([]byte, len(b)) + copy(n, b) + return n +} + +func (c *Conn) newConnID(seq int64) ([]byte, error) { + if c.testHooks != nil { + return c.testHooks.newConnID(seq) + } + return newRandomConnID(seq) +} + +func newRandomConnID(_ int64) ([]byte, error) { + // It is not necessary for connection IDs to be cryptographically secure, + // but it doesn't hurt. + id := make([]byte, connIDLen) + if _, err := rand.Read(id); err != nil { + // TODO: Surface this error as a metric or log event or something. + // rand.Read really shouldn't ever fail, but if it does, we should + // have a way to inform the user. + return nil, err + } + return id, nil +} diff --git a/internal/quic/conn_id_test.go b/internal/quic/conn_id_test.go new file mode 100644 index 000000000..44755ecf4 --- /dev/null +++ b/internal/quic/conn_id_test.go @@ -0,0 +1,588 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "crypto/tls" + "fmt" + "net/netip" + "strings" + "testing" +) + +func TestConnIDClientHandshake(t *testing.T) { + tc := newTestConn(t, clientSide) + // On initialization, the client chooses local and remote IDs. + // + // The order in which we allocate the two isn't actually important, + // but test is a lot simpler if we assume. + if got, want := tc.conn.connIDState.srcConnID(), testLocalConnID(0); !bytes.Equal(got, want) { + t.Errorf("after initialization: srcConnID = %x, want %x", got, want) + } + dstConnID, _ := tc.conn.connIDState.dstConnID() + if got, want := dstConnID, testLocalConnID(-1); !bytes.Equal(got, want) { + t.Errorf("after initialization: dstConnID = %x, want %x", got, want) + } + + // The server's first Initial packet provides the client with a + // non-transient remote connection ID. + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + dstConnID, _ = tc.conn.connIDState.dstConnID() + if got, want := dstConnID, testPeerConnID(0); !bytes.Equal(got, want) { + t.Errorf("after receiving Initial: dstConnID = %x, want %x", got, want) + } + + wantLocal := []connID{{ + cid: testLocalConnID(0), + seq: 0, + }} + if got := tc.conn.connIDState.local; !connIDListEqual(got, wantLocal) { + t.Errorf("local ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantLocal)) + } + wantRemote := []connID{{ + cid: testLocalConnID(-1), + seq: -1, + }, { + cid: testPeerConnID(0), + seq: 0, + }} + if got := tc.conn.connIDState.remote; !connIDListEqual(got, wantRemote) { + t.Errorf("remote ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantRemote)) + } +} + +func TestConnIDServerHandshake(t *testing.T) { + tc := newTestConn(t, serverSide) + // On initialization, the server is provided with the client-chosen + // transient connection ID, and allocates an ID of its own. + // The Initial packet sets the remote connection ID. + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial][:1], + }) + if got, want := tc.conn.connIDState.srcConnID(), testLocalConnID(0); !bytes.Equal(got, want) { + t.Errorf("after initClient: srcConnID = %q, want %q", got, want) + } + dstConnID, _ := tc.conn.connIDState.dstConnID() + if got, want := dstConnID, testPeerConnID(0); !bytes.Equal(got, want) { + t.Errorf("after initClient: dstConnID = %q, want %q", got, want) + } + + // The Initial flight of CRYPTO data includes transport parameters, + // which cause us to allocate another local connection ID. + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + off: 1, + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial][1:], + }) + wantLocal := []connID{{ + cid: testPeerConnID(-1), + seq: -1, + }, { + cid: testLocalConnID(0), + seq: 0, + }, { + cid: testLocalConnID(1), + seq: 1, + }} + if got := tc.conn.connIDState.local; !connIDListEqual(got, wantLocal) { + t.Errorf("local ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantLocal)) + } + wantRemote := []connID{{ + cid: testPeerConnID(0), + seq: 0, + }} + if got := tc.conn.connIDState.remote; !connIDListEqual(got, wantRemote) { + t.Errorf("remote ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantRemote)) + } + + // The client's first Handshake packet permits the server to discard the + // transient connection ID. + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + wantLocal = []connID{{ + cid: testLocalConnID(0), + seq: 0, + }, { + cid: testLocalConnID(1), + seq: 1, + }} + if got := tc.conn.connIDState.local; !connIDListEqual(got, wantLocal) { + t.Errorf("local ids: %v, want %v", fmtConnIDList(got), fmtConnIDList(wantLocal)) + } +} + +func connIDListEqual(a, b []connID) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i].seq != b[i].seq { + return false + } + if !bytes.Equal(a[i].cid, b[i].cid) { + return false + } + } + return true +} + +func fmtConnIDList(s []connID) string { + var strs []string + for _, cid := range s { + strs = append(strs, fmt.Sprintf("[seq:%v cid:{%x}]", cid.seq, cid.cid)) + } + return "{" + strings.Join(strs, " ") + "}" +} + +func TestNewRandomConnID(t *testing.T) { + cid, err := newRandomConnID(0) + if len(cid) != connIDLen || err != nil { + t.Fatalf("newConnID() = %x, %v; want %v bytes", cid, connIDLen, err) + } +} + +func TestConnIDPeerRequestsManyIDs(t *testing.T) { + // "An endpoint SHOULD ensure that its peer has a sufficient number + // of available and unused connection IDs." + // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4 + // + // "An endpoint MAY limit the total number of connection IDs + // issued for each connection [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-6 + // + // Peer requests 100 connection IDs. + // We give them 4 in total. + tc := newTestConn(t, serverSide, func(p *transportParameters) { + p.activeConnIDLimit = 100 + }) + tc.ignoreFrame(frameTypeAck) + tc.ignoreFrame(frameTypeCrypto) + + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.wantFrame("provide additional connection ID 1", + packetType1RTT, debugFrameNewConnectionID{ + seq: 1, + connID: testLocalConnID(1), + }) + tc.wantFrame("provide additional connection ID 2", + packetType1RTT, debugFrameNewConnectionID{ + seq: 2, + connID: testLocalConnID(2), + }) + tc.wantFrame("provide additional connection ID 3", + packetType1RTT, debugFrameNewConnectionID{ + seq: 3, + connID: testLocalConnID(3), + }) + tc.wantIdle("connection ID limit reached, no more to provide") +} + +func TestConnIDPeerProvidesTooManyIDs(t *testing.T) { + // "An endpoint MUST NOT provide more connection IDs than the peer's limit." + // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4 + tc := newTestConn(t, serverSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 2, + connID: testLocalConnID(2), + }) + tc.wantFrame("peer provided 3 connection IDs, our limit is 2", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errConnectionIDLimit, + }) +} + +func TestConnIDPeerTemporarilyExceedsActiveConnIDLimit(t *testing.T) { + // "An endpoint MAY send connection IDs that temporarily exceed a peer's limit + // if the NEW_CONNECTION_ID frame also requires the retirement of any excess [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-4 + tc := newTestConn(t, serverSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + retirePriorTo: 2, + seq: 2, + connID: testPeerConnID(2), + }, debugFrameNewConnectionID{ + retirePriorTo: 2, + seq: 3, + connID: testPeerConnID(3), + }) + tc.wantFrame("peer requested we retire conn id 0", + packetType1RTT, debugFrameRetireConnectionID{ + seq: 0, + }) + tc.wantFrame("peer requested we retire conn id 1", + packetType1RTT, debugFrameRetireConnectionID{ + seq: 1, + }) +} + +func TestConnIDPeerRetiresConnID(t *testing.T) { + // "An endpoint SHOULD supply a new connection ID when the peer retires a connection ID." + // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-6 + for _, side := range []connSide{ + clientSide, + serverSide, + } { + t.Run(side.String(), func(t *testing.T) { + tc := newTestConn(t, side) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameRetireConnectionID{ + seq: 0, + }) + tc.wantFrame("provide replacement connection ID", + packetType1RTT, debugFrameNewConnectionID{ + seq: 2, + retirePriorTo: 1, + connID: testLocalConnID(2), + }) + }) + } +} + +func TestConnIDPeerWithZeroLengthConnIDSendsNewConnectionID(t *testing.T) { + // "An endpoint that selects a zero-length connection ID during the handshake + // cannot issue a new connection ID." + // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.1-8 + tc := newTestConn(t, clientSide, func(p *transportParameters) { + p.initialSrcConnID = []byte{} + }) + tc.peerConnID = []byte{} + tc.ignoreFrame(frameTypeAck) + tc.uncheckedHandshake() + + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 1, + connID: testPeerConnID(1), + }) + tc.wantFrame("invalid NEW_CONNECTION_ID: previous conn id is zero-length", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errProtocolViolation, + }) +} + +func TestConnIDPeerRequestsRetirement(t *testing.T) { + // "Upon receipt of an increased Retire Prior To field, the peer MUST + // stop using the corresponding connection IDs and retire them with + // RETIRE_CONNECTION_ID frames [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-5 + tc := newTestConn(t, clientSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 2, + retirePriorTo: 1, + connID: testPeerConnID(2), + }) + tc.wantFrame("peer asked for conn id 0 to be retired", + packetType1RTT, debugFrameRetireConnectionID{ + seq: 0, + }) + if got, want := tc.lastPacket.dstConnID, testPeerConnID(1); !bytes.Equal(got, want) { + t.Fatalf("used destination conn id {%x}, want {%x}", got, want) + } +} + +func TestConnIDPeerDoesNotAcknowledgeRetirement(t *testing.T) { + // "An endpoint SHOULD limit the number of connection IDs it has retired locally + // for which RETIRE_CONNECTION_ID frames have not yet been acknowledged." + // https://www.rfc-editor.org/rfc/rfc9000#section-5.1.2-6 + tc := newTestConn(t, clientSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + tc.ignoreFrame(frameTypeRetireConnectionID) + + // Send a number of NEW_CONNECTION_ID frames, each retiring an old one. + for seq := int64(0); seq < 7; seq++ { + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: seq + 2, + retirePriorTo: seq + 1, + connID: testPeerConnID(seq + 2), + }) + // We're ignoring the RETIRE_CONNECTION_ID frames. + } + tc.wantFrame("number of retired, unacked conn ids is too large", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errConnectionIDLimit, + }) +} + +func TestConnIDRepeatedNewConnectionIDFrame(t *testing.T) { + // "Receipt of the same [NEW_CONNECTION_ID] frame multiple times + // MUST NOT be treated as a connection error. + // https://www.rfc-editor.org/rfc/rfc9000#section-19.15-7 + tc := newTestConn(t, clientSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + for i := 0; i < 4; i++ { + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 2, + retirePriorTo: 1, + connID: testPeerConnID(2), + }) + } + tc.wantFrame("peer asked for conn id to be retired", + packetType1RTT, debugFrameRetireConnectionID{ + seq: 0, + }) + tc.wantIdle("repeated NEW_CONNECTION_ID frames are not an error") +} + +func TestConnIDForSequenceNumberChanges(t *testing.T) { + // "[...] if a sequence number is used for different connection IDs, + // the endpoint MAY treat that receipt as a connection error + // of type PROTOCOL_VIOLATION." + // https://www.rfc-editor.org/rfc/rfc9000#section-19.15-8 + tc := newTestConn(t, clientSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + tc.ignoreFrame(frameTypeRetireConnectionID) + + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 2, + retirePriorTo: 1, + connID: testPeerConnID(2), + }) + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 2, + retirePriorTo: 1, + connID: testPeerConnID(3), + }) + tc.wantFrame("connection ID for sequence 0 has changed", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errProtocolViolation, + }) +} + +func TestConnIDRetirePriorToAfterNewConnID(t *testing.T) { + // "Receiving a value in the Retire Prior To field that is greater than + // that in the Sequence Number field MUST be treated as a connection error + // of type FRAME_ENCODING_ERROR. + // https://www.rfc-editor.org/rfc/rfc9000#section-19.15-9 + tc := newTestConn(t, serverSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + retirePriorTo: 3, + seq: 2, + connID: testPeerConnID(2), + }) + tc.wantFrame("invalid NEW_CONNECTION_ID: retired the new conn id", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errFrameEncoding, + }) +} + +func TestConnIDAlreadyRetired(t *testing.T) { + // "An endpoint that receives a NEW_CONNECTION_ID frame with a + // sequence number smaller than the Retire Prior To field of a + // previously received NEW_CONNECTION_ID frame MUST send a + // corresponding RETIRE_CONNECTION_ID frame [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-19.15-11 + tc := newTestConn(t, clientSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 4, + retirePriorTo: 3, + connID: testPeerConnID(4), + }) + tc.wantFrame("peer asked for conn id to be retired", + packetType1RTT, debugFrameRetireConnectionID{ + seq: 0, + }) + tc.wantFrame("peer asked for conn id to be retired", + packetType1RTT, debugFrameRetireConnectionID{ + seq: 1, + }) + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 2, + retirePriorTo: 0, + connID: testPeerConnID(2), + }) + tc.wantFrame("NEW_CONNECTION_ID was for an already-retired ID", + packetType1RTT, debugFrameRetireConnectionID{ + seq: 2, + }) +} + +func TestConnIDRepeatedRetireConnectionIDFrame(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + for i := 0; i < 4; i++ { + tc.writeFrames(packetType1RTT, + debugFrameRetireConnectionID{ + seq: 0, + }) + } + tc.wantFrame("issue new conn id after peer retires one", + packetType1RTT, debugFrameNewConnectionID{ + retirePriorTo: 1, + seq: 2, + connID: testLocalConnID(2), + }) + tc.wantIdle("repeated RETIRE_CONNECTION_ID frames are not an error") +} + +func TestConnIDRetiredUnsent(t *testing.T) { + // "Receipt of a RETIRE_CONNECTION_ID frame containing a sequence number + // greater than any previously sent to the peer MUST be treated as a + // connection error of type PROTOCOL_VIOLATION." + // https://www.rfc-editor.org/rfc/rfc9000#section-19.16-7 + tc := newTestConn(t, clientSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameRetireConnectionID{ + seq: 2, + }) + tc.wantFrame("invalid NEW_CONNECTION_ID: previous conn id is zero-length", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errProtocolViolation, + }) +} + +func TestConnIDUsePreferredAddressConnID(t *testing.T) { + // Peer gives us a connection ID in the preferred address transport parameter. + // We don't use the preferred address at this time, but we should use the + // connection ID. (It isn't tied to any specific address.) + // + // This test will probably need updating if/when we start using the preferred address. + cid := testPeerConnID(10) + tc := newTestConn(t, serverSide, func(p *transportParameters) { + p.preferredAddrV4 = netip.MustParseAddrPort("0.0.0.0:0") + p.preferredAddrV6 = netip.MustParseAddrPort("[::0]:0") + p.preferredAddrConnID = cid + p.preferredAddrResetToken = make([]byte, 16) + }) + tc.uncheckedHandshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 2, + retirePriorTo: 1, + connID: []byte{0xff}, + }) + tc.wantFrame("peer asked for conn id 0 to be retired", + packetType1RTT, debugFrameRetireConnectionID{ + seq: 0, + }) + if got, want := tc.lastPacket.dstConnID, cid; !bytes.Equal(got, want) { + t.Fatalf("used destination conn id {%x}, want {%x} from preferred address transport parameter", got, want) + } +} + +func TestConnIDPeerProvidesPreferredAddrAndTooManyConnIDs(t *testing.T) { + // Peer gives us more conn ids than our advertised limit, + // including a conn id in the preferred address transport parameter. + cid := testPeerConnID(10) + tc := newTestConn(t, serverSide, func(p *transportParameters) { + p.preferredAddrV4 = netip.MustParseAddrPort("0.0.0.0:0") + p.preferredAddrV6 = netip.MustParseAddrPort("[::0]:0") + p.preferredAddrConnID = cid + p.preferredAddrResetToken = make([]byte, 16) + }) + tc.uncheckedHandshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 2, + retirePriorTo: 0, + connID: testPeerConnID(2), + }) + tc.wantFrame("peer provided 3 connection IDs, our limit is 2", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errConnectionIDLimit, + }) +} + +func TestConnIDPeerWithZeroLengthIDProvidesPreferredAddr(t *testing.T) { + // Peer gives us more conn ids than our advertised limit, + // including a conn id in the preferred address transport parameter. + tc := newTestConn(t, serverSide, func(p *transportParameters) { + p.initialSrcConnID = []byte{} + p.preferredAddrV4 = netip.MustParseAddrPort("0.0.0.0:0") + p.preferredAddrV6 = netip.MustParseAddrPort("[::0]:0") + p.preferredAddrConnID = testPeerConnID(1) + p.preferredAddrResetToken = make([]byte, 16) + }) + tc.peerConnID = []byte{} + + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.wantFrame("peer with zero-length connection ID tried to provide another in transport parameters", + packetTypeInitial, debugFrameConnectionCloseTransport{ + code: errProtocolViolation, + }) +} + +func TestConnIDInitialSrcConnIDMismatch(t *testing.T) { + // "Endpoints MUST validate that received [initial_source_connection_id] + // parameters match received connection ID values." + // https://www.rfc-editor.org/rfc/rfc9000#section-7.3-3 + testSides(t, "", func(t *testing.T, side connSide) { + tc := newTestConn(t, side, func(p *transportParameters) { + p.initialSrcConnID = []byte("invalid") + }) + tc.ignoreFrame(frameTypeAck) + tc.ignoreFrame(frameTypeCrypto) + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + if side == clientSide { + // Server transport parameters are carried in the Handshake packet. + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + } + tc.wantFrame("initial_source_connection_id transport parameter mismatch", + packetTypeInitial, debugFrameConnectionCloseTransport{ + code: errTransportParameter, + }) + }) +} diff --git a/internal/quic/conn_loss.go b/internal/quic/conn_loss.go new file mode 100644 index 000000000..85bda314e --- /dev/null +++ b/internal/quic/conn_loss.go @@ -0,0 +1,83 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "fmt" + +// handleAckOrLoss deals with the final fate of a packet we sent: +// Either the peer acknowledges it, or we declare it lost. +// +// In order to handle packet loss, we must retain any information sent to the peer +// until the peer has acknowledged it. +// +// When information is acknowledged, we can discard it. +// +// When information is lost, we mark it for retransmission. +// See RFC 9000, Section 13.3 for a complete list of information which is retransmitted on loss. +// https://www.rfc-editor.org/rfc/rfc9000#section-13.3 +func (c *Conn) handleAckOrLoss(space numberSpace, sent *sentPacket, fate packetFate) { + // The list of frames in a sent packet is marshaled into a buffer in the sentPacket + // by the packetWriter. Unmarshal that buffer here. This code must be kept in sync with + // packetWriter.append*. + // + // A sent packet meets its fate (acked or lost) only once, so it's okay to consume + // the sentPacket's buffer here. + for !sent.done() { + switch f := sent.next(); f { + default: + panic(fmt.Sprintf("BUG: unhandled acked/lost frame type %x", f)) + case frameTypeAck: + // Unlike most information, loss of an ACK frame does not trigger + // retransmission. ACKs are sent in response to ack-eliciting packets, + // and always contain the latest information available. + // + // Acknowledgement of an ACK frame may allow us to discard information + // about older packets. + largest := packetNumber(sent.nextInt()) + if fate == packetAcked { + c.acks[space].handleAck(largest) + } + case frameTypeCrypto: + start, end := sent.nextRange() + c.crypto[space].ackOrLoss(start, end, fate) + case frameTypeMaxData: + c.ackOrLossMaxData(sent.num, fate) + case frameTypeResetStream, + frameTypeStopSending, + frameTypeMaxStreamData, + frameTypeStreamDataBlocked: + id := streamID(sent.nextInt()) + s := c.streamForID(id) + if s == nil { + continue + } + s.ackOrLoss(sent.num, f, fate) + case frameTypeStreamBase, + frameTypeStreamBase | streamFinBit: + id := streamID(sent.nextInt()) + start, end := sent.nextRange() + s := c.streamForID(id) + if s == nil { + continue + } + fin := f&streamFinBit != 0 + s.ackOrLossData(sent.num, start, end, fin, fate) + case frameTypeMaxStreamsBidi: + c.streams.remoteLimit[bidiStream].sendMax.ackLatestOrLoss(sent.num, fate) + case frameTypeMaxStreamsUni: + c.streams.remoteLimit[uniStream].sendMax.ackLatestOrLoss(sent.num, fate) + case frameTypeNewConnectionID: + seq := int64(sent.nextInt()) + c.connIDState.ackOrLossNewConnectionID(sent.num, seq, fate) + case frameTypeRetireConnectionID: + seq := int64(sent.nextInt()) + c.connIDState.ackOrLossRetireConnectionID(sent.num, seq, fate) + case frameTypeHandshakeDone: + c.handshakeConfirmed.ackOrLoss(sent.num, fate) + } + } +} diff --git a/internal/quic/conn_loss_test.go b/internal/quic/conn_loss_test.go new file mode 100644 index 000000000..9b8846251 --- /dev/null +++ b/internal/quic/conn_loss_test.go @@ -0,0 +1,685 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "crypto/tls" + "fmt" + "testing" +) + +// Frames may be retransmitted either when the packet containing the frame is lost, or on PTO. +// lostFrameTest runs a test in both configurations. +func lostFrameTest(t *testing.T, f func(t *testing.T, pto bool)) { + t.Run("lost", func(t *testing.T) { + f(t, false) + }) + t.Run("pto", func(t *testing.T) { + f(t, true) + }) +} + +// triggerLossOrPTO causes the conn to declare the last sent packet lost, +// or advances to the PTO timer. +func (tc *testConn) triggerLossOrPTO(ptype packetType, pto bool) { + tc.t.Helper() + if pto { + if !tc.conn.loss.ptoTimerArmed { + tc.t.Fatalf("PTO timer not armed, expected it to be") + } + if *testVV { + tc.t.Logf("advancing to PTO timer") + } + tc.advanceTo(tc.conn.loss.timer) + return + } + if *testVV { + *testVV = false + defer func() { + tc.t.Logf("cause conn to declare last packet lost") + *testVV = true + }() + } + defer func(ignoreFrames map[byte]bool) { + tc.ignoreFrames = ignoreFrames + }(tc.ignoreFrames) + tc.ignoreFrames = map[byte]bool{ + frameTypeAck: true, + frameTypePadding: true, + } + // Send three packets containing PINGs, and then respond with an ACK for the + // last one. This puts the last packet before the PINGs outside the packet + // reordering threshold, and it will be declared lost. + const lossThreshold = 3 + var num packetNumber + for i := 0; i < lossThreshold; i++ { + tc.conn.ping(spaceForPacketType(ptype)) + d := tc.readDatagram() + if d == nil { + tc.t.Fatalf("conn is idle; want PING frame") + } + if d.packets[0].ptype != ptype { + tc.t.Fatalf("conn sent %v packet; want %v", d.packets[0].ptype, ptype) + } + num = d.packets[0].num + } + tc.writeFrames(ptype, debugFrameAck{ + ranges: []i64range[packetNumber]{ + {num, num + 1}, + }, + }) +} + +func TestLostResetStreamFrame(t *testing.T) { + // "Cancellation of stream transmission, as carried in a RESET_STREAM frame, + // is sent until acknowledged or until all stream data is acknowledged by the peer [...]" + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.4 + lostFrameTest(t, func(t *testing.T, pto bool) { + tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters) + tc.ignoreFrame(frameTypeAck) + + s.Reset(1) + tc.wantFrame("reset stream", + packetType1RTT, debugFrameResetStream{ + id: s.id, + code: 1, + }) + + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("resent RESET_STREAM frame", + packetType1RTT, debugFrameResetStream{ + id: s.id, + code: 1, + }) + }) +} + +func TestLostStopSendingFrame(t *testing.T) { + // "[...] a request to cancel stream transmission, as encoded in a STOP_SENDING frame, + // is sent until the receiving part of the stream enters either a "Data Recvd" or + // "Reset Recvd" state [...]" + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.5 + // + // Technically, we can stop sending a STOP_SENDING frame if the peer sends + // us all the data for the stream or resets it. We don't bother tracking this, + // however, so we'll keep sending the frame until it is acked. This is harmless. + lostFrameTest(t, func(t *testing.T, pto bool) { + tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, permissiveTransportParameters) + tc.ignoreFrame(frameTypeAck) + + s.CloseRead() + tc.wantFrame("stream is read-closed", + packetType1RTT, debugFrameStopSending{ + id: s.id, + }) + + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("resent STOP_SENDING frame", + packetType1RTT, debugFrameStopSending{ + id: s.id, + }) + }) +} + +func TestLostCryptoFrame(t *testing.T) { + // "Data sent in CRYPTO frames is retransmitted [...] until all data has been acknowledged." + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.1 + lostFrameTest(t, func(t *testing.T, pto bool) { + tc := newTestConn(t, clientSide) + tc.ignoreFrame(frameTypeAck) + + tc.wantFrame("client sends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + tc.triggerLossOrPTO(packetTypeInitial, pto) + tc.wantFrame("client resends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + + tc.wantFrame("client sends Handshake CRYPTO frame", + packetTypeHandshake, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake], + }) + tc.wantFrame("client provides server with an additional connection ID", + packetType1RTT, debugFrameNewConnectionID{ + seq: 1, + connID: testLocalConnID(1), + }) + tc.triggerLossOrPTO(packetTypeHandshake, pto) + tc.wantFrame("client resends Handshake CRYPTO frame", + packetTypeHandshake, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake], + }) + }) +} + +func TestLostStreamFrameEmpty(t *testing.T) { + // A STREAM frame opening a stream, but containing no stream data, should + // be retransmitted if lost. + lostFrameTest(t, func(t *testing.T, pto bool) { + ctx := canceledContext() + tc := newTestConn(t, clientSide, permissiveTransportParameters) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + c, err := tc.conn.NewStream(ctx) + if err != nil { + t.Fatalf("NewStream: %v", err) + } + c.Write(nil) // open the stream + tc.wantFrame("created bidirectional stream 0", + packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, bidiStream, 0), + data: []byte{}, + }) + + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("resent stream frame", + packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, bidiStream, 0), + data: []byte{}, + }) + }) +} + +func TestLostStreamWithData(t *testing.T) { + // "Application data sent in STREAM frames is retransmitted in new STREAM + // frames unless the endpoint has sent a RESET_STREAM for that stream." + // https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.2 + // + // TODO: Lost stream frame after RESET_STREAM + lostFrameTest(t, func(t *testing.T, pto bool) { + data := []byte{0, 1, 2, 3, 4, 5, 6, 7} + tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) { + p.initialMaxStreamsUni = 1 + p.initialMaxData = 1 << 20 + p.initialMaxStreamDataUni = 1 << 20 + }) + s.Write(data[:4]) + tc.wantFrame("send [0,4)", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: data[:4], + }) + s.Write(data[4:8]) + tc.wantFrame("send [4,8)", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 4, + data: data[4:8], + }) + s.CloseWrite() + tc.wantFrame("send FIN", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 8, + fin: true, + data: []byte{}, + }) + + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("resend data", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + fin: true, + data: data[:8], + }) + }) +} + +func TestLostStreamPartialLoss(t *testing.T) { + // Conn sends four STREAM packets. + // ACKs are received for the packets containing bytes 0 and 2. + // The remaining packets are declared lost. + // The Conn resends only the lost data. + // + // This test doesn't have a PTO mode, because the ACK for the packet containing byte 2 + // starts the loss timer for the packet containing byte 1, and the PTO timer is not + // armed when the loss timer is. + data := []byte{0, 1, 2, 3} + tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) { + p.initialMaxStreamsUni = 1 + p.initialMaxData = 1 << 20 + p.initialMaxStreamDataUni = 1 << 20 + }) + for i := range data { + s.Write(data[i : i+1]) + tc.wantFrame(fmt.Sprintf("send STREAM frame with byte %v", i), + packetType1RTT, debugFrameStream{ + id: s.id, + off: int64(i), + data: data[i : i+1], + }) + if i%2 == 0 { + tc.writeAckForLatest() + } + } + const pto = false + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("resend byte 1", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 1, + data: data[1:2], + }) + tc.wantFrame("resend byte 3", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 3, + data: data[3:4], + }) + tc.wantIdle("no more frames sent after packet loss") +} + +func TestLostMaxDataFrame(t *testing.T) { + // "An updated value is sent in a MAX_DATA frame if the packet + // containing the most recently sent MAX_DATA frame is declared lost [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.7 + lostFrameTest(t, func(t *testing.T, pto bool) { + const maxWindowSize = 32 + buf := make([]byte, maxWindowSize) + tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) { + c.MaxConnReadBufferSize = 32 + }) + + // We send MAX_DATA = 63. + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: make([]byte, maxWindowSize), + }) + if n, err := s.Read(buf[:maxWindowSize-1]); err != nil || n != maxWindowSize-1 { + t.Fatalf("Read() = %v, %v; want %v, nil", n, err, maxWindowSize-1) + } + tc.wantFrame("conn window is extended after reading data", + packetType1RTT, debugFrameMaxData{ + max: (maxWindowSize * 2) - 1, + }) + + // MAX_DATA = 64, which is only one more byte, so we don't send the frame. + if n, err := s.Read(buf); err != nil || n != 1 { + t.Fatalf("Read() = %v, %v; want %v, nil", n, err, 1) + } + tc.wantIdle("read doesn't extend window enough to send another MAX_DATA") + + // The MAX_DATA = 63 packet was lost, so we send 64. + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("resent MAX_DATA includes most current value", + packetType1RTT, debugFrameMaxData{ + max: maxWindowSize * 2, + }) + }) +} + +func TestLostMaxStreamDataFrame(t *testing.T) { + // "[...] an updated value is sent when the packet containing + // the most recent MAX_STREAM_DATA frame for a stream is lost" + // https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.8 + lostFrameTest(t, func(t *testing.T, pto bool) { + const maxWindowSize = 32 + buf := make([]byte, maxWindowSize) + tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) { + c.MaxStreamReadBufferSize = maxWindowSize + }) + + // We send MAX_STREAM_DATA = 63. + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: make([]byte, maxWindowSize), + }) + if n, err := s.Read(buf[:maxWindowSize-1]); err != nil || n != maxWindowSize-1 { + t.Fatalf("Read() = %v, %v; want %v, nil", n, err, maxWindowSize-1) + } + tc.wantFrame("stream window is extended after reading data", + packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: (maxWindowSize * 2) - 1, + }) + + // MAX_STREAM_DATA = 64, which is only one more byte, so we don't send the frame. + if n, err := s.Read(buf); err != nil || n != 1 { + t.Fatalf("Read() = %v, %v; want %v, nil", n, err, 1) + } + tc.wantIdle("read doesn't extend window enough to send another MAX_STREAM_DATA") + + // The MAX_STREAM_DATA = 63 packet was lost, so we send 64. + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("resent MAX_STREAM_DATA includes most current value", + packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: maxWindowSize * 2, + }) + }) +} + +func TestLostMaxStreamDataFrameAfterStreamFinReceived(t *testing.T) { + // "An endpoint SHOULD stop sending MAX_STREAM_DATA frames when + // the receiving part of the stream enters a "Size Known" or "Reset Recvd" state." + // https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.8 + lostFrameTest(t, func(t *testing.T, pto bool) { + const maxWindowSize = 10 + buf := make([]byte, maxWindowSize) + tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, func(c *Config) { + c.MaxStreamReadBufferSize = maxWindowSize + }) + + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: make([]byte, maxWindowSize), + }) + if n, err := s.Read(buf); err != nil || n != maxWindowSize { + t.Fatalf("Read() = %v, %v; want %v, nil", n, err, maxWindowSize) + } + tc.wantFrame("stream window is extended after reading data", + packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: 2 * maxWindowSize, + }) + + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + off: maxWindowSize, + fin: true, + }) + + tc.ignoreFrame(frameTypePing) + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantIdle("lost MAX_STREAM_DATA not resent for stream in 'size known'") + }) +} + +func TestLostMaxStreamsFrameMostRecent(t *testing.T) { + // "[...] an updated value is sent when a packet containing the + // most recent MAX_STREAMS for a stream type frame is declared lost [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.9 + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + lostFrameTest(t, func(t *testing.T, pto bool) { + ctx := canceledContext() + tc := newTestConn(t, serverSide, func(c *Config) { + c.MaxUniRemoteStreams = 1 + c.MaxBidiRemoteStreams = 1 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, styp, 0), + fin: true, + }) + s, err := tc.conn.AcceptStream(ctx) + if err != nil { + t.Fatalf("AcceptStream() = %v", err) + } + s.CloseContext(ctx) + if styp == bidiStream { + tc.wantFrame("stream is closed", + packetType1RTT, debugFrameStream{ + id: s.id, + data: []byte{}, + fin: true, + }) + tc.writeAckForAll() + } + tc.wantFrame("closing stream updates peer's MAX_STREAMS", + packetType1RTT, debugFrameMaxStreams{ + streamType: styp, + max: 2, + }) + + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("lost MAX_STREAMS is resent", + packetType1RTT, debugFrameMaxStreams{ + streamType: styp, + max: 2, + }) + }) + }) +} + +func TestLostMaxStreamsFrameNotMostRecent(t *testing.T) { + // Send two MAX_STREAMS frames, lose the first one. + // + // No PTO mode for this test: The ack that causes the first frame + // to be lost arms the loss timer for the second, so the PTO timer is not armed. + const pto = false + ctx := canceledContext() + tc := newTestConn(t, serverSide, func(c *Config) { + c.MaxUniRemoteStreams = 2 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + for i := int64(0); i < 2; i++ { + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, uniStream, i), + fin: true, + }) + s, err := tc.conn.AcceptStream(ctx) + if err != nil { + t.Fatalf("AcceptStream() = %v", err) + } + if err := s.CloseContext(ctx); err != nil { + t.Fatalf("stream.Close() = %v", err) + } + tc.wantFrame("closing stream updates peer's MAX_STREAMS", + packetType1RTT, debugFrameMaxStreams{ + streamType: uniStream, + max: 3 + i, + }) + } + + // The second MAX_STREAMS frame is acked. + tc.writeAckForLatest() + + // The first MAX_STREAMS frame is lost. + tc.conn.ping(appDataSpace) + tc.wantFrame("connection should send a PING frame", + packetType1RTT, debugFramePing{}) + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantIdle("superseded MAX_DATA is not resent on loss") +} + +func TestLostStreamDataBlockedFrame(t *testing.T) { + // "A new [STREAM_DATA_BLOCKED] frame is sent if a packet containing + // the most recent frame for a scope is lost [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.10 + lostFrameTest(t, func(t *testing.T, pto bool) { + tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) { + p.initialMaxStreamsUni = 1 + p.initialMaxData = 1 << 20 + }) + + w := runAsync(tc, func(ctx context.Context) (int, error) { + return s.WriteContext(ctx, []byte{0, 1, 2, 3}) + }) + defer w.cancel() + tc.wantFrame("write is blocked by flow control", + packetType1RTT, debugFrameStreamDataBlocked{ + id: s.id, + max: 0, + }) + + tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: 1, + }) + tc.wantFrame("write makes some progress, but is still blocked by flow control", + packetType1RTT, debugFrameStreamDataBlocked{ + id: s.id, + max: 1, + }) + tc.wantFrame("write consuming available window", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: []byte{0}, + }) + + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("STREAM_DATA_BLOCKED is resent", + packetType1RTT, debugFrameStreamDataBlocked{ + id: s.id, + max: 1, + }) + tc.wantFrame("STREAM is resent as well", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: []byte{0}, + }) + }) +} + +func TestLostStreamDataBlockedFrameAfterStreamUnblocked(t *testing.T) { + // "A new [STREAM_DATA_BLOCKED] frame is sent [...] only while + // the endpoint is blocked on the corresponding limit." + // https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.10 + lostFrameTest(t, func(t *testing.T, pto bool) { + tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) { + p.initialMaxStreamsUni = 1 + p.initialMaxData = 1 << 20 + }) + + data := []byte{0, 1, 2, 3} + w := runAsync(tc, func(ctx context.Context) (int, error) { + return s.WriteContext(ctx, data) + }) + defer w.cancel() + tc.wantFrame("write is blocked by flow control", + packetType1RTT, debugFrameStreamDataBlocked{ + id: s.id, + max: 0, + }) + + tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: 10, + }) + tc.wantFrame("write completes after flow control available", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: data, + }) + + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("STREAM data is resent", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: data, + }) + tc.wantIdle("STREAM_DATA_BLOCKED is not resent, since the stream is not blocked") + }) +} + +func TestLostNewConnectionIDFrame(t *testing.T) { + // "New connection IDs are [...] retransmitted if the packet containing them is lost." + // https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.13 + lostFrameTest(t, func(t *testing.T, pto bool) { + tc := newTestConn(t, serverSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameRetireConnectionID{ + seq: 1, + }) + tc.wantFrame("provide a new connection ID after peer retires old one", + packetType1RTT, debugFrameNewConnectionID{ + seq: 2, + connID: testLocalConnID(2), + }) + + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("resend new connection ID", + packetType1RTT, debugFrameNewConnectionID{ + seq: 2, + connID: testLocalConnID(2), + }) + }) +} + +func TestLostRetireConnectionIDFrame(t *testing.T) { + // "[...] retired connection IDs are [...] retransmitted + // if the packet containing them is lost." + // https://www.rfc-editor.org/rfc/rfc9000#section-13.3-3.13 + lostFrameTest(t, func(t *testing.T, pto bool) { + tc := newTestConn(t, clientSide) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetType1RTT, + debugFrameNewConnectionID{ + seq: 2, + retirePriorTo: 1, + connID: testPeerConnID(2), + }) + tc.wantFrame("peer requested connection id be retired", + packetType1RTT, debugFrameRetireConnectionID{ + seq: 0, + }) + + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("resend RETIRE_CONNECTION_ID", + packetType1RTT, debugFrameRetireConnectionID{ + seq: 0, + }) + }) +} + +func TestLostHandshakeDoneFrame(t *testing.T) { + // "The HANDSHAKE_DONE frame MUST be retransmitted until it is acknowledged." + // https://www.rfc-editor.org/rfc/rfc9000.html#section-13.3-3.16 + lostFrameTest(t, func(t *testing.T, pto bool) { + tc := newTestConn(t, serverSide) + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.wantFrame("server sends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + tc.wantFrame("server sends Handshake CRYPTO frame", + packetTypeHandshake, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake], + }) + tc.wantFrame("server provides an additional connection ID", + packetType1RTT, debugFrameNewConnectionID{ + seq: 1, + connID: testLocalConnID(1), + }) + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + + tc.wantFrame("server sends HANDSHAKE_DONE after handshake completes", + packetType1RTT, debugFrameHandshakeDone{}) + + tc.triggerLossOrPTO(packetType1RTT, pto) + tc.wantFrame("server resends HANDSHAKE_DONE", + packetType1RTT, debugFrameHandshakeDone{}) + }) +} diff --git a/internal/quic/conn_recv.go b/internal/quic/conn_recv.go new file mode 100644 index 000000000..9b1ba1ae1 --- /dev/null +++ b/internal/quic/conn_recv.go @@ -0,0 +1,478 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "encoding/binary" + "errors" + "time" +) + +func (c *Conn) handleDatagram(now time.Time, dgram *datagram) { + buf := dgram.b + c.loss.datagramReceived(now, len(buf)) + if c.isDraining() { + return + } + for len(buf) > 0 { + var n int + ptype := getPacketType(buf) + switch ptype { + case packetTypeInitial: + if c.side == serverSide && len(dgram.b) < minimumClientInitialDatagramSize { + // Discard client-sent Initial packets in too-short datagrams. + // https://www.rfc-editor.org/rfc/rfc9000#section-14.1-4 + return + } + n = c.handleLongHeader(now, ptype, initialSpace, c.keysInitial.r, buf) + case packetTypeHandshake: + n = c.handleLongHeader(now, ptype, handshakeSpace, c.keysHandshake.r, buf) + case packetType1RTT: + n = c.handle1RTT(now, buf) + case packetTypeVersionNegotiation: + c.handleVersionNegotiation(now, buf) + return + default: + return + } + if n <= 0 { + // Invalid data at the end of a datagram is ignored. + break + } + c.idleTimeout = now.Add(c.maxIdleTimeout) + buf = buf[n:] + } +} + +func (c *Conn) handleLongHeader(now time.Time, ptype packetType, space numberSpace, k fixedKeys, buf []byte) int { + if !k.isSet() { + return skipLongHeaderPacket(buf) + } + + pnumMax := c.acks[space].largestSeen() + p, n := parseLongHeaderPacket(buf, k, pnumMax) + if n < 0 { + return -1 + } + if buf[0]&reservedLongBits != 0 { + // Reserved header bits must be 0. + // https://www.rfc-editor.org/rfc/rfc9000#section-17.2-8.2.1 + c.abort(now, localTransportError(errProtocolViolation)) + return -1 + } + if p.version != quicVersion1 { + // The peer has changed versions on us mid-handshake? + c.abort(now, localTransportError(errProtocolViolation)) + return -1 + } + + if !c.acks[space].shouldProcess(p.num) { + return n + } + + if logPackets { + logInboundLongPacket(c, p) + } + c.connIDState.handlePacket(c, p.ptype, p.srcConnID) + ackEliciting := c.handleFrames(now, ptype, space, p.payload) + c.acks[space].receive(now, space, p.num, ackEliciting) + if p.ptype == packetTypeHandshake && c.side == serverSide { + c.loss.validateClientAddress() + + // "[...] a server MUST discard Initial keys when it first successfully + // processes a Handshake packet [...]" + // https://www.rfc-editor.org/rfc/rfc9001#section-4.9.1-2 + c.discardKeys(now, initialSpace) + } + return n +} + +func (c *Conn) handle1RTT(now time.Time, buf []byte) int { + if !c.keysAppData.canRead() { + // 1-RTT packets extend to the end of the datagram, + // so skip the remainder of the datagram if we can't parse this. + return len(buf) + } + + pnumMax := c.acks[appDataSpace].largestSeen() + p, err := parse1RTTPacket(buf, &c.keysAppData, connIDLen, pnumMax) + if err != nil { + // A localTransportError terminates the connection. + // Other errors indicate an unparseable packet, but otherwise may be ignored. + if _, ok := err.(localTransportError); ok { + c.abort(now, err) + } + return -1 + } + if buf[0]&reserved1RTTBits != 0 { + // Reserved header bits must be 0. + // https://www.rfc-editor.org/rfc/rfc9000#section-17.3.1-4.8.1 + c.abort(now, localTransportError(errProtocolViolation)) + return -1 + } + + if !c.acks[appDataSpace].shouldProcess(p.num) { + return len(buf) + } + + if logPackets { + logInboundShortPacket(c, p) + } + ackEliciting := c.handleFrames(now, packetType1RTT, appDataSpace, p.payload) + c.acks[appDataSpace].receive(now, appDataSpace, p.num, ackEliciting) + return len(buf) +} + +var errVersionNegotiation = errors.New("server does not support QUIC version 1") + +func (c *Conn) handleVersionNegotiation(now time.Time, pkt []byte) { + if c.side != clientSide { + return // servers don't handle Version Negotiation packets + } + // "A client MUST discard any Version Negotiation packet if it has + // received and successfully processed any other packet [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-6.2-2 + if !c.keysInitial.canRead() { + return // discarded Initial keys, connection is already established + } + if c.acks[initialSpace].seen.numRanges() != 0 { + return // processed at least one packet + } + _, srcConnID, versions := parseVersionNegotiation(pkt) + if len(c.connIDState.remote) < 1 || !bytes.Equal(c.connIDState.remote[0].cid, srcConnID) { + return // Source Connection ID doesn't match what we sent + } + for len(versions) >= 4 { + ver := binary.BigEndian.Uint32(versions) + if ver == 1 { + // "A client MUST discard a Version Negotiation packet that lists + // the QUIC version selected by the client." + // https://www.rfc-editor.org/rfc/rfc9000#section-6.2-2 + return + } + versions = versions[4:] + } + // "A client that supports only this version of QUIC MUST + // abandon the current connection attempt if it receives + // a Version Negotiation packet, [with the two exceptions handled above]." + // https://www.rfc-editor.org/rfc/rfc9000#section-6.2-2 + c.abortImmediately(now, errVersionNegotiation) +} + +func (c *Conn) handleFrames(now time.Time, ptype packetType, space numberSpace, payload []byte) (ackEliciting bool) { + if len(payload) == 0 { + // "An endpoint MUST treat receipt of a packet containing no frames + // as a connection error of type PROTOCOL_VIOLATION." + // https://www.rfc-editor.org/rfc/rfc9000#section-12.4-3 + c.abort(now, localTransportError(errProtocolViolation)) + return false + } + // frameOK verifies that ptype is one of the packets in mask. + frameOK := func(c *Conn, ptype, mask packetType) (ok bool) { + if ptype&mask == 0 { + // "An endpoint MUST treat receipt of a frame in a packet type + // that is not permitted as a connection error of type + // PROTOCOL_VIOLATION." + // https://www.rfc-editor.org/rfc/rfc9000#section-12.4-3 + c.abort(now, localTransportError(errProtocolViolation)) + return false + } + return true + } + // Packet masks from RFC 9000 Table 3. + // https://www.rfc-editor.org/rfc/rfc9000#table-3 + const ( + IH_1 = packetTypeInitial | packetTypeHandshake | packetType1RTT + __01 = packetType0RTT | packetType1RTT + ___1 = packetType1RTT + ) + for len(payload) > 0 { + switch payload[0] { + case frameTypePadding, frameTypeAck, frameTypeAckECN, + frameTypeConnectionCloseTransport, frameTypeConnectionCloseApplication: + default: + ackEliciting = true + } + n := -1 + switch payload[0] { + case frameTypePadding: + // PADDING is OK in all spaces. + n = 1 + case frameTypePing: + // PING is OK in all spaces. + // + // A PING frame causes us to respond with an ACK by virtue of being + // an ack-eliciting frame, but requires no other action. + n = 1 + case frameTypeAck, frameTypeAckECN: + if !frameOK(c, ptype, IH_1) { + return + } + n = c.handleAckFrame(now, space, payload) + case frameTypeResetStream: + if !frameOK(c, ptype, __01) { + return + } + n = c.handleResetStreamFrame(now, space, payload) + case frameTypeStopSending: + if !frameOK(c, ptype, __01) { + return + } + n = c.handleStopSendingFrame(now, space, payload) + case frameTypeCrypto: + if !frameOK(c, ptype, IH_1) { + return + } + n = c.handleCryptoFrame(now, space, payload) + case frameTypeNewToken: + if !frameOK(c, ptype, ___1) { + return + } + _, n = consumeNewTokenFrame(payload) + case 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f: // STREAM + if !frameOK(c, ptype, __01) { + return + } + n = c.handleStreamFrame(now, space, payload) + case frameTypeMaxData: + if !frameOK(c, ptype, __01) { + return + } + n = c.handleMaxDataFrame(now, payload) + case frameTypeMaxStreamData: + if !frameOK(c, ptype, __01) { + return + } + n = c.handleMaxStreamDataFrame(now, payload) + case frameTypeMaxStreamsBidi, frameTypeMaxStreamsUni: + if !frameOK(c, ptype, __01) { + return + } + n = c.handleMaxStreamsFrame(now, payload) + case frameTypeDataBlocked: + if !frameOK(c, ptype, __01) { + return + } + _, n = consumeDataBlockedFrame(payload) + case frameTypeStreamsBlockedBidi, frameTypeStreamsBlockedUni: + if !frameOK(c, ptype, __01) { + return + } + _, _, n = consumeStreamsBlockedFrame(payload) + case frameTypeStreamDataBlocked: + if !frameOK(c, ptype, __01) { + return + } + _, _, n = consumeStreamDataBlockedFrame(payload) + case frameTypeNewConnectionID: + if !frameOK(c, ptype, __01) { + return + } + n = c.handleNewConnectionIDFrame(now, space, payload) + case frameTypeRetireConnectionID: + if !frameOK(c, ptype, __01) { + return + } + n = c.handleRetireConnectionIDFrame(now, space, payload) + case frameTypeConnectionCloseTransport: + // Transport CONNECTION_CLOSE is OK in all spaces. + n = c.handleConnectionCloseTransportFrame(now, payload) + case frameTypeConnectionCloseApplication: + if !frameOK(c, ptype, __01) { + return + } + n = c.handleConnectionCloseApplicationFrame(now, payload) + case frameTypeHandshakeDone: + if !frameOK(c, ptype, ___1) { + return + } + n = c.handleHandshakeDoneFrame(now, space, payload) + } + if n < 0 { + c.abort(now, localTransportError(errFrameEncoding)) + return false + } + payload = payload[n:] + } + return ackEliciting +} + +func (c *Conn) handleAckFrame(now time.Time, space numberSpace, payload []byte) int { + c.loss.receiveAckStart() + largest, ackDelay, n := consumeAckFrame(payload, func(rangeIndex int, start, end packetNumber) { + if end > c.loss.nextNumber(space) { + // Acknowledgement of a packet we never sent. + c.abort(now, localTransportError(errProtocolViolation)) + return + } + c.loss.receiveAckRange(now, space, rangeIndex, start, end, c.handleAckOrLoss) + }) + // Prior to receiving the peer's transport parameters, we cannot + // interpret the ACK Delay field because we don't know the ack_delay_exponent + // to apply. + // + // For servers, we should always know the ack_delay_exponent because the + // client's transport parameters are carried in its Initial packets and we + // won't send an ack-eliciting Initial packet until after receiving the last + // client Initial packet. + // + // For clients, we won't receive the server's transport parameters until handling + // its Handshake flight, which will probably happen after reading its ACK for our + // Initial packet(s). However, the peer's acknowledgement delay cannot reduce our + // adjusted RTT sample below min_rtt, and min_rtt is generally going to be set + // by the packet containing the ACK for our Initial flight. Therefore, the + // ACK Delay for an ACK in the Initial space is likely to be ignored anyway. + // + // Long story short, setting the delay to 0 prior to reading transport parameters + // is usually going to have no effect, will have only a minor effect in the rare + // cases when it happens, and there aren't any good alternatives anyway since we + // can't interpret the ACK Delay field without knowing the exponent. + var delay time.Duration + if c.peerAckDelayExponent >= 0 { + delay = ackDelay.Duration(uint8(c.peerAckDelayExponent)) + } + c.loss.receiveAckEnd(now, space, delay, c.handleAckOrLoss) + if space == appDataSpace { + c.keysAppData.handleAckFor(largest) + } + return n +} + +func (c *Conn) handleMaxDataFrame(now time.Time, payload []byte) int { + maxData, n := consumeMaxDataFrame(payload) + if n < 0 { + return -1 + } + c.streams.outflow.setMaxData(maxData) + return n +} + +func (c *Conn) handleMaxStreamDataFrame(now time.Time, payload []byte) int { + id, maxStreamData, n := consumeMaxStreamDataFrame(payload) + if n < 0 { + return -1 + } + if s := c.streamForFrame(now, id, sendStream); s != nil { + if err := s.handleMaxStreamData(maxStreamData); err != nil { + c.abort(now, err) + return -1 + } + } + return n +} + +func (c *Conn) handleMaxStreamsFrame(now time.Time, payload []byte) int { + styp, max, n := consumeMaxStreamsFrame(payload) + if n < 0 { + return -1 + } + c.streams.localLimit[styp].setMax(max) + return n +} + +func (c *Conn) handleResetStreamFrame(now time.Time, space numberSpace, payload []byte) int { + id, code, finalSize, n := consumeResetStreamFrame(payload) + if n < 0 { + return -1 + } + if s := c.streamForFrame(now, id, recvStream); s != nil { + if err := s.handleReset(code, finalSize); err != nil { + c.abort(now, err) + } + } + return n +} + +func (c *Conn) handleStopSendingFrame(now time.Time, space numberSpace, payload []byte) int { + id, code, n := consumeStopSendingFrame(payload) + if n < 0 { + return -1 + } + if s := c.streamForFrame(now, id, sendStream); s != nil { + if err := s.handleStopSending(code); err != nil { + c.abort(now, err) + } + } + return n +} + +func (c *Conn) handleCryptoFrame(now time.Time, space numberSpace, payload []byte) int { + off, data, n := consumeCryptoFrame(payload) + err := c.handleCrypto(now, space, off, data) + if err != nil { + c.abort(now, err) + return -1 + } + return n +} + +func (c *Conn) handleStreamFrame(now time.Time, space numberSpace, payload []byte) int { + id, off, fin, b, n := consumeStreamFrame(payload) + if n < 0 { + return -1 + } + if s := c.streamForFrame(now, id, recvStream); s != nil { + if err := s.handleData(off, b, fin); err != nil { + c.abort(now, err) + } + } + return n +} + +func (c *Conn) handleNewConnectionIDFrame(now time.Time, space numberSpace, payload []byte) int { + seq, retire, connID, resetToken, n := consumeNewConnectionIDFrame(payload) + if n < 0 { + return -1 + } + if err := c.connIDState.handleNewConnID(seq, retire, connID, resetToken); err != nil { + c.abort(now, err) + } + return n +} + +func (c *Conn) handleRetireConnectionIDFrame(now time.Time, space numberSpace, payload []byte) int { + seq, n := consumeRetireConnectionIDFrame(payload) + if n < 0 { + return -1 + } + if err := c.connIDState.handleRetireConnID(c, seq); err != nil { + c.abort(now, err) + } + return n +} + +func (c *Conn) handleConnectionCloseTransportFrame(now time.Time, payload []byte) int { + code, _, reason, n := consumeConnectionCloseTransportFrame(payload) + if n < 0 { + return -1 + } + c.enterDraining(peerTransportError{code: code, reason: reason}) + return n +} + +func (c *Conn) handleConnectionCloseApplicationFrame(now time.Time, payload []byte) int { + code, reason, n := consumeConnectionCloseApplicationFrame(payload) + if n < 0 { + return -1 + } + c.enterDraining(&ApplicationError{Code: code, Reason: reason}) + return n +} + +func (c *Conn) handleHandshakeDoneFrame(now time.Time, space numberSpace, payload []byte) int { + if c.side == serverSide { + // Clients should never send HANDSHAKE_DONE. + // https://www.rfc-editor.org/rfc/rfc9000#section-19.20-4 + c.abort(now, localTransportError(errProtocolViolation)) + return -1 + } + if !c.isClosingOrDraining() { + c.confirmHandshake(now) + } + return 1 +} diff --git a/internal/quic/conn_send.go b/internal/quic/conn_send.go new file mode 100644 index 000000000..00b02c2a3 --- /dev/null +++ b/internal/quic/conn_send.go @@ -0,0 +1,351 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "crypto/tls" + "errors" + "time" +) + +// maybeSend sends datagrams, if possible. +// +// If sending is blocked by pacing, it returns the next time +// a datagram may be sent. +// +// If sending is blocked indefinitely, it returns the zero Time. +func (c *Conn) maybeSend(now time.Time) (next time.Time) { + // Assumption: The congestion window is not underutilized. + // If congestion control, pacing, and anti-amplification all permit sending, + // but we have no packet to send, then we will declare the window underutilized. + c.loss.cc.setUnderutilized(false) + + // Send one datagram on each iteration of this loop, + // until we hit a limit or run out of data to send. + // + // For each number space where we have write keys, + // attempt to construct a packet in that space. + // If the packet contains no frames (we have no data in need of sending), + // abandon the packet. + // + // Speculatively constructing packets means we don't need + // separate code paths for "do we have data to send?" and + // "send the data" that need to be kept in sync. + for { + limit, next := c.loss.sendLimit(now) + if limit == ccBlocked { + // If anti-amplification blocks sending, then no packet can be sent. + return next + } + if !c.sendOK(now) { + return time.Time{} + } + // We may still send ACKs, even if congestion control or pacing limit sending. + + // Prepare to write a datagram of at most maxSendSize bytes. + c.w.reset(c.loss.maxSendSize()) + + dstConnID, ok := c.connIDState.dstConnID() + if !ok { + // It is currently not possible for us to end up without a connection ID, + // but handle the case anyway. + return time.Time{} + } + + // Initial packet. + pad := false + var sentInitial *sentPacket + if c.keysInitial.canWrite() { + pnumMaxAcked := c.acks[initialSpace].largestSeen() + pnum := c.loss.nextNumber(initialSpace) + p := longPacket{ + ptype: packetTypeInitial, + version: quicVersion1, + num: pnum, + dstConnID: dstConnID, + srcConnID: c.connIDState.srcConnID(), + } + c.w.startProtectedLongHeaderPacket(pnumMaxAcked, p) + c.appendFrames(now, initialSpace, pnum, limit) + if logPackets { + logSentPacket(c, packetTypeInitial, pnum, p.srcConnID, p.dstConnID, c.w.payload()) + } + sentInitial = c.w.finishProtectedLongHeaderPacket(pnumMaxAcked, c.keysInitial.w, p) + if sentInitial != nil { + // Client initial packets need to be sent in a datagram padded to + // at least 1200 bytes. We can't add the padding yet, however, + // since we may want to coalesce additional packets with this one. + if c.side == clientSide { + pad = true + } + } + } + + // Handshake packet. + if c.keysHandshake.canWrite() { + pnumMaxAcked := c.acks[handshakeSpace].largestSeen() + pnum := c.loss.nextNumber(handshakeSpace) + p := longPacket{ + ptype: packetTypeHandshake, + version: quicVersion1, + num: pnum, + dstConnID: dstConnID, + srcConnID: c.connIDState.srcConnID(), + } + c.w.startProtectedLongHeaderPacket(pnumMaxAcked, p) + c.appendFrames(now, handshakeSpace, pnum, limit) + if logPackets { + logSentPacket(c, packetTypeHandshake, pnum, p.srcConnID, p.dstConnID, c.w.payload()) + } + if sent := c.w.finishProtectedLongHeaderPacket(pnumMaxAcked, c.keysHandshake.w, p); sent != nil { + c.loss.packetSent(now, handshakeSpace, sent) + if c.side == clientSide { + // "[...] a client MUST discard Initial keys when it first + // sends a Handshake packet [...]" + // https://www.rfc-editor.org/rfc/rfc9001.html#section-4.9.1-2 + c.discardKeys(now, initialSpace) + } + } + } + + // 1-RTT packet. + if c.keysAppData.canWrite() { + pnumMaxAcked := c.acks[appDataSpace].largestSeen() + pnum := c.loss.nextNumber(appDataSpace) + c.w.start1RTTPacket(pnum, pnumMaxAcked, dstConnID) + c.appendFrames(now, appDataSpace, pnum, limit) + if pad && len(c.w.payload()) > 0 { + // 1-RTT packets have no length field and extend to the end + // of the datagram, so if we're sending a datagram that needs + // padding we need to add it inside the 1-RTT packet. + c.w.appendPaddingTo(minimumClientInitialDatagramSize) + pad = false + } + if logPackets { + logSentPacket(c, packetType1RTT, pnum, nil, dstConnID, c.w.payload()) + } + if sent := c.w.finish1RTTPacket(pnum, pnumMaxAcked, dstConnID, &c.keysAppData); sent != nil { + c.loss.packetSent(now, appDataSpace, sent) + } + } + + buf := c.w.datagram() + if len(buf) == 0 { + if limit == ccOK { + // We have nothing to send, and congestion control does not + // block sending. The congestion window is underutilized. + c.loss.cc.setUnderutilized(true) + } + return next + } + + if sentInitial != nil { + if pad { + // Pad out the datagram with zeros, coalescing the Initial + // packet with invalid packets that will be ignored by the peer. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-14.1-1 + for len(buf) < minimumClientInitialDatagramSize { + buf = append(buf, 0) + // Technically this padding isn't in any packet, but + // account it to the Initial packet in this datagram + // for purposes of flow control and loss recovery. + sentInitial.size++ + sentInitial.inFlight = true + } + } + // If we're a client and this Initial packet is coalesced + // with a Handshake packet, then we've discarded Initial keys + // since constructing the packet and shouldn't record it as in-flight. + if c.keysInitial.canWrite() { + c.loss.packetSent(now, initialSpace, sentInitial) + } + } + + c.listener.sendDatagram(buf, c.peerAddr) + } +} + +func (c *Conn) appendFrames(now time.Time, space numberSpace, pnum packetNumber, limit ccLimit) { + if c.lifetime.localErr != nil { + c.appendConnectionCloseFrame(now, space, c.lifetime.localErr) + return + } + + shouldSendAck := c.acks[space].shouldSendAck(now) + if limit != ccOK { + // ACKs are not limited by congestion control. + if shouldSendAck && c.appendAckFrame(now, space) { + c.acks[space].sentAck() + } + return + } + // We want to send an ACK frame if the ack controller wants to send a frame now, + // OR if we are sending a packet anyway and have ack-eliciting packets which we + // have not yet acked. + // + // We speculatively add ACK frames here, to put them at the front of the packet + // to avoid truncation. + // + // After adding all frames, if we don't need to send an ACK frame and have not + // added any other frames, we abandon the packet. + if c.appendAckFrame(now, space) { + defer func() { + // All frames other than ACK and PADDING are ack-eliciting, + // so if the packet is ack-eliciting we've added additional + // frames to it. + if !shouldSendAck && !c.w.sent.ackEliciting { + // There's nothing in this packet but ACK frames, and + // we don't want to send an ACK-only packet at this time. + // Abandoning the packet means we wrote an ACK frame for + // nothing, but constructing the frame is cheap. + c.w.abandonPacket() + return + } + // Either we are willing to send an ACK-only packet, + // or we've added additional frames. + c.acks[space].sentAck() + if !c.w.sent.ackEliciting && c.keysAppData.needAckEliciting() { + // The peer has initiated a key update. + // We haven't sent them any packets yet in the new phase. + // Make this an ack-eliciting packet. + // Their ack of this packet will complete the key update. + c.w.appendPingFrame() + } + }() + } + if limit != ccOK { + return + } + pto := c.loss.ptoExpired + + // TODO: Add all the other frames we can send. + + // CRYPTO + c.crypto[space].dataToSend(pto, func(off, size int64) int64 { + b, _ := c.w.appendCryptoFrame(off, int(size)) + c.crypto[space].sendData(off, b) + return int64(len(b)) + }) + + // Test-only PING frames. + if space == c.testSendPingSpace && c.testSendPing.shouldSendPTO(pto) { + if !c.w.appendPingFrame() { + return + } + c.testSendPing.setSent(pnum) + } + + if space == appDataSpace { + // HANDSHAKE_DONE + if c.handshakeConfirmed.shouldSendPTO(pto) { + if !c.w.appendHandshakeDoneFrame() { + return + } + c.handshakeConfirmed.setSent(pnum) + } + + // NEW_CONNECTION_ID, RETIRE_CONNECTION_ID + if !c.connIDState.appendFrames(&c.w, pnum, pto) { + return + } + + // All stream-related frames. This should come last in the packet, + // so large amounts of STREAM data don't crowd out other frames + // we may need to send. + if !c.appendStreamFrames(&c.w, pnum, pto) { + return + } + } + + // If this is a PTO probe and we haven't added an ack-eliciting frame yet, + // add a PING to make this an ack-eliciting probe. + // + // Technically, there are separate PTO timers for each number space. + // When a PTO timer expires, we MUST send an ack-eliciting packet in the + // timer's space. We SHOULD send ack-eliciting packets in every other space + // with in-flight data. (RFC 9002, section 6.2.4) + // + // What we actually do is send a single datagram containing an ack-eliciting packet + // for every space for which we have keys. + // + // We fill the PTO probe packets with new or unacknowledged data. For example, + // a PTO probe sent for the Initial space will generally retransmit previously + // sent but unacknowledged CRYPTO data. + // + // When sending a PTO probe datagram containing multiple packets, it is + // possible that an earlier packet will fill up the datagram, leaving no + // space for the remaining probe packet(s). This is not a problem in practice. + // + // A client discards Initial keys when it first sends a Handshake packet + // (RFC 9001 Section 4.9.1). Handshake keys are discarded when the handshake + // is confirmed (RFC 9001 Section 4.9.2). The PTO timer is not set for the + // Application Data packet number space until the handshake is confirmed + // (RFC 9002 Section 6.2.1). Therefore, the only times a PTO probe can fire + // while data for multiple spaces is in flight are: + // + // - a server's Initial or Handshake timers can fire while Initial and Handshake + // data is in flight; and + // + // - a client's Handshake timer can fire while Handshake and Application Data + // data is in flight. + // + // It is theoretically possible for a server's Initial CRYPTO data to overflow + // the maximum datagram size, but unlikely in practice; this space contains + // only the ServerHello TLS message, which is small. It's also unlikely that + // the Handshake PTO probe will fire while Initial data is in flight (this + // requires not just that the Initial CRYPTO data completely fill a datagram, + // but a quite specific arrangement of lost and retransmitted packets.) + // We don't bother worrying about this case here, since the worst case is + // that we send a PTO probe for the in-flight Initial data and drop the + // Handshake probe. + // + // If a client's Handshake PTO timer fires while Application Data data is in + // flight, it is possible that the resent Handshake CRYPTO data will crowd + // out the probe for the Application Data space. However, since this probe is + // optional (recall that the Application Data PTO timer is never set until + // after Handshake keys have been discarded), dropping it is acceptable. + if pto && !c.w.sent.ackEliciting { + c.w.appendPingFrame() + } +} + +func (c *Conn) appendAckFrame(now time.Time, space numberSpace) bool { + seen, delay := c.acks[space].acksToSend(now) + if len(seen) == 0 { + return false + } + d := unscaledAckDelayFromDuration(delay, ackDelayExponent) + return c.w.appendAckFrame(seen, d) +} + +func (c *Conn) appendConnectionCloseFrame(now time.Time, space numberSpace, err error) { + c.lifetime.connCloseSentTime = now + switch e := err.(type) { + case localTransportError: + c.w.appendConnectionCloseTransportFrame(transportError(e), 0, "") + case *ApplicationError: + if space != appDataSpace { + // "CONNECTION_CLOSE frames signaling application errors (type 0x1d) + // MUST only appear in the application data packet number space." + // https://www.rfc-editor.org/rfc/rfc9000#section-12.5-2.2 + c.w.appendConnectionCloseTransportFrame(errApplicationError, 0, "") + } else { + c.w.appendConnectionCloseApplicationFrame(e.Code, e.Reason) + } + default: + // TLS alerts are sent using error codes [0x0100,0x01ff). + // https://www.rfc-editor.org/rfc/rfc9000#section-20.1-2.36.1 + var alert tls.AlertError + if errors.As(err, &alert) { + // tls.AlertError is a uint8, so this can't exceed 0x01ff. + code := errTLSBase + transportError(alert) + c.w.appendConnectionCloseTransportFrame(code, 0, "") + } else { + c.w.appendConnectionCloseTransportFrame(errInternal, 0, "") + } + } +} diff --git a/internal/quic/conn_streams.go b/internal/quic/conn_streams.go new file mode 100644 index 000000000..a0793297e --- /dev/null +++ b/internal/quic/conn_streams.go @@ -0,0 +1,441 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "sync" + "sync/atomic" + "time" +) + +type streamsState struct { + queue queue[*Stream] // new, peer-created streams + + streamsMu sync.Mutex + streams map[streamID]*Stream + + // Limits on the number of streams, indexed by streamType. + localLimit [streamTypeCount]localStreamLimits + remoteLimit [streamTypeCount]remoteStreamLimits + + // Peer configuration provided in transport parameters. + peerInitialMaxStreamDataRemote [streamTypeCount]int64 // streams opened by us + peerInitialMaxStreamDataBidiLocal int64 // streams opened by them + + // Connection-level flow control. + inflow connInflow + outflow connOutflow + + // Streams with frames to send are stored in one of two circular linked lists, + // depending on whether they require connection-level flow control. + needSend atomic.Bool + sendMu sync.Mutex + queueMeta streamRing // streams with any non-flow-controlled frames + queueData streamRing // streams with only flow-controlled frames +} + +func (c *Conn) streamsInit() { + c.streams.streams = make(map[streamID]*Stream) + c.streams.queue = newQueue[*Stream]() + c.streams.localLimit[bidiStream].init() + c.streams.localLimit[uniStream].init() + c.streams.remoteLimit[bidiStream].init(c.config.maxBidiRemoteStreams()) + c.streams.remoteLimit[uniStream].init(c.config.maxUniRemoteStreams()) + c.inflowInit() +} + +// AcceptStream waits for and returns the next stream created by the peer. +func (c *Conn) AcceptStream(ctx context.Context) (*Stream, error) { + return c.streams.queue.get(ctx, c.testHooks) +} + +// NewStream creates a stream. +// +// If the peer's maximum stream limit for the connection has been reached, +// NewStream blocks until the limit is increased or the context expires. +func (c *Conn) NewStream(ctx context.Context) (*Stream, error) { + return c.newLocalStream(ctx, bidiStream) +} + +// NewSendOnlyStream creates a unidirectional, send-only stream. +// +// If the peer's maximum stream limit for the connection has been reached, +// NewSendOnlyStream blocks until the limit is increased or the context expires. +func (c *Conn) NewSendOnlyStream(ctx context.Context) (*Stream, error) { + return c.newLocalStream(ctx, uniStream) +} + +func (c *Conn) newLocalStream(ctx context.Context, styp streamType) (*Stream, error) { + c.streams.streamsMu.Lock() + defer c.streams.streamsMu.Unlock() + + num, err := c.streams.localLimit[styp].open(ctx, c) + if err != nil { + return nil, err + } + + s := newStream(c, newStreamID(c.side, styp, num)) + s.outmaxbuf = c.config.maxStreamWriteBufferSize() + s.outwin = c.streams.peerInitialMaxStreamDataRemote[styp] + if styp == bidiStream { + s.inmaxbuf = c.config.maxStreamReadBufferSize() + s.inwin = c.config.maxStreamReadBufferSize() + } + s.inUnlock() + s.outUnlock() + + c.streams.streams[s.id] = s + return s, nil +} + +// streamFrameType identifies which direction of a stream, +// from the local perspective, a frame is associated with. +// +// For example, STREAM is a recvStream frame, +// because it carries data from the peer to us. +type streamFrameType uint8 + +const ( + sendStream = streamFrameType(iota) // for example, MAX_DATA + recvStream // for example, STREAM_DATA_BLOCKED +) + +// streamForID returns the stream with the given id. +// If the stream does not exist, it returns nil. +func (c *Conn) streamForID(id streamID) *Stream { + c.streams.streamsMu.Lock() + defer c.streams.streamsMu.Unlock() + return c.streams.streams[id] +} + +// streamForFrame returns the stream with the given id. +// If the stream does not exist, it may be created. +// +// streamForFrame aborts the connection if the stream id, state, and frame type don't align. +// For example, it aborts the connection with a STREAM_STATE error if a MAX_DATA frame +// is received for a receive-only stream, or if the peer attempts to create a stream that +// should be originated locally. +// +// streamForFrame returns nil if the stream no longer exists or if an error occurred. +func (c *Conn) streamForFrame(now time.Time, id streamID, ftype streamFrameType) *Stream { + if id.streamType() == uniStream { + if (id.initiator() == c.side) != (ftype == sendStream) { + // Received an invalid frame for unidirectional stream. + // For example, a RESET_STREAM frame for a send-only stream. + c.abort(now, localTransportError(errStreamState)) + return nil + } + } + + c.streams.streamsMu.Lock() + defer c.streams.streamsMu.Unlock() + s, isOpen := c.streams.streams[id] + if s != nil { + return s + } + + num := id.num() + styp := id.streamType() + if id.initiator() == c.side { + if num < c.streams.localLimit[styp].opened { + // This stream was created by us, and has been closed. + return nil + } + // Received a frame for a stream that should be originated by us, + // but which we never created. + c.abort(now, localTransportError(errStreamState)) + return nil + } else { + // if isOpen, this is a stream that was implicitly opened by a + // previous frame for a larger-numbered stream, but we haven't + // actually created it yet. + if !isOpen && num < c.streams.remoteLimit[styp].opened { + // This stream was created by the peer, and has been closed. + return nil + } + } + + prevOpened := c.streams.remoteLimit[styp].opened + if err := c.streams.remoteLimit[styp].open(id); err != nil { + c.abort(now, err) + return nil + } + + // Receiving a frame for a stream implicitly creates all streams + // with the same initiator and type and a lower number. + // Add a nil entry to the streams map for each implicitly created stream. + for n := newStreamID(id.initiator(), id.streamType(), prevOpened); n < id; n += 4 { + c.streams.streams[n] = nil + } + + s = newStream(c, id) + s.inmaxbuf = c.config.maxStreamReadBufferSize() + s.inwin = c.config.maxStreamReadBufferSize() + if id.streamType() == bidiStream { + s.outmaxbuf = c.config.maxStreamWriteBufferSize() + s.outwin = c.streams.peerInitialMaxStreamDataBidiLocal + } + s.inUnlock() + s.outUnlock() + + c.streams.streams[id] = s + c.streams.queue.put(s) + return s +} + +// maybeQueueStreamForSend marks a stream as containing frames that need sending. +func (c *Conn) maybeQueueStreamForSend(s *Stream, state streamState) { + if state.wantQueue() == state.inQueue() { + return // already on the right queue + } + c.streams.sendMu.Lock() + defer c.streams.sendMu.Unlock() + state = s.state.load() // may have changed while waiting + c.queueStreamForSendLocked(s, state) + + c.streams.needSend.Store(true) + c.wake() +} + +// queueStreamForSendLocked moves a stream to the correct send queue, +// or removes it from all queues. +// +// state is the last known stream state. +func (c *Conn) queueStreamForSendLocked(s *Stream, state streamState) { + for { + wantQueue := state.wantQueue() + inQueue := state.inQueue() + if inQueue == wantQueue { + return // already on the right queue + } + + switch inQueue { + case metaQueue: + c.streams.queueMeta.remove(s) + case dataQueue: + c.streams.queueData.remove(s) + } + + switch wantQueue { + case metaQueue: + c.streams.queueMeta.append(s) + state = s.state.set(streamQueueMeta, streamQueueMeta|streamQueueData) + case dataQueue: + c.streams.queueData.append(s) + state = s.state.set(streamQueueData, streamQueueMeta|streamQueueData) + case noQueue: + state = s.state.set(0, streamQueueMeta|streamQueueData) + } + + // If the stream state changed while we were moving the stream, + // we might now be on the wrong queue. + // + // For example: + // - stream has data to send: streamOutSendData|streamQueueData + // - appendStreamFrames sends all the data: streamQueueData + // - concurrently, more data is written: streamOutSendData|streamQueueData + // - appendStreamFrames calls us with the last state it observed + // (streamQueueData). + // - We remove the stream from the queue and observe the updated state: + // streamOutSendData + // - We realize that the stream needs to go back on the data queue. + // + // Go back around the loop to confirm we're on the correct queue. + } +} + +// appendStreamFrames writes stream-related frames to the current packet. +// +// It returns true if no more frames need appending, +// false if not everything fit in the current packet. +func (c *Conn) appendStreamFrames(w *packetWriter, pnum packetNumber, pto bool) bool { + // MAX_DATA + if !c.appendMaxDataFrame(w, pnum, pto) { + return false + } + + // MAX_STREAM_DATA + if !c.streams.remoteLimit[uniStream].appendFrame(w, uniStream, pnum, pto) { + return false + } + if !c.streams.remoteLimit[bidiStream].appendFrame(w, bidiStream, pnum, pto) { + return false + } + + if pto { + return c.appendStreamFramesPTO(w, pnum) + } + if !c.streams.needSend.Load() { + return true + } + c.streams.sendMu.Lock() + defer c.streams.sendMu.Unlock() + // queueMeta contains streams with non-flow-controlled frames to send. + for c.streams.queueMeta.head != nil { + s := c.streams.queueMeta.head + state := s.state.load() + if state&(streamQueueMeta|streamConnRemoved) != streamQueueMeta { + panic("BUG: queueMeta stream is not streamQueueMeta") + } + if state&streamInSendMeta != 0 { + s.ingate.lock() + ok := s.appendInFramesLocked(w, pnum, pto) + state = s.inUnlockNoQueue() + if !ok { + return false + } + if state&streamInSendMeta != 0 { + panic("BUG: streamInSendMeta set after successfully appending frames") + } + } + if state&streamOutSendMeta != 0 { + s.outgate.lock() + // This might also append flow-controlled frames if we have any + // and available conn-level quota. That's fine. + ok := s.appendOutFramesLocked(w, pnum, pto) + state = s.outUnlockNoQueue() + // We're checking both ok and state, because appendOutFramesLocked + // might have filled up the packet with flow-controlled data. + // If so, we want to move the stream to queueData for any remaining frames. + if !ok && state&streamOutSendMeta != 0 { + return false + } + if state&streamOutSendMeta != 0 { + panic("BUG: streamOutSendMeta set after successfully appending frames") + } + } + // We've sent all frames for this stream, so remove it from the send queue. + c.streams.queueMeta.remove(s) + if state&(streamInDone|streamOutDone) == streamInDone|streamOutDone { + // Stream is finished, remove it from the conn. + state = s.state.set(streamConnRemoved, streamQueueMeta|streamConnRemoved) + delete(c.streams.streams, s.id) + + // Record finalization of remote streams, to know when + // to extend the peer's stream limit. + if s.id.initiator() != c.side { + c.streams.remoteLimit[s.id.streamType()].close() + } + } else { + state = s.state.set(0, streamQueueMeta|streamConnRemoved) + } + // The stream may have flow-controlled data to send, + // or something might have added non-flow-controlled frames after we + // unlocked the stream. + // If so, put the stream back on a queue. + c.queueStreamForSendLocked(s, state) + } + // queueData contains streams with flow-controlled frames. + for c.streams.queueData.head != nil { + avail := c.streams.outflow.avail() + if avail == 0 { + break // no flow control quota available + } + s := c.streams.queueData.head + s.outgate.lock() + ok := s.appendOutFramesLocked(w, pnum, pto) + state := s.outUnlockNoQueue() + if !ok { + // We've sent some data for this stream, but it still has more to send. + // If the stream got a reasonable chance to put data in a packet, + // advance sendHead to the next stream in line, to avoid starvation. + // We'll come back to this stream after going through the others. + // + // If the packet was already mostly out of space, leave sendHead alone + // and come back to this stream again on the next packet. + if avail > 512 { + c.streams.queueData.head = s.next + } + return false + } + if state&streamQueueData == 0 { + panic("BUG: queueData stream is not streamQueueData") + } + if state&streamOutSendData != 0 { + // We must have run out of connection-level flow control: + // appendOutFramesLocked says it wrote all it can, but there's + // still data to send. + // + // Advance sendHead to the next stream in line to avoid starvation. + if c.streams.outflow.avail() != 0 { + panic("BUG: streamOutSendData set and flow control available after send") + } + c.streams.queueData.head = s.next + return true + } + c.streams.queueData.remove(s) + state = s.state.set(0, streamQueueData) + c.queueStreamForSendLocked(s, state) + } + if c.streams.queueMeta.head == nil && c.streams.queueData.head == nil { + c.streams.needSend.Store(false) + } + return true +} + +// appendStreamFramesPTO writes stream-related frames to the current packet +// for a PTO probe. +// +// It returns true if no more frames need appending, +// false if not everything fit in the current packet. +func (c *Conn) appendStreamFramesPTO(w *packetWriter, pnum packetNumber) bool { + c.streams.sendMu.Lock() + defer c.streams.sendMu.Unlock() + const pto = true + for _, s := range c.streams.streams { + const pto = true + s.ingate.lock() + inOK := s.appendInFramesLocked(w, pnum, pto) + s.inUnlockNoQueue() + if !inOK { + return false + } + + s.outgate.lock() + outOK := s.appendOutFramesLocked(w, pnum, pto) + s.outUnlockNoQueue() + if !outOK { + return false + } + } + return true +} + +// A streamRing is a circular linked list of streams. +type streamRing struct { + head *Stream +} + +// remove removes s from the ring. +// s must be on the ring. +func (r *streamRing) remove(s *Stream) { + if s.next == s { + r.head = nil // s was the last stream in the ring + } else { + s.prev.next = s.next + s.next.prev = s.prev + if r.head == s { + r.head = s.next + } + } +} + +// append places s at the last position in the ring. +// s must not be attached to any ring. +func (r *streamRing) append(s *Stream) { + if r.head == nil { + r.head = s + s.next = s + s.prev = s + } else { + s.prev = r.head.prev + s.next = r.head + s.prev.next = s + s.next.prev = s + } +} diff --git a/internal/quic/conn_streams_test.go b/internal/quic/conn_streams_test.go new file mode 100644 index 000000000..69f982c3a --- /dev/null +++ b/internal/quic/conn_streams_test.go @@ -0,0 +1,480 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "fmt" + "io" + "math" + "testing" +) + +func TestStreamsCreate(t *testing.T) { + ctx := canceledContext() + tc := newTestConn(t, clientSide, permissiveTransportParameters) + tc.handshake() + + c, err := tc.conn.NewStream(ctx) + if err != nil { + t.Fatalf("NewStream: %v", err) + } + c.Write(nil) // open the stream + tc.wantFrame("created bidirectional stream 0", + packetType1RTT, debugFrameStream{ + id: 0, // client-initiated, bidi, number 0 + data: []byte{}, + }) + + c, err = tc.conn.NewSendOnlyStream(ctx) + if err != nil { + t.Fatalf("NewStream: %v", err) + } + c.Write(nil) // open the stream + tc.wantFrame("created unidirectional stream 0", + packetType1RTT, debugFrameStream{ + id: 2, // client-initiated, uni, number 0 + data: []byte{}, + }) + + c, err = tc.conn.NewStream(ctx) + if err != nil { + t.Fatalf("NewStream: %v", err) + } + c.Write(nil) // open the stream + tc.wantFrame("created bidirectional stream 1", + packetType1RTT, debugFrameStream{ + id: 4, // client-initiated, uni, number 4 + data: []byte{}, + }) +} + +func TestStreamsAccept(t *testing.T) { + ctx := canceledContext() + tc := newTestConn(t, serverSide) + tc.handshake() + + tc.writeFrames(packetType1RTT, + debugFrameStream{ + id: 0, // client-initiated, bidi, number 0 + }, + debugFrameStream{ + id: 2, // client-initiated, uni, number 0 + }, + debugFrameStream{ + id: 4, // client-initiated, bidi, number 1 + }) + + for _, accept := range []struct { + id streamID + readOnly bool + }{ + {0, false}, + {2, true}, + {4, false}, + } { + s, err := tc.conn.AcceptStream(ctx) + if err != nil { + t.Fatalf("conn.AcceptStream() = %v, want stream %v", err, accept.id) + } + if got, want := s.id, accept.id; got != want { + t.Fatalf("conn.AcceptStream() = stream %v, want %v", got, want) + } + if got, want := s.IsReadOnly(), accept.readOnly; got != want { + t.Fatalf("stream %v: s.IsReadOnly() = %v, want %v", accept.id, got, want) + } + } + + _, err := tc.conn.AcceptStream(ctx) + if err != context.Canceled { + t.Fatalf("conn.AcceptStream() = %v, want context.Canceled", err) + } +} + +func TestStreamsBlockingAccept(t *testing.T) { + tc := newTestConn(t, serverSide) + tc.handshake() + + a := runAsync(tc, func(ctx context.Context) (*Stream, error) { + return tc.conn.AcceptStream(ctx) + }) + if _, err := a.result(); err != errNotDone { + tc.t.Fatalf("AcceptStream() = _, %v; want errNotDone", err) + } + + sid := newStreamID(clientSide, bidiStream, 0) + tc.writeFrames(packetType1RTT, + debugFrameStream{ + id: sid, + }) + + s, err := a.result() + if err != nil { + t.Fatalf("conn.AcceptStream() = _, %v, want stream", err) + } + if got, want := s.id, sid; got != want { + t.Fatalf("conn.AcceptStream() = stream %v, want %v", got, want) + } + if got, want := s.IsReadOnly(), false; got != want { + t.Fatalf("s.IsReadOnly() = %v, want %v", got, want) + } +} + +func TestStreamsLocalStreamNotCreated(t *testing.T) { + // "An endpoint MUST terminate the connection with error STREAM_STATE_ERROR + // if it receives a STREAM frame for a locally initiated stream that has + // not yet been created [...]" + // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8-3 + tc := newTestConn(t, serverSide) + tc.handshake() + + tc.writeFrames(packetType1RTT, + debugFrameStream{ + id: 1, // server-initiated, bidi, number 0 + }) + tc.wantFrame("peer sent STREAM frame for an uncreated local stream", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errStreamState, + }) +} + +func TestStreamsLocalStreamClosed(t *testing.T) { + tc, s := newTestConnAndLocalStream(t, clientSide, uniStream, permissiveTransportParameters) + s.CloseWrite() + tc.wantFrame("FIN for closed stream", + packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, uniStream, 0), + fin: true, + data: []byte{}, + }) + tc.writeAckForAll() + + tc.writeFrames(packetType1RTT, debugFrameStopSending{ + id: newStreamID(clientSide, uniStream, 0), + }) + tc.wantIdle("frame for finalized stream is ignored") + + // ACKing the last stream packet should have cleaned up the stream. + // Check that we don't have any state left. + if got := len(tc.conn.streams.streams); got != 0 { + t.Fatalf("after close, len(tc.conn.streams.streams) = %v, want 0", got) + } + if tc.conn.streams.queueMeta.head != nil { + t.Fatalf("after close, stream send queue is not empty; should be") + } +} + +func TestStreamsStreamSendOnly(t *testing.T) { + // "An endpoint MUST terminate the connection with error STREAM_STATE_ERROR + // if it receives a STREAM frame for a locally initiated stream that has + // not yet been created [...]" + // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8-3 + ctx := canceledContext() + tc := newTestConn(t, serverSide, permissiveTransportParameters) + tc.handshake() + + c, err := tc.conn.NewSendOnlyStream(ctx) + if err != nil { + t.Fatalf("NewStream: %v", err) + } + c.Write(nil) // open the stream + tc.wantFrame("created unidirectional stream 0", + packetType1RTT, debugFrameStream{ + id: 3, // server-initiated, uni, number 0 + data: []byte{}, + }) + + tc.writeFrames(packetType1RTT, + debugFrameStream{ + id: 3, // server-initiated, bidi, number 0 + }) + tc.wantFrame("peer sent STREAM frame for a send-only stream", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errStreamState, + }) +} + +func TestStreamsWriteQueueFairness(t *testing.T) { + ctx := canceledContext() + const dataLen = 1 << 20 + const numStreams = 3 + tc := newTestConn(t, clientSide, func(p *transportParameters) { + p.initialMaxStreamsBidi = numStreams + p.initialMaxData = 1<<62 - 1 + p.initialMaxStreamDataBidiRemote = dataLen + }, func(c *Config) { + c.MaxStreamWriteBufferSize = dataLen + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + // Create a number of streams, and write a bunch of data to them. + // The streams are not limited by flow control. + // + // The first stream we create is going to immediately consume all + // available congestion window. + // + // Once we've created all the remaining streams, + // we start sending acks back to open up the congestion window. + // We verify that all streams can make progress. + data := make([]byte, dataLen) + var streams []*Stream + for i := 0; i < numStreams; i++ { + s, err := tc.conn.NewStream(ctx) + if err != nil { + t.Fatal(err) + } + streams = append(streams, s) + if n, err := s.WriteContext(ctx, data); n != len(data) || err != nil { + t.Fatalf("s.WriteContext() = %v, %v; want %v, nil", n, err, len(data)) + } + // Wait for the stream to finish writing whatever frames it can before + // congestion control blocks it. + tc.wait() + } + + sent := make([]int64, len(streams)) + for { + p := tc.readPacket() + if p == nil { + break + } + tc.writeFrames(packetType1RTT, debugFrameAck{ + ranges: []i64range[packetNumber]{{0, p.num}}, + }) + for _, f := range p.frames { + sf, ok := f.(debugFrameStream) + if !ok { + t.Fatalf("got unexpected frame (want STREAM): %v", sf) + } + if got, want := sf.off, sent[sf.id.num()]; got != want { + t.Fatalf("got frame: %v\nwant offset: %v", sf, want) + } + sent[sf.id.num()] = sf.off + int64(len(sf.data)) + // Look at the amount of data sent by all streams, excluding the first one. + // (The first stream got a head start when it consumed the initial window.) + // + // We expect that difference between the streams making the most and least progress + // so far will be less than the maximum datagram size. + minSent := sent[1] + maxSent := sent[1] + for _, s := range sent[2:] { + minSent = min(minSent, s) + maxSent = max(maxSent, s) + } + const maxDelta = maxUDPPayloadSize + if d := maxSent - minSent; d > maxDelta { + t.Fatalf("stream data sent: %v; delta=%v, want delta <= %v", sent, d, maxDelta) + } + } + } + // Final check that every stream sent the full amount of data expected. + for num, s := range sent { + if s != dataLen { + t.Errorf("stream %v sent %v bytes, want %v", num, s, dataLen) + } + } +} + +func TestStreamsShutdown(t *testing.T) { + // These tests verify that a stream is removed from the Conn's map of live streams + // after it is fully shut down. + // + // Each case consists of a setup step, after which one stream should exist, + // and a shutdown step, after which no streams should remain in the Conn. + for _, test := range []struct { + name string + side streamSide + styp streamType + setup func(*testing.T, *testConn, *Stream) + shutdown func(*testing.T, *testConn, *Stream) + }{{ + name: "closed", + side: localStream, + styp: uniStream, + setup: func(t *testing.T, tc *testConn, s *Stream) { + s.CloseContext(canceledContext()) + }, + shutdown: func(t *testing.T, tc *testConn, s *Stream) { + tc.writeAckForAll() + }, + }, { + name: "local close", + side: localStream, + styp: bidiStream, + setup: func(t *testing.T, tc *testConn, s *Stream) { + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: s.id, + }) + s.CloseContext(canceledContext()) + }, + shutdown: func(t *testing.T, tc *testConn, s *Stream) { + tc.writeAckForAll() + }, + }, { + name: "remote reset", + side: localStream, + styp: bidiStream, + setup: func(t *testing.T, tc *testConn, s *Stream) { + s.CloseContext(canceledContext()) + tc.wantIdle("all frames after CloseContext are ignored") + tc.writeAckForAll() + }, + shutdown: func(t *testing.T, tc *testConn, s *Stream) { + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: s.id, + }) + }, + }, { + name: "local close", + side: remoteStream, + styp: uniStream, + setup: func(t *testing.T, tc *testConn, s *Stream) { + ctx := canceledContext() + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + fin: true, + }) + if n, err := s.ReadContext(ctx, make([]byte, 16)); n != 0 || err != io.EOF { + t.Errorf("ReadContext() = %v, %v; want 0, io.EOF", n, err) + } + }, + shutdown: func(t *testing.T, tc *testConn, s *Stream) { + s.CloseRead() + }, + }} { + name := fmt.Sprintf("%v/%v/%v", test.side, test.styp, test.name) + t.Run(name, func(t *testing.T) { + tc, s := newTestConnAndStream(t, serverSide, test.side, test.styp, + permissiveTransportParameters) + tc.ignoreFrame(frameTypeStreamBase) + tc.ignoreFrame(frameTypeStopSending) + test.setup(t, tc, s) + tc.wantIdle("conn should be idle after setup") + if got, want := len(tc.conn.streams.streams), 1; got != want { + t.Fatalf("after setup: %v streams in Conn's map; want %v", got, want) + } + test.shutdown(t, tc, s) + tc.wantIdle("conn should be idle after shutdown") + if got, want := len(tc.conn.streams.streams), 0; got != want { + t.Fatalf("after shutdown: %v streams in Conn's map; want %v", got, want) + } + }) + } +} + +func TestStreamsCreateAndCloseRemote(t *testing.T) { + // This test exercises creating new streams in response to frames + // from the peer, and cleaning up after streams are fully closed. + // + // It's overfitted to the current implementation, but works through + // a number of corner cases in that implementation. + // + // Disable verbose logging in this test: It sends a lot of packets, + // and they're not especially interesting on their own. + defer func(vv bool) { + *testVV = vv + }(*testVV) + *testVV = false + ctx := canceledContext() + tc := newTestConn(t, serverSide, permissiveTransportParameters) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + type op struct { + id streamID + } + type streamOp op + type resetOp op + type acceptOp op + const noStream = math.MaxInt64 + stringID := func(id streamID) string { + return fmt.Sprintf("%v/%v", id.streamType(), id.num()) + } + for _, op := range []any{ + "opening bidi/5 implicitly opens bidi/0-4", + streamOp{newStreamID(clientSide, bidiStream, 5)}, + acceptOp{newStreamID(clientSide, bidiStream, 5)}, + "bidi/3 was implicitly opened", + streamOp{newStreamID(clientSide, bidiStream, 3)}, + acceptOp{newStreamID(clientSide, bidiStream, 3)}, + resetOp{newStreamID(clientSide, bidiStream, 3)}, + "bidi/3 is done, frames for it are discarded", + streamOp{newStreamID(clientSide, bidiStream, 3)}, + "open and close some uni streams as well", + streamOp{newStreamID(clientSide, uniStream, 0)}, + acceptOp{newStreamID(clientSide, uniStream, 0)}, + streamOp{newStreamID(clientSide, uniStream, 1)}, + acceptOp{newStreamID(clientSide, uniStream, 1)}, + streamOp{newStreamID(clientSide, uniStream, 2)}, + acceptOp{newStreamID(clientSide, uniStream, 2)}, + resetOp{newStreamID(clientSide, uniStream, 1)}, + resetOp{newStreamID(clientSide, uniStream, 0)}, + resetOp{newStreamID(clientSide, uniStream, 2)}, + "closing an implicitly opened stream causes us to accept it", + resetOp{newStreamID(clientSide, bidiStream, 0)}, + acceptOp{newStreamID(clientSide, bidiStream, 0)}, + resetOp{newStreamID(clientSide, bidiStream, 1)}, + acceptOp{newStreamID(clientSide, bidiStream, 1)}, + resetOp{newStreamID(clientSide, bidiStream, 2)}, + acceptOp{newStreamID(clientSide, bidiStream, 2)}, + "stream bidi/3 was reset previously", + resetOp{newStreamID(clientSide, bidiStream, 3)}, + resetOp{newStreamID(clientSide, bidiStream, 4)}, + acceptOp{newStreamID(clientSide, bidiStream, 4)}, + "stream bidi/5 was reset previously", + resetOp{newStreamID(clientSide, bidiStream, 5)}, + "stream bidi/6 was not implicitly opened", + resetOp{newStreamID(clientSide, bidiStream, 6)}, + acceptOp{newStreamID(clientSide, bidiStream, 6)}, + } { + if _, ok := op.(acceptOp); !ok { + if s, err := tc.conn.AcceptStream(ctx); err == nil { + t.Fatalf("accepted stream %v, want none", stringID(s.id)) + } + } + switch op := op.(type) { + case string: + t.Log("# " + op) + case streamOp: + t.Logf("open stream %v", stringID(op.id)) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: streamID(op.id), + }) + case resetOp: + t.Logf("reset stream %v", stringID(op.id)) + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: op.id, + }) + case acceptOp: + s, err := tc.conn.AcceptStream(ctx) + if err != nil { + t.Fatalf("AcceptStream() = %q; want stream %v", err, stringID(op.id)) + } + if s.id != op.id { + t.Fatalf("accepted stram %v; want stream %v", err, stringID(op.id)) + } + t.Logf("accepted stream %v", stringID(op.id)) + // Immediately close the stream, so the stream becomes done when the + // peer closes its end. + s.CloseContext(ctx) + } + p := tc.readPacket() + if p != nil { + tc.writeFrames(p.ptype, debugFrameAck{ + ranges: []i64range[packetNumber]{{0, p.num + 1}}, + }) + } + } + // Every stream should be fully closed now. + // Check that we don't have any state left. + if got := len(tc.conn.streams.streams); got != 0 { + t.Fatalf("after test, len(tc.conn.streams.streams) = %v, want 0", got) + } + if tc.conn.streams.queueMeta.head != nil { + t.Fatalf("after test, stream send queue is not empty; should be") + } +} diff --git a/internal/quic/conn_test.go b/internal/quic/conn_test.go new file mode 100644 index 000000000..6a359e89a --- /dev/null +++ b/internal/quic/conn_test.go @@ -0,0 +1,996 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "context" + "crypto/tls" + "errors" + "flag" + "fmt" + "math" + "net/netip" + "reflect" + "strings" + "testing" + "time" +) + +var testVV = flag.Bool("vv", false, "even more verbose test output") + +func TestConnTestConn(t *testing.T) { + tc := newTestConn(t, serverSide) + if got, want := tc.timeUntilEvent(), defaultMaxIdleTimeout; got != want { + t.Errorf("new conn timeout=%v, want %v (max_idle_timeout)", got, want) + } + + var ranAt time.Time + tc.conn.runOnLoop(func(now time.Time, c *Conn) { + ranAt = now + }) + if !ranAt.Equal(tc.now) { + t.Errorf("func ran on loop at %v, want %v", ranAt, tc.now) + } + tc.wait() + + nextTime := tc.now.Add(defaultMaxIdleTimeout / 2) + tc.advanceTo(nextTime) + tc.conn.runOnLoop(func(now time.Time, c *Conn) { + ranAt = now + }) + if !ranAt.Equal(nextTime) { + t.Errorf("func ran on loop at %v, want %v", ranAt, nextTime) + } + tc.wait() + + tc.advanceToTimer() + if !tc.conn.exited { + t.Errorf("after advancing to idle timeout, exited = false, want true") + } +} + +type testDatagram struct { + packets []*testPacket + paddedSize int +} + +func (d testDatagram) String() string { + var b strings.Builder + fmt.Fprintf(&b, "datagram with %v packets", len(d.packets)) + if d.paddedSize > 0 { + fmt.Fprintf(&b, " (padded to %v bytes)", d.paddedSize) + } + b.WriteString(":") + for _, p := range d.packets { + b.WriteString("\n") + b.WriteString(p.String()) + } + return b.String() +} + +type testPacket struct { + ptype packetType + version uint32 + num packetNumber + keyPhaseBit bool + keyNumber int + dstConnID []byte + srcConnID []byte + frames []debugFrame +} + +func (p testPacket) String() string { + var b strings.Builder + fmt.Fprintf(&b, " %v %v", p.ptype, p.num) + if p.version != 0 { + fmt.Fprintf(&b, " version=%v", p.version) + } + if p.srcConnID != nil { + fmt.Fprintf(&b, " src={%x}", p.srcConnID) + } + if p.dstConnID != nil { + fmt.Fprintf(&b, " dst={%x}", p.dstConnID) + } + for _, f := range p.frames { + fmt.Fprintf(&b, "\n %v", f) + } + return b.String() +} + +// maxTestKeyPhases is the maximum number of 1-RTT keys we'll generate in a test. +const maxTestKeyPhases = 3 + +// A testConn is a Conn whose external interactions (sending and receiving packets, +// setting timers) can be manipulated in tests. +type testConn struct { + t *testing.T + conn *Conn + listener *testListener + now time.Time + timer time.Time + timerLastFired time.Time + idlec chan struct{} // only accessed on the conn's loop + + // Keys are distinct from the conn's keys, + // because the test may know about keys before the conn does. + // For example, when sending a datagram with coalesced + // Initial and Handshake packets to a client conn, + // we use Handshake keys to encrypt the packet. + // The client only acquires those keys when it processes + // the Initial packet. + keysInitial fixedKeyPair + keysHandshake fixedKeyPair + rkeyAppData test1RTTKeys + wkeyAppData test1RTTKeys + rsecrets [numberSpaceCount]keySecret + wsecrets [numberSpaceCount]keySecret + + // testConn uses a test hook to snoop on the conn's TLS events. + // CRYPTO data produced by the conn's QUICConn is placed in + // cryptoDataOut. + // + // The peerTLSConn is is a QUICConn representing the peer. + // CRYPTO data produced by the conn is written to peerTLSConn, + // and data produced by peerTLSConn is placed in cryptoDataIn. + cryptoDataOut map[tls.QUICEncryptionLevel][]byte + cryptoDataIn map[tls.QUICEncryptionLevel][]byte + peerTLSConn *tls.QUICConn + + // Information about the conn's (fake) peer. + peerConnID []byte // source conn id of peer's packets + peerNextPacketNum [numberSpaceCount]packetNumber // next packet number to use + + // Datagrams, packets, and frames sent by the conn, + // but not yet processed by the test. + sentDatagrams [][]byte + sentPackets []*testPacket + sentFrames []debugFrame + lastPacket *testPacket + + recvDatagram chan *datagram + + // Transport parameters sent by the conn. + sentTransportParameters *transportParameters + + // Frame types to ignore in tests. + ignoreFrames map[byte]bool + + // Values to set in packets sent to the conn. + sendKeyNumber int + sendKeyPhaseBit bool + + asyncTestState +} + +type test1RTTKeys struct { + hdr headerKey + pkt [maxTestKeyPhases]packetKey +} + +type keySecret struct { + suite uint16 + secret []byte +} + +// newTestConn creates a Conn for testing. +// +// The Conn's event loop is controlled by the test, +// allowing test code to access Conn state directly +// by first ensuring the loop goroutine is idle. +func newTestConn(t *testing.T, side connSide, opts ...any) *testConn { + t.Helper() + tc := &testConn{ + t: t, + now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), + peerConnID: testPeerConnID(0), + ignoreFrames: map[byte]bool{ + frameTypePadding: true, // ignore PADDING by default + }, + cryptoDataOut: make(map[tls.QUICEncryptionLevel][]byte), + cryptoDataIn: make(map[tls.QUICEncryptionLevel][]byte), + recvDatagram: make(chan *datagram), + } + t.Cleanup(tc.cleanup) + + config := &Config{ + TLSConfig: newTestTLSConfig(side), + } + peerProvidedParams := defaultTransportParameters() + peerProvidedParams.initialSrcConnID = testPeerConnID(0) + if side == clientSide { + peerProvidedParams.originalDstConnID = testLocalConnID(-1) + } + for _, o := range opts { + switch o := o.(type) { + case func(*Config): + o(config) + case func(*tls.Config): + o(config.TLSConfig) + case func(p *transportParameters): + o(&peerProvidedParams) + default: + t.Fatalf("unknown newTestConn option %T", o) + } + } + + var initialConnID []byte + if side == serverSide { + // The initial connection ID for the server is chosen by the client. + initialConnID = testPeerConnID(-1) + } + + peerQUICConfig := &tls.QUICConfig{TLSConfig: newTestTLSConfig(side.peer())} + if side == clientSide { + tc.peerTLSConn = tls.QUICServer(peerQUICConfig) + } else { + tc.peerTLSConn = tls.QUICClient(peerQUICConfig) + } + tc.peerTLSConn.SetTransportParameters(marshalTransportParameters(peerProvidedParams)) + tc.peerTLSConn.Start(context.Background()) + + tc.listener = newTestListener(t, config, (*testConnHooks)(tc)) + conn, err := tc.listener.l.newConn( + tc.now, + side, + initialConnID, + netip.MustParseAddrPort("127.0.0.1:443")) + if err != nil { + tc.t.Fatal(err) + } + tc.conn = conn + + conn.keysAppData.updateAfter = maxPacketNumber // disable key updates + tc.keysInitial.r = conn.keysInitial.w + tc.keysInitial.w = conn.keysInitial.r + + tc.wait() + return tc +} + +// advance causes time to pass. +func (tc *testConn) advance(d time.Duration) { + tc.t.Helper() + tc.advanceTo(tc.now.Add(d)) +} + +// advanceTo sets the current time. +func (tc *testConn) advanceTo(now time.Time) { + tc.t.Helper() + if tc.now.After(now) { + tc.t.Fatalf("time moved backwards: %v -> %v", tc.now, now) + } + tc.now = now + if tc.timer.After(tc.now) { + return + } + tc.conn.sendMsg(timerEvent{}) + tc.wait() +} + +// advanceToTimer sets the current time to the time of the Conn's next timer event. +func (tc *testConn) advanceToTimer() { + if tc.timer.IsZero() { + tc.t.Fatalf("advancing to timer, but timer is not set") + } + tc.advanceTo(tc.timer) +} + +func (tc *testConn) timerDelay() time.Duration { + if tc.timer.IsZero() { + return math.MaxInt64 // infinite + } + if tc.timer.Before(tc.now) { + return 0 + } + return tc.timer.Sub(tc.now) +} + +const infiniteDuration = time.Duration(math.MaxInt64) + +// timeUntilEvent returns the amount of time until the next connection event. +func (tc *testConn) timeUntilEvent() time.Duration { + if tc.timer.IsZero() { + return infiniteDuration + } + if tc.timer.Before(tc.now) { + return 0 + } + return tc.timer.Sub(tc.now) +} + +// wait blocks until the conn becomes idle. +// The conn is idle when it is blocked waiting for a packet to arrive or a timer to expire. +// Tests shouldn't need to call wait directly. +// testConn methods that wake the Conn event loop will call wait for them. +func (tc *testConn) wait() { + tc.t.Helper() + idlec := make(chan struct{}) + fail := false + tc.conn.sendMsg(func(now time.Time, c *Conn) { + if tc.idlec != nil { + tc.t.Errorf("testConn.wait called concurrently") + fail = true + close(idlec) + } else { + // nextMessage will close idlec. + tc.idlec = idlec + } + }) + select { + case <-idlec: + case <-tc.conn.donec: + // We may have async ops that can proceed now that the conn is done. + tc.wakeAsync() + } + if fail { + panic(fail) + } +} + +func (tc *testConn) cleanup() { + if tc.conn == nil { + return + } + tc.conn.exit() + <-tc.conn.donec +} + +func (tc *testConn) logDatagram(text string, d *testDatagram) { + tc.t.Helper() + if !*testVV { + return + } + pad := "" + if d.paddedSize > 0 { + pad = fmt.Sprintf(" (padded to %v)", d.paddedSize) + } + tc.t.Logf("%v datagram%v", text, pad) + for _, p := range d.packets { + var s string + switch p.ptype { + case packetType1RTT: + s = fmt.Sprintf(" %v pnum=%v", p.ptype, p.num) + default: + s = fmt.Sprintf(" %v pnum=%v ver=%v dst={%x} src={%x}", p.ptype, p.num, p.version, p.dstConnID, p.srcConnID) + } + if p.keyPhaseBit { + s += fmt.Sprintf(" KeyPhase") + } + if p.keyNumber != 0 { + s += fmt.Sprintf(" keynum=%v", p.keyNumber) + } + tc.t.Log(s) + for _, f := range p.frames { + tc.t.Logf(" %v", f) + } + } +} + +// write sends the Conn a datagram. +func (tc *testConn) write(d *testDatagram) { + tc.t.Helper() + var buf []byte + tc.logDatagram("<- conn under test receives", d) + for _, p := range d.packets { + space := spaceForPacketType(p.ptype) + if p.num >= tc.peerNextPacketNum[space] { + tc.peerNextPacketNum[space] = p.num + 1 + } + pad := 0 + if p.ptype == packetType1RTT { + pad = d.paddedSize + } + buf = append(buf, tc.encodeTestPacket(p, pad)...) + } + for len(buf) < d.paddedSize { + buf = append(buf, 0) + } + // TODO: This should use tc.listener.write. + tc.conn.sendMsg(&datagram{ + b: buf, + }) + tc.wait() +} + +// writeFrame sends the Conn a datagram containing the given frames. +func (tc *testConn) writeFrames(ptype packetType, frames ...debugFrame) { + tc.t.Helper() + space := spaceForPacketType(ptype) + dstConnID := tc.conn.connIDState.local[0].cid + if tc.conn.connIDState.local[0].seq == -1 && ptype != packetTypeInitial { + // Only use the transient connection ID in Initial packets. + dstConnID = tc.conn.connIDState.local[1].cid + } + d := &testDatagram{ + packets: []*testPacket{{ + ptype: ptype, + num: tc.peerNextPacketNum[space], + keyNumber: tc.sendKeyNumber, + keyPhaseBit: tc.sendKeyPhaseBit, + frames: frames, + version: quicVersion1, + dstConnID: dstConnID, + srcConnID: tc.peerConnID, + }}, + } + if ptype == packetTypeInitial && tc.conn.side == serverSide { + d.paddedSize = 1200 + } + tc.write(d) +} + +// writeAckForAll sends the Conn a datagram containing an ack for all packets up to the +// last one received. +func (tc *testConn) writeAckForAll() { + tc.t.Helper() + if tc.lastPacket == nil { + return + } + tc.writeFrames(tc.lastPacket.ptype, debugFrameAck{ + ranges: []i64range[packetNumber]{{0, tc.lastPacket.num + 1}}, + }) +} + +// writeAckForLatest sends the Conn a datagram containing an ack for the +// most recent packet received. +func (tc *testConn) writeAckForLatest() { + tc.t.Helper() + if tc.lastPacket == nil { + return + } + tc.writeFrames(tc.lastPacket.ptype, debugFrameAck{ + ranges: []i64range[packetNumber]{{tc.lastPacket.num, tc.lastPacket.num + 1}}, + }) +} + +// ignoreFrame hides frames of the given type sent by the Conn. +func (tc *testConn) ignoreFrame(frameType byte) { + tc.ignoreFrames[frameType] = true +} + +// readDatagram reads the next datagram sent by the Conn. +// It returns nil if the Conn has no more datagrams to send at this time. +func (tc *testConn) readDatagram() *testDatagram { + tc.t.Helper() + tc.wait() + tc.sentPackets = nil + tc.sentFrames = nil + buf := tc.listener.read() + if buf == nil { + return nil + } + d := tc.parseTestDatagram(buf) + // Log the datagram before removing ignored frames. + // When things go wrong, it's useful to see all the frames. + tc.logDatagram("-> conn under test sends", d) + typeForFrame := func(f debugFrame) byte { + // This is very clunky, and points at a problem + // in how we specify what frames to ignore in tests. + // + // We mark frames to ignore using the frame type, + // but we've got a debugFrame data structure here. + // Perhaps we should be ignoring frames by debugFrame + // type instead: tc.ignoreFrame[debugFrameAck](). + switch f := f.(type) { + case debugFramePadding: + return frameTypePadding + case debugFramePing: + return frameTypePing + case debugFrameAck: + return frameTypeAck + case debugFrameResetStream: + return frameTypeResetStream + case debugFrameStopSending: + return frameTypeStopSending + case debugFrameCrypto: + return frameTypeCrypto + case debugFrameNewToken: + return frameTypeNewToken + case debugFrameStream: + return frameTypeStreamBase + case debugFrameMaxData: + return frameTypeMaxData + case debugFrameMaxStreamData: + return frameTypeMaxStreamData + case debugFrameMaxStreams: + if f.streamType == bidiStream { + return frameTypeMaxStreamsBidi + } else { + return frameTypeMaxStreamsUni + } + case debugFrameDataBlocked: + return frameTypeDataBlocked + case debugFrameStreamDataBlocked: + return frameTypeStreamDataBlocked + case debugFrameStreamsBlocked: + if f.streamType == bidiStream { + return frameTypeStreamsBlockedBidi + } else { + return frameTypeStreamsBlockedUni + } + case debugFrameNewConnectionID: + return frameTypeNewConnectionID + case debugFrameRetireConnectionID: + return frameTypeRetireConnectionID + case debugFramePathChallenge: + return frameTypePathChallenge + case debugFramePathResponse: + return frameTypePathResponse + case debugFrameConnectionCloseTransport: + return frameTypeConnectionCloseTransport + case debugFrameConnectionCloseApplication: + return frameTypeConnectionCloseApplication + case debugFrameHandshakeDone: + return frameTypeHandshakeDone + } + panic(fmt.Errorf("unhandled frame type %T", f)) + } + for _, p := range d.packets { + var frames []debugFrame + for _, f := range p.frames { + if !tc.ignoreFrames[typeForFrame(f)] { + frames = append(frames, f) + } + } + p.frames = frames + } + return d +} + +// readPacket reads the next packet sent by the Conn. +// It returns nil if the Conn has no more packets to send at this time. +func (tc *testConn) readPacket() *testPacket { + tc.t.Helper() + for len(tc.sentPackets) == 0 { + d := tc.readDatagram() + if d == nil { + return nil + } + tc.sentPackets = d.packets + } + p := tc.sentPackets[0] + tc.sentPackets = tc.sentPackets[1:] + tc.lastPacket = p + return p +} + +// readFrame reads the next frame sent by the Conn. +// It returns nil if the Conn has no more frames to send at this time. +func (tc *testConn) readFrame() (debugFrame, packetType) { + tc.t.Helper() + for len(tc.sentFrames) == 0 { + p := tc.readPacket() + if p == nil { + return nil, packetTypeInvalid + } + tc.sentFrames = p.frames + } + f := tc.sentFrames[0] + tc.sentFrames = tc.sentFrames[1:] + return f, tc.lastPacket.ptype +} + +// wantDatagram indicates that we expect the Conn to send a datagram. +func (tc *testConn) wantDatagram(expectation string, want *testDatagram) { + tc.t.Helper() + got := tc.readDatagram() + if !reflect.DeepEqual(got, want) { + tc.t.Fatalf("%v:\ngot datagram: %v\nwant datagram: %v", expectation, got, want) + } +} + +// wantPacket indicates that we expect the Conn to send a packet. +func (tc *testConn) wantPacket(expectation string, want *testPacket) { + tc.t.Helper() + got := tc.readPacket() + if !reflect.DeepEqual(got, want) { + tc.t.Fatalf("%v:\ngot packet: %v\nwant packet: %v", expectation, got, want) + } +} + +// wantFrame indicates that we expect the Conn to send a frame. +func (tc *testConn) wantFrame(expectation string, wantType packetType, want debugFrame) { + tc.t.Helper() + got, gotType := tc.readFrame() + if got == nil { + tc.t.Fatalf("%v:\nconnection is idle\nwant %v frame: %v", expectation, wantType, want) + } + if gotType != wantType { + tc.t.Fatalf("%v:\ngot %v packet, want %v\ngot frame: %v", expectation, gotType, wantType, got) + } + if !reflect.DeepEqual(got, want) { + tc.t.Fatalf("%v:\ngot frame: %v\nwant frame: %v", expectation, got, want) + } +} + +// wantFrameType indicates that we expect the Conn to send a frame, +// although we don't care about the contents. +func (tc *testConn) wantFrameType(expectation string, wantType packetType, want debugFrame) { + tc.t.Helper() + got, gotType := tc.readFrame() + if got == nil { + tc.t.Fatalf("%v:\nconnection is idle\nwant %v frame: %v", expectation, wantType, want) + } + if gotType != wantType { + tc.t.Fatalf("%v:\ngot %v packet, want %v\ngot frame: %v", expectation, gotType, wantType, got) + } + if reflect.TypeOf(got) != reflect.TypeOf(want) { + tc.t.Fatalf("%v:\ngot frame: %v\nwant frame of type: %v", expectation, got, want) + } +} + +// wantIdle indicates that we expect the Conn to not send any more frames. +func (tc *testConn) wantIdle(expectation string) { + tc.t.Helper() + switch { + case len(tc.sentFrames) > 0: + tc.t.Fatalf("expect: %v\nunexpectedly got: %v", expectation, tc.sentFrames[0]) + case len(tc.sentPackets) > 0: + tc.t.Fatalf("expect: %v\nunexpectedly got: %v", expectation, tc.sentPackets[0]) + } + if f, _ := tc.readFrame(); f != nil { + tc.t.Fatalf("expect: %v\nunexpectedly got: %v", expectation, f) + } +} + +func (tc *testConn) encodeTestPacket(p *testPacket, pad int) []byte { + tc.t.Helper() + var w packetWriter + w.reset(1200) + var pnumMaxAcked packetNumber + if p.ptype != packetType1RTT { + w.startProtectedLongHeaderPacket(pnumMaxAcked, longPacket{ + ptype: p.ptype, + version: p.version, + num: p.num, + dstConnID: p.dstConnID, + srcConnID: p.srcConnID, + }) + } else { + w.start1RTTPacket(p.num, pnumMaxAcked, p.dstConnID) + } + for _, f := range p.frames { + f.write(&w) + } + w.appendPaddingTo(pad) + if p.ptype != packetType1RTT { + var k fixedKeys + switch p.ptype { + case packetTypeInitial: + k = tc.keysInitial.w + case packetTypeHandshake: + k = tc.keysHandshake.w + } + if !k.isSet() { + tc.t.Fatalf("sending %v packet with no write key", p.ptype) + } + w.finishProtectedLongHeaderPacket(pnumMaxAcked, k, longPacket{ + ptype: p.ptype, + version: p.version, + num: p.num, + dstConnID: p.dstConnID, + srcConnID: p.srcConnID, + }) + } else { + if !tc.wkeyAppData.hdr.isSet() { + tc.t.Fatalf("sending 1-RTT packet with no write key") + } + // Somewhat hackish: Generate a temporary updatingKeyPair that will + // always use our desired key phase. + k := &updatingKeyPair{ + w: updatingKeys{ + hdr: tc.wkeyAppData.hdr, + pkt: [2]packetKey{ + tc.wkeyAppData.pkt[p.keyNumber], + tc.wkeyAppData.pkt[p.keyNumber], + }, + }, + updateAfter: maxPacketNumber, + } + if p.keyPhaseBit { + k.phase |= keyPhaseBit + } + w.finish1RTTPacket(p.num, pnumMaxAcked, p.dstConnID, k) + } + return w.datagram() +} + +func (tc *testConn) parseTestDatagram(buf []byte) *testDatagram { + tc.t.Helper() + bufSize := len(buf) + d := &testDatagram{} + size := len(buf) + for len(buf) > 0 { + if buf[0] == 0 { + d.paddedSize = bufSize + break + } + ptype := getPacketType(buf) + if isLongHeader(buf[0]) { + var k fixedKeyPair + switch ptype { + case packetTypeInitial: + k = tc.keysInitial + case packetTypeHandshake: + k = tc.keysHandshake + } + if !k.canRead() { + tc.t.Fatalf("reading %v packet with no read key", ptype) + } + var pnumMax packetNumber // TODO: Track packet numbers. + p, n := parseLongHeaderPacket(buf, k.r, pnumMax) + if n < 0 { + tc.t.Fatalf("packet parse error") + } + frames, err := tc.parseTestFrames(p.payload) + if err != nil { + tc.t.Fatal(err) + } + d.packets = append(d.packets, &testPacket{ + ptype: p.ptype, + version: p.version, + num: p.num, + dstConnID: p.dstConnID, + srcConnID: p.srcConnID, + frames: frames, + }) + buf = buf[n:] + } else { + if !tc.rkeyAppData.hdr.isSet() { + tc.t.Fatalf("reading 1-RTT packet with no read key") + } + var pnumMax packetNumber // TODO: Track packet numbers. + pnumOff := 1 + len(tc.peerConnID) + // Try unprotecting the packet with the first maxTestKeyPhases keys. + var phase int + var pnum packetNumber + var hdr []byte + var pay []byte + var err error + for phase = 0; phase < maxTestKeyPhases; phase++ { + b := append([]byte{}, buf...) + hdr, pay, pnum, err = tc.rkeyAppData.hdr.unprotect(b, pnumOff, pnumMax) + if err != nil { + tc.t.Fatalf("1-RTT packet header parse error") + } + k := tc.rkeyAppData.pkt[phase] + pay, err = k.unprotect(hdr, pay, pnum) + if err == nil { + break + } + } + if err != nil { + tc.t.Fatalf("1-RTT packet payload parse error") + } + frames, err := tc.parseTestFrames(pay) + if err != nil { + tc.t.Fatal(err) + } + d.packets = append(d.packets, &testPacket{ + ptype: packetType1RTT, + num: pnum, + dstConnID: hdr[1:][:len(tc.peerConnID)], + keyPhaseBit: hdr[0]&keyPhaseBit != 0, + keyNumber: phase, + frames: frames, + }) + buf = buf[len(buf):] + } + } + // This is rather hackish: If the last frame in the last packet + // in the datagram is PADDING, then remove it and record + // the padded size in the testDatagram.paddedSize. + // + // This makes it easier to write a test that expects a datagram + // padded to 1200 bytes. + if len(d.packets) > 0 && len(d.packets[len(d.packets)-1].frames) > 0 { + p := d.packets[len(d.packets)-1] + f := p.frames[len(p.frames)-1] + if _, ok := f.(debugFramePadding); ok { + p.frames = p.frames[:len(p.frames)-1] + d.paddedSize = size + } + } + return d +} + +func (tc *testConn) parseTestFrames(payload []byte) ([]debugFrame, error) { + tc.t.Helper() + var frames []debugFrame + for len(payload) > 0 { + f, n := parseDebugFrame(payload) + if n < 0 { + return nil, errors.New("error parsing frames") + } + frames = append(frames, f) + payload = payload[n:] + } + return frames, nil +} + +func spaceForPacketType(ptype packetType) numberSpace { + switch ptype { + case packetTypeInitial: + return initialSpace + case packetType0RTT: + panic("TODO: packetType0RTT") + case packetTypeHandshake: + return handshakeSpace + case packetTypeRetry: + panic("TODO: packetTypeRetry") + case packetType1RTT: + return appDataSpace + } + panic("unknown packet type") +} + +// testConnHooks implements connTestHooks. +type testConnHooks testConn + +// handleTLSEvent processes TLS events generated by +// the connection under test's tls.QUICConn. +// +// We maintain a second tls.QUICConn representing the peer, +// and feed the TLS handshake data into it. +// +// We stash TLS handshake data from both sides in the testConn, +// where it can be used by tests. +// +// We snoop packet protection keys out of the tls.QUICConns, +// and verify that both sides of the connection are getting +// matching keys. +func (tc *testConnHooks) handleTLSEvent(e tls.QUICEvent) { + checkKey := func(typ string, secrets *[numberSpaceCount]keySecret, e tls.QUICEvent) { + var space numberSpace + switch { + case e.Level == tls.QUICEncryptionLevelHandshake: + space = handshakeSpace + case e.Level == tls.QUICEncryptionLevelApplication: + space = appDataSpace + default: + tc.t.Errorf("unexpected encryption level %v", e.Level) + return + } + if secrets[space].secret == nil { + secrets[space].suite = e.Suite + secrets[space].secret = append([]byte{}, e.Data...) + } else if secrets[space].suite != e.Suite || !bytes.Equal(secrets[space].secret, e.Data) { + tc.t.Errorf("%v key mismatch for level for level %v", typ, e.Level) + } + } + setAppDataKey := func(suite uint16, secret []byte, k *test1RTTKeys) { + k.hdr.init(suite, secret) + for i := 0; i < len(k.pkt); i++ { + k.pkt[i].init(suite, secret) + secret = updateSecret(suite, secret) + } + } + switch e.Kind { + case tls.QUICSetReadSecret: + checkKey("write", &tc.wsecrets, e) + switch e.Level { + case tls.QUICEncryptionLevelHandshake: + tc.keysHandshake.w.init(e.Suite, e.Data) + case tls.QUICEncryptionLevelApplication: + setAppDataKey(e.Suite, e.Data, &tc.wkeyAppData) + } + case tls.QUICSetWriteSecret: + checkKey("read", &tc.rsecrets, e) + switch e.Level { + case tls.QUICEncryptionLevelHandshake: + tc.keysHandshake.r.init(e.Suite, e.Data) + case tls.QUICEncryptionLevelApplication: + setAppDataKey(e.Suite, e.Data, &tc.rkeyAppData) + } + case tls.QUICWriteData: + tc.cryptoDataOut[e.Level] = append(tc.cryptoDataOut[e.Level], e.Data...) + tc.peerTLSConn.HandleData(e.Level, e.Data) + } + for { + e := tc.peerTLSConn.NextEvent() + switch e.Kind { + case tls.QUICNoEvent: + return + case tls.QUICSetReadSecret: + checkKey("write", &tc.rsecrets, e) + switch e.Level { + case tls.QUICEncryptionLevelHandshake: + tc.keysHandshake.r.init(e.Suite, e.Data) + case tls.QUICEncryptionLevelApplication: + setAppDataKey(e.Suite, e.Data, &tc.rkeyAppData) + } + case tls.QUICSetWriteSecret: + checkKey("read", &tc.wsecrets, e) + switch e.Level { + case tls.QUICEncryptionLevelHandshake: + tc.keysHandshake.w.init(e.Suite, e.Data) + case tls.QUICEncryptionLevelApplication: + setAppDataKey(e.Suite, e.Data, &tc.wkeyAppData) + } + case tls.QUICWriteData: + tc.cryptoDataIn[e.Level] = append(tc.cryptoDataIn[e.Level], e.Data...) + case tls.QUICTransportParameters: + p, err := unmarshalTransportParams(e.Data) + if err != nil { + tc.t.Logf("sent unparseable transport parameters %x %v", e.Data, err) + } else { + tc.sentTransportParameters = &p + } + } + } +} + +// nextMessage is called by the Conn's event loop to request its next event. +func (tc *testConnHooks) nextMessage(msgc chan any, timer time.Time) (now time.Time, m any) { + tc.timer = timer + for { + if !timer.IsZero() && !timer.After(tc.now) { + if timer.Equal(tc.timerLastFired) { + // If the connection timer fires at time T, the Conn should take some + // action to advance the timer into the future. If the Conn reschedules + // the timer for the same time, it isn't making progress and we have a bug. + tc.t.Errorf("connection timer spinning; now=%v timer=%v", tc.now, timer) + } else { + tc.timerLastFired = timer + return tc.now, timerEvent{} + } + } + select { + case m := <-msgc: + return tc.now, m + default: + } + if !tc.wakeAsync() { + break + } + } + // If the message queue is empty, then the conn is idle. + if tc.idlec != nil { + idlec := tc.idlec + tc.idlec = nil + close(idlec) + } + m = <-msgc + return tc.now, m +} + +func (tc *testConnHooks) newConnID(seq int64) ([]byte, error) { + return testLocalConnID(seq), nil +} + +func (tc *testConnHooks) timeNow() time.Time { + return tc.now +} + +// testLocalConnID returns the connection ID with a given sequence number +// used by a Conn under test. +func testLocalConnID(seq int64) []byte { + cid := make([]byte, connIDLen) + copy(cid, []byte{0xc0, 0xff, 0xee}) + cid[len(cid)-1] = byte(seq) + return cid +} + +// testPeerConnID returns the connection ID with a given sequence number +// used by the fake peer of a Conn under test. +func testPeerConnID(seq int64) []byte { + // Use a different length than we choose for our own conn ids, + // to help catch any bad assumptions. + return []byte{0xbe, 0xee, 0xff, byte(seq)} +} + +// canceledContext returns a canceled Context. +// +// Functions which take a context preference progress over cancelation. +// For example, a read with a canceled context will return data if any is available. +// Tests use canceled contexts to perform non-blocking operations. +func canceledContext() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + return ctx +} diff --git a/internal/quic/crypto_stream.go b/internal/quic/crypto_stream.go new file mode 100644 index 000000000..8aa8f7b82 --- /dev/null +++ b/internal/quic/crypto_stream.go @@ -0,0 +1,138 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +// "Implementations MUST support buffering at least 4096 bytes of data +// received in out-of-order CRYPTO frames." +// https://www.rfc-editor.org/rfc/rfc9000.html#section-7.5-2 +// +// 4096 is too small for real-world cases, however, so we allow more. +const cryptoBufferSize = 1 << 20 + +// A cryptoStream is the stream of data passed in CRYPTO frames. +// There is one cryptoStream per packet number space. +type cryptoStream struct { + // CRYPTO data received from the peer. + in pipe + inset rangeset[int64] // bytes received + + // CRYPTO data queued for transmission to the peer. + out pipe + outunsent rangeset[int64] // bytes in need of sending + outacked rangeset[int64] // bytes acked by peer +} + +// handleCrypto processes data received in a CRYPTO frame. +func (s *cryptoStream) handleCrypto(off int64, b []byte, f func([]byte) error) error { + end := off + int64(len(b)) + if end-s.inset.min() > cryptoBufferSize { + return localTransportError(errCryptoBufferExceeded) + } + s.inset.add(off, end) + if off == s.in.start { + // Fast path: This is the next chunk of data in the stream, + // so just handle it immediately. + if err := f(b); err != nil { + return err + } + s.in.discardBefore(end) + } else { + // This is either data we've already processed, + // data we can't process yet, or a mix of both. + s.in.writeAt(b, off) + } + // s.in.start is the next byte in sequence. + // If it's in s.inset, we have bytes to provide. + // If it isn't, we don't--we're either out of data, + // or only have data that comes after the next byte. + if !s.inset.contains(s.in.start) { + return nil + } + // size is the size of the first contiguous chunk of bytes + // that have not been processed yet. + size := int(s.inset[0].end - s.in.start) + if size <= 0 { + return nil + } + err := s.in.read(s.in.start, size, f) + s.in.discardBefore(s.inset[0].end) + return err +} + +// write queues data for sending to the peer. +// It does not block or limit the amount of buffered data. +// QUIC connections don't communicate the amount of CRYPTO data they are willing to buffer, +// so we send what we have and the peer can close the connection if it is too much. +func (s *cryptoStream) write(b []byte) { + start := s.out.end + s.out.writeAt(b, start) + s.outunsent.add(start, s.out.end) +} + +// ackOrLoss reports that an CRYPTO frame sent by us has been acknowledged by the peer, or lost. +func (s *cryptoStream) ackOrLoss(start, end int64, fate packetFate) { + switch fate { + case packetAcked: + s.outacked.add(start, end) + s.outunsent.sub(start, end) + // If this ack is for data at the start of the send buffer, we can now discard it. + if s.outacked.contains(s.out.start) { + s.out.discardBefore(s.outacked[0].end) + } + case packetLost: + // Mark everything lost, but not previously acked, as needing retransmission. + // We do this by adding all the lost bytes to outunsent, and then + // removing everything already acked. + s.outunsent.add(start, end) + for _, a := range s.outacked { + s.outunsent.sub(a.start, a.end) + } + } +} + +// dataToSend reports what data should be sent in CRYPTO frames to the peer. +// It calls f with each range of data to send. +// f uses sendData to get the bytes to send, and returns the number of bytes sent. +// dataToSend calls f until no data is left, or f returns 0. +// +// This function is unusually indirect (why not just return a []byte, +// or implement io.Reader?). +// +// Returning a []byte to the caller either requires that we store the +// data to send contiguously (which we don't), allocate a temporary buffer +// and copy into it (inefficient), or return less data than we have available +// (requires complexity to avoid unnecessarily breaking data across frames). +// +// Accepting a []byte from the caller (io.Reader) makes packet construction +// difficult. Since CRYPTO data is encoded with a varint length prefix, the +// location of the data depends on the length of the data. (We could hardcode +// a 2-byte length, of course.) +// +// Instead, we tell the caller how much data is, the caller figures out where +// to put it (and possibly decides that it doesn't have space for this data +// in the packet after all), and the caller then makes a separate call to +// copy the data it wants into position. +func (s *cryptoStream) dataToSend(pto bool, f func(off, size int64) (sent int64)) { + for { + off, size := dataToSend(s.out.start, s.out.end, s.outunsent, s.outacked, pto) + if size == 0 { + return + } + n := f(off, size) + if n == 0 || pto { + return + } + } +} + +// sendData fills b with data to send to the peer, starting at off, +// and marks the data as sent. The caller must have already ascertained +// that there is data to send in this region using dataToSend. +func (s *cryptoStream) sendData(off int64, b []byte) { + s.out.copy(off, b) + s.outunsent.sub(off, off+int64(len(b))) +} diff --git a/internal/quic/crypto_stream_test.go b/internal/quic/crypto_stream_test.go new file mode 100644 index 000000000..a6c1e1b52 --- /dev/null +++ b/internal/quic/crypto_stream_test.go @@ -0,0 +1,265 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "crypto/rand" + "reflect" + "testing" +) + +func TestCryptoStreamReceive(t *testing.T) { + data := make([]byte, 1<<20) + rand.Read(data) // doesn't need to be crypto/rand, but non-deprecated and harmless + type frame struct { + start int64 + end int64 + want int + } + for _, test := range []struct { + name string + frames []frame + }{{ + name: "linear", + frames: []frame{{ + start: 0, + end: 1000, + want: 1000, + }, { + start: 1000, + end: 2000, + want: 2000, + }, { + // larger than any realistic packet can hold + start: 2000, + end: 1 << 20, + want: 1 << 20, + }}, + }, { + name: "out of order", + frames: []frame{{ + start: 1000, + end: 2000, + }, { + start: 2000, + end: 3000, + }, { + start: 0, + end: 1000, + want: 3000, + }}, + }, { + name: "resent", + frames: []frame{{ + start: 0, + end: 1000, + want: 1000, + }, { + start: 0, + end: 1000, + want: 1000, + }, { + start: 1000, + end: 2000, + want: 2000, + }, { + start: 0, + end: 1000, + want: 2000, + }, { + start: 1000, + end: 2000, + want: 2000, + }}, + }, { + name: "overlapping", + frames: []frame{{ + start: 0, + end: 1000, + want: 1000, + }, { + start: 3000, + end: 4000, + want: 1000, + }, { + start: 2000, + end: 3000, + want: 1000, + }, { + start: 1000, + end: 3000, + want: 4000, + }}, + }} { + t.Run(test.name, func(t *testing.T) { + var s cryptoStream + var got []byte + for _, f := range test.frames { + t.Logf("receive [%v,%v)", f.start, f.end) + s.handleCrypto( + f.start, + data[f.start:f.end], + func(b []byte) error { + t.Logf("got new bytes [%v,%v)", len(got), len(got)+len(b)) + got = append(got, b...) + return nil + }, + ) + if len(got) != f.want { + t.Fatalf("have bytes [0,%v), want [0,%v)", len(got), f.want) + } + for i := range got { + if got[i] != data[i] { + t.Fatalf("byte %v of received data = %v, want %v", i, got[i], data[i]) + } + } + } + }) + } +} + +func TestCryptoStreamSends(t *testing.T) { + data := make([]byte, 1<<20) + rand.Read(data) // doesn't need to be crypto/rand, but non-deprecated and harmless + type ( + sendOp i64range[int64] + ackOp i64range[int64] + lossOp i64range[int64] + ) + for _, test := range []struct { + name string + size int64 + ops []any + wantSend []i64range[int64] + wantPTOSend []i64range[int64] + }{{ + name: "writes with data remaining", + size: 4000, + ops: []any{ + sendOp{0, 1000}, + sendOp{1000, 2000}, + sendOp{2000, 3000}, + }, + wantSend: []i64range[int64]{ + {3000, 4000}, + }, + wantPTOSend: []i64range[int64]{ + {0, 4000}, + }, + }, { + name: "lost data is resent", + size: 4000, + ops: []any{ + sendOp{0, 1000}, + sendOp{1000, 2000}, + sendOp{2000, 3000}, + sendOp{3000, 4000}, + lossOp{1000, 2000}, + lossOp{3000, 4000}, + }, + wantSend: []i64range[int64]{ + {1000, 2000}, + {3000, 4000}, + }, + wantPTOSend: []i64range[int64]{ + {0, 4000}, + }, + }, { + name: "acked data at start of range", + size: 4000, + ops: []any{ + sendOp{0, 4000}, + ackOp{0, 1000}, + ackOp{1000, 2000}, + ackOp{2000, 3000}, + }, + wantSend: nil, + wantPTOSend: []i64range[int64]{ + {3000, 4000}, + }, + }, { + name: "acked data is not resent on pto", + size: 4000, + ops: []any{ + sendOp{0, 4000}, + ackOp{1000, 2000}, + }, + wantSend: nil, + wantPTOSend: []i64range[int64]{ + {0, 1000}, + }, + }, { + // This is an unusual, but possible scenario: + // Data is sent, resent, one of the two sends is acked, and the other is lost. + name: "acked and then lost data is not resent", + size: 4000, + ops: []any{ + sendOp{0, 4000}, + sendOp{1000, 2000}, // resent, no-op + ackOp{1000, 2000}, + lossOp{1000, 2000}, + }, + wantSend: nil, + wantPTOSend: []i64range[int64]{ + {0, 1000}, + }, + }, { + // The opposite of the above scenario: data is marked lost, and then acked + // before being resent. + name: "lost and then acked data is not resent", + size: 4000, + ops: []any{ + sendOp{0, 4000}, + sendOp{1000, 2000}, // resent, no-op + lossOp{1000, 2000}, + ackOp{1000, 2000}, + }, + wantSend: nil, + wantPTOSend: []i64range[int64]{ + {0, 1000}, + }, + }} { + t.Run(test.name, func(t *testing.T) { + var s cryptoStream + s.write(data[:test.size]) + for _, op := range test.ops { + switch op := op.(type) { + case sendOp: + t.Logf("send [%v,%v)", op.start, op.end) + b := make([]byte, op.end-op.start) + s.sendData(op.start, b) + case ackOp: + t.Logf("ack [%v,%v)", op.start, op.end) + s.ackOrLoss(op.start, op.end, packetAcked) + case lossOp: + t.Logf("loss [%v,%v)", op.start, op.end) + s.ackOrLoss(op.start, op.end, packetLost) + default: + t.Fatalf("unhandled type %T", op) + } + } + var gotSend []i64range[int64] + s.dataToSend(true, func(off, size int64) (wrote int64) { + gotSend = append(gotSend, i64range[int64]{off, off + size}) + return 0 + }) + if !reflect.DeepEqual(gotSend, test.wantPTOSend) { + t.Fatalf("got data to send on PTO: %v, want %v", gotSend, test.wantPTOSend) + } + gotSend = nil + s.dataToSend(false, func(off, size int64) (wrote int64) { + gotSend = append(gotSend, i64range[int64]{off, off + size}) + b := make([]byte, size) + s.sendData(off, b) + return int64(len(b)) + }) + if !reflect.DeepEqual(gotSend, test.wantSend) { + t.Fatalf("got data to send: %v, want %v", gotSend, test.wantSend) + } + }) + } +} diff --git a/internal/quic/dgram.go b/internal/quic/dgram.go new file mode 100644 index 000000000..79e6650fa --- /dev/null +++ b/internal/quic/dgram.go @@ -0,0 +1,38 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "net/netip" + "sync" +) + +type datagram struct { + b []byte + addr netip.AddrPort +} + +var datagramPool = sync.Pool{ + New: func() any { + return &datagram{ + b: make([]byte, maxUDPPayloadSize), + } + }, +} + +func newDatagram() *datagram { + m := datagramPool.Get().(*datagram) + m.b = m.b[:cap(m.b)] + return m +} + +func (m *datagram) recycle() { + if cap(m.b) != maxUDPPayloadSize { + return + } + datagramPool.Put(m) +} diff --git a/internal/quic/doc.go b/internal/quic/doc.go new file mode 100644 index 000000000..2fe17fe22 --- /dev/null +++ b/internal/quic/doc.go @@ -0,0 +1,9 @@ +// Copyright 2023 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 quic is an experimental, incomplete implementation of the QUIC protocol. +// This package is a work in progress, and is not ready for use at this time. +// +// This package implements (or will implement) RFC 9000, RFC 9001, and RFC 9002. +package quic diff --git a/internal/quic/errors.go b/internal/quic/errors.go new file mode 100644 index 000000000..8e01bb7cb --- /dev/null +++ b/internal/quic/errors.go @@ -0,0 +1,126 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "fmt" +) + +// A transportError is a transport error code from RFC 9000 Section 20.1. +// +// The transportError type doesn't implement the error interface to ensure we always +// distinguish between errors sent to and received from the peer. +// See the localTransportError and peerTransportError types below. +type transportError uint64 + +// https://www.rfc-editor.org/rfc/rfc9000.html#section-20.1 +const ( + errNo = transportError(0x00) + errInternal = transportError(0x01) + errConnectionRefused = transportError(0x02) + errFlowControl = transportError(0x03) + errStreamLimit = transportError(0x04) + errStreamState = transportError(0x05) + errFinalSize = transportError(0x06) + errFrameEncoding = transportError(0x07) + errTransportParameter = transportError(0x08) + errConnectionIDLimit = transportError(0x09) + errProtocolViolation = transportError(0x0a) + errInvalidToken = transportError(0x0b) + errApplicationError = transportError(0x0c) + errCryptoBufferExceeded = transportError(0x0d) + errKeyUpdateError = transportError(0x0e) + errAEADLimitReached = transportError(0x0f) + errNoViablePath = transportError(0x10) + errTLSBase = transportError(0x0100) // 0x0100-0x01ff; base + TLS code +) + +func (e transportError) String() string { + switch e { + case errNo: + return "NO_ERROR" + case errInternal: + return "INTERNAL_ERROR" + case errConnectionRefused: + return "CONNECTION_REFUSED" + case errFlowControl: + return "FLOW_CONTROL_ERROR" + case errStreamLimit: + return "STREAM_LIMIT_ERROR" + case errStreamState: + return "STREAM_STATE_ERROR" + case errFinalSize: + return "FINAL_SIZE_ERROR" + case errFrameEncoding: + return "FRAME_ENCODING_ERROR" + case errTransportParameter: + return "TRANSPORT_PARAMETER_ERROR" + case errConnectionIDLimit: + return "CONNECTION_ID_LIMIT_ERROR" + case errProtocolViolation: + return "PROTOCOL_VIOLATION" + case errInvalidToken: + return "INVALID_TOKEN" + case errApplicationError: + return "APPLICATION_ERROR" + case errCryptoBufferExceeded: + return "CRYPTO_BUFFER_EXCEEDED" + case errKeyUpdateError: + return "KEY_UPDATE_ERROR" + case errAEADLimitReached: + return "AEAD_LIMIT_REACHED" + case errNoViablePath: + return "NO_VIABLE_PATH" + } + if e >= 0x0100 && e <= 0x01ff { + return fmt.Sprintf("CRYPTO_ERROR(%v)", uint64(e)&0xff) + } + return fmt.Sprintf("ERROR %d", uint64(e)) +} + +// A localTransportError is an error sent to the peer. +type localTransportError transportError + +func (e localTransportError) Error() string { + return "closed connection: " + transportError(e).String() +} + +// A peerTransportError is an error received from the peer. +type peerTransportError struct { + code transportError + reason string +} + +func (e peerTransportError) Error() string { + return fmt.Sprintf("peer closed connection: %v: %q", e.code, e.reason) +} + +// A StreamErrorCode is an application protocol error code (RFC 9000, Section 20.2) +// indicating whay a stream is being closed. +type StreamErrorCode uint64 + +func (e StreamErrorCode) Error() string { + return fmt.Sprintf("stream error code %v", uint64(e)) +} + +// An ApplicationError is an application protocol error code (RFC 9000, Section 20.2). +// Application protocol errors may be sent when terminating a stream or connection. +type ApplicationError struct { + Code uint64 + Reason string +} + +func (e *ApplicationError) Error() string { + // TODO: Include the Reason string here, but sanitize it first. + return fmt.Sprintf("AppError %v", e.Code) +} + +// Is reports a match if err is an *ApplicationError with a matching Code. +func (e *ApplicationError) Is(err error) bool { + e2, ok := err.(*ApplicationError) + return ok && e2.Code == e.Code +} diff --git a/internal/quic/files_test.go b/internal/quic/files_test.go new file mode 100644 index 000000000..8113109e7 --- /dev/null +++ b/internal/quic/files_test.go @@ -0,0 +1,51 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "os" + "strings" + "testing" +) + +// TestFiles checks that every file in this package has a build constraint on Go 1.21. +// +// The QUIC implementation depends on crypto/tls features added in Go 1.21, +// so there's no point in trying to build on anything older. +// +// Drop this test when the x/net go.mod depends on 1.21 or newer. +func TestFiles(t *testing.T) { + f, err := os.Open(".") + if err != nil { + t.Fatal(err) + } + names, err := f.Readdirnames(-1) + if err != nil { + t.Fatal(err) + } + for _, name := range names { + if !strings.HasSuffix(name, ".go") { + continue + } + b, err := os.ReadFile(name) + if err != nil { + t.Fatal(err) + } + // Check for copyright header while we're in here. + if !bytes.Contains(b, []byte("The Go Authors.")) { + t.Errorf("%v: missing copyright", name) + } + // doc.go doesn't need a build constraint. + if name == "doc.go" { + continue + } + if !bytes.Contains(b, []byte("//go:build go1.21")) { + t.Errorf("%v: missing constraint on go1.21", name) + } + } +} diff --git a/internal/quic/frame_debug.go b/internal/quic/frame_debug.go new file mode 100644 index 000000000..7a5aee57b --- /dev/null +++ b/internal/quic/frame_debug.go @@ -0,0 +1,504 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "fmt" +) + +// A debugFrame is a representation of the contents of a QUIC frame, +// used for debug logs and testing but not the primary serving path. +type debugFrame interface { + String() string + write(w *packetWriter) bool +} + +func parseDebugFrame(b []byte) (f debugFrame, n int) { + if len(b) == 0 { + return nil, -1 + } + switch b[0] { + case frameTypePadding: + f, n = parseDebugFramePadding(b) + case frameTypePing: + f, n = parseDebugFramePing(b) + case frameTypeAck, frameTypeAckECN: + f, n = parseDebugFrameAck(b) + case frameTypeResetStream: + f, n = parseDebugFrameResetStream(b) + case frameTypeStopSending: + f, n = parseDebugFrameStopSending(b) + case frameTypeCrypto: + f, n = parseDebugFrameCrypto(b) + case frameTypeNewToken: + f, n = parseDebugFrameNewToken(b) + case frameTypeStreamBase, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f: + f, n = parseDebugFrameStream(b) + case frameTypeMaxData: + f, n = parseDebugFrameMaxData(b) + case frameTypeMaxStreamData: + f, n = parseDebugFrameMaxStreamData(b) + case frameTypeMaxStreamsBidi, frameTypeMaxStreamsUni: + f, n = parseDebugFrameMaxStreams(b) + case frameTypeDataBlocked: + f, n = parseDebugFrameDataBlocked(b) + case frameTypeStreamDataBlocked: + f, n = parseDebugFrameStreamDataBlocked(b) + case frameTypeStreamsBlockedBidi, frameTypeStreamsBlockedUni: + f, n = parseDebugFrameStreamsBlocked(b) + case frameTypeNewConnectionID: + f, n = parseDebugFrameNewConnectionID(b) + case frameTypeRetireConnectionID: + f, n = parseDebugFrameRetireConnectionID(b) + case frameTypePathChallenge: + f, n = parseDebugFramePathChallenge(b) + case frameTypePathResponse: + f, n = parseDebugFramePathResponse(b) + case frameTypeConnectionCloseTransport: + f, n = parseDebugFrameConnectionCloseTransport(b) + case frameTypeConnectionCloseApplication: + f, n = parseDebugFrameConnectionCloseApplication(b) + case frameTypeHandshakeDone: + f, n = parseDebugFrameHandshakeDone(b) + default: + return nil, -1 + } + return f, n +} + +// debugFramePadding is a sequence of PADDING frames. +type debugFramePadding struct { + size int +} + +func parseDebugFramePadding(b []byte) (f debugFramePadding, n int) { + for n < len(b) && b[n] == frameTypePadding { + n++ + } + f.size = n + return f, n +} + +func (f debugFramePadding) String() string { + return fmt.Sprintf("PADDING*%v", f.size) +} + +func (f debugFramePadding) write(w *packetWriter) bool { + if w.avail() == 0 { + return false + } + for i := 0; i < f.size && w.avail() > 0; i++ { + w.b = append(w.b, frameTypePadding) + } + return true +} + +// debugFramePing is a PING frame. +type debugFramePing struct{} + +func parseDebugFramePing(b []byte) (f debugFramePing, n int) { + return f, 1 +} + +func (f debugFramePing) String() string { + return "PING" +} + +func (f debugFramePing) write(w *packetWriter) bool { + return w.appendPingFrame() +} + +// debugFrameAck is an ACK frame. +type debugFrameAck struct { + ackDelay unscaledAckDelay + ranges []i64range[packetNumber] +} + +func parseDebugFrameAck(b []byte) (f debugFrameAck, n int) { + f.ranges = nil + _, f.ackDelay, n = consumeAckFrame(b, func(_ int, start, end packetNumber) { + f.ranges = append(f.ranges, i64range[packetNumber]{ + start: start, + end: end, + }) + }) + // Ranges are parsed smallest to highest; reverse ranges slice to order them high to low. + for i := 0; i < len(f.ranges)/2; i++ { + j := len(f.ranges) - 1 + f.ranges[i], f.ranges[j] = f.ranges[j], f.ranges[i] + } + return f, n +} + +func (f debugFrameAck) String() string { + s := fmt.Sprintf("ACK Delay=%v", f.ackDelay) + for _, r := range f.ranges { + s += fmt.Sprintf(" [%v,%v)", r.start, r.end) + } + return s +} + +func (f debugFrameAck) write(w *packetWriter) bool { + return w.appendAckFrame(rangeset[packetNumber](f.ranges), f.ackDelay) +} + +// debugFrameResetStream is a RESET_STREAM frame. +type debugFrameResetStream struct { + id streamID + code uint64 + finalSize int64 +} + +func parseDebugFrameResetStream(b []byte) (f debugFrameResetStream, n int) { + f.id, f.code, f.finalSize, n = consumeResetStreamFrame(b) + return f, n +} + +func (f debugFrameResetStream) String() string { + return fmt.Sprintf("RESET_STREAM ID=%v Code=%v FinalSize=%v", f.id, f.code, f.finalSize) +} + +func (f debugFrameResetStream) write(w *packetWriter) bool { + return w.appendResetStreamFrame(f.id, f.code, f.finalSize) +} + +// debugFrameStopSending is a STOP_SENDING frame. +type debugFrameStopSending struct { + id streamID + code uint64 +} + +func parseDebugFrameStopSending(b []byte) (f debugFrameStopSending, n int) { + f.id, f.code, n = consumeStopSendingFrame(b) + return f, n +} + +func (f debugFrameStopSending) String() string { + return fmt.Sprintf("STOP_SENDING ID=%v Code=%v", f.id, f.code) +} + +func (f debugFrameStopSending) write(w *packetWriter) bool { + return w.appendStopSendingFrame(f.id, f.code) +} + +// debugFrameCrypto is a CRYPTO frame. +type debugFrameCrypto struct { + off int64 + data []byte +} + +func parseDebugFrameCrypto(b []byte) (f debugFrameCrypto, n int) { + f.off, f.data, n = consumeCryptoFrame(b) + return f, n +} + +func (f debugFrameCrypto) String() string { + return fmt.Sprintf("CRYPTO Offset=%v Length=%v", f.off, len(f.data)) +} + +func (f debugFrameCrypto) write(w *packetWriter) bool { + b, added := w.appendCryptoFrame(f.off, len(f.data)) + copy(b, f.data) + return added +} + +// debugFrameNewToken is a NEW_TOKEN frame. +type debugFrameNewToken struct { + token []byte +} + +func parseDebugFrameNewToken(b []byte) (f debugFrameNewToken, n int) { + f.token, n = consumeNewTokenFrame(b) + return f, n +} + +func (f debugFrameNewToken) String() string { + return fmt.Sprintf("NEW_TOKEN Token=%x", f.token) +} + +func (f debugFrameNewToken) write(w *packetWriter) bool { + return w.appendNewTokenFrame(f.token) +} + +// debugFrameStream is a STREAM frame. +type debugFrameStream struct { + id streamID + fin bool + off int64 + data []byte +} + +func parseDebugFrameStream(b []byte) (f debugFrameStream, n int) { + f.id, f.off, f.fin, f.data, n = consumeStreamFrame(b) + return f, n +} + +func (f debugFrameStream) String() string { + fin := "" + if f.fin { + fin = " FIN" + } + return fmt.Sprintf("STREAM ID=%v%v Offset=%v Length=%v", f.id, fin, f.off, len(f.data)) +} + +func (f debugFrameStream) write(w *packetWriter) bool { + b, added := w.appendStreamFrame(f.id, f.off, len(f.data), f.fin) + copy(b, f.data) + return added +} + +// debugFrameMaxData is a MAX_DATA frame. +type debugFrameMaxData struct { + max int64 +} + +func parseDebugFrameMaxData(b []byte) (f debugFrameMaxData, n int) { + f.max, n = consumeMaxDataFrame(b) + return f, n +} + +func (f debugFrameMaxData) String() string { + return fmt.Sprintf("MAX_DATA Max=%v", f.max) +} + +func (f debugFrameMaxData) write(w *packetWriter) bool { + return w.appendMaxDataFrame(f.max) +} + +// debugFrameMaxStreamData is a MAX_STREAM_DATA frame. +type debugFrameMaxStreamData struct { + id streamID + max int64 +} + +func parseDebugFrameMaxStreamData(b []byte) (f debugFrameMaxStreamData, n int) { + f.id, f.max, n = consumeMaxStreamDataFrame(b) + return f, n +} + +func (f debugFrameMaxStreamData) String() string { + return fmt.Sprintf("MAX_STREAM_DATA ID=%v Max=%v", f.id, f.max) +} + +func (f debugFrameMaxStreamData) write(w *packetWriter) bool { + return w.appendMaxStreamDataFrame(f.id, f.max) +} + +// debugFrameMaxStreams is a MAX_STREAMS frame. +type debugFrameMaxStreams struct { + streamType streamType + max int64 +} + +func parseDebugFrameMaxStreams(b []byte) (f debugFrameMaxStreams, n int) { + f.streamType, f.max, n = consumeMaxStreamsFrame(b) + return f, n +} + +func (f debugFrameMaxStreams) String() string { + return fmt.Sprintf("MAX_STREAMS Type=%v Max=%v", f.streamType, f.max) +} + +func (f debugFrameMaxStreams) write(w *packetWriter) bool { + return w.appendMaxStreamsFrame(f.streamType, f.max) +} + +// debugFrameDataBlocked is a DATA_BLOCKED frame. +type debugFrameDataBlocked struct { + max int64 +} + +func parseDebugFrameDataBlocked(b []byte) (f debugFrameDataBlocked, n int) { + f.max, n = consumeDataBlockedFrame(b) + return f, n +} + +func (f debugFrameDataBlocked) String() string { + return fmt.Sprintf("DATA_BLOCKED Max=%v", f.max) +} + +func (f debugFrameDataBlocked) write(w *packetWriter) bool { + return w.appendDataBlockedFrame(f.max) +} + +// debugFrameStreamDataBlocked is a STREAM_DATA_BLOCKED frame. +type debugFrameStreamDataBlocked struct { + id streamID + max int64 +} + +func parseDebugFrameStreamDataBlocked(b []byte) (f debugFrameStreamDataBlocked, n int) { + f.id, f.max, n = consumeStreamDataBlockedFrame(b) + return f, n +} + +func (f debugFrameStreamDataBlocked) String() string { + return fmt.Sprintf("STREAM_DATA_BLOCKED ID=%v Max=%v", f.id, f.max) +} + +func (f debugFrameStreamDataBlocked) write(w *packetWriter) bool { + return w.appendStreamDataBlockedFrame(f.id, f.max) +} + +// debugFrameStreamsBlocked is a STREAMS_BLOCKED frame. +type debugFrameStreamsBlocked struct { + streamType streamType + max int64 +} + +func parseDebugFrameStreamsBlocked(b []byte) (f debugFrameStreamsBlocked, n int) { + f.streamType, f.max, n = consumeStreamsBlockedFrame(b) + return f, n +} + +func (f debugFrameStreamsBlocked) String() string { + return fmt.Sprintf("STREAMS_BLOCKED Type=%v Max=%v", f.streamType, f.max) +} + +func (f debugFrameStreamsBlocked) write(w *packetWriter) bool { + return w.appendStreamsBlockedFrame(f.streamType, f.max) +} + +// debugFrameNewConnectionID is a NEW_CONNECTION_ID frame. +type debugFrameNewConnectionID struct { + seq int64 + retirePriorTo int64 + connID []byte + token [16]byte +} + +func parseDebugFrameNewConnectionID(b []byte) (f debugFrameNewConnectionID, n int) { + f.seq, f.retirePriorTo, f.connID, f.token, n = consumeNewConnectionIDFrame(b) + return f, n +} + +func (f debugFrameNewConnectionID) String() string { + return fmt.Sprintf("NEW_CONNECTION_ID Seq=%v Retire=%v ID=%x Token=%x", f.seq, f.retirePriorTo, f.connID, f.token[:]) +} + +func (f debugFrameNewConnectionID) write(w *packetWriter) bool { + return w.appendNewConnectionIDFrame(f.seq, f.retirePriorTo, f.connID, f.token) +} + +// debugFrameRetireConnectionID is a NEW_CONNECTION_ID frame. +type debugFrameRetireConnectionID struct { + seq int64 +} + +func parseDebugFrameRetireConnectionID(b []byte) (f debugFrameRetireConnectionID, n int) { + f.seq, n = consumeRetireConnectionIDFrame(b) + return f, n +} + +func (f debugFrameRetireConnectionID) String() string { + return fmt.Sprintf("RETIRE_CONNECTION_ID Seq=%v", f.seq) +} + +func (f debugFrameRetireConnectionID) write(w *packetWriter) bool { + return w.appendRetireConnectionIDFrame(f.seq) +} + +// debugFramePathChallenge is a PATH_CHALLENGE frame. +type debugFramePathChallenge struct { + data uint64 +} + +func parseDebugFramePathChallenge(b []byte) (f debugFramePathChallenge, n int) { + f.data, n = consumePathChallengeFrame(b) + return f, n +} + +func (f debugFramePathChallenge) String() string { + return fmt.Sprintf("PATH_CHALLENGE Data=%016x", f.data) +} + +func (f debugFramePathChallenge) write(w *packetWriter) bool { + return w.appendPathChallengeFrame(f.data) +} + +// debugFramePathResponse is a PATH_RESPONSE frame. +type debugFramePathResponse struct { + data uint64 +} + +func parseDebugFramePathResponse(b []byte) (f debugFramePathResponse, n int) { + f.data, n = consumePathResponseFrame(b) + return f, n +} + +func (f debugFramePathResponse) String() string { + return fmt.Sprintf("PATH_RESPONSE Data=%016x", f.data) +} + +func (f debugFramePathResponse) write(w *packetWriter) bool { + return w.appendPathResponseFrame(f.data) +} + +// debugFrameConnectionCloseTransport is a CONNECTION_CLOSE frame carrying a transport error. +type debugFrameConnectionCloseTransport struct { + code transportError + frameType uint64 + reason string +} + +func parseDebugFrameConnectionCloseTransport(b []byte) (f debugFrameConnectionCloseTransport, n int) { + f.code, f.frameType, f.reason, n = consumeConnectionCloseTransportFrame(b) + return f, n +} + +func (f debugFrameConnectionCloseTransport) String() string { + s := fmt.Sprintf("CONNECTION_CLOSE Code=%v", f.code) + if f.frameType != 0 { + s += fmt.Sprintf(" FrameType=%v", f.frameType) + } + if f.reason != "" { + s += fmt.Sprintf(" Reason=%q", f.reason) + } + return s +} + +func (f debugFrameConnectionCloseTransport) write(w *packetWriter) bool { + return w.appendConnectionCloseTransportFrame(f.code, f.frameType, f.reason) +} + +// debugFrameConnectionCloseApplication is a CONNECTION_CLOSE frame carrying an application error. +type debugFrameConnectionCloseApplication struct { + code uint64 + reason string +} + +func parseDebugFrameConnectionCloseApplication(b []byte) (f debugFrameConnectionCloseApplication, n int) { + f.code, f.reason, n = consumeConnectionCloseApplicationFrame(b) + return f, n +} + +func (f debugFrameConnectionCloseApplication) String() string { + s := fmt.Sprintf("CONNECTION_CLOSE AppCode=%v", f.code) + if f.reason != "" { + s += fmt.Sprintf(" Reason=%q", f.reason) + } + return s +} + +func (f debugFrameConnectionCloseApplication) write(w *packetWriter) bool { + return w.appendConnectionCloseApplicationFrame(f.code, f.reason) +} + +// debugFrameHandshakeDone is a HANDSHAKE_DONE frame. +type debugFrameHandshakeDone struct{} + +func parseDebugFrameHandshakeDone(b []byte) (f debugFrameHandshakeDone, n int) { + return f, 1 +} + +func (f debugFrameHandshakeDone) String() string { + return "HANDSHAKE_DONE" +} + +func (f debugFrameHandshakeDone) write(w *packetWriter) bool { + return w.appendHandshakeDoneFrame() +} diff --git a/internal/quic/gate.go b/internal/quic/gate.go new file mode 100644 index 000000000..a2fb53711 --- /dev/null +++ b/internal/quic/gate.go @@ -0,0 +1,91 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "context" + +// An gate is a monitor (mutex + condition variable) with one bit of state. +// +// The condition may be either set or unset. +// Lock operations may be unconditional, or wait for the condition to be set. +// Unlock operations record the new state of the condition. +type gate struct { + // When unlocked, exactly one of set or unset contains a value. + // When locked, neither chan contains a value. + set chan struct{} + unset chan struct{} +} + +// newGate returns a new, unlocked gate with the condition unset. +func newGate() gate { + g := newLockedGate() + g.unlock(false) + return g +} + +// newLocked gate returns a new, locked gate. +func newLockedGate() gate { + return gate{ + set: make(chan struct{}, 1), + unset: make(chan struct{}, 1), + } +} + +// lock acquires the gate unconditionally. +// It reports whether the condition is set. +func (g *gate) lock() (set bool) { + select { + case <-g.set: + return true + case <-g.unset: + return false + } +} + +// waitAndLock waits until the condition is set before acquiring the gate. +// If the context expires, waitAndLock returns an error and does not acquire the gate. +func (g *gate) waitAndLock(ctx context.Context, testHooks connTestHooks) error { + if testHooks != nil { + return testHooks.waitUntil(ctx, g.lockIfSet) + } + select { + case <-g.set: + return nil + default: + } + select { + case <-g.set: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// lockIfSet acquires the gate if and only if the condition is set. +func (g *gate) lockIfSet() (acquired bool) { + select { + case <-g.set: + return true + default: + return false + } +} + +// unlock sets the condition and releases the gate. +func (g *gate) unlock(set bool) { + if set { + g.set <- struct{}{} + } else { + g.unset <- struct{}{} + } +} + +// unlock sets the condition to the result of f and releases the gate. +// Useful in defers. +func (g *gate) unlockFunc(f func() bool) { + g.unlock(f()) +} diff --git a/internal/quic/gate_test.go b/internal/quic/gate_test.go new file mode 100644 index 000000000..9e84a84bd --- /dev/null +++ b/internal/quic/gate_test.go @@ -0,0 +1,95 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "testing" + "time" +) + +func TestGateLockAndUnlock(t *testing.T) { + g := newGate() + if set := g.lock(); set { + t.Errorf("g.lock() of never-locked gate: true, want false") + } + unlockedc := make(chan struct{}) + donec := make(chan struct{}) + go func() { + defer close(donec) + set := g.lock() + select { + case <-unlockedc: + default: + t.Errorf("g.lock() succeeded while gate was held") + } + if !set { + t.Errorf("g.lock() of set gate: false, want true") + } + g.unlock(false) + }() + time.Sleep(1 * time.Millisecond) + close(unlockedc) + g.unlock(true) + <-donec + if set := g.lock(); set { + t.Errorf("g.lock() of unset gate: true, want false") + } +} + +func TestGateWaitAndLockContext(t *testing.T) { + g := newGate() + // waitAndLock is canceled + ctx, cancel := context.WithCancel(context.Background()) + go func() { + time.Sleep(1 * time.Millisecond) + cancel() + }() + if err := g.waitAndLock(ctx, nil); err != context.Canceled { + t.Errorf("g.waitAndLock() = %v, want context.Canceled", err) + } + // waitAndLock succeeds + set := false + go func() { + time.Sleep(1 * time.Millisecond) + g.lock() + set = true + g.unlock(true) + }() + if err := g.waitAndLock(context.Background(), nil); err != nil { + t.Errorf("g.waitAndLock() = %v, want nil", err) + } + if !set { + t.Errorf("g.waitAndLock() returned before gate was set") + } + g.unlock(true) + // waitAndLock succeeds when the gate is set and the context is canceled + if err := g.waitAndLock(ctx, nil); err != nil { + t.Errorf("g.waitAndLock() = %v, want nil", err) + } +} + +func TestGateLockIfSet(t *testing.T) { + g := newGate() + if locked := g.lockIfSet(); locked { + t.Errorf("g.lockIfSet() of unset gate = %v, want false", locked) + } + g.lock() + g.unlock(true) + if locked := g.lockIfSet(); !locked { + t.Errorf("g.lockIfSet() of set gate = %v, want true", locked) + } +} + +func TestGateUnlockFunc(t *testing.T) { + g := newGate() + go func() { + g.lock() + defer g.unlockFunc(func() bool { return true }) + }() + g.waitAndLock(context.Background(), nil) +} diff --git a/internal/quic/gotraceback_test.go b/internal/quic/gotraceback_test.go new file mode 100644 index 000000000..c22702faa --- /dev/null +++ b/internal/quic/gotraceback_test.go @@ -0,0 +1,26 @@ +// Copyright 2023 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. + +//go:build go1.21 && unix + +package quic + +import ( + "os" + "os/signal" + "runtime/debug" + "syscall" +) + +// When killed with SIGQUIT (C-\), print stacks with GOTRACEBACK=all rather than system, +// to reduce irrelevant noise when debugging hung tests. +func init() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGQUIT) + go func() { + <-ch + debug.SetTraceback("all") + panic("SIGQUIT") + }() +} diff --git a/internal/quic/key_update_test.go b/internal/quic/key_update_test.go new file mode 100644 index 000000000..4a4d67771 --- /dev/null +++ b/internal/quic/key_update_test.go @@ -0,0 +1,234 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "testing" +) + +func TestKeyUpdatePeerUpdates(t *testing.T) { + tc := newTestConn(t, serverSide) + tc.handshake() + tc.ignoreFrames = nil // ignore nothing + + // Peer initiates a key update. + tc.sendKeyNumber = 1 + tc.sendKeyPhaseBit = true + tc.writeFrames(packetType1RTT, debugFramePing{}) + + // We update to the new key. + tc.advanceToTimer() + tc.wantFrameType("conn ACKs last packet", + packetType1RTT, debugFrameAck{}) + tc.wantFrame("first packet after a key update is always ack-eliciting", + packetType1RTT, debugFramePing{}) + if got, want := tc.lastPacket.keyNumber, 1; got != want { + t.Errorf("after key rotation, conn sent packet with key %v, want %v", got, want) + } + if !tc.lastPacket.keyPhaseBit { + t.Errorf("after key rotation, conn failed to change Key Phase bit") + } + tc.wantIdle("conn has nothing to send") + + // Peer's ACK of a packet we sent in the new phase completes the update. + tc.writeAckForAll() + + // Peer initiates a second key update. + tc.sendKeyNumber = 2 + tc.sendKeyPhaseBit = false + tc.writeFrames(packetType1RTT, debugFramePing{}) + + // We update to the new key. + tc.advanceToTimer() + tc.wantFrameType("conn ACKs last packet", + packetType1RTT, debugFrameAck{}) + tc.wantFrame("first packet after a key update is always ack-eliciting", + packetType1RTT, debugFramePing{}) + if got, want := tc.lastPacket.keyNumber, 2; got != want { + t.Errorf("after key rotation, conn sent packet with key %v, want %v", got, want) + } + if tc.lastPacket.keyPhaseBit { + t.Errorf("after second key rotation, conn failed to change Key Phase bit") + } + tc.wantIdle("conn has nothing to send") +} + +func TestKeyUpdateAcceptPreviousPhaseKeys(t *testing.T) { + // "An endpoint SHOULD retain old keys for some time after + // unprotecting a packet sent using the new keys." + // https://www.rfc-editor.org/rfc/rfc9001#section-6.1-8 + tc := newTestConn(t, serverSide) + tc.handshake() + tc.ignoreFrames = nil // ignore nothing + + // Peer initiates a key update, skipping one packet number. + pnum0 := tc.peerNextPacketNum[appDataSpace] + tc.peerNextPacketNum[appDataSpace]++ + tc.sendKeyNumber = 1 + tc.sendKeyPhaseBit = true + tc.writeFrames(packetType1RTT, debugFramePing{}) + + // We update to the new key. + // This ACK is not delayed, because we've skipped a packet number. + tc.wantFrame("conn ACKs last packet", + packetType1RTT, debugFrameAck{ + ranges: []i64range[packetNumber]{ + {0, pnum0}, + {pnum0 + 1, pnum0 + 2}, + }, + }) + tc.wantFrame("first packet after a key update is always ack-eliciting", + packetType1RTT, debugFramePing{}) + if got, want := tc.lastPacket.keyNumber, 1; got != want { + t.Errorf("after key rotation, conn sent packet with key %v, want %v", got, want) + } + if !tc.lastPacket.keyPhaseBit { + t.Errorf("after key rotation, conn failed to change Key Phase bit") + } + tc.wantIdle("conn has nothing to send") + + // We receive the previously-skipped packet in the earlier key phase. + tc.peerNextPacketNum[appDataSpace] = pnum0 + tc.sendKeyNumber = 0 + tc.sendKeyPhaseBit = false + tc.writeFrames(packetType1RTT, debugFramePing{}) + + // We ack the reordered packet immediately, still in the new key phase. + tc.wantFrame("conn ACKs reordered packet", + packetType1RTT, debugFrameAck{ + ranges: []i64range[packetNumber]{ + {0, pnum0 + 2}, + }, + }) + tc.wantIdle("packet is not ack-eliciting") + if got, want := tc.lastPacket.keyNumber, 1; got != want { + t.Errorf("after key rotation, conn sent packet with key %v, want %v", got, want) + } + if !tc.lastPacket.keyPhaseBit { + t.Errorf("after key rotation, conn failed to change Key Phase bit") + } +} + +func TestKeyUpdateRejectPacketFromPriorPhase(t *testing.T) { + // "Packets with higher packet numbers MUST be protected with either + // the same or newer packet protection keys than packets with lower packet numbers." + // https://www.rfc-editor.org/rfc/rfc9001#section-6.4-2 + tc := newTestConn(t, serverSide) + tc.handshake() + tc.ignoreFrames = nil // ignore nothing + + // Peer initiates a key update. + tc.sendKeyNumber = 1 + tc.sendKeyPhaseBit = true + tc.writeFrames(packetType1RTT, debugFramePing{}) + + // We update to the new key. + tc.advanceToTimer() + tc.wantFrameType("conn ACKs last packet", + packetType1RTT, debugFrameAck{}) + tc.wantFrame("first packet after a key update is always ack-eliciting", + packetType1RTT, debugFramePing{}) + if got, want := tc.lastPacket.keyNumber, 1; got != want { + t.Errorf("after key rotation, conn sent packet with key %v, want %v", got, want) + } + if !tc.lastPacket.keyPhaseBit { + t.Errorf("after key rotation, conn failed to change Key Phase bit") + } + tc.wantIdle("conn has nothing to send") + + // Peer sends an ack-eliciting packet using the prior phase keys. + // We fail to unprotect the packet and ignore it. + skipped := tc.peerNextPacketNum[appDataSpace] + tc.sendKeyNumber = 0 + tc.sendKeyPhaseBit = false + tc.writeFrames(packetType1RTT, debugFramePing{}) + + // Peer sends an ack-eliciting packet using the current phase keys. + tc.sendKeyNumber = 1 + tc.sendKeyPhaseBit = true + tc.writeFrames(packetType1RTT, debugFramePing{}) + + // We ack the peer's packets, not including the one sent with the wrong keys. + tc.wantFrame("conn ACKs packets, not including packet sent with wrong keys", + packetType1RTT, debugFrameAck{ + ranges: []i64range[packetNumber]{ + {0, skipped}, + {skipped + 1, skipped + 2}, + }, + }) +} + +func TestKeyUpdateLocallyInitiated(t *testing.T) { + const updateAfter = 4 // initiate key update after 1-RTT packet 4 + tc := newTestConn(t, serverSide) + tc.conn.keysAppData.updateAfter = updateAfter + tc.handshake() + + for { + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.advanceToTimer() + tc.wantFrameType("conn ACKs last packet", + packetType1RTT, debugFrameAck{}) + if tc.lastPacket.num > updateAfter { + break + } + if got, want := tc.lastPacket.keyNumber, 0; got != want { + t.Errorf("before key update, conn sent packet with key %v, want %v", got, want) + } + if tc.lastPacket.keyPhaseBit { + t.Errorf("before key update, keyPhaseBit is set, want unset") + } + } + if got, want := tc.lastPacket.keyNumber, 1; got != want { + t.Errorf("after key update, conn sent packet with key %v, want %v", got, want) + } + if !tc.lastPacket.keyPhaseBit { + t.Errorf("after key update, keyPhaseBit is unset, want set") + } + tc.wantFrame("first packet after a key update is always ack-eliciting", + packetType1RTT, debugFramePing{}) + tc.wantIdle("no more frames") + + // Peer sends another packet using the prior phase keys. + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.advanceToTimer() + tc.wantFrameType("conn ACKs packet in prior phase", + packetType1RTT, debugFrameAck{}) + tc.wantIdle("packet is not ack-eliciting") + if got, want := tc.lastPacket.keyNumber, 1; got != want { + t.Errorf("after key update, conn sent packet with key %v, want %v", got, want) + } + + // Peer updates to the next phase. + tc.sendKeyNumber = 1 + tc.sendKeyPhaseBit = true + tc.writeAckForAll() + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.advanceToTimer() + tc.wantFrameType("conn ACKs packet in current phase", + packetType1RTT, debugFrameAck{}) + tc.wantIdle("packet is not ack-eliciting") + if got, want := tc.lastPacket.keyNumber, 1; got != want { + t.Errorf("after key update, conn sent packet with key %v, want %v", got, want) + } + + // Peer initiates its own update. + tc.sendKeyNumber = 2 + tc.sendKeyPhaseBit = false + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.advanceToTimer() + tc.wantFrameType("conn ACKs packet in current phase", + packetType1RTT, debugFrameAck{}) + tc.wantFrame("first packet after a key update is always ack-eliciting", + packetType1RTT, debugFramePing{}) + if got, want := tc.lastPacket.keyNumber, 2; got != want { + t.Errorf("after peer key update, conn sent packet with key %v, want %v", got, want) + } + if tc.lastPacket.keyPhaseBit { + t.Errorf("after peer key update, keyPhaseBit is unset, want set") + } +} diff --git a/internal/quic/listener.go b/internal/quic/listener.go new file mode 100644 index 000000000..96b1e4593 --- /dev/null +++ b/internal/quic/listener.go @@ -0,0 +1,322 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "errors" + "net" + "net/netip" + "sync" + "sync/atomic" + "time" +) + +// A Listener listens for QUIC traffic on a network address. +// It can accept inbound connections or create outbound ones. +// +// Multiple goroutines may invoke methods on a Listener simultaneously. +type Listener struct { + config *Config + udpConn udpConn + testHooks connTestHooks + + acceptQueue queue[*Conn] // new inbound connections + + connsMu sync.Mutex + conns map[*Conn]struct{} + closing bool // set when Close is called + closec chan struct{} // closed when the listen loop exits + + // The datagram receive loop keeps a mapping of connection IDs to conns. + // When a conn's connection IDs change, we add it to connIDUpdates and set + // connIDUpdateNeeded, and the receive loop updates its map. + connIDUpdateMu sync.Mutex + connIDUpdateNeeded atomic.Bool + connIDUpdates []connIDUpdate +} + +// A udpConn is a UDP connection. +// It is implemented by net.UDPConn. +type udpConn interface { + Close() error + LocalAddr() net.Addr + ReadMsgUDPAddrPort(b, control []byte) (n, controln, flags int, _ netip.AddrPort, _ error) + WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error) +} + +type connIDUpdate struct { + conn *Conn + retired bool + cid []byte +} + +// Listen listens on a local network address. +// The configuration config must be non-nil. +func Listen(network, address string, config *Config) (*Listener, error) { + if config.TLSConfig == nil { + return nil, errors.New("TLSConfig is not set") + } + a, err := net.ResolveUDPAddr(network, address) + if err != nil { + return nil, err + } + udpConn, err := net.ListenUDP(network, a) + if err != nil { + return nil, err + } + return newListener(udpConn, config, nil), nil +} + +func newListener(udpConn udpConn, config *Config, hooks connTestHooks) *Listener { + l := &Listener{ + config: config, + udpConn: udpConn, + testHooks: hooks, + conns: make(map[*Conn]struct{}), + acceptQueue: newQueue[*Conn](), + closec: make(chan struct{}), + } + go l.listen() + return l +} + +// LocalAddr returns the local network address. +func (l *Listener) LocalAddr() netip.AddrPort { + a, _ := l.udpConn.LocalAddr().(*net.UDPAddr) + return a.AddrPort() +} + +// Close closes the listener. +// Any blocked operations on the Listener or associated Conns and Stream will be unblocked +// and return errors. +// +// Close aborts every open connection. +// Data in stream read and write buffers is discarded. +// It waits for the peers of any open connection to acknowledge the connection has been closed. +func (l *Listener) Close(ctx context.Context) error { + l.acceptQueue.close(errors.New("listener closed")) + l.connsMu.Lock() + if !l.closing { + l.closing = true + for c := range l.conns { + c.Abort(errors.New("listener closed")) + } + if len(l.conns) == 0 { + l.udpConn.Close() + } + } + l.connsMu.Unlock() + select { + case <-l.closec: + case <-ctx.Done(): + l.connsMu.Lock() + for c := range l.conns { + c.exit() + } + l.connsMu.Unlock() + return ctx.Err() + } + return nil +} + +// Accept waits for and returns the next connection to the listener. +func (l *Listener) Accept(ctx context.Context) (*Conn, error) { + return l.acceptQueue.get(ctx, nil) +} + +// Dial creates and returns a connection to a network address. +func (l *Listener) Dial(ctx context.Context, network, address string) (*Conn, error) { + u, err := net.ResolveUDPAddr(network, address) + if err != nil { + return nil, err + } + addr := u.AddrPort() + addr = netip.AddrPortFrom(addr.Addr().Unmap(), addr.Port()) + c, err := l.newConn(time.Now(), clientSide, nil, addr) + if err != nil { + return nil, err + } + if err := c.waitReady(ctx); err != nil { + c.Abort(nil) + return nil, err + } + return c, nil +} + +func (l *Listener) newConn(now time.Time, side connSide, initialConnID []byte, peerAddr netip.AddrPort) (*Conn, error) { + l.connsMu.Lock() + defer l.connsMu.Unlock() + if l.closing { + return nil, errors.New("listener closed") + } + c, err := newConn(now, side, initialConnID, peerAddr, l.config, l, l.testHooks) + if err != nil { + return nil, err + } + l.conns[c] = struct{}{} + return c, nil +} + +// serverConnEstablished is called by a conn when the handshake completes +// for an inbound (serverSide) connection. +func (l *Listener) serverConnEstablished(c *Conn) { + l.acceptQueue.put(c) +} + +// connDrained is called by a conn when it leaves the draining state, +// either when the peer acknowledges connection closure or the drain timeout expires. +func (l *Listener) connDrained(c *Conn) { + l.connsMu.Lock() + defer l.connsMu.Unlock() + delete(l.conns, c) + if l.closing && len(l.conns) == 0 { + l.udpConn.Close() + } +} + +// connIDsChanged is called by a conn when its connection IDs change. +func (l *Listener) connIDsChanged(c *Conn, retired bool, cids []connID) { + l.connIDUpdateMu.Lock() + defer l.connIDUpdateMu.Unlock() + for _, cid := range cids { + l.connIDUpdates = append(l.connIDUpdates, connIDUpdate{ + conn: c, + retired: retired, + cid: cid.cid, + }) + } + l.connIDUpdateNeeded.Store(true) +} + +// updateConnIDs is called by the datagram receive loop to update its connection ID map. +func (l *Listener) updateConnIDs(conns map[string]*Conn) { + l.connIDUpdateMu.Lock() + defer l.connIDUpdateMu.Unlock() + for i, u := range l.connIDUpdates { + if u.retired { + delete(conns, string(u.cid)) + } else { + conns[string(u.cid)] = u.conn + } + l.connIDUpdates[i] = connIDUpdate{} // drop refs + } + l.connIDUpdates = l.connIDUpdates[:0] + l.connIDUpdateNeeded.Store(false) +} + +func (l *Listener) listen() { + defer close(l.closec) + conns := map[string]*Conn{} + for { + m := newDatagram() + // TODO: Read and process the ECN (explicit congestion notification) field. + // https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-13.4 + n, _, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(m.b, nil) + if err != nil { + // The user has probably closed the listener. + // We currently don't surface errors from other causes; + // we could check to see if the listener has been closed and + // record the unexpected error if it has not. + return + } + if n == 0 { + continue + } + if l.connIDUpdateNeeded.Load() { + l.updateConnIDs(conns) + } + m.addr = addr + m.b = m.b[:n] + l.handleDatagram(m, conns) + } +} + +func (l *Listener) handleDatagram(m *datagram, conns map[string]*Conn) { + dstConnID, ok := dstConnIDForDatagram(m.b) + if !ok { + m.recycle() + return + } + c := conns[string(dstConnID)] + if c == nil { + // TODO: Move this branch into a separate goroutine to avoid blocking + // the listener while processing packets. + l.handleUnknownDestinationDatagram(m) + return + } + + // TODO: This can block the listener while waiting for the conn to accept the dgram. + // Think about buffering between the receive loop and the conn. + c.sendMsg(m) +} + +func (l *Listener) handleUnknownDestinationDatagram(m *datagram) { + defer func() { + if m != nil { + m.recycle() + } + }() + if len(m.b) < minimumClientInitialDatagramSize { + return + } + p, ok := parseGenericLongHeaderPacket(m.b) + if !ok { + // Not a long header packet, or not parseable. + // Short header (1-RTT) packets don't contain enough information + // to do anything useful with if we don't recognize the + // connection ID. + return + } + + switch p.version { + case quicVersion1: + case 0: + // Version Negotiation for an unknown connection. + return + default: + // Unknown version. + l.sendVersionNegotiation(p, m.addr) + return + } + if getPacketType(m.b) != packetTypeInitial { + // This packet isn't trying to create a new connection. + // It might be associated with some connection we've lost state for. + // TODO: Send a stateless reset when appropriate. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-10.3 + return + } + var now time.Time + if l.testHooks != nil { + now = l.testHooks.timeNow() + } else { + now = time.Now() + } + var err error + c, err := l.newConn(now, serverSide, p.dstConnID, m.addr) + if err != nil { + // The accept queue is probably full. + // We could send a CONNECTION_CLOSE to the peer to reject the connection. + // Currently, we just drop the datagram. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-5.2.2-5 + return + } + c.sendMsg(m) + m = nil // don't recycle, sendMsg takes ownership +} + +func (l *Listener) sendVersionNegotiation(p genericLongPacket, addr netip.AddrPort) { + m := newDatagram() + m.b = appendVersionNegotiation(m.b[:0], p.srcConnID, p.dstConnID, quicVersion1) + l.sendDatagram(m.b, addr) + m.recycle() +} + +func (l *Listener) sendDatagram(p []byte, addr netip.AddrPort) error { + _, err := l.udpConn.WriteToUDPAddrPort(p, addr) + return err +} diff --git a/internal/quic/listener_test.go b/internal/quic/listener_test.go new file mode 100644 index 000000000..9d0f314ec --- /dev/null +++ b/internal/quic/listener_test.go @@ -0,0 +1,163 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "context" + "io" + "net" + "net/netip" + "testing" +) + +func TestConnect(t *testing.T) { + newLocalConnPair(t, &Config{}, &Config{}) +} + +func TestStreamTransfer(t *testing.T) { + ctx := context.Background() + cli, srv := newLocalConnPair(t, &Config{}, &Config{}) + data := makeTestData(1 << 20) + + srvdone := make(chan struct{}) + go func() { + defer close(srvdone) + s, err := srv.AcceptStream(ctx) + if err != nil { + t.Errorf("AcceptStream: %v", err) + return + } + b, err := io.ReadAll(s) + if err != nil { + t.Errorf("io.ReadAll(s): %v", err) + return + } + if !bytes.Equal(b, data) { + t.Errorf("read data mismatch (got %v bytes, want %v", len(b), len(data)) + } + if err := s.Close(); err != nil { + t.Errorf("s.Close() = %v", err) + } + }() + + s, err := cli.NewStream(ctx) + if err != nil { + t.Fatalf("NewStream: %v", err) + } + n, err := io.Copy(s, bytes.NewBuffer(data)) + if n != int64(len(data)) || err != nil { + t.Fatalf("io.Copy(s, data) = %v, %v; want %v, nil", n, err, len(data)) + } + if err := s.Close(); err != nil { + t.Fatalf("s.Close() = %v", err) + } +} + +func newLocalConnPair(t *testing.T, conf1, conf2 *Config) (clientConn, serverConn *Conn) { + t.Helper() + ctx := context.Background() + l1 := newLocalListener(t, serverSide, conf1) + l2 := newLocalListener(t, clientSide, conf2) + c2, err := l2.Dial(ctx, "udp", l1.LocalAddr().String()) + if err != nil { + t.Fatal(err) + } + c1, err := l1.Accept(ctx) + if err != nil { + t.Fatal(err) + } + return c2, c1 +} + +func newLocalListener(t *testing.T, side connSide, conf *Config) *Listener { + t.Helper() + if conf.TLSConfig == nil { + conf.TLSConfig = newTestTLSConfig(side) + } + l, err := Listen("udp", "127.0.0.1:0", conf) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + l.Close(context.Background()) + }) + return l +} + +type testListener struct { + t *testing.T + l *Listener + recvc chan *datagram + idlec chan struct{} + sentDatagrams [][]byte +} + +func newTestListener(t *testing.T, config *Config, testHooks connTestHooks) *testListener { + tl := &testListener{ + t: t, + recvc: make(chan *datagram), + idlec: make(chan struct{}), + } + tl.l = newListener((*testListenerUDPConn)(tl), config, testHooks) + t.Cleanup(tl.cleanup) + return tl +} + +func (tl *testListener) cleanup() { + tl.l.Close(canceledContext()) +} + +func (tl *testListener) wait() { + tl.idlec <- struct{}{} +} + +func (tl *testListener) write(d *datagram) { + tl.recvc <- d + tl.wait() +} + +func (tl *testListener) read() []byte { + tl.wait() + if len(tl.sentDatagrams) == 0 { + return nil + } + d := tl.sentDatagrams[0] + tl.sentDatagrams = tl.sentDatagrams[1:] + return d +} + +// testListenerUDPConn implements UDPConn. +type testListenerUDPConn testListener + +func (tl *testListenerUDPConn) Close() error { + close(tl.recvc) + return nil +} + +func (tl *testListenerUDPConn) LocalAddr() net.Addr { + return net.UDPAddrFromAddrPort(netip.MustParseAddrPort("127.0.0.1:443")) +} + +func (tl *testListenerUDPConn) ReadMsgUDPAddrPort(b, control []byte) (n, controln, flags int, _ netip.AddrPort, _ error) { + for { + select { + case d, ok := <-tl.recvc: + if !ok { + return 0, 0, 0, netip.AddrPort{}, io.EOF + } + n = copy(b, d.b) + return n, 0, 0, d.addr, nil + case <-tl.idlec: + } + } +} + +func (tl *testListenerUDPConn) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error) { + tl.sentDatagrams = append(tl.sentDatagrams, append([]byte(nil), b...)) + return len(b), nil +} diff --git a/internal/quic/log.go b/internal/quic/log.go new file mode 100644 index 000000000..d7248343b --- /dev/null +++ b/internal/quic/log.go @@ -0,0 +1,69 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "fmt" + "os" + "strings" +) + +var logPackets bool + +// Parse GODEBUG settings. +// +// GODEBUG=quiclogpackets=1 -- log every packet sent and received. +func init() { + s := os.Getenv("GODEBUG") + for len(s) > 0 { + var opt string + opt, s, _ = strings.Cut(s, ",") + switch opt { + case "quiclogpackets=1": + logPackets = true + } + } +} + +func logInboundLongPacket(c *Conn, p longPacket) { + if !logPackets { + return + } + prefix := c.String() + fmt.Printf("%v recv %v %v\n", prefix, p.ptype, p.num) + logFrames(prefix+" <- ", p.payload) +} + +func logInboundShortPacket(c *Conn, p shortPacket) { + if !logPackets { + return + } + prefix := c.String() + fmt.Printf("%v recv 1-RTT %v\n", prefix, p.num) + logFrames(prefix+" <- ", p.payload) +} + +func logSentPacket(c *Conn, ptype packetType, pnum packetNumber, src, dst, payload []byte) { + if !logPackets || len(payload) == 0 { + return + } + prefix := c.String() + fmt.Printf("%v send %v %v\n", prefix, ptype, pnum) + logFrames(prefix+" -> ", payload) +} + +func logFrames(prefix string, payload []byte) { + for len(payload) > 0 { + f, n := parseDebugFrame(payload) + if n < 0 { + fmt.Printf("%vBAD DATA\n", prefix) + break + } + payload = payload[n:] + fmt.Printf("%v%v\n", prefix, f) + } +} diff --git a/internal/quic/loss.go b/internal/quic/loss.go new file mode 100644 index 000000000..152815a29 --- /dev/null +++ b/internal/quic/loss.go @@ -0,0 +1,437 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "math" + "time" +) + +type lossState struct { + side connSide + + // True when the handshake is confirmed. + // https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2 + handshakeConfirmed bool + + // Peer's max_ack_delay transport parameter. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.28.1 + maxAckDelay time.Duration + + // Time of the next event: PTO expiration (if ptoTimerArmed is true), + // or loss detection. + // The connection must call lossState.advance when the timer expires. + timer time.Time + + // True when the PTO timer is set. + ptoTimerArmed bool + + // True when the PTO timer has expired and a probe packet has not yet been sent. + ptoExpired bool + + // Count of PTO expirations since the lack received acknowledgement. + // https://www.rfc-editor.org/rfc/rfc9002#section-6.2.1-9 + ptoBackoffCount int + + // Anti-amplification limit: Three times the amount of data received from + // the peer, less the amount of data sent. + // + // Set to antiAmplificationUnlimited (MaxInt) to disable the limit. + // The limit is always disabled for clients, and for servers after the + // peer's address is validated. + // + // Anti-amplification is per-address; this will need to change if/when we + // support address migration. + // + // https://www.rfc-editor.org/rfc/rfc9000#section-8-2 + antiAmplificationLimit int + + rtt rttState + pacer pacerState + cc *ccReno + + // Per-space loss detection state. + spaces [numberSpaceCount]struct { + sentPacketList + maxAcked packetNumber + lastAckEliciting packetNumber + } + + // Temporary state used when processing an ACK frame. + ackFrameRTT time.Duration // RTT from latest packet in frame + ackFrameContainsAckEliciting bool // newly acks an ack-eliciting packet? +} + +const antiAmplificationUnlimited = math.MaxInt + +func (c *lossState) init(side connSide, maxDatagramSize int, now time.Time) { + c.side = side + if side == clientSide { + // Clients don't have an anti-amplification limit. + c.antiAmplificationLimit = antiAmplificationUnlimited + } + c.rtt.init() + c.cc = newReno(maxDatagramSize) + c.pacer.init(now, c.cc.congestionWindow, timerGranularity) + + // Peer's assumed max_ack_delay, prior to receiving transport parameters. + // https://www.rfc-editor.org/rfc/rfc9000#section-18.2 + c.maxAckDelay = 25 * time.Millisecond + + for space := range c.spaces { + c.spaces[space].maxAcked = -1 + c.spaces[space].lastAckEliciting = -1 + } +} + +// setMaxAckDelay sets the max_ack_delay transport parameter received from the peer. +func (c *lossState) setMaxAckDelay(d time.Duration) { + if d >= (1<<14)*time.Millisecond { + // Values of 2^14 or greater are invalid. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-4.28.1 + return + } + c.maxAckDelay = d +} + +// confirmHandshake indicates the handshake has been confirmed. +func (c *lossState) confirmHandshake() { + c.handshakeConfirmed = true +} + +// validateClientAddress disables the anti-amplification limit after +// a server validates a client's address. +func (c *lossState) validateClientAddress() { + c.antiAmplificationLimit = antiAmplificationUnlimited +} + +// minDatagramSize is the minimum datagram size permitted by +// anti-amplification protection. +// +// Defining a minimum size avoids the case where, say, anti-amplification +// technically allows us to send a 1-byte datagram, but no such datagram +// can be constructed. +const minPacketSize = 128 + +type ccLimit int + +const ( + ccOK = ccLimit(iota) // OK to send + ccBlocked // sending blocked by anti-amplification + ccLimited // sending blocked by congestion control + ccPaced // sending allowed by congestion, but delayed by pacer +) + +// sendLimit reports whether sending is possible at this time. +// When sending is pacing limited, it returns the next time a packet may be sent. +func (c *lossState) sendLimit(now time.Time) (limit ccLimit, next time.Time) { + if c.antiAmplificationLimit < minPacketSize { + // When at the anti-amplification limit, we may not send anything. + return ccBlocked, time.Time{} + } + if c.ptoExpired { + // On PTO expiry, send a probe. + return ccOK, time.Time{} + } + if !c.cc.canSend() { + // Congestion control blocks sending. + return ccLimited, time.Time{} + } + if c.cc.bytesInFlight == 0 { + // If no bytes are in flight, send packet unpaced. + return ccOK, time.Time{} + } + canSend, next := c.pacer.canSend(now) + if !canSend { + // Pacer blocks sending. + return ccPaced, next + } + return ccOK, time.Time{} +} + +// maxSendSize reports the maximum datagram size that may be sent. +func (c *lossState) maxSendSize() int { + return min(c.antiAmplificationLimit, c.cc.maxDatagramSize) +} + +// advance is called when time passes. +// The lossf function is called for each packet newly detected as lost. +func (c *lossState) advance(now time.Time, lossf func(numberSpace, *sentPacket, packetFate)) { + c.pacer.advance(now, c.cc.congestionWindow, c.rtt.smoothedRTT) + if c.ptoTimerArmed && !c.timer.IsZero() && !c.timer.After(now) { + c.ptoExpired = true + c.timer = time.Time{} + c.ptoBackoffCount++ + } + c.detectLoss(now, lossf) +} + +// nextNumber returns the next packet number to use in a space. +func (c *lossState) nextNumber(space numberSpace) packetNumber { + return c.spaces[space].nextNum +} + +// packetSent records a sent packet. +func (c *lossState) packetSent(now time.Time, space numberSpace, sent *sentPacket) { + sent.time = now + c.spaces[space].add(sent) + size := sent.size + if c.antiAmplificationLimit != antiAmplificationUnlimited { + c.antiAmplificationLimit = max(0, c.antiAmplificationLimit-size) + } + if sent.inFlight { + c.cc.packetSent(now, space, sent) + c.pacer.packetSent(now, size, c.cc.congestionWindow, c.rtt.smoothedRTT) + if sent.ackEliciting { + c.spaces[space].lastAckEliciting = sent.num + c.ptoExpired = false // reset expired PTO timer after sending probe + } + c.scheduleTimer(now) + } +} + +// datagramReceived records a datagram (not packet!) received from the peer. +func (c *lossState) datagramReceived(now time.Time, size int) { + if c.antiAmplificationLimit != antiAmplificationUnlimited { + c.antiAmplificationLimit += 3 * size + // Reset the PTO timer, possibly to a point in the past, in which + // case the caller should execute it immediately. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2.1-2 + c.scheduleTimer(now) + if c.ptoTimerArmed && !c.timer.IsZero() && !c.timer.After(now) { + c.ptoExpired = true + c.timer = time.Time{} + } + } +} + +// receiveAckStart starts processing an ACK frame. +// Call receiveAckRange for each range in the frame. +// Call receiveAckFrameEnd after all ranges are processed. +func (c *lossState) receiveAckStart() { + c.ackFrameContainsAckEliciting = false + c.ackFrameRTT = -1 +} + +// receiveAckRange processes a range within an ACK frame. +// The ackf function is called for each newly-acknowledged packet. +func (c *lossState) receiveAckRange(now time.Time, space numberSpace, rangeIndex int, start, end packetNumber, ackf func(numberSpace, *sentPacket, packetFate)) { + // Limit our range to the intersection of the ACK range and + // the in-flight packets we have state for. + if s := c.spaces[space].start(); start < s { + start = s + } + if e := c.spaces[space].end(); end > e { + end = e + } + if start >= end { + return + } + if rangeIndex == 0 { + // If the latest packet in the ACK frame is newly-acked, + // record the RTT in c.ackFrameRTT. + sent := c.spaces[space].num(end - 1) + if !sent.acked { + c.ackFrameRTT = max(0, now.Sub(sent.time)) + } + } + for pnum := start; pnum < end; pnum++ { + sent := c.spaces[space].num(pnum) + if sent.acked || sent.lost { + continue + } + // This is a newly-acknowledged packet. + if pnum > c.spaces[space].maxAcked { + c.spaces[space].maxAcked = pnum + } + sent.acked = true + c.cc.packetAcked(now, sent) + ackf(space, sent, packetAcked) + if sent.ackEliciting { + c.ackFrameContainsAckEliciting = true + } + } +} + +// receiveAckEnd finishes processing an ack frame. +// The lossf function is called for each packet newly detected as lost. +func (c *lossState) receiveAckEnd(now time.Time, space numberSpace, ackDelay time.Duration, lossf func(numberSpace, *sentPacket, packetFate)) { + c.spaces[space].sentPacketList.clean() + // Update the RTT sample when the largest acknowledged packet in the ACK frame + // is newly acknowledged, and at least one newly acknowledged packet is ack-eliciting. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.1-2.2 + if c.ackFrameRTT >= 0 && c.ackFrameContainsAckEliciting { + c.rtt.updateSample(now, c.handshakeConfirmed, space, c.ackFrameRTT, ackDelay, c.maxAckDelay) + } + // Reset the PTO backoff. + // Exception: A client does not reset the backoff on acks for Initial packets. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-9 + if !(c.side == clientSide && space == initialSpace) { + c.ptoBackoffCount = 0 + } + // If the client has set a PTO timer with no packets in flight + // we want to restart that timer now. Clearing c.timer does this. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2.1-3 + c.timer = time.Time{} + c.detectLoss(now, lossf) + c.cc.packetBatchEnd(now, space, &c.rtt, c.maxAckDelay) +} + +// discardKeys is called when dropping packet protection keys for a number space. +func (c *lossState) discardKeys(now time.Time, space numberSpace) { + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.4 + for i := 0; i < c.spaces[space].size; i++ { + sent := c.spaces[space].nth(i) + c.cc.packetDiscarded(sent) + } + c.spaces[space].discard() + c.spaces[space].maxAcked = -1 + c.spaces[space].lastAckEliciting = -1 + c.scheduleTimer(now) +} + +func (c *lossState) lossDuration() time.Duration { + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.2 + return max((9*max(c.rtt.smoothedRTT, c.rtt.latestRTT))/8, timerGranularity) +} + +func (c *lossState) detectLoss(now time.Time, lossf func(numberSpace, *sentPacket, packetFate)) { + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.1-1 + const lossThreshold = 3 + + lossTime := now.Add(-c.lossDuration()) + for space := numberSpace(0); space < numberSpaceCount; space++ { + for i := 0; i < c.spaces[space].size; i++ { + sent := c.spaces[space].nth(i) + if sent.lost || sent.acked { + continue + } + // RFC 9002 Section 6.1 states that a packet is only declared lost if it + // is "in flight", which excludes packets that contain only ACK frames. + // However, we need some way to determine when to drop state for ACK-only + // packets, and the loss algorithm in Appendix A handles loss detection of + // not-in-flight packets identically to all others, so we do the same here. + switch { + case c.spaces[space].maxAcked-sent.num >= lossThreshold: + // Packet threshold + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.1 + fallthrough + case sent.num <= c.spaces[space].maxAcked && !sent.time.After(lossTime): + // Time threshold + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.2 + sent.lost = true + lossf(space, sent, packetLost) + if sent.inFlight { + c.cc.packetLost(now, space, sent, &c.rtt) + } + } + if !sent.lost { + break + } + } + c.spaces[space].clean() + } + c.scheduleTimer(now) +} + +// scheduleTimer sets the loss or PTO timer. +// +// The connection is responsible for arranging for advance to be called after +// the timer expires. +// +// The timer may be set to a point in the past, in which advance should be called +// immediately. We don't do this here, because executing the timer can cause +// packet loss events, and it's simpler for the connection if loss events only +// occur when advancing time. +func (c *lossState) scheduleTimer(now time.Time) { + c.ptoTimerArmed = false + + // Loss timer for sent packets. + // The loss timer is only started once a later packet has been acknowledged, + // and takes precedence over the PTO timer. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.2 + var oldestPotentiallyLost time.Time + for space := numberSpace(0); space < numberSpaceCount; space++ { + if c.spaces[space].size > 0 && c.spaces[space].start() <= c.spaces[space].maxAcked { + firstTime := c.spaces[space].nth(0).time + if oldestPotentiallyLost.IsZero() || firstTime.Before(oldestPotentiallyLost) { + oldestPotentiallyLost = firstTime + } + } + } + if !oldestPotentiallyLost.IsZero() { + c.timer = oldestPotentiallyLost.Add(c.lossDuration()) + return + } + + // PTO timer. + if c.ptoExpired { + // PTO timer has expired, don't restart it until we send a probe. + c.timer = time.Time{} + return + } + if c.antiAmplificationLimit >= 0 && c.antiAmplificationLimit < minPacketSize { + // Server is at its anti-amplification limit and can't send any more data. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2.1-1 + c.timer = time.Time{} + return + } + // Timer starts at the most recently sent ack-eliciting packet. + // Prior to confirming the handshake, we consider the Initial and Handshake + // number spaces; after, we consider only Application Data. + var last time.Time + if !c.handshakeConfirmed { + for space := initialSpace; space <= handshakeSpace; space++ { + sent := c.spaces[space].num(c.spaces[space].lastAckEliciting) + if sent == nil { + continue + } + if last.IsZero() || last.After(sent.time) { + last = sent.time + } + } + } else { + sent := c.spaces[appDataSpace].num(c.spaces[appDataSpace].lastAckEliciting) + if sent != nil { + last = sent.time + } + } + if last.IsZero() && + c.side == clientSide && + c.spaces[handshakeSpace].maxAcked < 0 && + !c.handshakeConfirmed { + // The client must always set a PTO timer prior to receiving an ack for a + // handshake packet or the handshake being confirmed. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2.1 + if !c.timer.IsZero() { + // If c.timer is non-zero here, we've already set the PTO timer and + // should leave it as-is rather than moving it forward. + c.ptoTimerArmed = true + return + } + last = now + } else if last.IsZero() { + c.timer = time.Time{} + return + } + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1 + pto := c.ptoBasePeriod() << c.ptoBackoffCount + c.timer = last.Add(pto) + c.ptoTimerArmed = true +} + +func (c *lossState) ptoBasePeriod() time.Duration { + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1 + pto := c.rtt.smoothedRTT + max(4*c.rtt.rttvar, timerGranularity) + if c.handshakeConfirmed { + // The max_ack_delay is the maximum amount of time the peer might delay sending + // an ack to us. We only take it into account for the Application Data space. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-4 + pto += c.maxAckDelay + } + return pto +} diff --git a/internal/quic/loss_test.go b/internal/quic/loss_test.go new file mode 100644 index 000000000..efbf1649e --- /dev/null +++ b/internal/quic/loss_test.go @@ -0,0 +1,1627 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "fmt" + "testing" + "time" +) + +func TestLossAntiAmplificationLimit(t *testing.T) { + test := newLossTest(t, serverSide, lossTestOpts{}) + test.datagramReceived(1200) + t.Logf("# consume anti-amplification capacity in a mix of packets") + test.send(initialSpace, 0, sentPacket{ + size: 1200, + ackEliciting: true, + inFlight: true, + }) + test.send(initialSpace, 1, sentPacket{ + size: 1200, + ackEliciting: false, + inFlight: false, + }) + test.send(initialSpace, 2, sentPacket{ + size: 1200, + ackEliciting: false, + inFlight: true, + }) + t.Logf("# send blocked by anti-amplification limit") + test.wantSendLimit(ccBlocked) + + t.Logf("# receiving a datagram unblocks server") + test.datagramReceived(100) + test.wantSendLimit(ccOK) + + t.Logf("# validating client address removes anti-amplification limit") + test.validateClientAddress() + test.wantSendLimit(ccOK) +} + +func TestLossRTTSampleNotGenerated(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{}) + test.send(initialSpace, 0, 1) + test.send(initialSpace, 2, sentPacket{ + ackEliciting: false, + inFlight: false, + }) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{1, 2}) + test.wantAck(initialSpace, 1) + test.wantVar("latest_rtt", 10*time.Millisecond) + t.Logf("# smoothed_rtt = latest_rtt") + test.wantVar("smoothed_rtt", 10*time.Millisecond) + t.Logf("# rttvar = latest_rtt / 2") + test.wantVar("rttvar", 5*time.Millisecond) + + // "...an ACK frame SHOULD NOT be used to update RTT estimates if + // it does not newly acknowledge the largest acknowledged packet." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.1-6 + t.Logf("# acks for older packets do not generate an RTT sample") + test.advance(1 * time.Millisecond) + test.ack(initialSpace, 1*time.Millisecond, i64range[packetNumber]{0, 2}) + test.wantAck(initialSpace, 0) + test.wantVar("smoothed_rtt", 10*time.Millisecond) + + // "An RTT sample MUST NOT be generated on receiving an ACK frame + // that does not newly acknowledge at least one ack-eliciting packet." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.1-7 + t.Logf("# acks for non-ack-eliciting packets do not generate an RTT sample") + test.advance(1 * time.Millisecond) + test.ack(initialSpace, 1*time.Millisecond, i64range[packetNumber]{0, 3}) + test.wantAck(initialSpace, 2) + test.wantVar("smoothed_rtt", 10*time.Millisecond) +} + +func TestLossMinRTT(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{}) + + // "min_rtt MUST be set to the latest_rtt on the first RTT sample." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-2 + t.Logf("# min_rtt set on first sample") + test.send(initialSpace, 0) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + test.wantVar("min_rtt", 10*time.Millisecond) + + // "min_rtt MUST be set to the lesser of min_rtt and latest_rtt [...] + // on all other samples." + t.Logf("# min_rtt does not increase") + test.send(initialSpace, 1) + test.advance(20 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 2}) + test.wantAck(initialSpace, 1) + test.wantVar("min_rtt", 10*time.Millisecond) + + t.Logf("# min_rtt decreases") + test.send(initialSpace, 2) + test.advance(5 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 3}) + test.wantAck(initialSpace, 2) + test.wantVar("min_rtt", 5*time.Millisecond) +} + +func TestLossMinRTTAfterCongestion(t *testing.T) { + // "Endpoints SHOULD set the min_rtt to the newest RTT sample + // after persistent congestion is established." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-5 + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + t.Logf("# establish initial RTT sample") + test.send(initialSpace, 0, testSentPacketSize(1200)) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + test.wantVar("min_rtt", 10*time.Millisecond) + + t.Logf("# send two packets spanning persistent congestion duration") + test.send(initialSpace, 1, testSentPacketSize(1200)) + t.Logf("# 2000ms >> persistent congestion duration") + test.advance(2000 * time.Millisecond) + test.wantPTOExpired() + test.send(initialSpace, 2, testSentPacketSize(1200)) + + t.Logf("# trigger loss of previous packets") + test.advance(10 * time.Millisecond) + test.send(initialSpace, 3, testSentPacketSize(1200)) + test.advance(20 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{3, 4}) + test.wantAck(initialSpace, 3) + test.wantLoss(initialSpace, 1, 2) + t.Logf("# persistent congestion detected") + + test.send(initialSpace, 4, testSentPacketSize(1200)) + test.advance(20 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{4, 5}) + test.wantAck(initialSpace, 4) + + t.Logf("# min_rtt set from first sample after persistent congestion") + test.wantVar("min_rtt", 20*time.Millisecond) +} + +func TestLossInitialRTTSample(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{}) + test.setMaxAckDelay(2 * time.Millisecond) + t.Logf("# initial smoothed_rtt and rtt values") + test.wantVar("smoothed_rtt", 333*time.Millisecond) + test.wantVar("rttvar", 333*time.Millisecond/2) + + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.3-11 + t.Logf("# first RTT sample") + test.send(initialSpace, 0) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + test.wantVar("latest_rtt", 10*time.Millisecond) + t.Logf("# smoothed_rtt = latest_rtt") + test.wantVar("smoothed_rtt", 10*time.Millisecond) + t.Logf("# rttvar = latest_rtt / 2") + test.wantVar("rttvar", 5*time.Millisecond) +} + +func TestLossSmoothedRTTIgnoresMaxAckDelayBeforeHandshakeConfirmed(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{}) + test.setMaxAckDelay(1 * time.Millisecond) + test.send(initialSpace, 0) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + smoothedRTT := 10 * time.Millisecond + rttvar := 5 * time.Millisecond + + // "[...] an endpoint [...] SHOULD ignore the peer's max_ack_delay + // until the handshake is confirmed [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.3-7.2 + t.Logf("# subsequent RTT sample") + test.send(handshakeSpace, 0) + test.advance(20 * time.Millisecond) + test.ack(handshakeSpace, 10*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(handshakeSpace, 0) + test.wantVar("latest_rtt", 20*time.Millisecond) + t.Logf("# ack_delay > max_ack_delay") + t.Logf("# handshake not confirmed, so ignore max_ack_delay") + t.Logf("# adjusted_rtt = latest_rtt - ackDelay") + adjustedRTT := 10 * time.Millisecond + t.Logf("# smoothed_rtt = 7/8 * smoothed_rtt + 1/8 * adjusted_rtt") + smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8 + test.wantVar("smoothed_rtt", smoothedRTT) + rttvarSample := abs(smoothedRTT - adjustedRTT) + t.Logf("# rttvar_sample = abs(smoothed_rtt - adjusted_rtt) = %v", rttvarSample) + t.Logf("# rttvar = 3/4 * rttvar + 1/4 * rttvar_sample") + rttvar = (3*rttvar + rttvarSample) / 4 + test.wantVar("rttvar", rttvar) +} + +func TestLossSmoothedRTTUsesMaxAckDelayAfterHandshakeConfirmed(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{}) + test.setMaxAckDelay(25 * time.Millisecond) + test.send(initialSpace, 0) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + smoothedRTT := 10 * time.Millisecond + rttvar := 5 * time.Millisecond + + test.confirmHandshake() + + // "[...] an endpoint [...] MUST use the lesser of the acknowledgment + // delay and the peer's max_ack_delay after the handshake is confirmed [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.3-7.3 + t.Logf("# subsequent RTT sample") + test.send(handshakeSpace, 0) + test.advance(50 * time.Millisecond) + test.ack(handshakeSpace, 40*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(handshakeSpace, 0) + test.wantVar("latest_rtt", 50*time.Millisecond) + t.Logf("# ack_delay > max_ack_delay") + t.Logf("# handshake confirmed, so adjusted_rtt clamps to max_ack_delay") + t.Logf("# adjusted_rtt = max_ack_delay") + adjustedRTT := 25 * time.Millisecond + rttvarSample := abs(smoothedRTT - adjustedRTT) + t.Logf("# rttvar_sample = abs(smoothed_rtt - adjusted_rtt) = %v", rttvarSample) + t.Logf("# rttvar = 3/4 * rttvar + 1/4 * rttvar_sample") + rttvar = (3*rttvar + rttvarSample) / 4 + test.wantVar("rttvar", rttvar) + t.Logf("# smoothed_rtt = 7/8 * smoothed_rtt + 1/8 * adjusted_rtt") + smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8 + test.wantVar("smoothed_rtt", smoothedRTT) +} + +func TestLossAckDelayReducesRTTBelowMinRTT(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{}) + test.send(initialSpace, 0) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + smoothedRTT := 10 * time.Millisecond + rttvar := 5 * time.Millisecond + + // "[...] an endpoint [...] MUST NOT subtract the acknowledgment delay + // from the RTT sample if the resulting value is smaller than the min_rtt." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.3-7.4 + t.Logf("# subsequent RTT sample") + test.send(handshakeSpace, 0) + test.advance(12 * time.Millisecond) + test.ack(handshakeSpace, 4*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(handshakeSpace, 0) + test.wantVar("latest_rtt", 12*time.Millisecond) + t.Logf("# latest_rtt - ack_delay < min_rtt, so adjusted_rtt = latest_rtt") + adjustedRTT := 12 * time.Millisecond + rttvarSample := abs(smoothedRTT - adjustedRTT) + t.Logf("# rttvar_sample = abs(smoothed_rtt - adjusted_rtt) = %v", rttvarSample) + t.Logf("# rttvar = 3/4 * rttvar + 1/4 * rttvar_sample") + rttvar = (3*rttvar + rttvarSample) / 4 + test.wantVar("rttvar", rttvar) + t.Logf("# smoothed_rtt = 7/8 * smoothed_rtt + 1/8 * adjusted_rtt") + smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8 + test.wantVar("smoothed_rtt", smoothedRTT) +} + +func TestLossPacketThreshold(t *testing.T) { + // "[...] the packet was sent kPacketThreshold packets before an + // acknowledged packet [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.1 + test := newLossTest(t, clientSide, lossTestOpts{}) + t.Logf("# acking a packet triggers loss of packets sent kPacketThreshold earlier") + test.send(appDataSpace, 0, 1, 2, 3, 4, 5, 6) + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{4, 5}) + test.wantAck(appDataSpace, 4) + test.wantLoss(appDataSpace, 0, 1) +} + +func TestLossOutOfOrderAcks(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{}) + t.Logf("# out of order acks, no loss") + test.send(appDataSpace, 0, 1, 2) + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{2, 3}) + test.wantAck(appDataSpace, 2) + + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{1, 2}) + test.wantAck(appDataSpace, 1) + + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(appDataSpace, 0) +} + +func TestLossSendAndAck(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{}) + test.send(appDataSpace, 0, 1, 2) + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{0, 3}) + test.wantAck(appDataSpace, 0, 1, 2) + // Redundant ACK doesn't trigger more ACK events. + // (If we did get an extra ACK, the test cleanup would notice and complain.) + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{0, 3}) +} + +func TestLossAckEveryOtherPacket(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{}) + test.send(appDataSpace, 0, 1, 2, 3, 4, 5, 6) + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(appDataSpace, 0) + + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{2, 3}) + test.wantAck(appDataSpace, 2) + + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{4, 5}) + test.wantAck(appDataSpace, 4) + test.wantLoss(appDataSpace, 1) + + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{6, 7}) + test.wantAck(appDataSpace, 6) + test.wantLoss(appDataSpace, 3) +} + +func TestLossMultipleSpaces(t *testing.T) { + // "Loss detection is separate per packet number space [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6-3 + test := newLossTest(t, clientSide, lossTestOpts{}) + t.Logf("# send packets in different spaces") + test.send(initialSpace, 0, 1, 2) + test.send(handshakeSpace, 0, 1, 2) + test.send(appDataSpace, 0, 1, 2) + + t.Logf("# ack one packet in each space") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{1, 2}) + test.wantAck(initialSpace, 1) + + test.ack(handshakeSpace, 0*time.Millisecond, i64range[packetNumber]{1, 2}) + test.wantAck(handshakeSpace, 1) + + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{1, 2}) + test.wantAck(appDataSpace, 1) + + t.Logf("# send more packets") + test.send(initialSpace, 3, 4, 5) + test.send(handshakeSpace, 3, 4, 5) + test.send(appDataSpace, 3, 4, 5) + + t.Logf("# ack the last packet, triggering loss") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{5, 6}) + test.wantAck(initialSpace, 5) + test.wantLoss(initialSpace, 0, 2) + + test.ack(handshakeSpace, 0*time.Millisecond, i64range[packetNumber]{5, 6}) + test.wantAck(handshakeSpace, 5) + test.wantLoss(handshakeSpace, 0, 2) + + test.ack(appDataSpace, 0*time.Millisecond, i64range[packetNumber]{5, 6}) + test.wantAck(appDataSpace, 5) + test.wantLoss(appDataSpace, 0, 2) +} + +func TestLossTimeThresholdFirstPacketLost(t *testing.T) { + // "[...] the packet [...] was sent long enough in the past." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1-3.2 + test := newLossTest(t, clientSide, lossTestOpts{}) + t.Logf("# packet 0 lost after time threshold passes") + test.send(initialSpace, 0, 1) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{1, 2}) + test.wantAck(initialSpace, 1) + + t.Logf("# latest_rtt == smoothed_rtt") + test.wantVar("smoothed_rtt", 10*time.Millisecond) + test.wantVar("latest_rtt", 10*time.Millisecond) + t.Logf("# timeout = 9/8 * max(smoothed_rtt, latest_rtt) - time_since_packet_sent") + test.wantTimeout(((10 * time.Millisecond * 9) / 8) - 10*time.Millisecond) + + test.advanceToLossTimer() + test.wantLoss(initialSpace, 0) +} + +func TestLossTimeThreshold(t *testing.T) { + // "The time threshold is: + // max(kTimeThreshold * max(smoothed_rtt, latest_rtt), kGranularity)" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.2-2 + for _, tc := range []struct { + name string + initialRTT time.Duration + latestRTT time.Duration + wantTimeout time.Duration + }{{ + name: "rtt increasing", + initialRTT: 10 * time.Millisecond, + latestRTT: 20 * time.Millisecond, + wantTimeout: 20 * time.Millisecond * 9 / 8, + }, { + name: "rtt decreasing", + initialRTT: 10 * time.Millisecond, + latestRTT: 5 * time.Millisecond, + wantTimeout: ((7*10*time.Millisecond + 5*time.Millisecond) / 8) * 9 / 8, + }, { + name: "rtt less than timer granularity", + initialRTT: 500 * time.Microsecond, + latestRTT: 500 * time.Microsecond, + wantTimeout: 1 * time.Millisecond, + }} { + t.Run(tc.name, func(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{}) + t.Logf("# first ack establishes smoothed_rtt") + test.send(initialSpace, 0) + test.advance(tc.initialRTT) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + + t.Logf("# ack of packet 2 starts loss timer for packet 1") + test.send(initialSpace, 1, 2) + test.advance(tc.latestRTT) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{2, 3}) + test.wantAck(initialSpace, 2) + + t.Logf("# smoothed_rtt = %v", test.c.rtt.smoothedRTT) + t.Logf("# latest_rtt = %v", test.c.rtt.latestRTT) + t.Logf("# timeout = max(9/8 * max(smoothed_rtt, latest_rtt), 1ms)") + t.Logf("# (measured since packet 1 sent)") + test.wantTimeout(tc.wantTimeout - tc.latestRTT) + + t.Logf("# advancing to the loss time causes loss of packet 1") + test.advanceToLossTimer() + test.wantLoss(initialSpace, 1) + }) + } +} + +func TestLossPTONotAckEliciting(t *testing.T) { + // "When an ack-eliciting packet is transmitted, + // the sender schedules a timer for the PTO period [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-1 + test := newLossTest(t, clientSide, lossTestOpts{}) + t.Logf("# PTO timer for first packet") + test.send(initialSpace, 0) + test.wantVar("smoothed_rtt", 333*time.Millisecond) // initial value + test.wantVar("rttvar", 333*time.Millisecond/2) // initial value + t.Logf("# PTO = smoothed_rtt + max(4*rttvar, 1ms)") + test.wantTimeout(999 * time.Millisecond) + + t.Logf("# sending a non-ack-eliciting packet doesn't adjust PTO") + test.advance(333 * time.Millisecond) + test.send(initialSpace, 1, sentPacket{ + ackEliciting: false, + }) + test.wantVar("smoothed_rtt", 333*time.Millisecond) // unchanged + test.wantVar("rttvar", 333*time.Millisecond/2) // unchanged + test.wantTimeout(666 * time.Millisecond) +} + +func TestLossPTOMaxAckDelay(t *testing.T) { + // "When the PTO is armed for Initial or Handshake packet number spaces, + // the max_ack_delay in the PTO period computation is set to 0 [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-4 + test := newLossTest(t, clientSide, lossTestOpts{}) + t.Logf("# PTO timer for first packet") + test.send(initialSpace, 0) + test.wantVar("smoothed_rtt", 333*time.Millisecond) // initial value + test.wantVar("rttvar", 333*time.Millisecond/2) // initial value + t.Logf("# PTO = smoothed_rtt + max(4*rttvar, 1ms)") + test.wantTimeout(999 * time.Millisecond) + + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + + t.Logf("# PTO timer for handshake packet") + test.send(handshakeSpace, 0) + test.wantVar("smoothed_rtt", 10*time.Millisecond) + test.wantVar("rttvar", 5*time.Millisecond) + t.Logf("# PTO = smoothed_rtt + max(4*rttvar, 1ms)") + test.wantTimeout(30 * time.Millisecond) + + test.advance(10 * time.Millisecond) + test.ack(handshakeSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(handshakeSpace, 0) + test.confirmHandshake() + + t.Logf("# PTO timer for appdata packet") + test.send(appDataSpace, 0) + test.wantVar("smoothed_rtt", 10*time.Millisecond) + test.wantVar("rttvar", 3750*time.Microsecond) + t.Logf("# PTO = smoothed_rtt + max(4*rttvar, 1ms) + max_ack_delay (25ms)") + test.wantTimeout(50 * time.Millisecond) +} + +func TestLossPTOUnderTimerGranularity(t *testing.T) { + // "The PTO period MUST be at least kGranularity [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-5 + test := newLossTest(t, clientSide, lossTestOpts{}) + test.send(initialSpace, 0) + test.advance(10 * time.Microsecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + + test.send(initialSpace, 1) + test.wantVar("smoothed_rtt", 10*time.Microsecond) + test.wantVar("rttvar", 5*time.Microsecond) + t.Logf("# PTO = smoothed_rtt + max(4*rttvar, 1ms)") + test.wantTimeout(10*time.Microsecond + 1*time.Millisecond) +} + +func TestLossPTOMultipleSpaces(t *testing.T) { + // "[...] the timer MUST be set to the earlier value of the Initial and Handshake + // packet number spaces." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-6 + test := newLossTest(t, clientSide, lossTestOpts{}) + t.Logf("# PTO timer for first packet") + test.send(initialSpace, 0) + test.wantVar("smoothed_rtt", 333*time.Millisecond) // initial value + test.wantVar("rttvar", 333*time.Millisecond/2) // initial value + t.Logf("# PTO = smoothed_rtt + max(4*rttvar, 1ms)") + test.wantTimeout(999 * time.Millisecond) + + t.Logf("# Initial and Handshake packets in flight, first takes precedence") + test.advance(333 * time.Millisecond) + test.send(handshakeSpace, 0) + test.wantTimeout(666 * time.Millisecond) + + t.Logf("# Initial packet acked, Handshake PTO timer armed") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + test.wantTimeout(999 * time.Millisecond) + + t.Logf("# send Initial, earlier Handshake PTO takes precedence") + test.advance(333 * time.Millisecond) + test.send(initialSpace, 1) + test.wantTimeout(666 * time.Millisecond) +} + +func TestLossPTOHandshakeConfirmation(t *testing.T) { + // "An endpoint MUST NOT set its PTO timer for the Application Data + // packet number space until the handshake is confirmed." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-7 + test := newLossTest(t, clientSide, lossTestOpts{}) + test.send(initialSpace, 0) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + + test.send(handshakeSpace, 0) + test.ack(handshakeSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(handshakeSpace, 0) + + test.send(appDataSpace, 0) + test.wantNoTimeout() +} + +func TestLossPTOBackoffDoubles(t *testing.T) { + // "When a PTO timer expires, the PTO backoff MUST be increased, + // resulting in the PTO period being set to twice its current value." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-9 + test := newLossTest(t, serverSide, lossTestOpts{}) + test.datagramReceived(1200) + test.send(initialSpace, 0) + test.wantVar("smoothed_rtt", 333*time.Millisecond) // initial value + test.wantVar("rttvar", 333*time.Millisecond/2) // initial value + t.Logf("# PTO = smoothed_rtt + max(4*rttvar, 1ms)") + test.wantTimeout(999 * time.Millisecond) + + t.Logf("# wait for PTO timer expiration") + test.advanceToLossTimer() + test.wantPTOExpired() + test.wantNoTimeout() + + t.Logf("# PTO timer doubles") + test.send(initialSpace, 1) + test.wantTimeout(2 * 999 * time.Millisecond) + test.advanceToLossTimer() + test.wantPTOExpired() + test.wantNoTimeout() + + t.Logf("# PTO timer doubles again") + test.send(initialSpace, 2) + test.wantTimeout(4 * 999 * time.Millisecond) + test.advanceToLossTimer() + test.wantPTOExpired() + test.wantNoTimeout() +} + +func TestLossPTOBackoffResetOnAck(t *testing.T) { + // "The PTO backoff factor is reset when an acknowledgment is received [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-9 + test := newLossTest(t, serverSide, lossTestOpts{}) + test.datagramReceived(1200) + + t.Logf("# first ack establishes smoothed_rtt = 10ms") + test.send(initialSpace, 0) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + t.Logf("# set rttvar for simplicity") + test.setRTTVar(0) + + t.Logf("# send packet 1 and wait for PTO") + test.send(initialSpace, 1) + test.wantTimeout(11 * time.Millisecond) + test.advanceToLossTimer() + test.wantPTOExpired() + test.wantNoTimeout() + + t.Logf("# send packet 2 & 3, PTO doubles") + test.send(initialSpace, 2, 3) + test.wantTimeout(22 * time.Millisecond) + + test.advance(10 * time.Millisecond) + t.Logf("# check remaining PTO (22ms - 10ms elapsed)") + test.wantTimeout(12 * time.Millisecond) + + t.Logf("# ACK to packet 2 resets PTO") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 3}) + test.wantAck(initialSpace, 1) + test.wantAck(initialSpace, 2) + + t.Logf("# check remaining PTO (11ms - 10ms elapsed)") + test.wantTimeout(1 * time.Millisecond) +} + +func TestLossPTOBackoffNotResetOnClientInitialAck(t *testing.T) { + // "[...] a client does not reset the PTO backoff factor on + // receiving acknowledgments in Initial packets." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-9 + test := newLossTest(t, clientSide, lossTestOpts{}) + + t.Logf("# first ack establishes smoothed_rtt = 10ms") + test.send(initialSpace, 0) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + t.Logf("# set rttvar for simplicity") + test.setRTTVar(0) + + t.Logf("# send packet 1 and wait for PTO") + test.send(initialSpace, 1) + test.wantTimeout(11 * time.Millisecond) + test.advanceToLossTimer() + test.wantPTOExpired() + test.wantNoTimeout() + + t.Logf("# send more packets, PTO doubles") + test.send(initialSpace, 2, 3) + test.send(handshakeSpace, 0) + test.wantTimeout(22 * time.Millisecond) + + test.advance(10 * time.Millisecond) + t.Logf("# check remaining PTO (22ms - 10ms elapsed)") + test.wantTimeout(12 * time.Millisecond) + + // TODO: Is this right? 6.2.1-9 says we don't reset the PTO *backoff*, not the PTO. + // 6.2.1-8 says we reset the PTO timer when an ack-eliciting packet is sent *or + // acknowledged*, but the pseudocode in appendix A doesn't appear to do the latter. + t.Logf("# ACK to Initial packet does not reset PTO for client") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 3}) + test.wantAck(initialSpace, 1) + test.wantAck(initialSpace, 2) + t.Logf("# check remaining PTO (22ms - 10ms elapsed)") + test.wantTimeout(12 * time.Millisecond) + + t.Logf("# ACK to handshake packet does reset PTO") + test.ack(handshakeSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(handshakeSpace, 0) + t.Logf("# check remaining PTO (12ms - 10ms elapsed)") + test.wantTimeout(1 * time.Millisecond) +} + +func TestLossPTONotSetWhenLossTimerSet(t *testing.T) { + // "The PTO timer MUST NOT be set if a timer is set + // for time threshold loss detection [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.1-12 + test := newLossTest(t, serverSide, lossTestOpts{}) + test.datagramReceived(1200) + t.Logf("# PTO timer set for first packets sent") + test.send(initialSpace, 0, 1) + test.wantVar("smoothed_rtt", 333*time.Millisecond) // initial value + test.wantVar("rttvar", 333*time.Millisecond/2) // initial value + t.Logf("# PTO = smoothed_rtt + max(4*rttvar, 1ms)") + test.wantTimeout(999 * time.Millisecond) + + t.Logf("# ack of packet 1 starts loss timer for 0, PTO overidden") + test.advance(333 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{1, 2}) + test.wantAck(initialSpace, 1) + + t.Logf("# latest_rtt == smoothed_rtt") + test.wantVar("smoothed_rtt", 333*time.Millisecond) + test.wantVar("latest_rtt", 333*time.Millisecond) + t.Logf("# timeout = 9/8 * max(smoothed_rtt, latest_rtt) - time_since_packet_sent") + test.wantTimeout(((333 * time.Millisecond * 9) / 8) - 333*time.Millisecond) +} + +func TestLossDiscardingKeysResetsTimers(t *testing.T) { + // "When Initial or Handshake keys are discarded, + // the PTO and loss detection timers MUST be reset" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2-3 + test := newLossTest(t, clientSide, lossTestOpts{}) + + t.Logf("# handshake packet sent 1ms after initial") + test.send(initialSpace, 0, 1) + test.advance(1 * time.Millisecond) + test.send(handshakeSpace, 0, 1) + test.advance(9 * time.Millisecond) + + t.Logf("# ack of Initial packet 2 starts loss timer for packet 1") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{1, 2}) + test.wantAck(initialSpace, 1) + + test.advance(1 * time.Millisecond) + t.Logf("# smoothed_rtt = %v", 10*time.Millisecond) + t.Logf("# latest_rtt = %v", 10*time.Millisecond) + t.Logf("# timeout = max(9/8 * max(smoothed_rtt, latest_rtt), 1ms)") + t.Logf("# (measured since Initial packet 1 sent)") + test.wantTimeout((10 * time.Millisecond * 9 / 8) - 11*time.Millisecond) + + t.Logf("# ack of Handshake packet 2 starts loss timer for packet 1") + test.ack(handshakeSpace, 0*time.Millisecond, i64range[packetNumber]{1, 2}) + test.wantAck(handshakeSpace, 1) + + t.Logf("# dropping Initial keys sets timer to Handshake timeout") + test.discardKeys(initialSpace) + test.wantTimeout((10 * time.Millisecond * 9 / 8) - 10*time.Millisecond) +} + +func TestLossNoPTOAtAntiAmplificationLimit(t *testing.T) { + // "If no additional data can be sent [because the server is at the + // anti-amplification limit], the server's PTO timer MUST NOT be armed [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2.1-1 + test := newLossTest(t, serverSide, lossTestOpts{ + maxDatagramSize: 1 << 20, // large initial congestion window + }) + test.datagramReceived(1200) + test.send(initialSpace, 0, sentPacket{ + ackEliciting: true, + inFlight: true, + size: 1200, + }) + test.wantTimeout(999 * time.Millisecond) + + t.Logf("PTO timer should be disabled when at the anti-amplification limit") + test.send(initialSpace, 1, sentPacket{ + ackEliciting: false, + inFlight: true, + size: 2 * 1200, + }) + test.wantNoTimeout() + + // "When the server receives a datagram from the client, the amplification + // limit is increased and the server resets the PTO timer." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2.1-2 + t.Logf("PTO timer should be reset when datagrams are received") + test.datagramReceived(1200) + test.wantTimeout(999 * time.Millisecond) + + // "If the PTO timer is then set to a time in the past, it is executed immediately." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2.1-2 + test.send(initialSpace, 2, sentPacket{ + ackEliciting: true, + inFlight: true, + size: 3 * 1200, + }) + test.wantNoTimeout() + t.Logf("resetting expired PTO timer should exeute immediately") + test.advance(1000 * time.Millisecond) + test.datagramReceived(1200) + test.wantPTOExpired() + test.wantNoTimeout() +} + +func TestLossClientSetsPTOWhenHandshakeUnacked(t *testing.T) { + // "[...] the client MUST set the PTO timer if the client has not + // received an acknowledgment for any of its Handshake packets and + // the handshake is not confirmed [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2.1-3 + test := newLossTest(t, clientSide, lossTestOpts{}) + test.send(initialSpace, 0) + + test.wantVar("smoothed_rtt", 333*time.Millisecond) // initial value + test.wantVar("rttvar", 333*time.Millisecond/2) // initial value + t.Logf("# PTO = smoothed_rtt + max(4*rttvar, 1ms)") + test.wantTimeout(999 * time.Millisecond) + + test.advance(333 * time.Millisecond) + test.wantTimeout(666 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + t.Logf("# PTO timer set for a client before handshake ack even if no packets in flight") + test.wantTimeout(999 * time.Millisecond) + + test.advance(333 * time.Millisecond) + test.wantTimeout(666 * time.Millisecond) +} + +func TestLossKeysDiscarded(t *testing.T) { + // "The sender MUST discard all recovery state associated with + // [packets in number spaces with discarded keys] and MUST remove + // them from the count of bytes in flight." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.4-1 + test := newLossTest(t, clientSide, lossTestOpts{}) + test.send(initialSpace, 0, testSentPacketSize(1200)) + test.send(handshakeSpace, 0, testSentPacketSize(600)) + test.wantVar("bytes_in_flight", 1800) + + test.discardKeys(initialSpace) + test.wantVar("bytes_in_flight", 600) + + test.discardKeys(handshakeSpace) + test.wantVar("bytes_in_flight", 0) +} + +func TestLossInitialCongestionWindow(t *testing.T) { + // "Endpoints SHOULD use an initial congestion window of [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.2-1 + + // "[...] 10 times the maximum datagram size [...]" + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + t.Logf("# congestion_window = 10*max_datagram_size (1200)") + test.wantVar("congestion_window", 12000) + + // "[...] while limiting the window to the larger of 14720 bytes [...]" + test = newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1500, + }) + t.Logf("# congestion_window limited to 14720 bytes") + test.wantVar("congestion_window", 14720) + + // "[...] or twice the maximum datagram size." + test = newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 10000, + }) + t.Logf("# congestion_window limited to 2*max_datagram_size (10000)") + test.wantVar("congestion_window", 20000) + + for _, tc := range []struct { + maxDatagramSize int + wantInitialBurst int + }{{ + // "[...] 10 times the maximum datagram size [...]" + maxDatagramSize: 1200, + wantInitialBurst: 12000, + }, { + // "[...] while limiting the window to the larger of 14720 bytes [...]" + maxDatagramSize: 1500, + wantInitialBurst: 14720, + }, { + // "[...] or twice the maximum datagram size." + maxDatagramSize: 10000, + wantInitialBurst: 20000, + }} { + t.Run(fmt.Sprintf("max_datagram_size=%v", tc.maxDatagramSize), func(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: tc.maxDatagramSize, + }) + + var num packetNumber + window := tc.wantInitialBurst + for window >= tc.maxDatagramSize { + t.Logf("# %v bytes of initial congestion window remain", window) + test.send(initialSpace, num, sentPacket{ + ackEliciting: true, + inFlight: true, + size: tc.maxDatagramSize, + }) + window -= tc.maxDatagramSize + num++ + } + t.Logf("# congestion window (%v) < max_datagram_size, congestion control blocks send", window) + test.wantSendLimit(ccLimited) + }) + } +} + +func TestLossBytesInFlight(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + t.Logf("# sent packets are added to bytes_in_flight") + test.wantVar("bytes_in_flight", 0) + test.send(initialSpace, 0, testSentPacketSize(1200)) + test.wantVar("bytes_in_flight", 1200) + test.send(initialSpace, 1, testSentPacketSize(800)) + test.wantVar("bytes_in_flight", 2000) + + t.Logf("# acked packets are removed from bytes_in_flight") + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{1, 2}) + test.wantAck(initialSpace, 1) + test.wantVar("bytes_in_flight", 1200) + + t.Logf("# lost packets are removed from bytes_in_flight") + test.advanceToLossTimer() + test.wantLoss(initialSpace, 0) + test.wantVar("bytes_in_flight", 0) +} + +func TestLossCongestionWindowLimit(t *testing.T) { + // "An endpoint MUST NOT send a packet if it would cause bytes_in_flight + // [...] to be larger than the congestion window [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7-7 + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + t.Logf("# consume the initial congestion window") + test.send(initialSpace, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, testSentPacketSize(1200)) + test.wantSendLimit(ccLimited) + + t.Logf("# give the pacer bucket time to refill") + test.advance(333 * time.Millisecond) // initial RTT + + t.Logf("# sending limited by congestion window, not the pacer") + test.wantVar("congestion_window", 12000) + test.wantVar("bytes_in_flight", 12000) + test.wantVar("pacer_bucket", 12000) + test.wantSendLimit(ccLimited) + + t.Logf("# receiving an ack opens up the congestion window") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + test.wantSendLimit(ccOK) +} + +func TestLossCongestionStates(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + t.Logf("# consume the initial congestion window") + test.send(initialSpace, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, testSentPacketSize(1200)) + test.wantSendLimit(ccLimited) + test.wantVar("congestion_window", 12000) + + // "While a sender is in slow start, the congestion window + // increases by the number of bytes acknowledged [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.1-2 + test.advance(333 * time.Millisecond) + t.Logf("# congestion window increases by number of bytes acked (1200)") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + test.wantVar("congestion_window", 13200) // 12000 + 1200 + + t.Logf("# congestion window increases by number of bytes acked (2400)") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 3}) + test.wantAck(initialSpace, 1, 2) + test.wantVar("congestion_window", 15600) // 12000 + 3*1200 + + // TODO: ECN-CE count + + // "The sender MUST exit slow start and enter a recovery period + // when a packet is lost [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.1-3 + t.Logf("# loss of a packet triggers entry to a recovery period") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{6, 7}) + test.wantAck(initialSpace, 6) + test.wantLoss(initialSpace, 3) + + // "On entering a recovery period, a sender MUST set the slow start + // threshold to half the value of the congestion window when loss is detected." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.2-2 + t.Logf("# slow_start_threshold = congestion_window / 2") + test.wantVar("slow_start_threshold", 7800) // 15600/2 + + // "[...] a single packet can be sent prior to reduction [of the congestion window]." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.2-3 + test.send(initialSpace, 10, testSentPacketSize(1200)) + + // "The congestion window MUST be set to the reduced value of the slow start + // threshold before exiting the recovery period." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.2-2 + t.Logf("# congestion window reduced to slow start threshold") + test.wantVar("congestion_window", 7800) + + t.Logf("# acks for packets sent before recovery started do not affect congestion") + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 10}) + test.wantAck(initialSpace, 4, 5, 7, 8, 9) + test.wantVar("slow_start_threshold", 7800) + test.wantVar("congestion_window", 7800) + + // "A recovery period ends and the sender enters congestion avoidance when + // a packet sent during the recovery period is acknowledged." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.2-5 + t.Logf("# recovery ends and congestion avoidance begins when packet 10 is acked") + test.advance(333 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 11}) + test.wantAck(initialSpace, 10) + + // "[...] limit the increase to the congestion window to at most one + // maximum datagram size for each congestion window that is acknowledged." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.3-2 + t.Logf("# after processing acks for one congestion window's worth of data...") + test.send(initialSpace, 11, 12, 13, 14, 15, 16, testSentPacketSize(1200)) + test.advance(333 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 17}) + test.wantAck(initialSpace, 11, 12, 13, 14, 15, 16) + t.Logf("# ...congestion window increases by max_datagram_size") + test.wantVar("congestion_window", 9000) // 7800 + 1200 + + // "The sender exits congestion avoidance and enters a recovery period + // when a packet is lost [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.3.3-3 + test.send(initialSpace, 17, 18, 19, 20, 21, testSentPacketSize(1200)) + test.advance(333 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{18, 21}) + test.wantAck(initialSpace, 18, 19, 20) + test.wantLoss(initialSpace, 17) + t.Logf("# slow_start_threshold = congestion_window / 2") + test.wantVar("slow_start_threshold", 4500) +} + +func TestLossMinimumCongestionWindow(t *testing.T) { + // "The RECOMMENDED [minimum congestion window] is 2 * max_datagram_size." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.2-4 + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + test.send(initialSpace, 0, 1, 2, 3, testSentPacketSize(1200)) + test.wantVar("congestion_window", 12000) + + t.Logf("# enter recovery") + test.advance(333 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{3, 4}) + test.wantAck(initialSpace, 3) + test.wantLoss(initialSpace, 0) + test.wantVar("congestion_window", 6000) + + t.Logf("# enter congestion avoidance and return to recovery") + test.send(initialSpace, 4, 5, 6, 7) + test.advance(333 * time.Millisecond) + test.wantLoss(initialSpace, 1, 2) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{7, 8}) + test.wantAck(initialSpace, 7) + test.wantLoss(initialSpace, 4) + test.wantVar("congestion_window", 3000) + + t.Logf("# enter congestion avoidance and return to recovery") + test.send(initialSpace, 8, 9, 10, 11) + test.advance(333 * time.Millisecond) + test.wantLoss(initialSpace, 5, 6) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{11, 12}) + test.wantAck(initialSpace, 11) + test.wantLoss(initialSpace, 8) + t.Logf("# congestion window does not fall below 2*max_datagram_size") + test.wantVar("congestion_window", 2400) + + t.Logf("# enter congestion avoidance and return to recovery") + test.send(initialSpace, 12, 13, 14, 15) + test.advance(333 * time.Millisecond) + test.wantLoss(initialSpace, 9, 10) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{15, 16}) + test.wantAck(initialSpace, 15) + test.wantLoss(initialSpace, 12) + t.Logf("# congestion window does not fall below 2*max_datagram_size") + test.wantVar("congestion_window", 2400) +} + +func TestLossPersistentCongestion(t *testing.T) { + // "When persistent congestion is declared, the sender's congestion + // window MUST be reduced to the minimum congestion window [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.6.2-6 + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + test.send(initialSpace, 0, testSentPacketSize(1200)) + test.c.cc.setUnderutilized(true) + + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + + t.Logf("# set rttvar for simplicity") + test.setRTTVar(0) + test.wantVar("smoothed_rtt", 10*time.Millisecond) + t.Logf("# persistent congestion duration = 3*(smoothed_rtt + timerGranularity + max_ack_delay)") + t.Logf("# persistent congestion duration = 108ms") + + t.Logf("# sending packets 1-5 over 108ms") + test.send(initialSpace, 1, testSentPacketSize(1200)) + + test.advance(11 * time.Millisecond) // total 11ms + test.wantPTOExpired() + test.send(initialSpace, 2, testSentPacketSize(1200)) + + test.advance(22 * time.Millisecond) // total 33ms + test.wantPTOExpired() + test.send(initialSpace, 3, testSentPacketSize(1200)) + + test.advance(44 * time.Millisecond) // total 77ms + test.wantPTOExpired() + test.send(initialSpace, 4, testSentPacketSize(1200)) + + test.advance(31 * time.Millisecond) // total 108ms + test.send(initialSpace, 5, testSentPacketSize(1200)) + t.Logf("# 108ms between packets 1-5") + + test.wantVar("congestion_window", 12000) + t.Logf("# triggering loss of packets 1-5") + test.send(initialSpace, 6, 7, 8, testSentPacketSize(1200)) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{8, 9}) + test.wantAck(initialSpace, 8) + test.wantLoss(initialSpace, 1, 2, 3, 4, 5) + + t.Logf("# lost packets spanning persistent congestion duration") + t.Logf("# congestion_window = 2 * max_datagram_size (minimum)") + test.wantVar("congestion_window", 2400) +} + +func TestLossSimplePersistentCongestion(t *testing.T) { + // Simpler version of TestLossPersistentCongestion which acts as a + // base for subsequent tests. + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + + t.Logf("# establish initial RTT sample") + test.send(initialSpace, 0, testSentPacketSize(1200)) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + + t.Logf("# send two packets spanning persistent congestion duration") + test.send(initialSpace, 1, testSentPacketSize(1200)) + t.Logf("# 2000ms >> persistent congestion duration") + test.advance(2000 * time.Millisecond) + test.wantPTOExpired() + test.send(initialSpace, 2, testSentPacketSize(1200)) + + t.Logf("# trigger loss of previous packets") + test.advance(10 * time.Millisecond) + test.send(initialSpace, 3, testSentPacketSize(1200)) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{3, 4}) + test.wantAck(initialSpace, 3) + test.wantLoss(initialSpace, 1, 2) + + t.Logf("# persistent congestion detected") + test.wantVar("congestion_window", 2400) +} + +func TestLossPersistentCongestionAckElicitingPackets(t *testing.T) { + // "These two packets MUST be ack-eliciting [...]" + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.6.2-3 + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + + t.Logf("# establish initial RTT sample") + test.send(initialSpace, 0, testSentPacketSize(1200)) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + + t.Logf("# send two packets spanning persistent congestion duration") + test.send(initialSpace, 1, testSentPacketSize(1200)) + t.Logf("# 2000ms >> persistent congestion duration") + test.advance(2000 * time.Millisecond) + test.wantPTOExpired() + test.send(initialSpace, 2, sentPacket{ + inFlight: true, + ackEliciting: false, + size: 1200, + }) + test.send(initialSpace, 3, testSentPacketSize(1200)) // PTO probe + + t.Logf("# trigger loss of previous packets") + test.advance(10 * time.Millisecond) + test.send(initialSpace, 4, testSentPacketSize(1200)) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{3, 5}) + test.wantAck(initialSpace, 3) + test.wantAck(initialSpace, 4) + test.wantLoss(initialSpace, 1, 2) + + t.Logf("# persistent congestion not detected: packet 2 is not ack-eliciting") + test.wantVar("congestion_window", (12000+1200+1200-1200)/2) +} + +func TestLossNoPersistentCongestionWithoutRTTSample(t *testing.T) { + // "The persistent congestion period SHOULD NOT start until there + // is at least one RTT sample." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.6.2-4 + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + + t.Logf("# packets sent before initial RTT sample") + test.send(initialSpace, 0, testSentPacketSize(1200)) + test.advance(2000 * time.Millisecond) + test.wantPTOExpired() + test.send(initialSpace, 1, testSentPacketSize(1200)) + + test.advance(10 * time.Millisecond) + test.send(initialSpace, 2, testSentPacketSize(1200)) + + t.Logf("# first ack establishes RTT sample") + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{2, 3}) + test.wantAck(initialSpace, 2) + test.wantLoss(initialSpace, 0, 1) + + t.Logf("# loss of packets before initial RTT sample does not cause persistent congestion") + test.wantVar("congestion_window", 12000/2) +} + +func TestLossPacerRefillRate(t *testing.T) { + // "A sender SHOULD pace sending of all in-flight packets based on + // input from the congestion controller." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.7-1 + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + t.Logf("# consume the initial congestion window") + test.send(initialSpace, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, testSentPacketSize(1200)) + test.wantSendLimit(ccLimited) + test.wantVar("pacer_bucket", 0) + test.wantVar("congestion_window", 12000) + + t.Logf("# first RTT sample establishes smoothed_rtt") + rtt := 100 * time.Millisecond + test.advance(rtt) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 10}) + test.wantAck(initialSpace, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + test.wantVar("congestion_window", 24000) // 12000 + 10*1200 + test.wantVar("smoothed_rtt", rtt) + + t.Logf("# advance 1 RTT to let the pacer bucket refill completely") + test.advance(100 * time.Millisecond) + t.Logf("# pacer_bucket = initial_congestion_window") + test.wantVar("pacer_bucket", 12000) + + t.Logf("# consume capacity from the pacer bucket") + test.send(initialSpace, 10, testSentPacketSize(1200)) + test.wantVar("pacer_bucket", 10800) // 12000 - 1200 + test.send(initialSpace, 11, testSentPacketSize(600)) + test.wantVar("pacer_bucket", 10200) // 10800 - 600 + test.send(initialSpace, 12, testSentPacketSize(600)) + test.wantVar("pacer_bucket", 9600) // 10200 - 600 + test.send(initialSpace, 13, 14, 15, 16, testSentPacketSize(1200)) + test.wantVar("pacer_bucket", 4800) // 9600 - 4*1200 + + t.Logf("# advance 1/10 of an RTT, bucket refills") + test.advance(rtt / 10) + t.Logf("# pacer_bucket += 1.25 * (1/10) * congestion_window") + t.Logf("# += 3000") + test.wantVar("pacer_bucket", 7800) +} + +func TestLossPacerNextSendTime(t *testing.T) { + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + t.Logf("# consume the initial congestion window") + test.send(initialSpace, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, testSentPacketSize(1200)) + test.wantSendLimit(ccLimited) + test.wantVar("pacer_bucket", 0) + test.wantVar("congestion_window", 12000) + + t.Logf("# first RTT sample establishes smoothed_rtt") + rtt := 100 * time.Millisecond + test.advance(rtt) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 10}) + test.wantAck(initialSpace, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + test.wantVar("congestion_window", 24000) // 12000 + 10*1200 + test.wantVar("smoothed_rtt", rtt) + + t.Logf("# advance 1 RTT to let the pacer bucket refill completely") + test.advance(100 * time.Millisecond) + t.Logf("# pacer_bucket = initial_congestion_window") + test.wantVar("pacer_bucket", 12000) + + t.Logf("# consume the refilled pacer bucket") + test.send(initialSpace, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, testSentPacketSize(1200)) + test.wantSendLimit(ccPaced) + + t.Logf("# refill rate = 1.25 * congestion_window / rtt") + test.wantSendDelay(rtt / 25) // rtt / (1.25 * 24000 / 1200) + + t.Logf("# no capacity available yet") + test.advance(rtt / 50) + test.wantVar("pacer_bucket", -600) + test.wantSendLimit(ccPaced) + + t.Logf("# capacity available") + test.advance(rtt / 50) + test.wantVar("pacer_bucket", 0) + test.wantSendLimit(ccOK) +} + +func TestLossCongestionWindowUnderutilized(t *testing.T) { + // "When bytes in flight is smaller than the congestion window + // and sending is not pacing limited [...] the congestion window + // SHOULD NOT be increased in either slow start or congestion avoidance." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-7.8-1 + test := newLossTest(t, clientSide, lossTestOpts{ + maxDatagramSize: 1200, + }) + test.send(initialSpace, 0, testSentPacketSize(1200)) + test.setUnderutilized(true) + t.Logf("# underutilized: %v", test.c.cc.underutilized) + test.wantVar("congestion_window", 12000) + + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 1}) + test.wantAck(initialSpace, 0) + t.Logf("# congestion window does not increase, because window is underutilized") + test.wantVar("congestion_window", 12000) + + t.Logf("# refill pacer bucket") + test.advance(10 * time.Millisecond) + test.wantVar("pacer_bucket", 12000) + + test.send(initialSpace, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, testSentPacketSize(1200)) + test.setUnderutilized(false) + test.advance(10 * time.Millisecond) + test.ack(initialSpace, 0*time.Millisecond, i64range[packetNumber]{0, 11}) + test.wantAck(initialSpace, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + t.Logf("# congestion window increases") + test.wantVar("congestion_window", 24000) +} + +type lossTest struct { + t *testing.T + c lossState + now time.Time + fates map[spaceNum]packetFate + failed bool +} + +type lossTestOpts struct { + maxDatagramSize int +} + +func newLossTest(t *testing.T, side connSide, opts lossTestOpts) *lossTest { + c := &lossTest{ + t: t, + now: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + fates: make(map[spaceNum]packetFate), + } + maxDatagramSize := 1200 + if opts.maxDatagramSize != 0 { + maxDatagramSize = opts.maxDatagramSize + } + c.c.init(side, maxDatagramSize, c.now) + t.Cleanup(func() { + if !c.failed { + c.checkUnexpectedEvents() + } + }) + return c +} + +type spaceNum struct { + space numberSpace + num packetNumber +} + +func (c *lossTest) checkUnexpectedEvents() { + c.t.Helper() + for sn, fate := range c.fates { + c.t.Errorf("ERROR: unexpected %v: %v %v", fate, sn.space, sn.num) + } + if c.c.ptoExpired { + c.t.Errorf("ERROR: PTO timer unexpectedly expired") + } +} + +func (c *lossTest) setSmoothedRTT(d time.Duration) { + c.t.Helper() + c.checkUnexpectedEvents() + c.t.Logf("set smoothed_rtt to %v", d) + c.c.rtt.smoothedRTT = d +} + +func (c *lossTest) setRTTVar(d time.Duration) { + c.t.Helper() + c.checkUnexpectedEvents() + c.t.Logf("set rttvar to %v", d) + c.c.rtt.rttvar = d +} + +func (c *lossTest) setUnderutilized(v bool) { + c.t.Logf("set congestion window underutilized: %v", v) + c.c.cc.setUnderutilized(v) +} + +func (c *lossTest) advance(d time.Duration) { + c.t.Helper() + c.checkUnexpectedEvents() + c.t.Logf("advance time %v", d) + c.now = c.now.Add(d) + c.c.advance(c.now, c.onAckOrLoss) +} + +func (c *lossTest) advanceToLossTimer() { + c.t.Helper() + c.checkUnexpectedEvents() + d := c.c.timer.Sub(c.now) + c.t.Logf("advance time %v (up to loss timer)", d) + if d < 0 { + c.t.Fatalf("loss timer is in the past") + } + c.now = c.c.timer + c.c.advance(c.now, c.onAckOrLoss) +} + +type testSentPacketSize int + +func (c *lossTest) send(spaceID numberSpace, opts ...any) { + c.t.Helper() + c.checkUnexpectedEvents() + var nums []packetNumber + prototype := sentPacket{ + ackEliciting: true, + inFlight: true, + } + for _, o := range opts { + switch o := o.(type) { + case sentPacket: + prototype = o + case testSentPacketSize: + prototype.size = int(o) + case int: + nums = append(nums, packetNumber(o)) + case packetNumber: + nums = append(nums, o) + case i64range[packetNumber]: + for num := o.start; num < o.end; num++ { + nums = append(nums, num) + } + } + } + c.t.Logf("send %v %v", spaceID, nums) + limit, _ := c.c.sendLimit(c.now) + if prototype.inFlight && limit != ccOK { + c.t.Fatalf("congestion control blocks sending packet") + } + if !prototype.inFlight && limit == ccBlocked { + c.t.Fatalf("congestion control blocks sending packet") + } + for _, num := range nums { + sent := &sentPacket{} + *sent = prototype + sent.num = num + c.c.packetSent(c.now, spaceID, sent) + } +} + +func (c *lossTest) datagramReceived(size int) { + c.t.Helper() + c.checkUnexpectedEvents() + c.t.Logf("receive %v-byte datagram", size) + c.c.datagramReceived(c.now, size) +} + +func (c *lossTest) ack(spaceID numberSpace, ackDelay time.Duration, rs ...i64range[packetNumber]) { + c.t.Helper() + c.checkUnexpectedEvents() + c.c.receiveAckStart() + var acked rangeset[packetNumber] + for _, r := range rs { + c.t.Logf("ack %v delay=%v [%v,%v)", spaceID, ackDelay, r.start, r.end) + acked.add(r.start, r.end) + } + for i, r := range rs { + c.t.Logf("ack %v delay=%v [%v,%v)", spaceID, ackDelay, r.start, r.end) + c.c.receiveAckRange(c.now, spaceID, i, r.start, r.end, c.onAckOrLoss) + } + c.c.receiveAckEnd(c.now, spaceID, ackDelay, c.onAckOrLoss) +} + +func (c *lossTest) onAckOrLoss(space numberSpace, sent *sentPacket, fate packetFate) { + c.t.Logf("%v %v %v", fate, space, sent.num) + if _, ok := c.fates[spaceNum{space, sent.num}]; ok { + c.t.Errorf("ERROR: duplicate %v for %v %v", fate, space, sent.num) + } + c.fates[spaceNum{space, sent.num}] = fate +} + +func (c *lossTest) confirmHandshake() { + c.t.Helper() + c.checkUnexpectedEvents() + c.t.Logf("confirm handshake") + c.c.confirmHandshake() +} + +func (c *lossTest) validateClientAddress() { + c.t.Helper() + c.checkUnexpectedEvents() + c.t.Logf("validate client address") + c.c.validateClientAddress() +} + +func (c *lossTest) discardKeys(spaceID numberSpace) { + c.t.Helper() + c.checkUnexpectedEvents() + c.t.Logf("discard %s keys", spaceID) + c.c.discardKeys(c.now, spaceID) +} + +func (c *lossTest) setMaxAckDelay(d time.Duration) { + c.t.Helper() + c.checkUnexpectedEvents() + c.t.Logf("set max_ack_delay = %v", d) + c.c.setMaxAckDelay(d) +} + +func (c *lossTest) wantAck(spaceID numberSpace, nums ...packetNumber) { + c.t.Helper() + for _, num := range nums { + if c.fates[spaceNum{spaceID, num}] != packetAcked { + c.t.Fatalf("expected ack for %v %v\n", spaceID, num) + } + delete(c.fates, spaceNum{spaceID, num}) + } +} + +func (c *lossTest) wantLoss(spaceID numberSpace, nums ...packetNumber) { + c.t.Helper() + for _, num := range nums { + if c.fates[spaceNum{spaceID, num}] != packetLost { + c.t.Fatalf("expected loss of %v %v\n", spaceID, num) + } + delete(c.fates, spaceNum{spaceID, num}) + } +} + +func (c *lossTest) wantPTOExpired() { + c.t.Helper() + if !c.c.ptoExpired { + c.t.Fatalf("expected PTO timer to expire") + } else { + c.t.Logf("PTO TIMER EXPIRED") + } + c.c.ptoExpired = false +} + +func (l ccLimit) String() string { + switch l { + case ccOK: + return "ccOK" + case ccBlocked: + return "ccBlocked" + case ccLimited: + return "ccLimited" + case ccPaced: + return "ccPaced" + } + return "BUG" +} + +func (c *lossTest) wantSendLimit(want ccLimit) { + c.t.Helper() + if got, _ := c.c.sendLimit(c.now); got != want { + c.t.Fatalf("congestion control send limit is %v, want %v", got, want) + } +} + +func (c *lossTest) wantSendDelay(want time.Duration) { + c.t.Helper() + limit, next := c.c.sendLimit(c.now) + if limit != ccPaced { + c.t.Fatalf("congestion control limit is %v, want %v", limit, ccPaced) + } + got := next.Sub(c.now) + if got != want { + c.t.Fatalf("delay until next send is %v, want %v", got, want) + } +} + +func (c *lossTest) wantVar(name string, want any) { + c.t.Helper() + var got any + switch name { + case "latest_rtt": + got = c.c.rtt.latestRTT + case "min_rtt": + got = c.c.rtt.minRTT + case "smoothed_rtt": + got = c.c.rtt.smoothedRTT + case "rttvar": + got = c.c.rtt.rttvar + case "congestion_window": + got = c.c.cc.congestionWindow + case "slow_start_threshold": + got = c.c.cc.slowStartThreshold + case "bytes_in_flight": + got = c.c.cc.bytesInFlight + case "pacer_bucket": + got = c.c.pacer.bucket + default: + c.t.Fatalf("unknown var %q", name) + } + if got != want { + c.t.Fatalf("%v = %v, want %v\n", name, got, want) + } else { + c.t.Logf("%v = %v", name, got) + } +} + +func (c *lossTest) wantTimeout(want time.Duration) { + c.t.Helper() + if c.c.timer.IsZero() { + c.t.Fatalf("loss detection timer is not set, want %v", want) + } + got := c.c.timer.Sub(c.now) + if got != want { + c.t.Fatalf("loss detection timer expires in %v, want %v", got, want) + } + c.t.Logf("loss detection timer expires in %v", got) +} + +func (c *lossTest) wantNoTimeout() { + c.t.Helper() + if !c.c.timer.IsZero() { + d := c.c.timer.Sub(c.now) + c.t.Fatalf("loss detection timer expires in %v, want not set", d) + } + c.t.Logf("loss detection timer is not set") +} + +func (f packetFate) String() string { + switch f { + case packetAcked: + return "ACK" + case packetLost: + return "LOSS" + default: + panic("unknown packetFate") + } +} diff --git a/internal/quic/math.go b/internal/quic/math.go new file mode 100644 index 000000000..f9dd7545a --- /dev/null +++ b/internal/quic/math.go @@ -0,0 +1,14 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +func abs[T ~int | ~int64](a T) T { + if a < 0 { + return -a + } + return a +} diff --git a/internal/quic/pacer.go b/internal/quic/pacer.go new file mode 100644 index 000000000..bcba76936 --- /dev/null +++ b/internal/quic/pacer.go @@ -0,0 +1,131 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "time" +) + +// A pacerState controls the rate at which packets are sent using a leaky-bucket rate limiter. +// +// The pacer limits the maximum size of a burst of packets. +// When a burst exceeds this limit, it spreads subsequent packets +// over time. +// +// The bucket is initialized to the maximum burst size (ten packets by default), +// and fills at the rate: +// +// 1.25 * congestion_window / smoothed_rtt +// +// A sender can send one congestion window of packets per RTT, +// since the congestion window consumed by each packet is returned +// one round-trip later by the responding ack. +// The pacer permits sending at slightly faster than this rate to +// avoid underutilizing the congestion window. +// +// The pacer permits the bucket to become negative, and permits +// sending when non-negative. This biases slightly in favor of +// sending packets over limiting them, and permits bursts one +// packet greater than the configured maximum, but permits the pacer +// to be ignorant of the maximum packet size. +// +// https://www.rfc-editor.org/rfc/rfc9002.html#section-7.7 +type pacerState struct { + bucket int // measured in bytes + maxBucket int + timerGranularity time.Duration + lastUpdate time.Time + nextSend time.Time +} + +func (p *pacerState) init(now time.Time, maxBurst int, timerGranularity time.Duration) { + // Bucket is limited to maximum burst size, which is the initial congestion window. + // https://www.rfc-editor.org/rfc/rfc9002#section-7.7-2 + p.maxBucket = maxBurst + p.bucket = p.maxBucket + p.timerGranularity = timerGranularity + p.lastUpdate = now + p.nextSend = now +} + +// pacerBytesForInterval returns the number of bytes permitted over an interval. +// +// rate = 1.25 * congestion_window / smoothed_rtt +// bytes = interval * rate +// +// https://www.rfc-editor.org/rfc/rfc9002#section-7.7-6 +func pacerBytesForInterval(interval time.Duration, congestionWindow int, rtt time.Duration) int { + bytes := (int64(interval) * int64(congestionWindow)) / int64(rtt) + bytes = (bytes * 5) / 4 // bytes *= 1.25 + return int(bytes) +} + +// pacerIntervalForBytes returns the amount of time required for a number of bytes. +// +// time_per_byte = (smoothed_rtt / congestion_window) / 1.25 +// interval = time_per_byte * bytes +// +// https://www.rfc-editor.org/rfc/rfc9002#section-7.7-8 +func pacerIntervalForBytes(bytes int, congestionWindow int, rtt time.Duration) time.Duration { + interval := (int64(rtt) * int64(bytes)) / int64(congestionWindow) + interval = (interval * 4) / 5 // interval /= 1.25 + return time.Duration(interval) +} + +// advance is called when time passes. +func (p *pacerState) advance(now time.Time, congestionWindow int, rtt time.Duration) { + elapsed := now.Sub(p.lastUpdate) + if elapsed < 0 { + // Time has gone backward? + elapsed = 0 + p.nextSend = now // allow a packet through to get back on track + if p.bucket < 0 { + p.bucket = 0 + } + } + p.lastUpdate = now + if rtt == 0 { + // Avoid divide by zero in the implausible case that we measure no RTT. + p.bucket = p.maxBucket + return + } + // Refill the bucket. + delta := pacerBytesForInterval(elapsed, congestionWindow, rtt) + p.bucket = min(p.bucket+delta, p.maxBucket) +} + +// packetSent is called to record transmission of a packet. +func (p *pacerState) packetSent(now time.Time, size, congestionWindow int, rtt time.Duration) { + p.bucket -= size + if p.bucket < -congestionWindow { + // Never allow the bucket to fall more than one congestion window in arrears. + // We can only fall this far behind if the sender is sending unpaced packets, + // the congestion window has been exceeded, or the RTT is less than the + // timer granularity. + // + // Limiting the minimum bucket size limits the maximum pacer delay + // to RTT/1.25. + p.bucket = -congestionWindow + } + if p.bucket >= 0 { + p.nextSend = now + return + } + // Next send occurs when the bucket has refilled to 0. + delay := pacerIntervalForBytes(-p.bucket, congestionWindow, rtt) + p.nextSend = now.Add(delay) +} + +// canSend reports whether a packet can be sent now. +// If it returns false, next is the time when the next packet can be sent. +func (p *pacerState) canSend(now time.Time) (canSend bool, next time.Time) { + // If the next send time is within the timer granularity, send immediately. + if p.nextSend.After(now.Add(p.timerGranularity)) { + return false, p.nextSend + } + return true, time.Time{} +} diff --git a/internal/quic/pacer_test.go b/internal/quic/pacer_test.go new file mode 100644 index 000000000..9c69da038 --- /dev/null +++ b/internal/quic/pacer_test.go @@ -0,0 +1,224 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "testing" + "time" +) + +func TestPacerStartup(t *testing.T) { + p := &pacerTest{ + cwnd: 10000, + rtt: 100 * time.Millisecond, + timerGranularity: 1 * time.Millisecond, + } + p.init(t) + t.Logf("# initial burst permits sending ten packets") + for i := 0; i < 10; i++ { + p.sendPacket(1000) + } + + t.Logf("# empty bucket allows for one more packet") + p.sendPacket(1000) + + t.Logf("# sending 1000 byte packets with 8ms interval:") + t.Logf("# (smoothed_rtt * packet_size / congestion_window) / 1.25") + t.Logf("# (100ms * 1000 / 10000) / 1.25 = 8ms") + p.wantSendDelay(8 * time.Millisecond) + p.advance(8 * time.Millisecond) + p.sendPacket(1000) + p.wantSendDelay(8 * time.Millisecond) + + t.Logf("# accumulate enough window for two packets") + p.advance(16 * time.Millisecond) + p.sendPacket(1000) + p.sendPacket(1000) + p.wantSendDelay(8 * time.Millisecond) + + t.Logf("# window does not grow to more than burst limit") + p.advance(1 * time.Second) + for i := 0; i < 11; i++ { + p.sendPacket(1000) + } + p.wantSendDelay(8 * time.Millisecond) +} + +func TestPacerTimerGranularity(t *testing.T) { + p := &pacerTest{ + cwnd: 10000, + rtt: 100 * time.Millisecond, + timerGranularity: 1 * time.Millisecond, + } + p.init(t) + t.Logf("# consume initial burst") + for i := 0; i < 11; i++ { + p.sendPacket(1000) + } + p.wantSendDelay(8 * time.Millisecond) + + t.Logf("# small advance in time does not permit sending") + p.advance(4 * time.Millisecond) + p.wantSendDelay(4 * time.Millisecond) + + t.Logf("# advancing to within timerGranularity of next send permits send") + p.advance(3 * time.Millisecond) + p.wantSendDelay(0) + + t.Logf("# early send adds skipped delay (1ms) to next send (8ms)") + p.sendPacket(1000) + p.wantSendDelay(9 * time.Millisecond) +} + +func TestPacerChangingRate(t *testing.T) { + p := &pacerTest{ + cwnd: 10000, + rtt: 100 * time.Millisecond, + timerGranularity: 0, + } + p.init(t) + t.Logf("# consume initial burst") + for i := 0; i < 11; i++ { + p.sendPacket(1000) + } + p.wantSendDelay(8 * time.Millisecond) + p.advance(8 * time.Millisecond) + + t.Logf("# set congestion window to 20000, 1000 byte interval is 4ms") + p.cwnd = 20000 + p.sendPacket(1000) + p.wantSendDelay(4 * time.Millisecond) + p.advance(4 * time.Millisecond) + + t.Logf("# set rtt to 200ms, 1000 byte interval is 8ms") + p.rtt = 200 * time.Millisecond + p.sendPacket(1000) + p.wantSendDelay(8 * time.Millisecond) + p.advance(8 * time.Millisecond) + + t.Logf("# set congestion window to 40000, 1000 byte interval is 4ms") + p.cwnd = 40000 + p.advance(8 * time.Millisecond) + p.sendPacket(1000) + p.sendPacket(1000) + p.sendPacket(1000) + p.wantSendDelay(4 * time.Millisecond) +} + +func TestPacerTimeReverses(t *testing.T) { + p := &pacerTest{ + cwnd: 10000, + rtt: 100 * time.Millisecond, + timerGranularity: 0, + } + p.init(t) + t.Logf("# consume initial burst") + for i := 0; i < 11; i++ { + p.sendPacket(1000) + } + p.wantSendDelay(8 * time.Millisecond) + t.Logf("# reverse time") + p.advance(-4 * time.Millisecond) + p.sendPacket(1000) + p.wantSendDelay(8 * time.Millisecond) + p.advance(8 * time.Millisecond) + p.sendPacket(1000) + p.wantSendDelay(8 * time.Millisecond) +} + +func TestPacerZeroRTT(t *testing.T) { + p := &pacerTest{ + cwnd: 10000, + rtt: 0, + timerGranularity: 0, + } + p.init(t) + t.Logf("# with rtt 0, the pacer does not limit sending") + for i := 0; i < 20; i++ { + p.sendPacket(1000) + } + p.advance(1 * time.Second) + for i := 0; i < 20; i++ { + p.sendPacket(1000) + } +} + +func TestPacerZeroCongestionWindow(t *testing.T) { + p := &pacerTest{ + cwnd: 10000, + rtt: 100 * time.Millisecond, + timerGranularity: 0, + } + p.init(t) + p.cwnd = 0 + t.Logf("# with cwnd 0, the pacer does not limit sending") + for i := 0; i < 20; i++ { + p.sendPacket(1000) + } +} + +type pacerTest struct { + t *testing.T + p pacerState + timerGranularity time.Duration + cwnd int + rtt time.Duration + now time.Time +} + +func newPacerTest(t *testing.T, congestionWindow int, rtt time.Duration) *pacerTest { + p := &pacerTest{ + now: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + cwnd: congestionWindow, + rtt: rtt, + } + p.p.init(p.now, congestionWindow, p.timerGranularity) + return p +} + +func (p *pacerTest) init(t *testing.T) { + p.t = t + p.now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) + p.p.init(p.now, p.cwnd, p.timerGranularity) + t.Logf("# initial congestion window: %v", p.cwnd) + t.Logf("# timer granularity: %v", p.timerGranularity) +} + +func (p *pacerTest) advance(d time.Duration) { + p.t.Logf("advance time %v", d) + p.now = p.now.Add(d) + p.p.advance(p.now, p.cwnd, p.rtt) +} + +func (p *pacerTest) sendPacket(size int) { + if canSend, next := p.p.canSend(p.now); !canSend { + p.t.Fatalf("ERROR: pacer unexpectedly blocked send, delay=%v", next.Sub(p.now)) + } + p.t.Logf("send packet of size %v", size) + p.p.packetSent(p.now, size, p.cwnd, p.rtt) +} + +func (p *pacerTest) wantSendDelay(want time.Duration) { + wantCanSend := want == 0 + gotCanSend, next := p.p.canSend(p.now) + var got time.Duration + if !gotCanSend { + got = next.Sub(p.now) + } + p.t.Logf("# pacer send delay: %v", got) + if got != want || gotCanSend != wantCanSend { + p.t.Fatalf("ERROR: pacer send delay = %v (can send: %v); want %v, %v", got, gotCanSend, want, wantCanSend) + } +} + +func (p *pacerTest) sendDelay() time.Duration { + canSend, next := p.p.canSend(p.now) + if canSend { + return 0 + } + return next.Sub(p.now) +} diff --git a/internal/quic/packet.go b/internal/quic/packet.go new file mode 100644 index 000000000..7d69f96d2 --- /dev/null +++ b/internal/quic/packet.go @@ -0,0 +1,248 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "encoding/binary" + "fmt" +) + +// packetType is a QUIC packet type. +// https://www.rfc-editor.org/rfc/rfc9000.html#section-17 +type packetType byte + +const ( + packetTypeInvalid = packetType(iota) + packetTypeInitial + packetType0RTT + packetTypeHandshake + packetTypeRetry + packetType1RTT + packetTypeVersionNegotiation +) + +func (p packetType) String() string { + switch p { + case packetTypeInitial: + return "Initial" + case packetType0RTT: + return "0-RTT" + case packetTypeHandshake: + return "Handshake" + case packetTypeRetry: + return "Retry" + case packetType1RTT: + return "1-RTT" + } + return fmt.Sprintf("unknown packet type %v", byte(p)) +} + +// Bits set in the first byte of a packet. +const ( + headerFormLong = 0x80 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.2.1 + headerFormShort = 0x00 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.3.1-4.2.1 + fixedBit = 0x40 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.4.1 + reservedLongBits = 0x0c // https://www.rfc-editor.org/rfc/rfc9000#section-17.2-8.2.1 + reserved1RTTBits = 0x18 // https://www.rfc-editor.org/rfc/rfc9000#section-17.3.1-4.8.1 + keyPhaseBit = 0x04 // https://www.rfc-editor.org/rfc/rfc9000#section-17.3.1-4.10.1 +) + +// Long Packet Type bits. +// https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2-3.6.1 +const ( + longPacketTypeInitial = 0 << 4 + longPacketType0RTT = 1 << 4 + longPacketTypeHandshake = 2 << 4 + longPacketTypeRetry = 3 << 4 +) + +// Frame types. +// https://www.rfc-editor.org/rfc/rfc9000.html#section-19 +const ( + frameTypePadding = 0x00 + frameTypePing = 0x01 + frameTypeAck = 0x02 + frameTypeAckECN = 0x03 + frameTypeResetStream = 0x04 + frameTypeStopSending = 0x05 + frameTypeCrypto = 0x06 + frameTypeNewToken = 0x07 + frameTypeStreamBase = 0x08 // low three bits carry stream flags + frameTypeMaxData = 0x10 + frameTypeMaxStreamData = 0x11 + frameTypeMaxStreamsBidi = 0x12 + frameTypeMaxStreamsUni = 0x13 + frameTypeDataBlocked = 0x14 + frameTypeStreamDataBlocked = 0x15 + frameTypeStreamsBlockedBidi = 0x16 + frameTypeStreamsBlockedUni = 0x17 + frameTypeNewConnectionID = 0x18 + frameTypeRetireConnectionID = 0x19 + frameTypePathChallenge = 0x1a + frameTypePathResponse = 0x1b + frameTypeConnectionCloseTransport = 0x1c + frameTypeConnectionCloseApplication = 0x1d + frameTypeHandshakeDone = 0x1e +) + +// The low three bits of STREAM frames. +// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8 +const ( + streamOffBit = 0x04 + streamLenBit = 0x02 + streamFinBit = 0x01 +) + +// isLongHeader returns true if b is the first byte of a long header. +func isLongHeader(b byte) bool { + return b&headerFormLong == headerFormLong +} + +// getPacketType returns the type of a packet. +func getPacketType(b []byte) packetType { + if len(b) == 0 { + return packetTypeInvalid + } + if !isLongHeader(b[0]) { + if b[0]&fixedBit != fixedBit { + return packetTypeInvalid + } + return packetType1RTT + } + if len(b) < 5 { + return packetTypeInvalid + } + if b[1] == 0 && b[2] == 0 && b[3] == 0 && b[4] == 0 { + // Version Negotiation packets don't necessarily set the fixed bit. + return packetTypeVersionNegotiation + } + if b[0]&fixedBit != fixedBit { + return packetTypeInvalid + } + switch b[0] & 0x30 { + case longPacketTypeInitial: + return packetTypeInitial + case longPacketType0RTT: + return packetType0RTT + case longPacketTypeHandshake: + return packetTypeHandshake + case longPacketTypeRetry: + return packetTypeRetry + } + return packetTypeInvalid +} + +// dstConnIDForDatagram returns the destination connection ID field of the +// first QUIC packet in a datagram. +func dstConnIDForDatagram(pkt []byte) (id []byte, ok bool) { + if len(pkt) < 1 { + return nil, false + } + var n int + var b []byte + if isLongHeader(pkt[0]) { + if len(pkt) < 6 { + return nil, false + } + n = int(pkt[5]) + b = pkt[6:] + } else { + n = connIDLen + b = pkt[1:] + } + if len(b) < n { + return nil, false + } + return b[:n], true +} + +// parseVersionNegotiation parses a Version Negotiation packet. +// The returned versions is a slice of big-endian uint32s. +// It returns (nil, nil, nil) for an invalid packet. +func parseVersionNegotiation(pkt []byte) (dstConnID, srcConnID, versions []byte) { + p, ok := parseGenericLongHeaderPacket(pkt) + if !ok { + return nil, nil, nil + } + if len(p.data)%4 != 0 { + return nil, nil, nil + } + return p.dstConnID, p.srcConnID, p.data +} + +// appendVersionNegotiation appends a Version Negotiation packet to pkt, +// returning the result. +func appendVersionNegotiation(pkt, dstConnID, srcConnID []byte, versions ...uint32) []byte { + pkt = append(pkt, headerFormLong|fixedBit) // header byte + pkt = append(pkt, 0, 0, 0, 0) // Version (0 for Version Negotiation) + pkt = appendUint8Bytes(pkt, dstConnID) // Destination Connection ID + pkt = appendUint8Bytes(pkt, srcConnID) // Source Connection ID + for _, v := range versions { + pkt = binary.BigEndian.AppendUint32(pkt, v) // Supported Version + } + return pkt +} + +// A longPacket is a long header packet. +type longPacket struct { + ptype packetType + version uint32 + num packetNumber + dstConnID []byte + srcConnID []byte + payload []byte + + // The extra data depends on the packet type: + // Initial: Token. + // Retry: Retry token and integrity tag. + extra []byte +} + +// A shortPacket is a short header (1-RTT) packet. +type shortPacket struct { + num packetNumber + payload []byte +} + +// A genericLongPacket is a long header packet of an arbitrary QUIC version. +// https://www.rfc-editor.org/rfc/rfc8999#section-5.1 +type genericLongPacket struct { + version uint32 + dstConnID []byte + srcConnID []byte + data []byte +} + +func parseGenericLongHeaderPacket(b []byte) (p genericLongPacket, ok bool) { + if len(b) < 5 || !isLongHeader(b[0]) { + return genericLongPacket{}, false + } + b = b[1:] + // Version (32), + var n int + p.version, n = consumeUint32(b) + if n < 0 { + return genericLongPacket{}, false + } + b = b[n:] + // Destination Connection ID Length (8), + // Destination Connection ID (0..2048), + p.dstConnID, n = consumeUint8Bytes(b) + if n < 0 || len(p.dstConnID) > 2048/8 { + return genericLongPacket{}, false + } + b = b[n:] + // Source Connection ID Length (8), + // Source Connection ID (0..2048), + p.srcConnID, n = consumeUint8Bytes(b) + if n < 0 || len(p.dstConnID) > 2048/8 { + return genericLongPacket{}, false + } + b = b[n:] + p.data = b + return p, true +} diff --git a/internal/quic/packet_codec_test.go b/internal/quic/packet_codec_test.go new file mode 100644 index 000000000..7b01bb00d --- /dev/null +++ b/internal/quic/packet_codec_test.go @@ -0,0 +1,721 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "crypto/tls" + "reflect" + "testing" +) + +func TestParseLongHeaderPacket(t *testing.T) { + // Example Initial packet from: + // https://www.rfc-editor.org/rfc/rfc9001.html#section-a.3 + cid := unhex(`8394c8f03e515708`) + initialServerKeys := initialKeys(cid, clientSide).r + pkt := unhex(` + cf000000010008f067a5502a4262b500 4075c0d95a482cd0991cd25b0aac406a + 5816b6394100f37a1c69797554780bb3 8cc5a99f5ede4cf73c3ec2493a1839b3 + dbcba3f6ea46c5b7684df3548e7ddeb9 c3bf9c73cc3f3bded74b562bfb19fb84 + 022f8ef4cdd93795d77d06edbb7aaf2f 58891850abbdca3d20398c276456cbc4 + 2158407dd074ee + `) + want := longPacket{ + ptype: packetTypeInitial, + version: 1, + num: 1, + dstConnID: []byte{}, + srcConnID: unhex(`f067a5502a4262b5`), + payload: unhex(` + 02000000000600405a020000560303ee fce7f7b37ba1d1632e96677825ddf739 + 88cfc79825df566dc5430b9a045a1200 130100002e00330024001d00209d3c94 + 0d89690b84d08a60993c144eca684d10 81287c834d5311bcf32bb9da1a002b00 + 020304 + `), + extra: []byte{}, + } + + // Parse the packet. + got, n := parseLongHeaderPacket(pkt, initialServerKeys, 0) + if n != len(pkt) { + t.Errorf("parseLongHeaderPacket: n=%v, want %v", n, len(pkt)) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("parseLongHeaderPacket:\n got: %+v\nwant: %+v", got, want) + } + + // Skip the packet. + if got, want := skipLongHeaderPacket(pkt), len(pkt); got != want { + t.Errorf("skipLongHeaderPacket: n=%v, want %v", got, want) + } + + // Parse truncated versions of the packet; every attempt should fail. + for i := 0; i < len(pkt); i++ { + if _, n := parseLongHeaderPacket(pkt[:i], initialServerKeys, 0); n != -1 { + t.Fatalf("parse truncated long header packet: n=%v, want -1\ninput: %x", n, pkt[:i]) + } + if n := skipLongHeaderPacket(pkt[:i]); n != -1 { + t.Errorf("skip truncated long header packet: n=%v, want -1", n) + } + } + + // Parse with the wrong keys. + invalidKeys := initialKeys([]byte{}, clientSide).w + if _, n := parseLongHeaderPacket(pkt, invalidKeys, 0); n != -1 { + t.Fatalf("parse long header packet with wrong keys: n=%v, want -1", n) + } +} + +func TestRoundtripEncodeLongPacket(t *testing.T) { + var aes128Keys, aes256Keys, chachaKeys fixedKeys + aes128Keys.init(tls.TLS_AES_128_GCM_SHA256, []byte("secret")) + aes256Keys.init(tls.TLS_AES_256_GCM_SHA384, []byte("secret")) + chachaKeys.init(tls.TLS_CHACHA20_POLY1305_SHA256, []byte("secret")) + for _, test := range []struct { + desc string + p longPacket + k fixedKeys + }{{ + desc: "Initial, 1-byte number, AES128", + p: longPacket{ + ptype: packetTypeInitial, + version: 0x11223344, + num: 0, // 1-byte encodeing + dstConnID: []byte{1, 2, 3, 4}, + srcConnID: []byte{5, 6, 7, 8}, + payload: []byte("payload"), + extra: []byte("token"), + }, + k: aes128Keys, + }, { + desc: "0-RTT, 2-byte number, AES256", + p: longPacket{ + ptype: packetType0RTT, + version: 0x11223344, + num: 0x100, // 2-byte encoding + dstConnID: []byte{1, 2, 3, 4}, + srcConnID: []byte{5, 6, 7, 8}, + payload: []byte("payload"), + }, + k: aes256Keys, + }, { + desc: "0-RTT, 3-byte number, AES256", + p: longPacket{ + ptype: packetType0RTT, + version: 0x11223344, + num: 0x10000, // 2-byte encoding + dstConnID: []byte{1, 2, 3, 4}, + srcConnID: []byte{5, 6, 7, 8}, + payload: []byte{0}, + }, + k: aes256Keys, + }, { + desc: "Handshake, 4-byte number, ChaCha20Poly1305", + p: longPacket{ + ptype: packetTypeHandshake, + version: 0x11223344, + num: 0x1000000, // 4-byte encoding + dstConnID: []byte{1, 2, 3, 4}, + srcConnID: []byte{5, 6, 7, 8}, + payload: []byte("payload"), + }, + k: chachaKeys, + }} { + t.Run(test.desc, func(t *testing.T) { + var w packetWriter + w.reset(1200) + w.startProtectedLongHeaderPacket(0, test.p) + w.b = append(w.b, test.p.payload...) + w.finishProtectedLongHeaderPacket(0, test.k, test.p) + pkt := w.datagram() + + got, n := parseLongHeaderPacket(pkt, test.k, 0) + if n != len(pkt) { + t.Errorf("parseLongHeaderPacket: n=%v, want %v", n, len(pkt)) + } + if !reflect.DeepEqual(got, test.p) { + t.Errorf("Round-trip encode/decode did not preserve packet.\nsent: %+v\n got: %+v\nwire: %x", test.p, got, pkt) + } + }) + } +} + +func TestRoundtripEncodeShortPacket(t *testing.T) { + var aes128Keys, aes256Keys, chachaKeys updatingKeyPair + aes128Keys.r.init(tls.TLS_AES_128_GCM_SHA256, []byte("secret")) + aes256Keys.r.init(tls.TLS_AES_256_GCM_SHA384, []byte("secret")) + chachaKeys.r.init(tls.TLS_CHACHA20_POLY1305_SHA256, []byte("secret")) + aes128Keys.w = aes128Keys.r + aes256Keys.w = aes256Keys.r + chachaKeys.w = chachaKeys.r + aes128Keys.updateAfter = maxPacketNumber + aes256Keys.updateAfter = maxPacketNumber + chachaKeys.updateAfter = maxPacketNumber + connID := make([]byte, connIDLen) + for i := range connID { + connID[i] = byte(i) + } + for _, test := range []struct { + desc string + num packetNumber + payload []byte + k updatingKeyPair + }{{ + desc: "1-byte number, AES128", + num: 0, // 1-byte encoding, + payload: []byte("payload"), + k: aes128Keys, + }, { + desc: "2-byte number, AES256", + num: 0x100, // 2-byte encoding + payload: []byte("payload"), + k: aes256Keys, + }, { + desc: "3-byte number, ChaCha20Poly1305", + num: 0x10000, // 3-byte encoding + payload: []byte("payload"), + k: chachaKeys, + }, { + desc: "4-byte number, ChaCha20Poly1305", + num: 0x1000000, // 4-byte encoding + payload: []byte{0}, + k: chachaKeys, + }} { + t.Run(test.desc, func(t *testing.T) { + var w packetWriter + w.reset(1200) + w.start1RTTPacket(test.num, 0, connID) + w.b = append(w.b, test.payload...) + w.finish1RTTPacket(test.num, 0, connID, &test.k) + pkt := w.datagram() + p, err := parse1RTTPacket(pkt, &test.k, connIDLen, 0) + if err != nil { + t.Errorf("parse1RTTPacket: err=%v, want nil", err) + } + if p.num != test.num || !bytes.Equal(p.payload, test.payload) { + t.Errorf("Round-trip encode/decode did not preserve packet.\nsent: num=%v, payload={%x}\ngot: num=%v, payload={%x}", test.num, test.payload, p.num, p.payload) + } + }) + } +} + +func TestFrameEncodeDecode(t *testing.T) { + for _, test := range []struct { + s string + f debugFrame + b []byte + truncated []byte + }{{ + s: "PADDING*1", + f: debugFramePadding{ + size: 1, + }, + b: []byte{ + 0x00, // Type (i) = 0x00, + + }, + }, { + s: "PING", + f: debugFramePing{}, + b: []byte{ + 0x01, // TYPE(i) = 0x01 + }, + }, { + s: "ACK Delay=10 [0,16) [17,32) [48,64)", + f: debugFrameAck{ + ackDelay: 10, + ranges: []i64range[packetNumber]{ + {0x00, 0x10}, + {0x11, 0x20}, + {0x30, 0x40}, + }, + }, + b: []byte{ + 0x02, // TYPE (i) = 0x02 + 0x3f, // Largest Acknowledged (i) + 10, // ACK Delay (i) + 0x02, // ACK Range Count (i) + 0x0f, // First ACK Range (i) + 0x0f, // Gap (i) + 0x0e, // ACK Range Length (i) + 0x00, // Gap (i) + 0x0f, // ACK Range Length (i) + }, + truncated: []byte{ + 0x02, // TYPE (i) = 0x02 + 0x3f, // Largest Acknowledged (i) + 10, // ACK Delay (i) + 0x01, // ACK Range Count (i) + 0x0f, // First ACK Range (i) + 0x0f, // Gap (i) + 0x0e, // ACK Range Length (i) + }, + }, { + s: "RESET_STREAM ID=1 Code=2 FinalSize=3", + f: debugFrameResetStream{ + id: 1, + code: 2, + finalSize: 3, + }, + b: []byte{ + 0x04, // TYPE(i) = 0x04 + 0x01, // Stream ID (i), + 0x02, // Application Protocol Error Code (i), + 0x03, // Final Size (i), + }, + }, { + s: "STOP_SENDING ID=1 Code=2", + f: debugFrameStopSending{ + id: 1, + code: 2, + }, + b: []byte{ + 0x05, // TYPE(i) = 0x05 + 0x01, // Stream ID (i), + 0x02, // Application Protocol Error Code (i), + }, + }, { + s: "CRYPTO Offset=1 Length=2", + f: debugFrameCrypto{ + off: 1, + data: []byte{3, 4}, + }, + b: []byte{ + 0x06, // Type (i) = 0x06, + 0x01, // Offset (i), + 0x02, // Length (i), + 0x03, 0x04, // Crypto Data (..), + }, + truncated: []byte{ + 0x06, // Type (i) = 0x06, + 0x01, // Offset (i), + 0x01, // Length (i), + 0x03, + }, + }, { + s: "NEW_TOKEN Token=0304", + f: debugFrameNewToken{ + token: []byte{3, 4}, + }, + b: []byte{ + 0x07, // Type (i) = 0x07, + 0x02, // Token Length (i), + 0x03, 0x04, // Token (..), + }, + }, { + s: "STREAM ID=1 Offset=0 Length=0", + f: debugFrameStream{ + id: 1, + fin: false, + off: 0, + data: []byte{}, + }, + b: []byte{ + 0x0a, // Type (i) = 0x08..0x0f, + 0x01, // Stream ID (i), + // [Offset (i)], + 0x00, // [Length (i)], + // Stream Data (..), + }, + }, { + s: "STREAM ID=100 Offset=4 Length=3", + f: debugFrameStream{ + id: 100, + fin: false, + off: 4, + data: []byte{0xa0, 0xa1, 0xa2}, + }, + b: []byte{ + 0x0e, // Type (i) = 0x08..0x0f, + 0x40, 0x64, // Stream ID (i), + 0x04, // [Offset (i)], + 0x03, // [Length (i)], + 0xa0, 0xa1, 0xa2, // Stream Data (..), + }, + truncated: []byte{ + 0x0e, // Type (i) = 0x08..0x0f, + 0x40, 0x64, // Stream ID (i), + 0x04, // [Offset (i)], + 0x02, // [Length (i)], + 0xa0, 0xa1, // Stream Data (..), + }, + }, { + s: "STREAM ID=100 FIN Offset=4 Length=3", + f: debugFrameStream{ + id: 100, + fin: true, + off: 4, + data: []byte{0xa0, 0xa1, 0xa2}, + }, + b: []byte{ + 0x0f, // Type (i) = 0x08..0x0f, + 0x40, 0x64, // Stream ID (i), + 0x04, // [Offset (i)], + 0x03, // [Length (i)], + 0xa0, 0xa1, 0xa2, // Stream Data (..), + }, + truncated: []byte{ + 0x0e, // Type (i) = 0x08..0x0f, + 0x40, 0x64, // Stream ID (i), + 0x04, // [Offset (i)], + 0x02, // [Length (i)], + 0xa0, 0xa1, // Stream Data (..), + }, + }, { + s: "STREAM ID=1 FIN Offset=100 Length=0", + f: debugFrameStream{ + id: 1, + fin: true, + off: 100, + data: []byte{}, + }, + b: []byte{ + 0x0f, // Type (i) = 0x08..0x0f, + 0x01, // Stream ID (i), + 0x40, 0x64, // [Offset (i)], + 0x00, // [Length (i)], + // Stream Data (..), + }, + }, { + s: "MAX_DATA Max=10", + f: debugFrameMaxData{ + max: 10, + }, + b: []byte{ + 0x10, // Type (i) = 0x10, + 0x0a, // Maximum Data (i), + }, + }, { + s: "MAX_STREAM_DATA ID=1 Max=10", + f: debugFrameMaxStreamData{ + id: 1, + max: 10, + }, + b: []byte{ + 0x11, // Type (i) = 0x11, + 0x01, // Stream ID (i), + 0x0a, // Maximum Stream Data (i), + }, + }, { + s: "MAX_STREAMS Type=bidi Max=1", + f: debugFrameMaxStreams{ + streamType: bidiStream, + max: 1, + }, + b: []byte{ + 0x12, // Type (i) = 0x12..0x13, + 0x01, // Maximum Streams (i), + }, + }, { + s: "MAX_STREAMS Type=uni Max=1", + f: debugFrameMaxStreams{ + streamType: uniStream, + max: 1, + }, + b: []byte{ + 0x13, // Type (i) = 0x12..0x13, + 0x01, // Maximum Streams (i), + }, + }, { + s: "DATA_BLOCKED Max=1", + f: debugFrameDataBlocked{ + max: 1, + }, + b: []byte{ + 0x14, // Type (i) = 0x14, + 0x01, // Maximum Data (i), + }, + }, { + s: "STREAM_DATA_BLOCKED ID=1 Max=2", + f: debugFrameStreamDataBlocked{ + id: 1, + max: 2, + }, + b: []byte{ + 0x15, // Type (i) = 0x15, + 0x01, // Stream ID (i), + 0x02, // Maximum Stream Data (i), + }, + }, { + s: "STREAMS_BLOCKED Type=bidi Max=1", + f: debugFrameStreamsBlocked{ + streamType: bidiStream, + max: 1, + }, + b: []byte{ + 0x16, // Type (i) = 0x16..0x17, + 0x01, // Maximum Streams (i), + }, + }, { + s: "STREAMS_BLOCKED Type=uni Max=1", + f: debugFrameStreamsBlocked{ + streamType: uniStream, + max: 1, + }, + b: []byte{ + 0x17, // Type (i) = 0x16..0x17, + 0x01, // Maximum Streams (i), + }, + }, { + s: "NEW_CONNECTION_ID Seq=3 Retire=2 ID=a0a1a2a3 Token=0102030405060708090a0b0c0d0e0f10", + f: debugFrameNewConnectionID{ + seq: 3, + retirePriorTo: 2, + connID: []byte{0xa0, 0xa1, 0xa2, 0xa3}, + token: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, + }, + b: []byte{ + 0x18, // Type (i) = 0x18, + 0x03, // Sequence Number (i), + 0x02, // Retire Prior To (i), + 0x04, // Length (8), + 0xa0, 0xa1, 0xa2, 0xa3, // Connection ID (8..160), + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // Stateless Reset Token (128), + }, + }, { + s: "RETIRE_CONNECTION_ID Seq=1", + f: debugFrameRetireConnectionID{ + seq: 1, + }, + b: []byte{ + 0x19, // Type (i) = 0x19, + 0x01, // Sequence Number (i), + }, + }, { + s: "PATH_CHALLENGE Data=0123456789abcdef", + f: debugFramePathChallenge{ + data: 0x0123456789abcdef, + }, + b: []byte{ + 0x1a, // Type (i) = 0x1a, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data (64), + }, + }, { + s: "PATH_RESPONSE Data=0123456789abcdef", + f: debugFramePathResponse{ + data: 0x0123456789abcdef, + }, + b: []byte{ + 0x1b, // Type (i) = 0x1b, + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, // Data (64), + }, + }, { + s: `CONNECTION_CLOSE Code=INTERNAL_ERROR FrameType=2 Reason="oops"`, + f: debugFrameConnectionCloseTransport{ + code: 1, + frameType: 2, + reason: "oops", + }, + b: []byte{ + 0x1c, // Type (i) = 0x1c..0x1d, + 0x01, // Error Code (i), + 0x02, // [Frame Type (i)], + 0x04, // Reason Phrase Length (i), + 'o', 'o', 'p', 's', // Reason Phrase (..), + }, + }, { + s: `CONNECTION_CLOSE AppCode=1 Reason="oops"`, + f: debugFrameConnectionCloseApplication{ + code: 1, + reason: "oops", + }, + b: []byte{ + 0x1d, // Type (i) = 0x1c..0x1d, + 0x01, // Error Code (i), + 0x04, // Reason Phrase Length (i), + 'o', 'o', 'p', 's', // Reason Phrase (..), + }, + }, { + s: "HANDSHAKE_DONE", + f: debugFrameHandshakeDone{}, + b: []byte{ + 0x1e, // Type (i) = 0x1e, + }, + }} { + var w packetWriter + w.reset(1200) + w.start1RTTPacket(0, 0, nil) + w.pktLim = w.payOff + len(test.b) + if added := test.f.write(&w); !added { + t.Errorf("encoding %v with %v bytes available: write unexpectedly failed", test.f, len(test.b)) + } + if got, want := w.payload(), test.b; !bytes.Equal(got, want) { + t.Errorf("encoding %v:\ngot {%x}\nwant {%x}", test.f, got, want) + } + gotf, n := parseDebugFrame(test.b) + if n != len(test.b) || !reflect.DeepEqual(gotf, test.f) { + t.Errorf("decoding {%x}:\ndecoded %v bytes, want %v\ngot: %v\nwant: %v", test.b, n, len(test.b), gotf, test.f) + } + if got, want := test.f.String(), test.s; got != want { + t.Errorf("frame.String():\ngot %q\nwant %q", got, want) + } + + // Try encoding the frame into too little space. + // Most frames will result in an error; some (like STREAM frames) will truncate + // the data written. + w.reset(1200) + w.start1RTTPacket(0, 0, nil) + w.pktLim = w.payOff + len(test.b) - 1 + if added := test.f.write(&w); added { + if test.truncated == nil { + t.Errorf("encoding %v with %v-1 bytes available: write unexpectedly succeeded", test.f, len(test.b)) + } else if got, want := w.payload(), test.truncated; !bytes.Equal(got, want) { + t.Errorf("encoding %v with %v-1 bytes available:\ngot {%x}\nwant {%x}", test.f, len(test.b), got, want) + } + } + + // Try parsing truncated data. + for i := 0; i < len(test.b); i++ { + f, n := parseDebugFrame(test.b[:i]) + if n >= 0 { + t.Errorf("decoding truncated frame {%x}:\ngot: %v\nwant error", test.b[:i], f) + } + } + } +} + +func TestFrameDecode(t *testing.T) { + for _, test := range []struct { + desc string + want debugFrame + b []byte + }{{ + desc: "STREAM frame with LEN bit unset", + want: debugFrameStream{ + id: 1, + fin: false, + data: []byte{0x01, 0x02, 0x03}, + }, + b: []byte{ + 0x08, // Type (i) = 0x08..0x0f, + 0x01, // Stream ID (i), + // [Offset (i)], + // [Length (i)], + 0x01, 0x02, 0x03, // Stream Data (..), + }, + }, { + desc: "ACK frame with ECN counts", + want: debugFrameAck{ + ackDelay: 10, + ranges: []i64range[packetNumber]{ + {0, 1}, + }, + }, + b: []byte{ + 0x03, // TYPE (i) = 0x02..0x03 + 0x00, // Largest Acknowledged (i) + 10, // ACK Delay (i) + 0x00, // ACK Range Count (i) + 0x00, // First ACK Range (i) + 0x01, 0x02, 0x03, // [ECN Counts (..)], + }, + }} { + got, n := parseDebugFrame(test.b) + if n != len(test.b) || !reflect.DeepEqual(got, test.want) { + t.Errorf("decoding {%x}:\ndecoded %v bytes, want %v\ngot: %v\nwant: %v", test.b, n, len(test.b), got, test.want) + } + } +} + +func TestFrameDecodeErrors(t *testing.T) { + for _, test := range []struct { + name string + b []byte + }{{ + name: "ACK [-1,0]", + b: []byte{ + 0x02, // TYPE (i) = 0x02 + 0x00, // Largest Acknowledged (i) + 0x00, // ACK Delay (i) + 0x00, // ACK Range Count (i) + 0x01, // First ACK Range (i) + }, + }, { + name: "ACK [-1,16]", + b: []byte{ + 0x02, // TYPE (i) = 0x02 + 0x10, // Largest Acknowledged (i) + 0x00, // ACK Delay (i) + 0x00, // ACK Range Count (i) + 0x11, // First ACK Range (i) + }, + }, { + name: "ACK [-1,0],[1,2]", + b: []byte{ + 0x02, // TYPE (i) = 0x02 + 0x02, // Largest Acknowledged (i) + 0x00, // ACK Delay (i) + 0x01, // ACK Range Count (i) + 0x00, // First ACK Range (i) + 0x01, // Gap (i) + 0x01, // ACK Range Length (i) + }, + }, { + name: "NEW_TOKEN with zero-length token", + b: []byte{ + 0x07, // Type (i) = 0x07, + 0x00, // Token Length (i), + }, + }, { + name: "MAX_STREAMS with too many streams", + b: func() []byte { + // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.11-5.2.1 + return appendVarint([]byte{frameTypeMaxStreamsBidi}, (1<<60)+1) + }(), + }, { + name: "NEW_CONNECTION_ID too small", + b: []byte{ + 0x18, // Type (i) = 0x18, + 0x03, // Sequence Number (i), + 0x02, // Retire Prior To (i), + 0x00, // Length (8), + // Connection ID (8..160), + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // Stateless Reset Token (128), + }, + }, { + name: "NEW_CONNECTION_ID too large", + b: []byte{ + 0x18, // Type (i) = 0x18, + 0x03, // Sequence Number (i), + 0x02, // Retire Prior To (i), + 21, // Length (8), + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, // Connection ID (8..160), + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // Stateless Reset Token (128), + }, + }, { + name: "NEW_CONNECTION_ID sequence smaller than retire", + b: []byte{ + 0x18, // Type (i) = 0x18, + 0x02, // Sequence Number (i), + 0x03, // Retire Prior To (i), + 0x02, // Length (8), + 0xff, 0xff, // Connection ID (8..160), + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // Stateless Reset Token (128), + }, + }} { + f, n := parseDebugFrame(test.b) + if n >= 0 { + t.Errorf("%v: no error when parsing invalid frame {%x}\ngot: %v", test.name, test.b, f) + } + } +} + +func FuzzParseLongHeaderPacket(f *testing.F) { + cid := unhex(`0000000000000000`) + initialServerKeys := initialKeys(cid, clientSide).r + f.Fuzz(func(t *testing.T, in []byte) { + parseLongHeaderPacket(in, initialServerKeys, 0) + }) +} + +func FuzzFrameDecode(f *testing.F) { + f.Fuzz(func(t *testing.T, in []byte) { + parseDebugFrame(in) + }) +} diff --git a/internal/quic/packet_number.go b/internal/quic/packet_number.go new file mode 100644 index 000000000..206053e58 --- /dev/null +++ b/internal/quic/packet_number.go @@ -0,0 +1,74 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +// A packetNumber is a QUIC packet number. +// Packet numbers are integers in the range [0, 2^62-1]. +// +// https://www.rfc-editor.org/rfc/rfc9000.html#section-12.3 +type packetNumber int64 + +const maxPacketNumber = 1<<62 - 1 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.1-1 + +// decodePacketNumber decodes a truncated packet number, given +// the largest acknowledged packet number in this number space, +// the truncated number received in a packet, and the size of the +// number received in bytes. +// +// https://www.rfc-editor.org/rfc/rfc9000.html#section-17.1 +// https://www.rfc-editor.org/rfc/rfc9000.html#section-a.3 +func decodePacketNumber(largest, truncated packetNumber, numLenInBytes int) packetNumber { + expected := largest + 1 + win := packetNumber(1) << (uint(numLenInBytes) * 8) + hwin := win / 2 + mask := win - 1 + candidate := (expected &^ mask) | truncated + if candidate <= expected-hwin && candidate < (1<<62)-win { + return candidate + win + } + if candidate > expected+hwin && candidate >= win { + return candidate - win + } + return candidate +} + +// appendPacketNumber appends an encoded packet number to b. +// The packet number must be larger than the largest acknowledged packet number. +// When no packets have been acknowledged yet, largestAck is -1. +// +// https://www.rfc-editor.org/rfc/rfc9000.html#section-17.1-5 +func appendPacketNumber(b []byte, pnum, largestAck packetNumber) []byte { + switch packetNumberLength(pnum, largestAck) { + case 1: + return append(b, byte(pnum)) + case 2: + return append(b, byte(pnum>>8), byte(pnum)) + case 3: + return append(b, byte(pnum>>16), byte(pnum>>8), byte(pnum)) + default: + return append(b, byte(pnum>>24), byte(pnum>>16), byte(pnum>>8), byte(pnum)) + } +} + +// packetNumberLength returns the minimum length, in bytes, needed to encode +// a packet number given the largest acknowledged packet number. +// The packet number must be larger than the largest acknowledged packet number. +// +// https://www.rfc-editor.org/rfc/rfc9000.html#section-17.1-5 +func packetNumberLength(pnum, largestAck packetNumber) int { + d := pnum - largestAck + switch { + case d < 0x80: + return 1 + case d < 0x8000: + return 2 + case d < 0x800000: + return 3 + default: + return 4 + } +} diff --git a/internal/quic/packet_number_test.go b/internal/quic/packet_number_test.go new file mode 100644 index 000000000..4d8516ae6 --- /dev/null +++ b/internal/quic/packet_number_test.go @@ -0,0 +1,185 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "encoding/binary" + "testing" +) + +func TestDecodePacketNumber(t *testing.T) { + for _, test := range []struct { + largest packetNumber + truncated packetNumber + want packetNumber + size int + }{{ + largest: 0, + truncated: 1, + size: 4, + want: 1, + }, { + largest: 0, + truncated: 0, + size: 1, + want: 0, + }, { + largest: 0x00, + truncated: 0x01, + size: 1, + want: 0x01, + }, { + largest: 0x00, + truncated: 0xff, + size: 1, + want: 0xff, + }, { + largest: 0xff, + truncated: 0x01, + size: 1, + want: 0x101, + }, { + largest: 0x1000, + truncated: 0xff, + size: 1, + want: 0xfff, + }, { + largest: 0xa82f30ea, + truncated: 0x9b32, + size: 2, + want: 0xa82f9b32, + }} { + got := decodePacketNumber(test.largest, test.truncated, test.size) + if got != test.want { + t.Errorf("decodePacketNumber(largest=0x%x, truncated=0x%x, size=%v) = 0x%x, want 0x%x", test.largest, test.truncated, test.size, got, test.want) + } + } +} + +func TestEncodePacketNumber(t *testing.T) { + for _, test := range []struct { + largestAcked packetNumber + pnum packetNumber + wantSize int + }{{ + largestAcked: -1, + pnum: 0, + wantSize: 1, + }, { + largestAcked: 1000, + pnum: 1000 + 0x7f, + wantSize: 1, + }, { + largestAcked: 1000, + pnum: 1000 + 0x80, // 0x468 + wantSize: 2, + }, { + largestAcked: 0x12345678, + pnum: 0x12345678 + 0x7fff, // 0x305452663 + wantSize: 2, + }, { + largestAcked: 0x12345678, + pnum: 0x12345678 + 0x8000, + wantSize: 3, + }, { + largestAcked: 0, + pnum: 0x7fffff, + wantSize: 3, + }, { + largestAcked: 0, + pnum: 0x800000, + wantSize: 4, + }, { + largestAcked: 0xabe8bc, + pnum: 0xac5c02, + wantSize: 2, + }, { + largestAcked: 0xabe8bc, + pnum: 0xace8fe, + wantSize: 3, + }} { + size := packetNumberLength(test.pnum, test.largestAcked) + if got, want := size, test.wantSize; got != want { + t.Errorf("packetNumberLength(num=%x, maxAck=%x) = %v, want %v", test.pnum, test.largestAcked, got, want) + } + var enc packetNumber + switch size { + case 1: + enc = test.pnum & 0xff + case 2: + enc = test.pnum & 0xffff + case 3: + enc = test.pnum & 0xffffff + case 4: + enc = test.pnum & 0xffffffff + } + wantBytes := binary.BigEndian.AppendUint32(nil, uint32(enc))[4-size:] + gotBytes := appendPacketNumber(nil, test.pnum, test.largestAcked) + if !bytes.Equal(gotBytes, wantBytes) { + t.Errorf("appendPacketNumber(num=%v, maxAck=%x) = {%x}, want {%x}", test.pnum, test.largestAcked, gotBytes, wantBytes) + } + gotNum := decodePacketNumber(test.largestAcked, enc, size) + if got, want := gotNum, test.pnum; got != want { + t.Errorf("packetNumberLength(num=%x, maxAck=%x) = %v, but decoded number=%x", test.pnum, test.largestAcked, size, got) + } + } +} + +func FuzzPacketNumber(f *testing.F) { + truncatedNumber := func(in []byte) packetNumber { + var truncated packetNumber + for _, b := range in { + truncated = (truncated << 8) | packetNumber(b) + } + return truncated + } + f.Fuzz(func(t *testing.T, in []byte, largestAckedInt64 int64) { + largestAcked := packetNumber(largestAckedInt64) + if len(in) < 1 || len(in) > 4 || largestAcked < 0 || largestAcked > maxPacketNumber { + return + } + truncatedIn := truncatedNumber(in) + decoded := decodePacketNumber(largestAcked, truncatedIn, len(in)) + + // Check that the decoded packet number's least significant bits match the input. + var mask packetNumber + for i := 0; i < len(in); i++ { + mask = (mask << 8) | 0xff + } + if truncatedIn != decoded&mask { + t.Fatalf("decoding mismatch: input=%x largestAcked=%v decoded=0x%x", in, largestAcked, decoded) + } + + // We don't support encoding packet numbers less than largestAcked (since packet numbers + // never decrease), so skip the encoder tests if this would make us go backwards. + if decoded < largestAcked { + return + } + + // We might encode this number using a different length than we received, + // but the common portions should match. + encoded := appendPacketNumber(nil, decoded, largestAcked) + a, b := in, encoded + if len(b) < len(a) { + a, b = b, a + } + for len(a) < len(b) { + b = b[1:] + } + if len(a) == 0 || !bytes.Equal(a, b) { + t.Fatalf("encoding mismatch: input=%x largestAcked=%v decoded=%v reencoded=%x", in, largestAcked, decoded, encoded) + } + + if g := decodePacketNumber(largestAcked, truncatedNumber(encoded), len(encoded)); g != decoded { + t.Fatalf("packet encode/decode mismatch: pnum=%v largestAcked=%v encoded=%x got=%v", decoded, largestAcked, encoded, g) + } + if l := packetNumberLength(decoded, largestAcked); l != len(encoded) { + t.Fatalf("packet number length mismatch: pnum=%v largestAcked=%v encoded=%x len=%v", decoded, largestAcked, encoded, l) + } + }) +} diff --git a/internal/quic/packet_parser.go b/internal/quic/packet_parser.go new file mode 100644 index 000000000..ce0433902 --- /dev/null +++ b/internal/quic/packet_parser.go @@ -0,0 +1,520 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +// parseLongHeaderPacket parses a QUIC long header packet. +// +// It does not parse Version Negotiation packets. +// +// On input, pkt contains a long header packet (possibly followed by more packets), +// k the decryption keys for the packet, and pnumMax the largest packet number seen +// in the number space of this packet. +// +// parseLongHeaderPacket returns the parsed packet with protection removed +// and its length in bytes. +// +// It returns an empty packet and -1 if the packet could not be parsed. +func parseLongHeaderPacket(pkt []byte, k fixedKeys, pnumMax packetNumber) (p longPacket, n int) { + if len(pkt) < 5 || !isLongHeader(pkt[0]) { + return longPacket{}, -1 + } + + // Header Form (1) = 1, + // Fixed Bit (1) = 1, + // Long Packet Type (2), + // Type-Specific Bits (4), + b := pkt + p.ptype = getPacketType(b) + if p.ptype == packetTypeInvalid { + return longPacket{}, -1 + } + b = b[1:] + // Version (32), + p.version, n = consumeUint32(b) + if n < 0 { + return longPacket{}, -1 + } + b = b[n:] + if p.version == 0 { + // Version Negotiation packet; not handled here. + return longPacket{}, -1 + } + + // Destination Connection ID Length (8), + // Destination Connection ID (0..160), + p.dstConnID, n = consumeUint8Bytes(b) + if n < 0 || len(p.dstConnID) > 20 { + return longPacket{}, -1 + } + b = b[n:] + + // Source Connection ID Length (8), + // Source Connection ID (0..160), + p.srcConnID, n = consumeUint8Bytes(b) + if n < 0 || len(p.dstConnID) > 20 { + return longPacket{}, -1 + } + b = b[n:] + + switch p.ptype { + case packetTypeInitial: + // Token Length (i), + // Token (..), + p.extra, n = consumeVarintBytes(b) + if n < 0 { + return longPacket{}, -1 + } + b = b[n:] + case packetTypeRetry: + // Retry Token (..), + // Retry Integrity Tag (128), + p.extra = b + return p, len(pkt) + } + + // Length (i), + payLen, n := consumeVarint(b) + if n < 0 { + return longPacket{}, -1 + } + b = b[n:] + if uint64(len(b)) < payLen { + return longPacket{}, -1 + } + + // Packet Number (8..32), + // Packet Payload (..), + pnumOff := len(pkt) - len(b) + pkt = pkt[:pnumOff+int(payLen)] + + if k.isSet() { + var err error + p.payload, p.num, err = k.unprotect(pkt, pnumOff, pnumMax) + if err != nil { + return longPacket{}, -1 + } + } + return p, len(pkt) +} + +// skipLongHeaderPacket returns the length of the long header packet at the start of pkt, +// or -1 if the buffer does not contain a valid packet. +func skipLongHeaderPacket(pkt []byte) int { + // Header byte, 4 bytes of version. + n := 5 + if len(pkt) <= n { + return -1 + } + // Destination connection ID length, destination connection ID. + n += 1 + int(pkt[n]) + if len(pkt) <= n { + return -1 + } + // Source connection ID length, source connection ID. + n += 1 + int(pkt[n]) + if len(pkt) <= n { + return -1 + } + if getPacketType(pkt) == packetTypeInitial { + // Token length, token. + _, nn := consumeVarintBytes(pkt[n:]) + if nn < 0 { + return -1 + } + n += nn + } + // Length, packet number, payload. + _, nn := consumeVarintBytes(pkt[n:]) + if nn < 0 { + return -1 + } + n += nn + if len(pkt) < n { + return -1 + } + return n +} + +// parse1RTTPacket parses a QUIC 1-RTT (short header) packet. +// +// On input, pkt contains a short header packet, k the decryption keys for the packet, +// and pnumMax the largest packet number seen in the number space of this packet. +func parse1RTTPacket(pkt []byte, k *updatingKeyPair, dstConnIDLen int, pnumMax packetNumber) (p shortPacket, err error) { + pay, pnum, err := k.unprotect(pkt, 1+dstConnIDLen, pnumMax) + if err != nil { + return shortPacket{}, err + } + p.num = pnum + p.payload = pay + return p, nil +} + +// Consume functions return n=-1 on conditions which result in FRAME_ENCODING_ERROR, +// which includes both general parse failures and specific violations of frame +// constraints. + +func consumeAckFrame(frame []byte, f func(rangeIndex int, start, end packetNumber)) (largest packetNumber, ackDelay unscaledAckDelay, n int) { + b := frame[1:] // type + + largestAck, n := consumeVarint(b) + if n < 0 { + return 0, 0, -1 + } + b = b[n:] + + v, n := consumeVarintInt64(b) + if n < 0 { + return 0, 0, -1 + } + b = b[n:] + ackDelay = unscaledAckDelay(v) + + ackRangeCount, n := consumeVarint(b) + if n < 0 { + return 0, 0, -1 + } + b = b[n:] + + rangeMax := packetNumber(largestAck) + for i := uint64(0); ; i++ { + rangeLen, n := consumeVarint(b) + if n < 0 { + return 0, 0, -1 + } + b = b[n:] + rangeMin := rangeMax - packetNumber(rangeLen) + if rangeMin < 0 || rangeMin > rangeMax { + return 0, 0, -1 + } + f(int(i), rangeMin, rangeMax+1) + + if i == ackRangeCount { + break + } + + gap, n := consumeVarint(b) + if n < 0 { + return 0, 0, -1 + } + b = b[n:] + + rangeMax = rangeMin - packetNumber(gap) - 2 + } + + if frame[0] != frameTypeAckECN { + return packetNumber(largestAck), ackDelay, len(frame) - len(b) + } + + ect0Count, n := consumeVarint(b) + if n < 0 { + return 0, 0, -1 + } + b = b[n:] + ect1Count, n := consumeVarint(b) + if n < 0 { + return 0, 0, -1 + } + b = b[n:] + ecnCECount, n := consumeVarint(b) + if n < 0 { + return 0, 0, -1 + } + b = b[n:] + + // TODO: Make use of ECN feedback. + // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.3.2 + _ = ect0Count + _ = ect1Count + _ = ecnCECount + + return packetNumber(largestAck), ackDelay, len(frame) - len(b) +} + +func consumeResetStreamFrame(b []byte) (id streamID, code uint64, finalSize int64, n int) { + n = 1 + idInt, nn := consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, 0, -1 + } + n += nn + code, nn = consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, 0, -1 + } + n += nn + v, nn := consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, 0, -1 + } + n += nn + finalSize = int64(v) + return streamID(idInt), code, finalSize, n +} + +func consumeStopSendingFrame(b []byte) (id streamID, code uint64, n int) { + n = 1 + idInt, nn := consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, -1 + } + n += nn + code, nn = consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, -1 + } + n += nn + return streamID(idInt), code, n +} + +func consumeCryptoFrame(b []byte) (off int64, data []byte, n int) { + n = 1 + v, nn := consumeVarint(b[n:]) + if nn < 0 { + return 0, nil, -1 + } + off = int64(v) + n += nn + data, nn = consumeVarintBytes(b[n:]) + if nn < 0 { + return 0, nil, -1 + } + n += nn + return off, data, n +} + +func consumeNewTokenFrame(b []byte) (token []byte, n int) { + n = 1 + data, nn := consumeVarintBytes(b[n:]) + if nn < 0 { + return nil, -1 + } + if len(data) == 0 { + return nil, -1 + } + n += nn + return data, n +} + +func consumeStreamFrame(b []byte) (id streamID, off int64, fin bool, data []byte, n int) { + fin = (b[0] & 0x01) != 0 + n = 1 + idInt, nn := consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, false, nil, -1 + } + n += nn + if b[0]&0x04 != 0 { + v, nn := consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, false, nil, -1 + } + n += nn + off = int64(v) + } + if b[0]&0x02 != 0 { + data, nn = consumeVarintBytes(b[n:]) + if nn < 0 { + return 0, 0, false, nil, -1 + } + n += nn + } else { + data = b[n:] + n += len(data) + } + if off+int64(len(data)) >= 1<<62 { + return 0, 0, false, nil, -1 + } + return streamID(idInt), off, fin, data, n +} + +func consumeMaxDataFrame(b []byte) (max int64, n int) { + n = 1 + v, nn := consumeVarint(b[n:]) + if nn < 0 { + return 0, -1 + } + n += nn + return int64(v), n +} + +func consumeMaxStreamDataFrame(b []byte) (id streamID, max int64, n int) { + n = 1 + v, nn := consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, -1 + } + n += nn + id = streamID(v) + v, nn = consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, -1 + } + n += nn + max = int64(v) + return id, max, n +} + +func consumeMaxStreamsFrame(b []byte) (typ streamType, max int64, n int) { + switch b[0] { + case frameTypeMaxStreamsBidi: + typ = bidiStream + case frameTypeMaxStreamsUni: + typ = uniStream + default: + return 0, 0, -1 + } + n = 1 + v, nn := consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, -1 + } + n += nn + if v > maxStreamsLimit { + return 0, 0, -1 + } + return typ, int64(v), n +} + +func consumeStreamDataBlockedFrame(b []byte) (id streamID, max int64, n int) { + n = 1 + v, nn := consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, -1 + } + n += nn + id = streamID(v) + max, nn = consumeVarintInt64(b[n:]) + if nn < 0 { + return 0, 0, -1 + } + n += nn + return id, max, n +} + +func consumeDataBlockedFrame(b []byte) (max int64, n int) { + n = 1 + max, nn := consumeVarintInt64(b[n:]) + if nn < 0 { + return 0, -1 + } + n += nn + return max, n +} + +func consumeStreamsBlockedFrame(b []byte) (typ streamType, max int64, n int) { + if b[0] == frameTypeStreamsBlockedBidi { + typ = bidiStream + } else { + typ = uniStream + } + n = 1 + max, nn := consumeVarintInt64(b[n:]) + if nn < 0 { + return 0, 0, -1 + } + n += nn + return typ, max, n +} + +func consumeNewConnectionIDFrame(b []byte) (seq, retire int64, connID []byte, resetToken [16]byte, n int) { + n = 1 + var nn int + seq, nn = consumeVarintInt64(b[n:]) + if nn < 0 { + return 0, 0, nil, [16]byte{}, -1 + } + n += nn + retire, nn = consumeVarintInt64(b[n:]) + if nn < 0 { + return 0, 0, nil, [16]byte{}, -1 + } + n += nn + if seq < retire { + return 0, 0, nil, [16]byte{}, -1 + } + connID, nn = consumeVarintBytes(b[n:]) + if nn < 0 { + return 0, 0, nil, [16]byte{}, -1 + } + if len(connID) < 1 || len(connID) > 20 { + return 0, 0, nil, [16]byte{}, -1 + } + n += nn + if len(b[n:]) < len(resetToken) { + return 0, 0, nil, [16]byte{}, -1 + } + copy(resetToken[:], b[n:]) + n += len(resetToken) + return seq, retire, connID, resetToken, n +} + +func consumeRetireConnectionIDFrame(b []byte) (seq int64, n int) { + n = 1 + var nn int + seq, nn = consumeVarintInt64(b[n:]) + if nn < 0 { + return 0, -1 + } + n += nn + return seq, n +} + +func consumePathChallengeFrame(b []byte) (data uint64, n int) { + n = 1 + var nn int + data, nn = consumeUint64(b[n:]) + if nn < 0 { + return 0, -1 + } + n += nn + return data, n +} + +func consumePathResponseFrame(b []byte) (data uint64, n int) { + return consumePathChallengeFrame(b) // identical frame format +} + +func consumeConnectionCloseTransportFrame(b []byte) (code transportError, frameType uint64, reason string, n int) { + n = 1 + var nn int + var codeInt uint64 + codeInt, nn = consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, "", -1 + } + code = transportError(codeInt) + n += nn + frameType, nn = consumeVarint(b[n:]) + if nn < 0 { + return 0, 0, "", -1 + } + n += nn + reasonb, nn := consumeVarintBytes(b[n:]) + if nn < 0 { + return 0, 0, "", -1 + } + n += nn + reason = string(reasonb) + return code, frameType, reason, n +} + +func consumeConnectionCloseApplicationFrame(b []byte) (code uint64, reason string, n int) { + n = 1 + var nn int + code, nn = consumeVarint(b[n:]) + if nn < 0 { + return 0, "", -1 + } + n += nn + reasonb, nn := consumeVarintBytes(b[n:]) + if nn < 0 { + return 0, "", -1 + } + n += nn + reason = string(reasonb) + return code, reason, n +} diff --git a/internal/quic/packet_protection.go b/internal/quic/packet_protection.go new file mode 100644 index 000000000..7b141ac49 --- /dev/null +++ b/internal/quic/packet_protection.go @@ -0,0 +1,535 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "crypto/tls" + "errors" + "hash" + + "golang.org/x/crypto/chacha20" + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/cryptobyte" + "golang.org/x/crypto/hkdf" +) + +var errInvalidPacket = errors.New("quic: invalid packet") + +// headerProtectionSampleSize is the size of the ciphertext sample used for header protection. +// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.2 +const headerProtectionSampleSize = 16 + +// aeadOverhead is the difference in size between the AEAD output and input. +// All cipher suites defined for use with QUIC have 16 bytes of overhead. +const aeadOverhead = 16 + +// A headerKey applies or removes header protection. +// https://www.rfc-editor.org/rfc/rfc9001#section-5.4 +type headerKey struct { + hp headerProtection +} + +func (k headerKey) isSet() bool { + return k.hp != nil +} + +func (k *headerKey) init(suite uint16, secret []byte) { + h, keySize := hashForSuite(suite) + hpKey := hkdfExpandLabel(h.New, secret, "quic hp", nil, keySize) + switch suite { + case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384: + c, err := aes.NewCipher(hpKey) + if err != nil { + panic(err) + } + k.hp = &aesHeaderProtection{cipher: c} + case tls.TLS_CHACHA20_POLY1305_SHA256: + k.hp = chaCha20HeaderProtection{hpKey} + default: + panic("BUG: unknown cipher suite") + } +} + +// protect applies header protection. +// pnumOff is the offset of the packet number in the packet. +func (k headerKey) protect(hdr []byte, pnumOff int) { + // Apply header protection. + pnumSize := int(hdr[0]&0x03) + 1 + sample := hdr[pnumOff+4:][:headerProtectionSampleSize] + mask := k.hp.headerProtection(sample) + if isLongHeader(hdr[0]) { + hdr[0] ^= mask[0] & 0x0f + } else { + hdr[0] ^= mask[0] & 0x1f + } + for i := 0; i < pnumSize; i++ { + hdr[pnumOff+i] ^= mask[1+i] + } +} + +// unprotect removes header protection. +// pnumOff is the offset of the packet number in the packet. +// pnumMax is the largest packet number seen in the number space of this packet. +func (k headerKey) unprotect(pkt []byte, pnumOff int, pnumMax packetNumber) (hdr, pay []byte, pnum packetNumber, _ error) { + if len(pkt) < pnumOff+4+headerProtectionSampleSize { + return nil, nil, 0, errInvalidPacket + } + numpay := pkt[pnumOff:] + sample := numpay[4:][:headerProtectionSampleSize] + mask := k.hp.headerProtection(sample) + if isLongHeader(pkt[0]) { + pkt[0] ^= mask[0] & 0x0f + } else { + pkt[0] ^= mask[0] & 0x1f + } + pnumLen := int(pkt[0]&0x03) + 1 + pnum = packetNumber(0) + for i := 0; i < pnumLen; i++ { + numpay[i] ^= mask[1+i] + pnum = (pnum << 8) | packetNumber(numpay[i]) + } + pnum = decodePacketNumber(pnumMax, pnum, pnumLen) + hdr = pkt[:pnumOff+pnumLen] + pay = numpay[pnumLen:] + return hdr, pay, pnum, nil +} + +// headerProtection is the header_protection function as defined in: +// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.1 +// +// This function takes a sample of the packet ciphertext +// and returns a 5-byte mask which will be applied to the +// protected portions of the packet header. +type headerProtection interface { + headerProtection(sample []byte) (mask [5]byte) +} + +// AES-based header protection. +// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.3 +type aesHeaderProtection struct { + cipher cipher.Block + scratch [aes.BlockSize]byte +} + +func (hp *aesHeaderProtection) headerProtection(sample []byte) (mask [5]byte) { + hp.cipher.Encrypt(hp.scratch[:], sample) + copy(mask[:], hp.scratch[:]) + return mask +} + +// ChaCha20-based header protection. +// https://www.rfc-editor.org/rfc/rfc9001#section-5.4.4 +type chaCha20HeaderProtection struct { + key []byte +} + +func (hp chaCha20HeaderProtection) headerProtection(sample []byte) (mask [5]byte) { + counter := uint32(sample[3])<<24 | uint32(sample[2])<<16 | uint32(sample[1])<<8 | uint32(sample[0]) + nonce := sample[4:16] + c, err := chacha20.NewUnauthenticatedCipher(hp.key, nonce) + if err != nil { + panic(err) + } + c.SetCounter(counter) + c.XORKeyStream(mask[:], mask[:]) + return mask +} + +// A packetKey applies or removes packet protection. +// https://www.rfc-editor.org/rfc/rfc9001#section-5.1 +type packetKey struct { + aead cipher.AEAD // AEAD function used for packet protection. + iv []byte // IV used to construct the AEAD nonce. +} + +func (k *packetKey) init(suite uint16, secret []byte) { + // https://www.rfc-editor.org/rfc/rfc9001#section-5.1 + h, keySize := hashForSuite(suite) + key := hkdfExpandLabel(h.New, secret, "quic key", nil, keySize) + switch suite { + case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384: + k.aead = newAESAEAD(key) + case tls.TLS_CHACHA20_POLY1305_SHA256: + k.aead = newChaCha20AEAD(key) + default: + panic("BUG: unknown cipher suite") + } + k.iv = hkdfExpandLabel(h.New, secret, "quic iv", nil, k.aead.NonceSize()) +} + +func newAESAEAD(key []byte) cipher.AEAD { + c, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + aead, err := cipher.NewGCM(c) + if err != nil { + panic(err) + } + return aead +} + +func newChaCha20AEAD(key []byte) cipher.AEAD { + var err error + aead, err := chacha20poly1305.New(key) + if err != nil { + panic(err) + } + return aead +} + +func (k packetKey) protect(hdr, pay []byte, pnum packetNumber) []byte { + k.xorIV(pnum) + defer k.xorIV(pnum) + return k.aead.Seal(hdr, k.iv, pay, hdr) +} + +func (k packetKey) unprotect(hdr, pay []byte, pnum packetNumber) (dec []byte, err error) { + k.xorIV(pnum) + defer k.xorIV(pnum) + return k.aead.Open(pay[:0], k.iv, pay, hdr) +} + +// xorIV xors the packet protection IV with the packet number. +func (k packetKey) xorIV(pnum packetNumber) { + k.iv[len(k.iv)-8] ^= uint8(pnum >> 56) + k.iv[len(k.iv)-7] ^= uint8(pnum >> 48) + k.iv[len(k.iv)-6] ^= uint8(pnum >> 40) + k.iv[len(k.iv)-5] ^= uint8(pnum >> 32) + k.iv[len(k.iv)-4] ^= uint8(pnum >> 24) + k.iv[len(k.iv)-3] ^= uint8(pnum >> 16) + k.iv[len(k.iv)-2] ^= uint8(pnum >> 8) + k.iv[len(k.iv)-1] ^= uint8(pnum) +} + +// A fixedKeys is a header protection key and fixed packet protection key. +// The packet protection key is fixed (it does not update). +// +// Fixed keys are used for Initial and Handshake keys, which do not update. +type fixedKeys struct { + hdr headerKey + pkt packetKey +} + +func (k *fixedKeys) init(suite uint16, secret []byte) { + k.hdr.init(suite, secret) + k.pkt.init(suite, secret) +} + +func (k fixedKeys) isSet() bool { + return k.hdr.hp != nil +} + +// protect applies packet protection to a packet. +// +// On input, hdr contains the packet header, pay the unencrypted payload, +// pnumOff the offset of the packet number in the header, and pnum the untruncated +// packet number. +// +// protect returns the result of appending the encrypted payload to hdr and +// applying header protection. +func (k fixedKeys) protect(hdr, pay []byte, pnumOff int, pnum packetNumber) []byte { + pkt := k.pkt.protect(hdr, pay, pnum) + k.hdr.protect(pkt, pnumOff) + return pkt +} + +// unprotect removes packet protection from a packet. +// +// On input, pkt contains the full protected packet, pnumOff the offset of +// the packet number in the header, and pnumMax the largest packet number +// seen in the number space of this packet. +// +// unprotect removes header protection from the header in pkt, and returns +// the unprotected payload and packet number. +func (k fixedKeys) unprotect(pkt []byte, pnumOff int, pnumMax packetNumber) (pay []byte, num packetNumber, err error) { + hdr, pay, pnum, err := k.hdr.unprotect(pkt, pnumOff, pnumMax) + if err != nil { + return nil, 0, err + } + pay, err = k.pkt.unprotect(hdr, pay, pnum) + if err != nil { + return nil, 0, err + } + return pay, pnum, nil +} + +// A fixedKeyPair is a read/write pair of fixed keys. +type fixedKeyPair struct { + r, w fixedKeys +} + +func (k *fixedKeyPair) discard() { + *k = fixedKeyPair{} +} + +func (k *fixedKeyPair) canRead() bool { + return k.r.isSet() +} + +func (k *fixedKeyPair) canWrite() bool { + return k.w.isSet() +} + +// An updatingKeys is a header protection key and updatable packet protection key. +// updatingKeys are used for 1-RTT keys, where the packet protection key changes +// over the lifetime of a connection. +// https://www.rfc-editor.org/rfc/rfc9001#section-6 +type updatingKeys struct { + suite uint16 + hdr headerKey + pkt [2]packetKey // current, next + nextSecret []byte // secret used to generate pkt[1] +} + +func (k *updatingKeys) init(suite uint16, secret []byte) { + k.suite = suite + k.hdr.init(suite, secret) + // Initialize pkt[1] with secret_0, and then call update to generate secret_1. + k.pkt[1].init(suite, secret) + k.nextSecret = secret + k.update() +} + +// update performs a key update. +// The current key in pkt[0] is discarded. +// The next key in pkt[1] becomes the current key. +// A new next key is generated in pkt[1]. +func (k *updatingKeys) update() { + k.nextSecret = updateSecret(k.suite, k.nextSecret) + k.pkt[0] = k.pkt[1] + k.pkt[1].init(k.suite, k.nextSecret) +} + +func updateSecret(suite uint16, secret []byte) (nextSecret []byte) { + h, _ := hashForSuite(suite) + return hkdfExpandLabel(h.New, secret, "quic ku", nil, len(secret)) +} + +// An updatingKeyPair is a read/write pair of updating keys. +// +// We keep two keys (current and next) in both read and write directions. +// When an incoming packet's phase matches the current phase bit, +// we unprotect it using the current keys; otherwise we use the next keys. +// +// When updating=false, outgoing packets are protected using the current phase. +// +// An update is initiated and updating is set to true when: +// - we decide to initiate a key update; or +// - we successfully unprotect a packet using the next keys, +// indicating the peer has initiated a key update. +// +// When updating=true, outgoing packets are protected using the next phase. +// We do not change the current phase bit or generate new keys yet. +// +// The update concludes when we receive an ACK frame for a packet sent +// with the next keys. At this time, we set updating to false, flip the +// phase bit, and update the keys. This permits us to handle up to 1-RTT +// of reordered packets before discarding the previous phase's keys after +// an update. +type updatingKeyPair struct { + phase uint8 // current key phase (r.pkt[0], w.pkt[0]) + updating bool + authFailures int64 // total packet unprotect failures + minSent packetNumber // min packet number sent since entering the updating state + minReceived packetNumber // min packet number received in the next phase + updateAfter packetNumber // packet number after which to initiate key update + r, w updatingKeys +} + +func (k *updatingKeyPair) init() { + // 1-RTT packets until the first key update. + // + // We perform the first key update early in the connection so a peer + // which does not support key updates will fail rapidly, + // rather than after the connection has been long established. + k.updateAfter = 1000 +} + +func (k *updatingKeyPair) canRead() bool { + return k.r.hdr.hp != nil +} + +func (k *updatingKeyPair) canWrite() bool { + return k.w.hdr.hp != nil +} + +// handleAckFor finishes a key update after receiving an ACK for a packet in the next phase. +func (k *updatingKeyPair) handleAckFor(pnum packetNumber) { + if k.updating && pnum >= k.minSent { + k.updating = false + k.phase ^= keyPhaseBit + k.r.update() + k.w.update() + } +} + +// needAckEliciting reports whether we should send an ack-eliciting packet in the next phase. +// The first packet sent in a phase is ack-eliciting, since the peer must acknowledge a +// packet in the new phase for us to finish the update. +func (k *updatingKeyPair) needAckEliciting() bool { + return k.updating && k.minSent == maxPacketNumber +} + +// protect applies packet protection to a packet. +// Parameters and returns are as for fixedKeyPair.protect. +func (k *updatingKeyPair) protect(hdr, pay []byte, pnumOff int, pnum packetNumber) []byte { + var pkt []byte + if k.updating { + hdr[0] |= k.phase ^ keyPhaseBit + pkt = k.w.pkt[1].protect(hdr, pay, pnum) + k.minSent = min(pnum, k.minSent) + } else { + hdr[0] |= k.phase + pkt = k.w.pkt[0].protect(hdr, pay, pnum) + if pnum >= k.updateAfter { + // Initiate a key update, starting with the next packet we send. + // + // We do this after protecting the current packet + // to allow Conn.appendFrames to ensure that the first packet sent + // in the new phase is ack-eliciting. + k.updating = true + k.minSent = maxPacketNumber + k.minReceived = maxPacketNumber + // The lowest confidentiality limit for a supported AEAD is 2^23 packets. + // https://www.rfc-editor.org/rfc/rfc9001#section-6.6-5 + // + // Schedule our next update for half that. + k.updateAfter += (1 << 22) + } + } + k.w.hdr.protect(pkt, pnumOff) + return pkt +} + +// unprotect removes packet protection from a packet. +// Parameters and returns are as for fixedKeyPair.unprotect. +func (k *updatingKeyPair) unprotect(pkt []byte, pnumOff int, pnumMax packetNumber) (pay []byte, pnum packetNumber, err error) { + hdr, pay, pnum, err := k.r.hdr.unprotect(pkt, pnumOff, pnumMax) + if err != nil { + return nil, 0, err + } + // To avoid timing signals that might indicate the key phase bit is invalid, + // we always attempt to unprotect the packet with one key. + // + // If the key phase bit matches and the packet number doesn't come after + // the start of an in-progress update, use the current phase. + // Otherwise, use the next phase. + if hdr[0]&keyPhaseBit == k.phase && (!k.updating || pnum < k.minReceived) { + pay, err = k.r.pkt[0].unprotect(hdr, pay, pnum) + } else { + pay, err = k.r.pkt[1].unprotect(hdr, pay, pnum) + if err == nil { + if !k.updating { + // The peer has initiated a key update. + k.updating = true + k.minSent = maxPacketNumber + k.minReceived = pnum + } else { + k.minReceived = min(pnum, k.minReceived) + } + } + } + if err != nil { + k.authFailures++ + if k.authFailures >= aeadIntegrityLimit(k.r.suite) { + return nil, 0, localTransportError(errAEADLimitReached) + } + return nil, 0, err + } + return pay, pnum, nil +} + +// aeadIntegrityLimit returns the integrity limit for an AEAD: +// The maximum number of received packets that may fail authentication +// before closing the connection. +// +// https://www.rfc-editor.org/rfc/rfc9001#section-6.6-4 +func aeadIntegrityLimit(suite uint16) int64 { + switch suite { + case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384: + return 1 << 52 + case tls.TLS_CHACHA20_POLY1305_SHA256: + return 1 << 36 + default: + panic("BUG: unknown cipher suite") + } +} + +// https://www.rfc-editor.org/rfc/rfc9001#section-5.2-2 +var initialSalt = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a} + +// initialKeys returns the keys used to protect Initial packets. +// +// The Initial packet keys are derived from the Destination Connection ID +// field in the client's first Initial packet. +// +// https://www.rfc-editor.org/rfc/rfc9001#section-5.2 +func initialKeys(cid []byte, side connSide) fixedKeyPair { + initialSecret := hkdf.Extract(sha256.New, cid, initialSalt) + var clientKeys fixedKeys + clientSecret := hkdfExpandLabel(sha256.New, initialSecret, "client in", nil, sha256.Size) + clientKeys.init(tls.TLS_AES_128_GCM_SHA256, clientSecret) + var serverKeys fixedKeys + serverSecret := hkdfExpandLabel(sha256.New, initialSecret, "server in", nil, sha256.Size) + serverKeys.init(tls.TLS_AES_128_GCM_SHA256, serverSecret) + if side == clientSide { + return fixedKeyPair{r: serverKeys, w: clientKeys} + } else { + return fixedKeyPair{w: serverKeys, r: clientKeys} + } +} + +// checkCipherSuite returns an error if suite is not a supported cipher suite. +func checkCipherSuite(suite uint16) error { + switch suite { + case tls.TLS_AES_128_GCM_SHA256: + case tls.TLS_AES_256_GCM_SHA384: + case tls.TLS_CHACHA20_POLY1305_SHA256: + default: + return errors.New("invalid cipher suite") + } + return nil +} + +func hashForSuite(suite uint16) (h crypto.Hash, keySize int) { + switch suite { + case tls.TLS_AES_128_GCM_SHA256: + return crypto.SHA256, 128 / 8 + case tls.TLS_AES_256_GCM_SHA384: + return crypto.SHA384, 256 / 8 + case tls.TLS_CHACHA20_POLY1305_SHA256: + return crypto.SHA256, chacha20.KeySize + default: + panic("BUG: unknown cipher suite") + } +} + +// hdkfExpandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1. +// +// Copied from crypto/tls/key_schedule.go. +func hkdfExpandLabel(hash func() hash.Hash, secret []byte, label string, context []byte, length int) []byte { + var hkdfLabel cryptobyte.Builder + hkdfLabel.AddUint16(uint16(length)) + hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes([]byte("tls13 ")) + b.AddBytes([]byte(label)) + }) + hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(context) + }) + out := make([]byte, length) + n, err := hkdf.Expand(hash, secret, hkdfLabel.BytesOrPanic()).Read(out) + if err != nil || n != length { + panic("quic: HKDF-Expand-Label invocation failed unexpectedly") + } + return out +} diff --git a/internal/quic/packet_protection_test.go b/internal/quic/packet_protection_test.go new file mode 100644 index 000000000..1fe130731 --- /dev/null +++ b/internal/quic/packet_protection_test.go @@ -0,0 +1,163 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "crypto/tls" + "testing" +) + +func TestPacketProtection(t *testing.T) { + // Test cases from: + // https://www.rfc-editor.org/rfc/rfc9001#section-appendix.a + cid := unhex(`8394c8f03e515708`) + k := initialKeys(cid, clientSide) + initialClientKeys, initialServerKeys := k.w, k.r + for _, test := range []struct { + name string + k fixedKeys + pnum packetNumber + hdr []byte + pay []byte + prot []byte + }{{ + name: "Client Initial", + k: initialClientKeys, + pnum: 2, + hdr: unhex(` + c300000001088394c8f03e5157080000 449e00000002 + `), + pay: pad(1162, unhex(` + 060040f1010000ed0303ebf8fa56f129 39b9584a3896472ec40bb863cfd3e868 + 04fe3a47f06a2b69484c000004130113 02010000c000000010000e00000b6578 + 616d706c652e636f6dff01000100000a 00080006001d00170018001000070005 + 04616c706e0005000501000000000033 00260024001d00209370b2c9caa47fba + baf4559fedba753de171fa71f50f1ce1 5d43e994ec74d748002b000302030400 + 0d0010000e0403050306030203080408 050806002d00020101001c0002400100 + 3900320408ffffffffffffffff050480 00ffff07048000ffff08011001048000 + 75300901100f088394c8f03e51570806 048000ffff + `)), + prot: unhex(` + c000000001088394c8f03e5157080000 449e7b9aec34d1b1c98dd7689fb8ec11 + d242b123dc9bd8bab936b47d92ec356c 0bab7df5976d27cd449f63300099f399 + 1c260ec4c60d17b31f8429157bb35a12 82a643a8d2262cad67500cadb8e7378c + 8eb7539ec4d4905fed1bee1fc8aafba1 7c750e2c7ace01e6005f80fcb7df6212 + 30c83711b39343fa028cea7f7fb5ff89 eac2308249a02252155e2347b63d58c5 + 457afd84d05dfffdb20392844ae81215 4682e9cf012f9021a6f0be17ddd0c208 + 4dce25ff9b06cde535d0f920a2db1bf3 62c23e596d11a4f5a6cf3948838a3aec + 4e15daf8500a6ef69ec4e3feb6b1d98e 610ac8b7ec3faf6ad760b7bad1db4ba3 + 485e8a94dc250ae3fdb41ed15fb6a8e5 eba0fc3dd60bc8e30c5c4287e53805db + 059ae0648db2f64264ed5e39be2e20d8 2df566da8dd5998ccabdae053060ae6c + 7b4378e846d29f37ed7b4ea9ec5d82e7 961b7f25a9323851f681d582363aa5f8 + 9937f5a67258bf63ad6f1a0b1d96dbd4 faddfcefc5266ba6611722395c906556 + be52afe3f565636ad1b17d508b73d874 3eeb524be22b3dcbc2c7468d54119c74 + 68449a13d8e3b95811a198f3491de3e7 fe942b330407abf82a4ed7c1b311663a + c69890f4157015853d91e923037c227a 33cdd5ec281ca3f79c44546b9d90ca00 + f064c99e3dd97911d39fe9c5d0b23a22 9a234cb36186c4819e8b9c5927726632 + 291d6a418211cc2962e20fe47feb3edf 330f2c603a9d48c0fcb5699dbfe58964 + 25c5bac4aee82e57a85aaf4e2513e4f0 5796b07ba2ee47d80506f8d2c25e50fd + 14de71e6c418559302f939b0e1abd576 f279c4b2e0feb85c1f28ff18f58891ff + ef132eef2fa09346aee33c28eb130ff2 8f5b766953334113211996d20011a198 + e3fc433f9f2541010ae17c1bf202580f 6047472fb36857fe843b19f5984009dd + c324044e847a4f4a0ab34f719595de37 252d6235365e9b84392b061085349d73 + 203a4a13e96f5432ec0fd4a1ee65accd d5e3904df54c1da510b0ff20dcc0c77f + cb2c0e0eb605cb0504db87632cf3d8b4 dae6e705769d1de354270123cb11450e + fc60ac47683d7b8d0f811365565fd98c 4c8eb936bcab8d069fc33bd801b03ade + a2e1fbc5aa463d08ca19896d2bf59a07 1b851e6c239052172f296bfb5e724047 + 90a2181014f3b94a4e97d117b4381303 68cc39dbb2d198065ae3986547926cd2 + 162f40a29f0c3c8745c0f50fba3852e5 66d44575c29d39a03f0cda721984b6f4 + 40591f355e12d439ff150aab7613499d bd49adabc8676eef023b15b65bfc5ca0 + 6948109f23f350db82123535eb8a7433 bdabcb909271a6ecbcb58b936a88cd4e + 8f2e6ff5800175f113253d8fa9ca8885 c2f552e657dc603f252e1a8e308f76f0 + be79e2fb8f5d5fbbe2e30ecadd220723 c8c0aea8078cdfcb3868263ff8f09400 + 54da48781893a7e49ad5aff4af300cd8 04a6b6279ab3ff3afb64491c85194aab + 760d58a606654f9f4400e8b38591356f bf6425aca26dc85244259ff2b19c41b9 + f96f3ca9ec1dde434da7d2d392b905dd f3d1f9af93d1af5950bd493f5aa731b4 + 056df31bd267b6b90a079831aaf579be 0a39013137aac6d404f518cfd4684064 + 7e78bfe706ca4cf5e9c5453e9f7cfd2b 8b4c8d169a44e55c88d4a9a7f9474241 + e221af44860018ab0856972e194cd934 + `), + }, { + name: "Server Initial", + k: initialServerKeys, + pnum: 1, + hdr: unhex(` + c1000000010008f067a5502a4262b500 40750001 + `), + pay: unhex(` + 02000000000600405a020000560303ee fce7f7b37ba1d1632e96677825ddf739 + 88cfc79825df566dc5430b9a045a1200 130100002e00330024001d00209d3c94 + 0d89690b84d08a60993c144eca684d10 81287c834d5311bcf32bb9da1a002b00 + 020304 + `), + prot: unhex(` + cf000000010008f067a5502a4262b500 4075c0d95a482cd0991cd25b0aac406a + 5816b6394100f37a1c69797554780bb3 8cc5a99f5ede4cf73c3ec2493a1839b3 + dbcba3f6ea46c5b7684df3548e7ddeb9 c3bf9c73cc3f3bded74b562bfb19fb84 + 022f8ef4cdd93795d77d06edbb7aaf2f 58891850abbdca3d20398c276456cbc4 + 2158407dd074ee + `), + }, { + name: "ChaCha20_Poly1305 Short Header", + k: func() fixedKeys { + secret := unhex(` + 9ac312a7f877468ebe69422748ad00a1 + 5443f18203a07d6060f688f30f21632b + `) + var k fixedKeys + k.init(tls.TLS_CHACHA20_POLY1305_SHA256, secret) + return k + }(), + pnum: 654360564, + hdr: unhex(`4200bff4`), + pay: unhex(`01`), + prot: unhex(` + 4cfe4189655e5cd55c41f69080575d79 99c25a5bfb + `), + }} { + test := test + t.Run(test.name, func(t *testing.T) { + pnumLen := int(test.hdr[0]&0x03) + 1 + pnumOff := len(test.hdr) - pnumLen + + b := append([]byte{}, test.hdr...) + gotProt := test.k.protect(b, test.pay, pnumOff, test.pnum) + if got, want := gotProt, test.prot; !bytes.Equal(got, want) { + t.Errorf("Protected payload does not match:") + t.Errorf("got: %x", got) + t.Errorf("want: %x", want) + } + + pkt := append([]byte{}, test.prot...) + gotPay, gotNum, err := test.k.unprotect(pkt, pnumOff, test.pnum-1) + if err != nil { + t.Fatalf("Unexpected error unprotecting packet: %v", err) + } + if got, want := pkt[:len(test.hdr)], test.hdr; !bytes.Equal(got, want) { + t.Errorf("Unprotected header does not match:") + t.Errorf("got: %x", got) + t.Errorf("want: %x", want) + } + if got, want := gotPay, test.pay; !bytes.Equal(got, want) { + t.Errorf("Unprotected payload does not match:") + t.Errorf("got: %x", got) + t.Errorf("want: %x", want) + } + if got, want := gotNum, test.pnum; got != want { + t.Errorf("Unprotected packet number does not match: got %v, want %v", got, want) + } + }) + } +} + +func pad(n int, b []byte) []byte { + for len(b) < n { + b = append(b, 0) + } + return b +} diff --git a/internal/quic/packet_test.go b/internal/quic/packet_test.go new file mode 100644 index 000000000..58c584e16 --- /dev/null +++ b/internal/quic/packet_test.go @@ -0,0 +1,247 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "reflect" + "strings" + "testing" +) + +func TestPacketHeader(t *testing.T) { + for _, test := range []struct { + name string + packet []byte + isLongHeader bool + packetType packetType + dstConnID []byte + }{{ + // Initial packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.1 + // (truncated) + name: "rfc9001_a1", + packet: unhex(` + c000000001088394c8f03e5157080000 449e7b9aec34d1b1c98dd7689fb8ec11 + `), + isLongHeader: true, + packetType: packetTypeInitial, + dstConnID: unhex(`8394c8f03e515708`), + }, { + // Initial packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.3 + // (truncated) + name: "rfc9001_a3", + packet: unhex(` + cf000000010008f067a5502a4262b500 4075c0d95a482cd0991cd25b0aac406a + `), + isLongHeader: true, + packetType: packetTypeInitial, + dstConnID: []byte{}, + }, { + // Retry packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.4 + name: "rfc9001_a4", + packet: unhex(` + ff000000010008f067a5502a4262b574 6f6b656e04a265ba2eff4d829058fb3f + 0f2496ba + `), + isLongHeader: true, + packetType: packetTypeRetry, + dstConnID: []byte{}, + }, { + // Short header packet from https://www.rfc-editor.org/rfc/rfc9001#section-a.5 + name: "rfc9001_a5", + packet: unhex(` + 4cfe4189655e5cd55c41f69080575d7999c25a5bfb + `), + isLongHeader: false, + packetType: packetType1RTT, + dstConnID: unhex(`fe4189655e5cd55c`), + }, { + // Version Negotiation packet. + name: "version_negotiation", + packet: unhex(` + 80 00000000 01ff0001020304 + `), + isLongHeader: true, + packetType: packetTypeVersionNegotiation, + dstConnID: []byte{0xff}, + }, { + // Too-short packet. + name: "truncated_after_connid_length", + packet: unhex(` + cf0000000105 + `), + isLongHeader: true, + packetType: packetTypeInitial, + dstConnID: nil, + }, { + // Too-short packet. + name: "truncated_after_version", + packet: unhex(` + cf00000001 + `), + isLongHeader: true, + packetType: packetTypeInitial, + dstConnID: nil, + }, { + // Much too short packet. + name: "truncated_in_version", + packet: unhex(` + cf000000 + `), + isLongHeader: true, + packetType: packetTypeInvalid, + dstConnID: nil, + }} { + t.Run(test.name, func(t *testing.T) { + if got, want := isLongHeader(test.packet[0]), test.isLongHeader; got != want { + t.Errorf("packet %x:\nisLongHeader(packet) = %v, want %v", test.packet, got, want) + } + if got, want := getPacketType(test.packet), test.packetType; got != want { + t.Errorf("packet %x:\ngetPacketType(packet) = %v, want %v", test.packet, got, want) + } + gotConnID, gotOK := dstConnIDForDatagram(test.packet) + wantConnID, wantOK := test.dstConnID, test.dstConnID != nil + if !bytes.Equal(gotConnID, wantConnID) || gotOK != wantOK { + t.Errorf("packet %x:\ndstConnIDForDatagram(packet) = {%x}, %v; want {%x}, %v", test.packet, gotConnID, gotOK, wantConnID, wantOK) + } + }) + } +} + +func TestEncodeDecodeVersionNegotiation(t *testing.T) { + dstConnID := []byte("this is a very long destination connection id") + srcConnID := []byte("this is a very long source connection id") + versions := []uint32{1, 0xffffffff} + got := appendVersionNegotiation([]byte{}, dstConnID, srcConnID, versions...) + want := bytes.Join([][]byte{{ + 0b1100_0000, // header byte + 0, 0, 0, 0, // Version + byte(len(dstConnID)), + }, dstConnID, { + byte(len(srcConnID)), + }, srcConnID, { + 0x00, 0x00, 0x00, 0x01, + 0xff, 0xff, 0xff, 0xff, + }}, nil) + if !bytes.Equal(got, want) { + t.Fatalf("appendVersionNegotiation(nil, %x, %x, %v):\ngot %x\nwant %x", + dstConnID, srcConnID, versions, got, want) + } + gotDst, gotSrc, gotVersionBytes := parseVersionNegotiation(got) + if got, want := gotDst, dstConnID; !bytes.Equal(got, want) { + t.Errorf("parseVersionNegotiation: got dstConnID = %x, want %x", got, want) + } + if got, want := gotSrc, srcConnID; !bytes.Equal(got, want) { + t.Errorf("parseVersionNegotiation: got srcConnID = %x, want %x", got, want) + } + var gotVersions []uint32 + for len(gotVersionBytes) >= 4 { + gotVersions = append(gotVersions, binary.BigEndian.Uint32(gotVersionBytes)) + gotVersionBytes = gotVersionBytes[4:] + } + if got, want := gotVersions, versions; !reflect.DeepEqual(got, want) { + t.Errorf("parseVersionNegotiation: got versions = %v, want %v", got, want) + } +} + +func TestParseGenericLongHeaderPacket(t *testing.T) { + for _, test := range []struct { + name string + packet []byte + version uint32 + dstConnID []byte + srcConnID []byte + data []byte + }{{ + name: "long header packet", + packet: unhex(` + 80 01020304 04a1a2a3a4 05b1b2b3b4b5 c1 + `), + version: 0x01020304, + dstConnID: unhex(`a1a2a3a4`), + srcConnID: unhex(`b1b2b3b4b5`), + data: unhex(`c1`), + }, { + name: "zero everything", + packet: unhex(` + 80 00000000 00 00 + `), + version: 0, + dstConnID: []byte{}, + srcConnID: []byte{}, + data: []byte{}, + }} { + t.Run(test.name, func(t *testing.T) { + p, ok := parseGenericLongHeaderPacket(test.packet) + if !ok { + t.Fatalf("parseGenericLongHeaderPacket() = _, false; want true") + } + if got, want := p.version, test.version; got != want { + t.Errorf("version = %v, want %v", got, want) + } + if got, want := p.dstConnID, test.dstConnID; !bytes.Equal(got, want) { + t.Errorf("Destination Connection ID = {%x}, want {%x}", got, want) + } + if got, want := p.srcConnID, test.srcConnID; !bytes.Equal(got, want) { + t.Errorf("Source Connection ID = {%x}, want {%x}", got, want) + } + if got, want := p.data, test.data; !bytes.Equal(got, want) { + t.Errorf("Data = {%x}, want {%x}", got, want) + } + }) + } +} + +func TestParseGenericLongHeaderPacketErrors(t *testing.T) { + for _, test := range []struct { + name string + packet []byte + }{{ + name: "short header packet", + packet: unhex(` + 00 01020304 04a1a2a3a4 05b1b2b3b4b5 c1 + `), + }, { + name: "packet too short", + packet: unhex(` + 80 000000 + `), + }, { + name: "destination id too long", + packet: unhex(` + 80 00000000 02 00 + `), + }, { + name: "source id too long", + packet: unhex(` + 80 00000000 00 01 + `), + }} { + t.Run(test.name, func(t *testing.T) { + _, ok := parseGenericLongHeaderPacket(test.packet) + if ok { + t.Fatalf("parseGenericLongHeaderPacket() = _, true; want false") + } + }) + } +} + +func unhex(s string) []byte { + b, err := hex.DecodeString(strings.Map(func(c rune) rune { + switch c { + case ' ', '\t', '\n': + return -1 + } + return c + }, s)) + if err != nil { + panic(err) + } + return b +} diff --git a/internal/quic/packet_writer.go b/internal/quic/packet_writer.go new file mode 100644 index 000000000..0c2b2ee41 --- /dev/null +++ b/internal/quic/packet_writer.go @@ -0,0 +1,552 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "encoding/binary" +) + +// A packetWriter constructs QUIC datagrams. +// +// A datagram consists of one or more packets. +// A packet consists of a header followed by one or more frames. +// +// Packets are written in three steps: +// - startProtectedLongHeaderPacket or start1RTT packet prepare the packet; +// - append*Frame appends frames to the payload; and +// - finishProtectedLongHeaderPacket or finish1RTT finalize the packet. +// +// The start functions are efficient, so we can start speculatively +// writing a packet before we know whether we have any frames to +// put in it. The finish functions will abandon the packet if the +// payload contains no data. +type packetWriter struct { + dgramLim int // max datagram size + pktLim int // max packet size + pktOff int // offset of the start of the current packet + payOff int // offset of the payload of the current packet + b []byte + sent *sentPacket +} + +// reset prepares to write a datagram of at most lim bytes. +func (w *packetWriter) reset(lim int) { + if cap(w.b) < lim { + w.b = make([]byte, 0, lim) + } + w.dgramLim = lim + w.b = w.b[:0] +} + +// datagram returns the current datagram. +func (w *packetWriter) datagram() []byte { + return w.b +} + +// payload returns the payload of the current packet. +func (w *packetWriter) payload() []byte { + return w.b[w.payOff:] +} + +func (w *packetWriter) abandonPacket() { + w.b = w.b[:w.payOff] + w.sent.reset() +} + +// startProtectedLongHeaderPacket starts writing an Initial, 0-RTT, or Handshake packet. +func (w *packetWriter) startProtectedLongHeaderPacket(pnumMaxAcked packetNumber, p longPacket) { + if w.sent == nil { + w.sent = newSentPacket() + } + w.pktOff = len(w.b) + hdrSize := 1 // packet type + hdrSize += 4 // version + hdrSize += 1 + len(p.dstConnID) + hdrSize += 1 + len(p.srcConnID) + switch p.ptype { + case packetTypeInitial: + hdrSize += sizeVarint(uint64(len(p.extra))) + len(p.extra) + } + hdrSize += 2 // length, hardcoded to a 2-byte varint + pnumOff := len(w.b) + hdrSize + hdrSize += packetNumberLength(p.num, pnumMaxAcked) + payOff := len(w.b) + hdrSize + // Check if we have enough space to hold the packet, including the header, + // header protection sample (RFC 9001, section 5.4.2), and encryption overhead. + if pnumOff+4+headerProtectionSampleSize+aeadOverhead >= w.dgramLim { + // Set the limit on the packet size to be the current write buffer length, + // ensuring that any writes to the payload fail. + w.payOff = len(w.b) + w.pktLim = len(w.b) + return + } + w.payOff = payOff + w.pktLim = w.dgramLim - aeadOverhead + // We hardcode the payload length field to be 2 bytes, which limits the payload + // (including the packet number) to 16383 bytes (the largest 2-byte QUIC varint). + // + // Most networks don't support datagrams over 1472 bytes, and even Ethernet + // jumbo frames are generally only about 9000 bytes. + if lim := pnumOff + 16383 - aeadOverhead; lim < w.pktLim { + w.pktLim = lim + } + w.b = w.b[:payOff] +} + +// finishProtectedLongHeaderPacket finishes writing an Initial, 0-RTT, or Handshake packet, +// canceling the packet if it contains no payload. +// It returns a sentPacket describing the packet, or nil if no packet was written. +func (w *packetWriter) finishProtectedLongHeaderPacket(pnumMaxAcked packetNumber, k fixedKeys, p longPacket) *sentPacket { + if len(w.b) == w.payOff { + // The payload is empty, so just abandon the packet. + w.b = w.b[:w.pktOff] + return nil + } + pnumLen := packetNumberLength(p.num, pnumMaxAcked) + plen := w.padPacketLength(pnumLen) + hdr := w.b[:w.pktOff] + var typeBits byte + switch p.ptype { + case packetTypeInitial: + typeBits = longPacketTypeInitial + case packetType0RTT: + typeBits = longPacketType0RTT + case packetTypeHandshake: + typeBits = longPacketTypeHandshake + case packetTypeRetry: + typeBits = longPacketTypeRetry + } + hdr = append(hdr, headerFormLong|fixedBit|typeBits|byte(pnumLen-1)) + hdr = binary.BigEndian.AppendUint32(hdr, p.version) + hdr = appendUint8Bytes(hdr, p.dstConnID) + hdr = appendUint8Bytes(hdr, p.srcConnID) + switch p.ptype { + case packetTypeInitial: + hdr = appendVarintBytes(hdr, p.extra) // token + } + + // Packet length, always encoded as a 2-byte varint. + hdr = append(hdr, 0x40|byte(plen>>8), byte(plen)) + + pnumOff := len(hdr) + hdr = appendPacketNumber(hdr, p.num, pnumMaxAcked) + + k.protect(hdr[w.pktOff:], w.b[len(hdr):], pnumOff-w.pktOff, p.num) + return w.finish(p.num) +} + +// start1RTTPacket starts writing a 1-RTT (short header) packet. +func (w *packetWriter) start1RTTPacket(pnum, pnumMaxAcked packetNumber, dstConnID []byte) { + if w.sent == nil { + w.sent = newSentPacket() + } + w.pktOff = len(w.b) + hdrSize := 1 // packet type + hdrSize += len(dstConnID) + // Ensure we have enough space to hold the packet, including the header, + // header protection sample (RFC 9001, section 5.4.2), and encryption overhead. + if len(w.b)+hdrSize+4+headerProtectionSampleSize+aeadOverhead >= w.dgramLim { + w.payOff = len(w.b) + w.pktLim = len(w.b) + return + } + hdrSize += packetNumberLength(pnum, pnumMaxAcked) + w.payOff = len(w.b) + hdrSize + w.pktLim = w.dgramLim - aeadOverhead + w.b = w.b[:w.payOff] +} + +// finish1RTTPacket finishes writing a 1-RTT packet, +// canceling the packet if it contains no payload. +// It returns a sentPacket describing the packet, or nil if no packet was written. +func (w *packetWriter) finish1RTTPacket(pnum, pnumMaxAcked packetNumber, dstConnID []byte, k *updatingKeyPair) *sentPacket { + if len(w.b) == w.payOff { + // The payload is empty, so just abandon the packet. + w.b = w.b[:w.pktOff] + return nil + } + // TODO: Spin + pnumLen := packetNumberLength(pnum, pnumMaxAcked) + hdr := w.b[:w.pktOff] + hdr = append(hdr, 0x40|byte(pnumLen-1)) + hdr = append(hdr, dstConnID...) + pnumOff := len(hdr) + hdr = appendPacketNumber(hdr, pnum, pnumMaxAcked) + w.padPacketLength(pnumLen) + k.protect(hdr[w.pktOff:], w.b[len(hdr):], pnumOff-w.pktOff, pnum) + return w.finish(pnum) +} + +// padPacketLength pads out the payload of the current packet to the minimum size, +// and returns the combined length of the packet number and payload (used for the Length +// field of long header packets). +func (w *packetWriter) padPacketLength(pnumLen int) int { + plen := len(w.b) - w.payOff + pnumLen + aeadOverhead + // "To ensure that sufficient data is available for sampling, packets are + // padded so that the combined lengths of the encoded packet number and + // protected payload is at least 4 bytes longer than the sample required + // for header protection." + // https://www.rfc-editor.org/rfc/rfc9001.html#section-5.4.2 + for plen < 4+headerProtectionSampleSize { + w.b = append(w.b, 0) + plen++ + } + return plen +} + +// finish finishes the current packet after protection is applied. +func (w *packetWriter) finish(pnum packetNumber) *sentPacket { + w.b = w.b[:len(w.b)+aeadOverhead] + w.sent.size = len(w.b) - w.pktOff + w.sent.num = pnum + sent := w.sent + w.sent = nil + return sent +} + +// avail reports how many more bytes may be written to the current packet. +func (w *packetWriter) avail() int { + return w.pktLim - len(w.b) +} + +// appendPaddingTo appends PADDING frames until the total datagram size +// (including AEAD overhead of the current packet) is n. +func (w *packetWriter) appendPaddingTo(n int) { + n -= aeadOverhead + lim := w.pktLim + if n < lim { + lim = n + } + if len(w.b) >= lim { + return + } + for len(w.b) < lim { + w.b = append(w.b, frameTypePadding) + } + // Packets are considered in flight when they contain a PADDING frame. + // https://www.rfc-editor.org/rfc/rfc9002.html#section-2-3.6.1 + w.sent.inFlight = true +} + +func (w *packetWriter) appendPingFrame() (added bool) { + if len(w.b) >= w.pktLim { + return false + } + w.b = append(w.b, frameTypePing) + // Mark this packet as ack-eliciting and in-flight, + // but there's no need to record the presence of a PING frame in it. + w.sent.ackEliciting = true + w.sent.inFlight = true + return true +} + +// appendAckFrame appends an ACK frame to the payload. +// It includes at least the most recent range in the rangeset +// (the range with the largest packet numbers), +// followed by as many additional ranges as fit within the packet. +// +// We always place ACK frames at the start of packets, +// we limit the number of ack ranges retained, and +// we set a minimum packet payload size. +// As a result, appendAckFrame will rarely if ever drop ranges +// in practice. +// +// In the event that ranges are dropped, the impact is limited +// to the peer potentially failing to receive an acknowledgement +// for an older packet during a period of high packet loss or +// reordering. This may result in unnecessary retransmissions. +func (w *packetWriter) appendAckFrame(seen rangeset[packetNumber], delay unscaledAckDelay) (added bool) { + if len(seen) == 0 { + return false + } + var ( + largest = uint64(seen.max()) + firstRange = uint64(seen[len(seen)-1].size() - 1) + ) + if w.avail() < 1+sizeVarint(largest)+sizeVarint(uint64(delay))+1+sizeVarint(firstRange) { + return false + } + w.b = append(w.b, frameTypeAck) + w.b = appendVarint(w.b, largest) + w.b = appendVarint(w.b, uint64(delay)) + // The range count is technically a varint, but we'll reserve a single byte for it + // and never add more than 62 ranges (the maximum varint that fits in a byte). + rangeCountOff := len(w.b) + w.b = append(w.b, 0) + w.b = appendVarint(w.b, firstRange) + rangeCount := byte(0) + for i := len(seen) - 2; i >= 0; i-- { + gap := uint64(seen[i+1].start - seen[i].end - 1) + size := uint64(seen[i].size() - 1) + if w.avail() < sizeVarint(gap)+sizeVarint(size) || rangeCount > 62 { + break + } + w.b = appendVarint(w.b, gap) + w.b = appendVarint(w.b, size) + rangeCount++ + } + w.b[rangeCountOff] = rangeCount + w.sent.appendNonAckElicitingFrame(frameTypeAck) + w.sent.appendInt(uint64(seen.max())) + return true +} + +func (w *packetWriter) appendNewTokenFrame(token []byte) (added bool) { + if w.avail() < 1+sizeVarint(uint64(len(token)))+len(token) { + return false + } + w.b = append(w.b, frameTypeNewToken) + w.b = appendVarintBytes(w.b, token) + return true +} + +func (w *packetWriter) appendResetStreamFrame(id streamID, code uint64, finalSize int64) (added bool) { + if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(code)+sizeVarint(uint64(finalSize)) { + return false + } + w.b = append(w.b, frameTypeResetStream) + w.b = appendVarint(w.b, uint64(id)) + w.b = appendVarint(w.b, code) + w.b = appendVarint(w.b, uint64(finalSize)) + w.sent.appendAckElicitingFrame(frameTypeResetStream) + w.sent.appendInt(uint64(id)) + return true +} + +func (w *packetWriter) appendStopSendingFrame(id streamID, code uint64) (added bool) { + if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(code) { + return false + } + w.b = append(w.b, frameTypeStopSending) + w.b = appendVarint(w.b, uint64(id)) + w.b = appendVarint(w.b, code) + w.sent.appendAckElicitingFrame(frameTypeStopSending) + w.sent.appendInt(uint64(id)) + return true +} + +// appendCryptoFrame appends a CRYPTO frame. +// It returns a []byte into which the data should be written and whether a frame was added. +// The returned []byte may be smaller than size if the packet cannot hold all the data. +func (w *packetWriter) appendCryptoFrame(off int64, size int) (_ []byte, added bool) { + max := w.avail() + max -= 1 // frame type + max -= sizeVarint(uint64(off)) // offset + max -= sizeVarint(uint64(size)) // maximum length + if max <= 0 { + return nil, false + } + if max < size { + size = max + } + w.b = append(w.b, frameTypeCrypto) + w.b = appendVarint(w.b, uint64(off)) + w.b = appendVarint(w.b, uint64(size)) + start := len(w.b) + w.b = w.b[:start+size] + w.sent.appendAckElicitingFrame(frameTypeCrypto) + w.sent.appendOffAndSize(off, size) + return w.b[start:][:size], true +} + +// appendStreamFrame appends a STREAM frame. +// It returns a []byte into which the data should be written and whether a frame was added. +// The returned []byte may be smaller than size if the packet cannot hold all the data. +func (w *packetWriter) appendStreamFrame(id streamID, off int64, size int, fin bool) (_ []byte, added bool) { + typ := uint8(frameTypeStreamBase | streamLenBit) + max := w.avail() + max -= 1 // frame type + max -= sizeVarint(uint64(id)) + if off != 0 { + max -= sizeVarint(uint64(off)) + typ |= streamOffBit + } + max -= sizeVarint(uint64(size)) // maximum length + if max < 0 || (max == 0 && size > 0) { + return nil, false + } + if max < size { + size = max + } else if fin { + typ |= streamFinBit + } + w.b = append(w.b, typ) + w.b = appendVarint(w.b, uint64(id)) + if off != 0 { + w.b = appendVarint(w.b, uint64(off)) + } + w.b = appendVarint(w.b, uint64(size)) + start := len(w.b) + w.b = w.b[:start+size] + if fin { + w.sent.appendAckElicitingFrame(frameTypeStreamBase | streamFinBit) + } else { + w.sent.appendAckElicitingFrame(frameTypeStreamBase) + } + w.sent.appendInt(uint64(id)) + w.sent.appendOffAndSize(off, size) + return w.b[start:][:size], true +} + +func (w *packetWriter) appendMaxDataFrame(max int64) (added bool) { + if w.avail() < 1+sizeVarint(uint64(max)) { + return false + } + w.b = append(w.b, frameTypeMaxData) + w.b = appendVarint(w.b, uint64(max)) + w.sent.appendAckElicitingFrame(frameTypeMaxData) + return true +} + +func (w *packetWriter) appendMaxStreamDataFrame(id streamID, max int64) (added bool) { + if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(uint64(max)) { + return false + } + w.b = append(w.b, frameTypeMaxStreamData) + w.b = appendVarint(w.b, uint64(id)) + w.b = appendVarint(w.b, uint64(max)) + w.sent.appendAckElicitingFrame(frameTypeMaxStreamData) + w.sent.appendInt(uint64(id)) + return true +} + +func (w *packetWriter) appendMaxStreamsFrame(streamType streamType, max int64) (added bool) { + if w.avail() < 1+sizeVarint(uint64(max)) { + return false + } + var typ byte + if streamType == bidiStream { + typ = frameTypeMaxStreamsBidi + } else { + typ = frameTypeMaxStreamsUni + } + w.b = append(w.b, typ) + w.b = appendVarint(w.b, uint64(max)) + w.sent.appendAckElicitingFrame(typ) + return true +} + +func (w *packetWriter) appendDataBlockedFrame(max int64) (added bool) { + if w.avail() < 1+sizeVarint(uint64(max)) { + return false + } + w.b = append(w.b, frameTypeDataBlocked) + w.b = appendVarint(w.b, uint64(max)) + w.sent.appendAckElicitingFrame(frameTypeDataBlocked) + return true +} + +func (w *packetWriter) appendStreamDataBlockedFrame(id streamID, max int64) (added bool) { + if w.avail() < 1+sizeVarint(uint64(id))+sizeVarint(uint64(max)) { + return false + } + w.b = append(w.b, frameTypeStreamDataBlocked) + w.b = appendVarint(w.b, uint64(id)) + w.b = appendVarint(w.b, uint64(max)) + w.sent.appendAckElicitingFrame(frameTypeStreamDataBlocked) + w.sent.appendInt(uint64(id)) + return true +} + +func (w *packetWriter) appendStreamsBlockedFrame(typ streamType, max int64) (added bool) { + if w.avail() < 1+sizeVarint(uint64(max)) { + return false + } + var ftype byte + if typ == bidiStream { + ftype = frameTypeStreamsBlockedBidi + } else { + ftype = frameTypeStreamsBlockedUni + } + w.b = append(w.b, ftype) + w.b = appendVarint(w.b, uint64(max)) + w.sent.appendAckElicitingFrame(ftype) + return true +} + +func (w *packetWriter) appendNewConnectionIDFrame(seq, retirePriorTo int64, connID []byte, token [16]byte) (added bool) { + if w.avail() < 1+sizeVarint(uint64(seq))+sizeVarint(uint64(retirePriorTo))+1+len(connID)+len(token) { + return false + } + w.b = append(w.b, frameTypeNewConnectionID) + w.b = appendVarint(w.b, uint64(seq)) + w.b = appendVarint(w.b, uint64(retirePriorTo)) + w.b = appendUint8Bytes(w.b, connID) + w.b = append(w.b, token[:]...) + w.sent.appendAckElicitingFrame(frameTypeNewConnectionID) + w.sent.appendInt(uint64(seq)) + return true +} + +func (w *packetWriter) appendRetireConnectionIDFrame(seq int64) (added bool) { + if w.avail() < 1+sizeVarint(uint64(seq)) { + return false + } + w.b = append(w.b, frameTypeRetireConnectionID) + w.b = appendVarint(w.b, uint64(seq)) + w.sent.appendAckElicitingFrame(frameTypeRetireConnectionID) + w.sent.appendInt(uint64(seq)) + return true +} + +func (w *packetWriter) appendPathChallengeFrame(data uint64) (added bool) { + if w.avail() < 1+8 { + return false + } + w.b = append(w.b, frameTypePathChallenge) + w.b = binary.BigEndian.AppendUint64(w.b, data) + w.sent.appendAckElicitingFrame(frameTypePathChallenge) + return true +} + +func (w *packetWriter) appendPathResponseFrame(data uint64) (added bool) { + if w.avail() < 1+8 { + return false + } + w.b = append(w.b, frameTypePathResponse) + w.b = binary.BigEndian.AppendUint64(w.b, data) + w.sent.appendAckElicitingFrame(frameTypePathResponse) + return true +} + +// appendConnectionCloseTransportFrame appends a CONNECTION_CLOSE frame +// carrying a transport error code. +func (w *packetWriter) appendConnectionCloseTransportFrame(code transportError, frameType uint64, reason string) (added bool) { + if w.avail() < 1+sizeVarint(uint64(code))+sizeVarint(frameType)+sizeVarint(uint64(len(reason)))+len(reason) { + return false + } + w.b = append(w.b, frameTypeConnectionCloseTransport) + w.b = appendVarint(w.b, uint64(code)) + w.b = appendVarint(w.b, frameType) + w.b = appendVarintBytes(w.b, []byte(reason)) + // We don't record CONNECTION_CLOSE frames in w.sent, since they are never acked or + // detected as lost. + return true +} + +// appendConnectionCloseTransportFrame appends a CONNECTION_CLOSE frame +// carrying an application protocol error code. +func (w *packetWriter) appendConnectionCloseApplicationFrame(code uint64, reason string) (added bool) { + if w.avail() < 1+sizeVarint(code)+sizeVarint(uint64(len(reason)))+len(reason) { + return false + } + w.b = append(w.b, frameTypeConnectionCloseApplication) + w.b = appendVarint(w.b, code) + w.b = appendVarintBytes(w.b, []byte(reason)) + // We don't record CONNECTION_CLOSE frames in w.sent, since they are never acked or + // detected as lost. + return true +} + +func (w *packetWriter) appendHandshakeDoneFrame() (added bool) { + if w.avail() < 1 { + return false + } + w.b = append(w.b, frameTypeHandshakeDone) + w.sent.appendAckElicitingFrame(frameTypeHandshakeDone) + return true +} diff --git a/internal/quic/ping.go b/internal/quic/ping.go new file mode 100644 index 000000000..3e7d9c51b --- /dev/null +++ b/internal/quic/ping.go @@ -0,0 +1,16 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "time" + +func (c *Conn) ping(space numberSpace) { + c.sendMsg(func(now time.Time, c *Conn) { + c.testSendPing.setUnsent() + c.testSendPingSpace = space + }) +} diff --git a/internal/quic/ping_test.go b/internal/quic/ping_test.go new file mode 100644 index 000000000..a8fdf2567 --- /dev/null +++ b/internal/quic/ping_test.go @@ -0,0 +1,43 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "testing" + +func TestPing(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.handshake() + + tc.conn.ping(appDataSpace) + tc.wantFrame("connection should send a PING frame", + packetType1RTT, debugFramePing{}) + + tc.advanceToTimer() + tc.wantFrame("on PTO, connection should send another PING frame", + packetType1RTT, debugFramePing{}) + + tc.wantIdle("after sending PTO probe, no additional frames to send") +} + +func TestAck(t *testing.T) { + tc := newTestConn(t, serverSide) + tc.handshake() + + // Send two packets, to trigger an immediate ACK. + tc.writeFrames(packetType1RTT, + debugFramePing{}, + ) + tc.writeFrames(packetType1RTT, + debugFramePing{}, + ) + tc.wantFrame("connection should respond to ack-eliciting packet with an ACK frame", + packetType1RTT, + debugFrameAck{ + ranges: []i64range[packetNumber]{{0, 4}}, + }, + ) +} diff --git a/internal/quic/pipe.go b/internal/quic/pipe.go new file mode 100644 index 000000000..978a4f3d8 --- /dev/null +++ b/internal/quic/pipe.go @@ -0,0 +1,149 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "sync" +) + +// A pipe is a byte buffer used in implementing streams. +// +// A pipe contains a window of stream data. +// Random access reads and writes are supported within the window. +// Writing past the end of the window extends it. +// Data may be discarded from the start of the pipe, advancing the window. +type pipe struct { + start int64 + end int64 + head *pipebuf + tail *pipebuf +} + +type pipebuf struct { + off int64 + b []byte + next *pipebuf +} + +func (pb *pipebuf) end() int64 { + return pb.off + int64(len(pb.b)) +} + +var pipebufPool = sync.Pool{ + New: func() any { + return &pipebuf{ + b: make([]byte, 4096), + } + }, +} + +func newPipebuf() *pipebuf { + return pipebufPool.Get().(*pipebuf) +} + +func (b *pipebuf) recycle() { + b.off = 0 + b.next = nil + pipebufPool.Put(b) +} + +// writeAt writes len(b) bytes to the pipe at offset off. +// +// Writes to offsets before p.start are discarded. +// Writes to offsets after p.end extend the pipe window. +func (p *pipe) writeAt(b []byte, off int64) { + end := off + int64(len(b)) + if end > p.end { + p.end = end + } else if end <= p.start { + return + } + + if off < p.start { + // Discard the portion of b which falls before p.start. + trim := p.start - off + b = b[trim:] + off = p.start + } + + if p.head == nil { + p.head = newPipebuf() + p.head.off = p.start + p.tail = p.head + } + pb := p.head + if off >= p.tail.off { + // Common case: Writing past the end of the pipe. + pb = p.tail + } + for { + pboff := off - pb.off + if pboff < int64(len(pb.b)) { + n := copy(pb.b[pboff:], b) + if n == len(b) { + return + } + off += int64(n) + b = b[n:] + } + if pb.next == nil { + pb.next = newPipebuf() + pb.next.off = pb.off + int64(len(pb.b)) + p.tail = pb.next + } + pb = pb.next + } +} + +// copy copies len(b) bytes into b starting from off. +// The pipe must contain [off, off+len(b)). +func (p *pipe) copy(off int64, b []byte) { + dst := b[:0] + p.read(off, len(b), func(c []byte) error { + dst = append(dst, c...) + return nil + }) +} + +// read calls f with the data in [off, off+n) +// The data may be provided sequentially across multiple calls to f. +func (p *pipe) read(off int64, n int, f func([]byte) error) error { + if off < p.start { + panic("invalid read range") + } + for pb := p.head; pb != nil && n > 0; pb = pb.next { + if off >= pb.end() { + continue + } + b := pb.b[off-pb.off:] + if len(b) > n { + b = b[:n] + } + off += int64(len(b)) + n -= len(b) + if err := f(b); err != nil { + return err + } + } + if n > 0 { + panic("invalid read range") + } + return nil +} + +// discardBefore discards all data prior to off. +func (p *pipe) discardBefore(off int64) { + for p.head != nil && p.head.end() < off { + head := p.head + p.head = p.head.next + head.recycle() + } + if p.head == nil { + p.tail = nil + } + p.start = off +} diff --git a/internal/quic/pipe_test.go b/internal/quic/pipe_test.go new file mode 100644 index 000000000..7a05ff4d4 --- /dev/null +++ b/internal/quic/pipe_test.go @@ -0,0 +1,95 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "math/rand" + "testing" +) + +func TestPipeWrites(t *testing.T) { + type writeOp struct { + start, end int64 + } + type discardBeforeOp struct { + off int64 + } + type op any + src := make([]byte, 65536) + rand.New(rand.NewSource(0)).Read(src) + for _, test := range []struct { + desc string + ops []op + }{{ + desc: "sequential writes", + ops: []op{ + writeOp{0, 1024}, + writeOp{1024, 4096}, + writeOp{4096, 65536}, + }, + }, { + desc: "disordered overlapping writes", + ops: []op{ + writeOp{2000, 8000}, + writeOp{0, 3000}, + writeOp{7000, 12000}, + }, + }, { + desc: "write to discarded region", + ops: []op{ + writeOp{0, 65536}, + discardBeforeOp{32768}, + writeOp{0, 1000}, + writeOp{3000, 5000}, + writeOp{0, 32768}, + }, + }, { + desc: "write overlaps discarded region", + ops: []op{ + discardBeforeOp{10000}, + writeOp{0, 20000}, + }, + }, { + desc: "discard everything", + ops: []op{ + writeOp{0, 10000}, + discardBeforeOp{10000}, + writeOp{10000, 20000}, + }, + }} { + var p pipe + var wantset rangeset[int64] + var wantStart, wantEnd int64 + for i, o := range test.ops { + switch o := o.(type) { + case writeOp: + p.writeAt(src[o.start:o.end], o.start) + wantset.add(o.start, o.end) + wantset.sub(0, wantStart) + if o.end > wantEnd { + wantEnd = o.end + } + case discardBeforeOp: + p.discardBefore(o.off) + wantset.sub(0, o.off) + wantStart = o.off + } + if p.start != wantStart || p.end != wantEnd { + t.Errorf("%v: after %#v p contains [%v,%v), want [%v,%v)", test.desc, test.ops[:i+1], p.start, p.end, wantStart, wantEnd) + } + for _, r := range wantset { + want := src[r.start:][:r.size()] + got := make([]byte, r.size()) + p.copy(r.start, got) + if !bytes.Equal(got, want) { + t.Errorf("%v after %#v, mismatch in data in %v", test.desc, test.ops[:i+1], r) + } + } + } + } +} diff --git a/internal/quic/queue.go b/internal/quic/queue.go new file mode 100644 index 000000000..7085e578b --- /dev/null +++ b/internal/quic/queue.go @@ -0,0 +1,65 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "context" + +// A queue is an unbounded queue of some item (new connections and streams). +type queue[T any] struct { + // The gate condition is set if the queue is non-empty or closed. + gate gate + err error + q []T +} + +func newQueue[T any]() queue[T] { + return queue[T]{gate: newGate()} +} + +// close closes the queue, causing pending and future pop operations +// to return immediately with err. +func (q *queue[T]) close(err error) { + q.gate.lock() + defer q.unlock() + if q.err == nil { + q.err = err + } +} + +// put appends an item to the queue. +// It returns true if the item was added, false if the queue is closed. +func (q *queue[T]) put(v T) bool { + q.gate.lock() + defer q.unlock() + if q.err != nil { + return false + } + q.q = append(q.q, v) + return true +} + +// get removes the first item from the queue, blocking until ctx is done, an item is available, +// or the queue is closed. +func (q *queue[T]) get(ctx context.Context, testHooks connTestHooks) (T, error) { + var zero T + if err := q.gate.waitAndLock(ctx, testHooks); err != nil { + return zero, err + } + defer q.unlock() + if q.err != nil { + return zero, q.err + } + v := q.q[0] + copy(q.q[:], q.q[1:]) + q.q[len(q.q)-1] = zero + q.q = q.q[:len(q.q)-1] + return v, nil +} + +func (q *queue[T]) unlock() { + q.gate.unlock(q.err != nil || len(q.q) > 0) +} diff --git a/internal/quic/queue_test.go b/internal/quic/queue_test.go new file mode 100644 index 000000000..d78216b0e --- /dev/null +++ b/internal/quic/queue_test.go @@ -0,0 +1,59 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "io" + "testing" + "time" +) + +func TestQueue(t *testing.T) { + nonblocking, cancel := context.WithCancel(context.Background()) + cancel() + + q := newQueue[int]() + if got, err := q.get(nonblocking, nil); err != context.Canceled { + t.Fatalf("q.get() = %v, %v, want nil, contex.Canceled", got, err) + } + + if !q.put(1) { + t.Fatalf("q.put(1) = false, want true") + } + if !q.put(2) { + t.Fatalf("q.put(2) = false, want true") + } + if got, err := q.get(nonblocking, nil); got != 1 || err != nil { + t.Fatalf("q.get() = %v, %v, want 1, nil", got, err) + } + if got, err := q.get(nonblocking, nil); got != 2 || err != nil { + t.Fatalf("q.get() = %v, %v, want 2, nil", got, err) + } + if got, err := q.get(nonblocking, nil); err != context.Canceled { + t.Fatalf("q.get() = %v, %v, want nil, contex.Canceled", got, err) + } + + go func() { + time.Sleep(1 * time.Millisecond) + q.put(3) + }() + if got, err := q.get(context.Background(), nil); got != 3 || err != nil { + t.Fatalf("q.get() = %v, %v, want 3, nil", got, err) + } + + if !q.put(4) { + t.Fatalf("q.put(2) = false, want true") + } + q.close(io.EOF) + if got, err := q.get(context.Background(), nil); got != 0 || err != io.EOF { + t.Fatalf("q.get() = %v, %v, want 0, io.EOF", got, err) + } + if q.put(5) { + t.Fatalf("q.put(5) = true, want false") + } +} diff --git a/internal/quic/quic.go b/internal/quic/quic.go new file mode 100644 index 000000000..9de97b6d8 --- /dev/null +++ b/internal/quic/quic.go @@ -0,0 +1,201 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "time" +) + +// QUIC versions. +// We only support v1 at this time. +const ( + quicVersion1 = 1 + quicVersion2 = 0x6b3343cf // https://www.rfc-editor.org/rfc/rfc9369 +) + +// connIDLen is the length in bytes of connection IDs chosen by this package. +// Since 1-RTT packets don't include a connection ID length field, +// we use a consistent length for all our IDs. +// https://www.rfc-editor.org/rfc/rfc9000.html#section-5.1-6 +const connIDLen = 8 + +// Local values of various transport parameters. +// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2 +const ( + defaultMaxIdleTimeout = 30 * time.Second // max_idle_timeout + + // The max_udp_payload_size transport parameter is the size of our + // network receive buffer. + // + // Set this to the largest UDP packet that can be sent over + // Ethernet without using jumbo frames: 1500 byte Ethernet frame, + // minus 20 byte IPv4 header and 8 byte UDP header. + // + // The maximum possible UDP payload is 65527 bytes. Supporting this + // without wasting memory in unused receive buffers will require some + // care. For now, just limit ourselves to the most common case. + maxUDPPayloadSize = 1472 + + ackDelayExponent = 3 // ack_delay_exponent + maxAckDelay = 25 * time.Millisecond // max_ack_delay + + // The active_conn_id_limit transport parameter is the maximum + // number of connection IDs from the peer we're willing to store. + // + // maxPeerActiveConnIDLimit is the maximum number of connection IDs + // we're willing to send to the peer. + // + // https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2-6.2.1 + activeConnIDLimit = 2 + maxPeerActiveConnIDLimit = 4 +) + +// Local timer granularity. +// https://www.rfc-editor.org/rfc/rfc9002.html#section-6.1.2-6 +const timerGranularity = 1 * time.Millisecond + +// Minimum size of a UDP datagram sent by a client carrying an Initial packet. +// https://www.rfc-editor.org/rfc/rfc9000#section-14.1 +const minimumClientInitialDatagramSize = 1200 + +// Maximum number of streams of a given type which may be created. +// https://www.rfc-editor.org/rfc/rfc9000.html#section-4.6-2 +const maxStreamsLimit = 1 << 60 + +// Maximum number of streams we will allow the peer to create implicitly. +// A stream ID that is used out of order results in all streams of that type +// with lower-numbered IDs also being opened. To limit the amount of work we +// will do in response to a single frame, we cap the peer's stream limit to +// this value. +const implicitStreamLimit = 100 + +// A connSide distinguishes between the client and server sides of a connection. +type connSide int8 + +const ( + clientSide = connSide(iota) + serverSide +) + +func (s connSide) String() string { + switch s { + case clientSide: + return "client" + case serverSide: + return "server" + default: + return "BUG" + } +} + +func (s connSide) peer() connSide { + if s == clientSide { + return serverSide + } else { + return clientSide + } +} + +// A numberSpace is the context in which a packet number applies. +// https://www.rfc-editor.org/rfc/rfc9000.html#section-12.3-7 +type numberSpace byte + +const ( + initialSpace = numberSpace(iota) + handshakeSpace + appDataSpace + numberSpaceCount +) + +func (n numberSpace) String() string { + switch n { + case initialSpace: + return "Initial" + case handshakeSpace: + return "Handshake" + case appDataSpace: + return "AppData" + default: + return "BUG" + } +} + +// A streamType is the type of a stream: bidirectional or unidirectional. +type streamType uint8 + +const ( + bidiStream = streamType(iota) + uniStream + streamTypeCount +) + +func (s streamType) String() string { + switch s { + case bidiStream: + return "bidi" + case uniStream: + return "uni" + default: + return "BUG" + } +} + +// A streamID is a QUIC stream ID. +// https://www.rfc-editor.org/rfc/rfc9000.html#section-2.1 +type streamID uint64 + +// The two least significant bits of a stream ID indicate the initiator +// and directionality of the stream. The upper bits are the stream number. +// Each of the four possible combinations of initiator and direction +// each has a distinct number space. +const ( + clientInitiatedStreamBit = 0x0 + serverInitiatedStreamBit = 0x1 + initiatorStreamBitMask = 0x1 + + bidiStreamBit = 0x0 + uniStreamBit = 0x2 + dirStreamBitMask = 0x2 +) + +func newStreamID(initiator connSide, typ streamType, num int64) streamID { + id := streamID(num << 2) + if typ == uniStream { + id |= uniStreamBit + } + if initiator == serverSide { + id |= serverInitiatedStreamBit + } + return id +} + +func (s streamID) initiator() connSide { + if s&initiatorStreamBitMask == serverInitiatedStreamBit { + return serverSide + } + return clientSide +} + +func (s streamID) num() int64 { + return int64(s) >> 2 +} + +func (s streamID) streamType() streamType { + if s&dirStreamBitMask == uniStreamBit { + return uniStream + } + return bidiStream +} + +// packetFate is the fate of a sent packet: Either acknowledged by the peer, +// or declared lost. +type packetFate byte + +const ( + packetLost = packetFate(iota) + packetAcked +) diff --git a/internal/quic/quic_test.go b/internal/quic/quic_test.go new file mode 100644 index 000000000..1281b54ee --- /dev/null +++ b/internal/quic/quic_test.go @@ -0,0 +1,37 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "testing" +) + +func testSides(t *testing.T, name string, f func(*testing.T, connSide)) { + if name != "" { + name += "/" + } + t.Run(name+"server", func(t *testing.T) { f(t, serverSide) }) + t.Run(name+"client", func(t *testing.T) { f(t, clientSide) }) +} + +func testStreamTypes(t *testing.T, name string, f func(*testing.T, streamType)) { + if name != "" { + name += "/" + } + t.Run(name+"bidi", func(t *testing.T) { f(t, bidiStream) }) + t.Run(name+"uni", func(t *testing.T) { f(t, uniStream) }) +} + +func testSidesAndStreamTypes(t *testing.T, name string, f func(*testing.T, connSide, streamType)) { + if name != "" { + name += "/" + } + t.Run(name+"server/bidi", func(t *testing.T) { f(t, serverSide, bidiStream) }) + t.Run(name+"client/bidi", func(t *testing.T) { f(t, clientSide, bidiStream) }) + t.Run(name+"server/uni", func(t *testing.T) { f(t, serverSide, uniStream) }) + t.Run(name+"client/uni", func(t *testing.T) { f(t, clientSide, uniStream) }) +} diff --git a/internal/quic/rangeset.go b/internal/quic/rangeset.go new file mode 100644 index 000000000..4966a99d2 --- /dev/null +++ b/internal/quic/rangeset.go @@ -0,0 +1,187 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +// A rangeset is a set of int64s, stored as an ordered list of non-overlapping, +// non-empty ranges. +// +// Rangesets are efficient for small numbers of ranges, +// which is expected to be the common case. +type rangeset[T ~int64] []i64range[T] + +type i64range[T ~int64] struct { + start, end T // [start, end) +} + +// size returns the size of the range. +func (r i64range[T]) size() T { + return r.end - r.start +} + +// contains reports whether v is in the range. +func (r i64range[T]) contains(v T) bool { + return r.start <= v && v < r.end +} + +// add adds [start, end) to the set, combining it with existing ranges if necessary. +func (s *rangeset[T]) add(start, end T) { + if start == end { + return + } + for i := range *s { + r := &(*s)[i] + if r.start > end { + // The new range comes before range i. + s.insertrange(i, start, end) + return + } + if start > r.end { + // The new range comes after range i. + continue + } + // The new range is adjacent to or overlapping range i. + if start < r.start { + r.start = start + } + if end <= r.end { + return + } + // Possibly coalesce subsquent ranges into range i. + r.end = end + j := i + 1 + for ; j < len(*s) && r.end >= (*s)[j].start; j++ { + if e := (*s)[j].end; e > r.end { + // Range j ends after the new range. + r.end = e + } + } + s.removeranges(i+1, j) + return + } + *s = append(*s, i64range[T]{start, end}) +} + +// sub removes [start, end) from the set. +func (s *rangeset[T]) sub(start, end T) { + removefrom, removeto := -1, -1 + for i := range *s { + r := &(*s)[i] + if end < r.start { + break + } + if r.end < start { + continue + } + switch { + case start <= r.start && end >= r.end: + // Remove the entire range. + if removefrom == -1 { + removefrom = i + } + removeto = i + 1 + case start <= r.start: + // Remove a prefix. + r.start = end + case end >= r.end: + // Remove a suffix. + r.end = start + default: + // Remove the middle, leaving two new ranges. + rend := r.end + r.end = start + s.insertrange(i+1, end, rend) + return + } + } + if removefrom != -1 { + s.removeranges(removefrom, removeto) + } +} + +// contains reports whether s contains v. +func (s rangeset[T]) contains(v T) bool { + for _, r := range s { + if v >= r.end { + continue + } + if r.start <= v { + return true + } + return false + } + return false +} + +// rangeContaining returns the range containing v, or the range [0,0) if v is not in s. +func (s rangeset[T]) rangeContaining(v T) i64range[T] { + for _, r := range s { + if v >= r.end { + continue + } + if r.start <= v { + return r + } + break + } + return i64range[T]{0, 0} +} + +// min returns the minimum value in the set, or 0 if empty. +func (s rangeset[T]) min() T { + if len(s) == 0 { + return 0 + } + return s[0].start +} + +// max returns the maximum value in the set, or 0 if empty. +func (s rangeset[T]) max() T { + if len(s) == 0 { + return 0 + } + return s[len(s)-1].end - 1 +} + +// end returns the end of the last range in the set, or 0 if empty. +func (s rangeset[T]) end() T { + if len(s) == 0 { + return 0 + } + return s[len(s)-1].end +} + +// numRanges returns the number of ranges in the rangeset. +func (s rangeset[T]) numRanges() int { + return len(s) +} + +// isrange reports if the rangeset covers exactly the range [start, end). +func (s rangeset[T]) isrange(start, end T) bool { + switch len(s) { + case 0: + return start == 0 && end == 0 + case 1: + return s[0].start == start && s[0].end == end + } + return false +} + +// removeranges removes ranges [i,j). +func (s *rangeset[T]) removeranges(i, j int) { + if i == j { + return + } + copy((*s)[i:], (*s)[j:]) + *s = (*s)[:len(*s)-(j-i)] +} + +// insert adds a new range at index i. +func (s *rangeset[T]) insertrange(i int, start, end T) { + *s = append(*s, i64range[T]{}) + copy((*s)[i+1:], (*s)[i:]) + (*s)[i] = i64range[T]{start, end} +} diff --git a/internal/quic/rangeset_test.go b/internal/quic/rangeset_test.go new file mode 100644 index 000000000..2027f14b8 --- /dev/null +++ b/internal/quic/rangeset_test.go @@ -0,0 +1,317 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "reflect" + "testing" +) + +func TestRangeSize(t *testing.T) { + for _, test := range []struct { + r i64range[int64] + want int64 + }{{ + r: i64range[int64]{0, 100}, + want: 100, + }, { + r: i64range[int64]{10, 20}, + want: 10, + }} { + if got := test.r.size(); got != test.want { + t.Errorf("%+v.size = %v, want %v", test.r, got, test.want) + } + } +} + +func TestRangeContains(t *testing.T) { + r := i64range[int64]{5, 10} + for _, i := range []int64{0, 4, 10, 15} { + if r.contains(i) { + t.Errorf("%v.contains(%v) = true, want false", r, i) + } + } + for _, i := range []int64{5, 6, 7, 8, 9} { + if !r.contains(i) { + t.Errorf("%v.contains(%v) = false, want true", r, i) + } + } +} + +func TestRangesetAdd(t *testing.T) { + for _, test := range []struct { + desc string + set rangeset[int64] + add i64range[int64] + want rangeset[int64] + }{{ + desc: "add to empty set", + set: rangeset[int64]{}, + add: i64range[int64]{0, 100}, + want: rangeset[int64]{{0, 100}}, + }, { + desc: "add empty range", + set: rangeset[int64]{}, + add: i64range[int64]{100, 100}, + want: rangeset[int64]{}, + }, { + desc: "append nonadjacent range", + set: rangeset[int64]{{100, 200}}, + add: i64range[int64]{300, 400}, + want: rangeset[int64]{{100, 200}, {300, 400}}, + }, { + desc: "prepend nonadjacent range", + set: rangeset[int64]{{100, 200}}, + add: i64range[int64]{0, 50}, + want: rangeset[int64]{{0, 50}, {100, 200}}, + }, { + desc: "insert nonadjacent range", + set: rangeset[int64]{{100, 200}, {500, 600}}, + add: i64range[int64]{300, 400}, + want: rangeset[int64]{{100, 200}, {300, 400}, {500, 600}}, + }, { + desc: "prepend adjacent range", + set: rangeset[int64]{{100, 200}}, + add: i64range[int64]{50, 100}, + want: rangeset[int64]{{50, 200}}, + }, { + desc: "append adjacent range", + set: rangeset[int64]{{100, 200}}, + add: i64range[int64]{200, 250}, + want: rangeset[int64]{{100, 250}}, + }, { + desc: "prepend overlapping range", + set: rangeset[int64]{{100, 200}}, + add: i64range[int64]{50, 150}, + want: rangeset[int64]{{50, 200}}, + }, { + desc: "append overlapping range", + set: rangeset[int64]{{100, 200}}, + add: i64range[int64]{150, 250}, + want: rangeset[int64]{{100, 250}}, + }, { + desc: "replace range", + set: rangeset[int64]{{100, 200}}, + add: i64range[int64]{50, 250}, + want: rangeset[int64]{{50, 250}}, + }, { + desc: "prepend and combine", + set: rangeset[int64]{{100, 200}, {300, 400}, {500, 600}}, + add: i64range[int64]{50, 300}, + want: rangeset[int64]{{50, 400}, {500, 600}}, + }, { + desc: "combine several ranges", + set: rangeset[int64]{{100, 200}, {300, 400}, {500, 600}, {700, 800}, {900, 1000}}, + add: i64range[int64]{300, 850}, + want: rangeset[int64]{{100, 200}, {300, 850}, {900, 1000}}, + }} { + test := test + t.Run(test.desc, func(t *testing.T) { + got := test.set + got.add(test.add.start, test.add.end) + if !reflect.DeepEqual(got, test.want) { + t.Errorf("add [%v,%v) to %v", test.add.start, test.add.end, test.set) + t.Errorf(" got: %v", got) + t.Errorf(" want: %v", test.want) + } + }) + } +} + +func TestRangesetSub(t *testing.T) { + for _, test := range []struct { + desc string + set rangeset[int64] + sub i64range[int64] + want rangeset[int64] + }{{ + desc: "subtract from empty set", + set: rangeset[int64]{}, + sub: i64range[int64]{0, 100}, + want: rangeset[int64]{}, + }, { + desc: "subtract empty range", + set: rangeset[int64]{{0, 100}}, + sub: i64range[int64]{0, 0}, + want: rangeset[int64]{{0, 100}}, + }, { + desc: "subtract not present in set", + set: rangeset[int64]{{0, 100}, {200, 300}}, + sub: i64range[int64]{100, 200}, + want: rangeset[int64]{{0, 100}, {200, 300}}, + }, { + desc: "subtract prefix", + set: rangeset[int64]{{100, 200}}, + sub: i64range[int64]{0, 150}, + want: rangeset[int64]{{150, 200}}, + }, { + desc: "subtract suffix", + set: rangeset[int64]{{100, 200}}, + sub: i64range[int64]{150, 300}, + want: rangeset[int64]{{100, 150}}, + }, { + desc: "subtract middle", + set: rangeset[int64]{{0, 100}}, + sub: i64range[int64]{40, 60}, + want: rangeset[int64]{{0, 40}, {60, 100}}, + }, { + desc: "subtract from two ranges", + set: rangeset[int64]{{0, 100}, {200, 300}}, + sub: i64range[int64]{50, 250}, + want: rangeset[int64]{{0, 50}, {250, 300}}, + }, { + desc: "subtract removes range", + set: rangeset[int64]{{0, 100}, {200, 300}, {400, 500}}, + sub: i64range[int64]{200, 300}, + want: rangeset[int64]{{0, 100}, {400, 500}}, + }, { + desc: "subtract removes multiple ranges", + set: rangeset[int64]{{0, 100}, {200, 300}, {400, 500}, {600, 700}}, + sub: i64range[int64]{50, 650}, + want: rangeset[int64]{{0, 50}, {650, 700}}, + }, { + desc: "subtract only range", + set: rangeset[int64]{{0, 100}}, + sub: i64range[int64]{0, 100}, + want: rangeset[int64]{}, + }} { + test := test + t.Run(test.desc, func(t *testing.T) { + got := test.set + got.sub(test.sub.start, test.sub.end) + if !reflect.DeepEqual(got, test.want) { + t.Errorf("sub [%v,%v) from %v", test.sub.start, test.sub.end, test.set) + t.Errorf(" got: %v", got) + t.Errorf(" want: %v", test.want) + } + }) + } +} + +func TestRangesetContains(t *testing.T) { + var s rangeset[int64] + s.add(10, 20) + s.add(30, 40) + for i := int64(0); i < 50; i++ { + want := (i >= 10 && i < 20) || (i >= 30 && i < 40) + if got := s.contains(i); got != want { + t.Errorf("%v.contains(%v) = %v, want %v", s, i, got, want) + } + } +} + +func TestRangesetRangeContaining(t *testing.T) { + var s rangeset[int64] + s.add(10, 20) + s.add(30, 40) + for _, test := range []struct { + v int64 + want i64range[int64] + }{ + {0, i64range[int64]{0, 0}}, + {9, i64range[int64]{0, 0}}, + {10, i64range[int64]{10, 20}}, + {15, i64range[int64]{10, 20}}, + {19, i64range[int64]{10, 20}}, + {20, i64range[int64]{0, 0}}, + {29, i64range[int64]{0, 0}}, + {30, i64range[int64]{30, 40}}, + {39, i64range[int64]{30, 40}}, + {40, i64range[int64]{0, 0}}, + } { + got := s.rangeContaining(test.v) + if got != test.want { + t.Errorf("%v.rangeContaining(%v) = %v, want %v", s, test.v, got, test.want) + } + } +} + +func TestRangesetLimits(t *testing.T) { + for _, test := range []struct { + s rangeset[int64] + wantMin int64 + wantMax int64 + wantEnd int64 + }{{ + s: rangeset[int64]{}, + wantMin: 0, + wantMax: 0, + wantEnd: 0, + }, { + s: rangeset[int64]{{10, 20}}, + wantMin: 10, + wantMax: 19, + wantEnd: 20, + }, { + s: rangeset[int64]{{10, 20}, {30, 40}, {50, 60}}, + wantMin: 10, + wantMax: 59, + wantEnd: 60, + }} { + if got, want := test.s.min(), test.wantMin; got != want { + t.Errorf("%+v.min() = %v, want %v", test.s, got, want) + } + if got, want := test.s.max(), test.wantMax; got != want { + t.Errorf("%+v.max() = %v, want %v", test.s, got, want) + } + if got, want := test.s.end(), test.wantEnd; got != want { + t.Errorf("%+v.end() = %v, want %v", test.s, got, want) + } + } +} + +func TestRangesetIsRange(t *testing.T) { + for _, test := range []struct { + s rangeset[int64] + r i64range[int64] + want bool + }{{ + s: rangeset[int64]{{0, 100}}, + r: i64range[int64]{0, 100}, + want: true, + }, { + s: rangeset[int64]{{0, 100}}, + r: i64range[int64]{0, 101}, + want: false, + }, { + s: rangeset[int64]{{0, 10}, {11, 100}}, + r: i64range[int64]{0, 100}, + want: false, + }, { + s: rangeset[int64]{}, + r: i64range[int64]{0, 0}, + want: true, + }, { + s: rangeset[int64]{}, + r: i64range[int64]{0, 1}, + want: false, + }} { + if got := test.s.isrange(test.r.start, test.r.end); got != test.want { + t.Errorf("%+v.isrange(%v, %v) = %v, want %v", test.s, test.r.start, test.r.end, got, test.want) + } + } +} + +func TestRangesetNumRanges(t *testing.T) { + for _, test := range []struct { + s rangeset[int64] + want int + }{{ + s: rangeset[int64]{}, + want: 0, + }, { + s: rangeset[int64]{{0, 100}}, + want: 1, + }, { + s: rangeset[int64]{{0, 100}, {200, 300}}, + want: 2, + }} { + if got, want := test.s.numRanges(), test.want; got != want { + t.Errorf("%+v.numRanges() = %v, want %v", test.s, got, want) + } + } +} diff --git a/internal/quic/rtt.go b/internal/quic/rtt.go new file mode 100644 index 000000000..4942f8cca --- /dev/null +++ b/internal/quic/rtt.go @@ -0,0 +1,73 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "time" +) + +type rttState struct { + minRTT time.Duration + latestRTT time.Duration + smoothedRTT time.Duration + rttvar time.Duration // RTT variation + firstSampleTime time.Time // time of first RTT sample +} + +func (r *rttState) init() { + r.minRTT = -1 // -1 indicates the first sample has not been taken yet + + // "[...] the initial RTT SHOULD be set to 333 milliseconds." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-6.2.2-1 + const initialRTT = 333 * time.Millisecond + + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.3-12 + r.smoothedRTT = initialRTT + r.rttvar = initialRTT / 2 +} + +func (r *rttState) establishPersistentCongestion() { + // "Endpoints SHOULD set the min_rtt to the newest RTT sample + // after persistent congestion is established." + // https://www.rfc-editor.org/rfc/rfc9002#section-5.2-5 + r.minRTT = r.latestRTT +} + +// updateRTTSample is called when we generate a new RTT sample. +// https://www.rfc-editor.org/rfc/rfc9002.html#section-5 +func (r *rttState) updateSample(now time.Time, handshakeConfirmed bool, spaceID numberSpace, latestRTT, ackDelay, maxAckDelay time.Duration) { + r.latestRTT = latestRTT + + if r.minRTT < 0 { + // First RTT sample. + // "min_rtt MUST be set to the latest_rtt on the first RTT sample." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-2 + r.minRTT = latestRTT + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.3-14 + r.smoothedRTT = latestRTT + r.rttvar = latestRTT / 2 + r.firstSampleTime = now + return + } + + // "min_rtt MUST be set to the lesser of min_rtt and latest_rtt [...] + // on all other samples." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-2 + r.minRTT = min(r.minRTT, latestRTT) + + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.3-16 + if handshakeConfirmed { + ackDelay = min(ackDelay, maxAckDelay) + } + adjustedRTT := latestRTT - ackDelay + if adjustedRTT < r.minRTT { + adjustedRTT = latestRTT + } + rttvarSample := abs(r.smoothedRTT - adjustedRTT) + r.rttvar = (3*r.rttvar + rttvarSample) / 4 + r.smoothedRTT = ((7 * r.smoothedRTT) + adjustedRTT) / 8 +} diff --git a/internal/quic/rtt_test.go b/internal/quic/rtt_test.go new file mode 100644 index 000000000..2f20b3629 --- /dev/null +++ b/internal/quic/rtt_test.go @@ -0,0 +1,168 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "testing" + "time" +) + +func TestRTTMinRTT(t *testing.T) { + var ( + handshakeConfirmed = false + ackDelay = 0 * time.Millisecond + maxAckDelay = 25 * time.Millisecond + now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) + ) + rtt := &rttState{} + rtt.init() + + // "min_rtt MUST be set to the latest_rtt on the first RTT sample." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-2 + rtt.updateSample(now, handshakeConfirmed, initialSpace, 10*time.Millisecond, ackDelay, maxAckDelay) + if got, want := rtt.latestRTT, 10*time.Millisecond; got != want { + t.Errorf("on first sample: latest_rtt = %v, want %v", got, want) + } + if got, want := rtt.minRTT, 10*time.Millisecond; got != want { + t.Errorf("on first sample: min_rtt = %v, want %v", got, want) + } + + // "min_rtt MUST be set to the lesser of min_rtt and latest_rtt [...] + // on all other samples." + rtt.updateSample(now, handshakeConfirmed, initialSpace, 20*time.Millisecond, ackDelay, maxAckDelay) + if got, want := rtt.latestRTT, 20*time.Millisecond; got != want { + t.Errorf("on increasing sample: latest_rtt = %v, want %v", got, want) + } + if got, want := rtt.minRTT, 10*time.Millisecond; got != want { + t.Errorf("on increasing sample: min_rtt = %v, want %v (no change)", got, want) + } + + rtt.updateSample(now, handshakeConfirmed, initialSpace, 5*time.Millisecond, ackDelay, maxAckDelay) + if got, want := rtt.latestRTT, 5*time.Millisecond; got != want { + t.Errorf("on new minimum: latest_rtt = %v, want %v", got, want) + } + if got, want := rtt.minRTT, 5*time.Millisecond; got != want { + t.Errorf("on new minimum: min_rtt = %v, want %v", got, want) + } + + // "Endpoints SHOULD set the min_rtt to the newest RTT sample + // after persistent congestion is established." + // https://www.rfc-editor.org/rfc/rfc9002.html#section-5.2-5 + rtt.updateSample(now, handshakeConfirmed, initialSpace, 15*time.Millisecond, ackDelay, maxAckDelay) + if got, want := rtt.latestRTT, 15*time.Millisecond; got != want { + t.Errorf("on increasing sample: latest_rtt = %v, want %v", got, want) + } + if got, want := rtt.minRTT, 5*time.Millisecond; got != want { + t.Errorf("on increasing sample: min_rtt = %v, want %v (no change)", got, want) + } + rtt.establishPersistentCongestion() + if got, want := rtt.minRTT, 15*time.Millisecond; got != want { + t.Errorf("after persistent congestion: min_rtt = %v, want %v", got, want) + } +} + +func TestRTTInitialRTT(t *testing.T) { + var ( + handshakeConfirmed = false + ackDelay = 0 * time.Millisecond + maxAckDelay = 25 * time.Millisecond + now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) + ) + rtt := &rttState{} + rtt.init() + + // "When no previous RTT is available, + // the initial RTT SHOULD be set to 333 milliseconds." + // https://www.rfc-editor.org/rfc/rfc9002#section-6.2.2-1 + if got, want := rtt.smoothedRTT, 333*time.Millisecond; got != want { + t.Errorf("initial smoothed_rtt = %v, want %v", got, want) + } + if got, want := rtt.rttvar, 333*time.Millisecond/2; got != want { + t.Errorf("initial rttvar = %v, want %v", got, want) + } + + rtt.updateSample(now, handshakeConfirmed, initialSpace, 10*time.Millisecond, ackDelay, maxAckDelay) + smoothedRTT := 10 * time.Millisecond + if got, want := rtt.smoothedRTT, smoothedRTT; got != want { + t.Errorf("after first rtt sample of 10ms, smoothed_rtt = %v, want %v", got, want) + } + rttvar := 5 * time.Millisecond + if got, want := rtt.rttvar, rttvar; got != want { + t.Errorf("after first rtt sample of 10ms, rttvar = %v, want %v", got, want) + } + + // "[...] MAY ignore the acknowledgment delay for Initial packets [...]" + // https://www.rfc-editor.org/rfc/rfc9002#section-5.3-7.1 + ackDelay = 1 * time.Millisecond + rtt.updateSample(now, handshakeConfirmed, initialSpace, 10*time.Millisecond, ackDelay, maxAckDelay) + adjustedRTT := 10 * time.Millisecond + smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8 + if got, want := rtt.smoothedRTT, smoothedRTT; got != want { + t.Errorf("smoothed_rtt = %v, want %v", got, want) + } + rttvarSample := abs(smoothedRTT - adjustedRTT) + rttvar = (3*rttvar + rttvarSample) / 4 + if got, want := rtt.rttvar, rttvar; got != want { + t.Errorf("rttvar = %v, want %v", got, want) + } + + // "[...] SHOULD ignore the peer's max_ack_delay until the handshake is confirmed [...]" + // https://www.rfc-editor.org/rfc/rfc9002#section-5.3-7.2 + ackDelay = 30 * time.Millisecond + maxAckDelay = 25 * time.Millisecond + rtt.updateSample(now, handshakeConfirmed, handshakeSpace, 40*time.Millisecond, ackDelay, maxAckDelay) + adjustedRTT = 10 * time.Millisecond // latest_rtt (40ms) - ack_delay (30ms) + smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8 + if got, want := rtt.smoothedRTT, smoothedRTT; got != want { + t.Errorf("smoothed_rtt = %v, want %v", got, want) + } + rttvarSample = abs(smoothedRTT - adjustedRTT) + rttvar = (3*rttvar + rttvarSample) / 4 + if got, want := rtt.rttvar, rttvar; got != want { + t.Errorf("rttvar = %v, want %v", got, want) + } + + // "[...] MUST use the lesser of the acknowledgment delay and + // the peer's max_ack_delay after the handshake is confirmed [...]" + // https://www.rfc-editor.org/rfc/rfc9002#section-5.3-7.3 + ackDelay = 30 * time.Millisecond + maxAckDelay = 25 * time.Millisecond + handshakeConfirmed = true + rtt.updateSample(now, handshakeConfirmed, handshakeSpace, 40*time.Millisecond, ackDelay, maxAckDelay) + adjustedRTT = 15 * time.Millisecond // latest_rtt (40ms) - max_ack_delay (25ms) + rttvarSample = abs(smoothedRTT - adjustedRTT) + rttvar = (3*rttvar + rttvarSample) / 4 + if got, want := rtt.rttvar, rttvar; got != want { + t.Errorf("rttvar = %v, want %v", got, want) + } + smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8 + if got, want := rtt.smoothedRTT, smoothedRTT; got != want { + t.Errorf("smoothed_rtt = %v, want %v", got, want) + } + + // "[...] MUST NOT subtract the acknowledgment delay from + // the RTT sample if the resulting value is smaller than the min_rtt." + // https://www.rfc-editor.org/rfc/rfc9002#section-5.3-7.4 + ackDelay = 25 * time.Millisecond + maxAckDelay = 25 * time.Millisecond + handshakeConfirmed = true + rtt.updateSample(now, handshakeConfirmed, handshakeSpace, 30*time.Millisecond, ackDelay, maxAckDelay) + if got, want := rtt.minRTT, 10*time.Millisecond; got != want { + t.Errorf("min_rtt = %v, want %v", got, want) + } + // latest_rtt (30ms) - ack_delay (25ms) = 5ms, which is less than min_rtt (10ms) + adjustedRTT = 30 * time.Millisecond // latest_rtt + rttvarSample = abs(smoothedRTT - adjustedRTT) + rttvar = (3*rttvar + rttvarSample) / 4 + if got, want := rtt.rttvar, rttvar; got != want { + t.Errorf("rttvar = %v, want %v", got, want) + } + smoothedRTT = (7*smoothedRTT + adjustedRTT) / 8 + if got, want := rtt.smoothedRTT, smoothedRTT; got != want { + t.Errorf("smoothed_rtt = %v, want %v", got, want) + } +} diff --git a/internal/quic/sent_packet.go b/internal/quic/sent_packet.go new file mode 100644 index 000000000..4f11aa136 --- /dev/null +++ b/internal/quic/sent_packet.go @@ -0,0 +1,104 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "sync" + "time" +) + +// A sentPacket tracks state related to an in-flight packet we sent, +// to be committed when the peer acks it or resent if the packet is lost. +type sentPacket struct { + num packetNumber + size int // size in bytes + time time.Time // time sent + + ackEliciting bool // https://www.rfc-editor.org/rfc/rfc9002.html#section-2-3.4.1 + inFlight bool // https://www.rfc-editor.org/rfc/rfc9002.html#section-2-3.6.1 + acked bool // ack has been received + lost bool // packet is presumed lost + + // Frames sent in the packet. + // + // This is an abbreviated version of the packet payload, containing only the information + // we need to process an ack for or loss of this packet. + // For example, a CRYPTO frame is recorded as the frame type (0x06), offset, and length, + // but does not include the sent data. + // + // This buffer is written by packetWriter.append* and read by Conn.handleAckOrLoss. + b []byte + n int // read offset into b +} + +var sentPool = sync.Pool{ + New: func() any { + return &sentPacket{} + }, +} + +func newSentPacket() *sentPacket { + sent := sentPool.Get().(*sentPacket) + sent.reset() + return sent +} + +// recycle returns a sentPacket to the pool. +func (sent *sentPacket) recycle() { + sentPool.Put(sent) +} + +func (sent *sentPacket) reset() { + *sent = sentPacket{ + b: sent.b[:0], + } +} + +// The append* methods record information about frames in the packet. + +func (sent *sentPacket) appendNonAckElicitingFrame(frameType byte) { + sent.b = append(sent.b, frameType) +} + +func (sent *sentPacket) appendAckElicitingFrame(frameType byte) { + sent.ackEliciting = true + sent.inFlight = true + sent.b = append(sent.b, frameType) +} + +func (sent *sentPacket) appendInt(v uint64) { + sent.b = appendVarint(sent.b, v) +} + +func (sent *sentPacket) appendOffAndSize(start int64, size int) { + sent.b = appendVarint(sent.b, uint64(start)) + sent.b = appendVarint(sent.b, uint64(size)) +} + +// The next* methods read back information about frames in the packet. + +func (sent *sentPacket) next() (frameType byte) { + f := sent.b[sent.n] + sent.n++ + return f +} + +func (sent *sentPacket) nextInt() uint64 { + v, n := consumeVarint(sent.b[sent.n:]) + sent.n += n + return v +} + +func (sent *sentPacket) nextRange() (start, end int64) { + start = int64(sent.nextInt()) + end = start + int64(sent.nextInt()) + return start, end +} + +func (sent *sentPacket) done() bool { + return sent.n == len(sent.b) +} diff --git a/internal/quic/sent_packet_list.go b/internal/quic/sent_packet_list.go new file mode 100644 index 000000000..6fb712a7a --- /dev/null +++ b/internal/quic/sent_packet_list.go @@ -0,0 +1,95 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +// A sentPacketList is a ring buffer of sentPackets. +// +// Processing an ack for a packet causes all older packets past a small threshold +// to be discarded (RFC 9002, Section 6.1.1), so the list of in-flight packets is +// not sparse and will contain at most a few acked/lost packets we no longer +// care about. +type sentPacketList struct { + nextNum packetNumber // next packet number to add to the buffer + off int // offset of first packet in the buffer + size int // number of packets + p []*sentPacket +} + +// start is the first packet in the list. +func (s *sentPacketList) start() packetNumber { + return s.nextNum - packetNumber(s.size) +} + +// end is one after the last packet in the list. +// If the list is empty, start == end. +func (s *sentPacketList) end() packetNumber { + return s.nextNum +} + +// discard clears the list. +func (s *sentPacketList) discard() { + *s = sentPacketList{} +} + +// add appends a packet to the list. +func (s *sentPacketList) add(sent *sentPacket) { + if s.nextNum != sent.num { + panic("inserting out-of-order packet") + } + s.nextNum++ + if s.size >= len(s.p) { + s.grow() + } + i := (s.off + s.size) % len(s.p) + s.size++ + s.p[i] = sent +} + +// nth returns a packet by index. +func (s *sentPacketList) nth(n int) *sentPacket { + index := (s.off + n) % len(s.p) + return s.p[index] +} + +// num returns a packet by number. +// It returns nil if the packet is not in the list. +func (s *sentPacketList) num(num packetNumber) *sentPacket { + i := int(num - s.start()) + if i < 0 || i >= s.size { + return nil + } + return s.nth(i) +} + +// clean removes all acked or lost packets from the head of the list. +func (s *sentPacketList) clean() { + for s.size > 0 { + sent := s.p[s.off] + if !sent.acked && !sent.lost { + return + } + sent.recycle() + s.p[s.off] = nil + s.off = (s.off + 1) % len(s.p) + s.size-- + } + s.off = 0 +} + +// grow increases the buffer to hold more packaets. +func (s *sentPacketList) grow() { + newSize := len(s.p) * 2 + if newSize == 0 { + newSize = 64 + } + p := make([]*sentPacket, newSize) + for i := 0; i < s.size; i++ { + p[i] = s.nth(i) + } + s.p = p + s.off = 0 +} diff --git a/internal/quic/sent_packet_list_test.go b/internal/quic/sent_packet_list_test.go new file mode 100644 index 000000000..2f7f4d2c6 --- /dev/null +++ b/internal/quic/sent_packet_list_test.go @@ -0,0 +1,107 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "testing" + +func TestSentPacketListSlidingWindow(t *testing.T) { + // Record 1000 sent packets, acking everything outside the most recent 10. + list := &sentPacketList{} + const window = 10 + for i := packetNumber(0); i < 1000; i++ { + list.add(&sentPacket{num: i}) + if i < window { + continue + } + prev := i - window + sent := list.num(prev) + if sent == nil { + t.Fatalf("packet %v not in list", prev) + } + if sent.num != prev { + t.Fatalf("list.num(%v) = packet %v", prev, sent.num) + } + if got := list.nth(0); got != sent { + t.Fatalf("list.nth(0) != list.num(%v)", prev) + } + sent.acked = true + list.clean() + if got := list.num(prev); got != nil { + t.Fatalf("list.num(%v) = packet %v, expected it to be discarded", prev, got.num) + } + if got, want := list.start(), prev+1; got != want { + t.Fatalf("list.start() = %v, want %v", got, want) + } + if got, want := list.end(), i+1; got != want { + t.Fatalf("list.end() = %v, want %v", got, want) + } + if got, want := list.size, window; got != want { + t.Fatalf("list.size = %v, want %v", got, want) + } + } +} + +func TestSentPacketListGrows(t *testing.T) { + // Record 1000 sent packets. + list := &sentPacketList{} + const count = 1000 + for i := packetNumber(0); i < count; i++ { + list.add(&sentPacket{num: i}) + } + if got, want := list.start(), packetNumber(0); got != want { + t.Fatalf("list.start() = %v, want %v", got, want) + } + if got, want := list.end(), packetNumber(count); got != want { + t.Fatalf("list.end() = %v, want %v", got, want) + } + if got, want := list.size, count; got != want { + t.Fatalf("list.size = %v, want %v", got, want) + } + for i := packetNumber(0); i < count; i++ { + sent := list.num(i) + if sent == nil { + t.Fatalf("packet %v not in list", i) + } + if sent.num != i { + t.Fatalf("list.num(%v) = packet %v", i, sent.num) + } + if got := list.nth(int(i)); got != sent { + t.Fatalf("list.nth(%v) != list.num(%v)", int(i), i) + } + } +} + +func TestSentPacketListCleanAll(t *testing.T) { + list := &sentPacketList{} + // Record 10 sent packets. + const count = 10 + for i := packetNumber(0); i < count; i++ { + list.add(&sentPacket{num: i}) + } + // Mark all the packets as acked. + for i := packetNumber(0); i < count; i++ { + list.num(i).acked = true + } + list.clean() + if got, want := list.size, 0; got != want { + t.Fatalf("list.size = %v, want %v", got, want) + } + list.add(&sentPacket{num: 10}) + if got, want := list.size, 1; got != want { + t.Fatalf("list.size = %v, want %v", got, want) + } + sent := list.num(10) + if sent == nil { + t.Fatalf("packet %v not in list", 10) + } + if sent.num != 10 { + t.Fatalf("list.num(10) = %v", sent.num) + } + if got := list.nth(0); got != sent { + t.Fatalf("list.nth(0) != list.num(10)") + } +} diff --git a/internal/quic/sent_packet_test.go b/internal/quic/sent_packet_test.go new file mode 100644 index 000000000..c0b04e676 --- /dev/null +++ b/internal/quic/sent_packet_test.go @@ -0,0 +1,53 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "testing" + +func TestSentPacket(t *testing.T) { + frames := []any{ + byte(frameTypePing), + byte(frameTypeStreamBase), + uint64(1), + i64range[int64]{1 << 20, 1<<20 + 1024}, + } + // Record sent frames. + sent := newSentPacket() + for _, f := range frames { + switch f := f.(type) { + case byte: + sent.appendAckElicitingFrame(f) + case uint64: + sent.appendInt(f) + case i64range[int64]: + sent.appendOffAndSize(f.start, int(f.size())) + } + } + // Read the record. + for i, want := range frames { + if done := sent.done(); done { + t.Fatalf("before consuming contents, sent.done() = true, want false") + } + switch want := want.(type) { + case byte: + if got := sent.next(); got != want { + t.Fatalf("%v: sent.next() = %v, want %v", i, got, want) + } + case uint64: + if got := sent.nextInt(); got != want { + t.Fatalf("%v: sent.nextInt() = %v, want %v", i, got, want) + } + case i64range[int64]: + if start, end := sent.nextRange(); start != want.start || end != want.end { + t.Fatalf("%v: sent.nextRange() = [%v,%v), want %v", i, start, end, want) + } + } + } + if done := sent.done(); !done { + t.Fatalf("after consuming contents, sent.done() = false, want true") + } +} diff --git a/internal/quic/sent_val.go b/internal/quic/sent_val.go new file mode 100644 index 000000000..31f69e47d --- /dev/null +++ b/internal/quic/sent_val.go @@ -0,0 +1,105 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +// A sentVal tracks sending some piece of information to the peer. +// It tracks whether the information has been sent, acked, and +// (when in-flight) the most recent packet to carry it. +// +// For example, a sentVal can track sending of a RESET_STREAM frame. +// +// - unset: stream is active, no need to send RESET_STREAM +// - unsent: we should send a RESET_STREAM, but have not yet +// - sent: we have sent a RESET_STREAM, but have not received an ack +// - received: we have sent a RESET_STREAM, and the peer has acked the packet that contained it +// +// In the "sent" state, a sentVal also tracks the latest packet number to carry +// the information. (QUIC packet numbers are always at most 62 bits in size, +// so the sentVal keeps the number in the low 62 bits and the state in the high 2 bits.) +type sentVal uint64 + +const ( + sentValUnset = 0 // unset + sentValUnsent = 1 << 62 // set, not sent to the peer + sentValSent = 2 << 62 // set, sent to the peer but not yet acked; pnum is set + sentValReceived = 3 << 62 // set, peer acked receipt + + sentValStateMask = 3 << 62 +) + +// isSet reports whether the value is set. +func (s sentVal) isSet() bool { return s != 0 } + +// shouldSend reports whether the value is set and has not been sent to the peer. +func (s sentVal) shouldSend() bool { return s.state() == sentValUnsent } + +// shouldSend reports whether the value needs to be sent to the peer. +// The value needs to be sent if it is set and has not been sent. +// If pto is true, indicating that we are sending a PTO probe, the value +// should also be sent if it is set and has not been acknowledged. +func (s sentVal) shouldSendPTO(pto bool) bool { + st := s.state() + return st == sentValUnsent || (pto && st == sentValSent) +} + +// isReceived reports whether the value has been received by the peer. +func (s sentVal) isReceived() bool { return s == sentValReceived } + +// set sets the value and records that it should be sent to the peer. +// If the value has already been sent, it is not resent. +func (s *sentVal) set() { + if *s == 0 { + *s = sentValUnsent + } +} + +// reset sets the value to the unsent state. +func (s *sentVal) setUnsent() { *s = sentValUnsent } + +// clear sets the value to the unset state. +func (s *sentVal) clear() { *s = sentValUnset } + +// setSent sets the value to the send state and records the number of the most recent +// packet containing the value. +func (s *sentVal) setSent(pnum packetNumber) { + *s = sentValSent | sentVal(pnum) +} + +// setReceived sets the value to the received state. +func (s *sentVal) setReceived() { *s = sentValReceived } + +// ackOrLoss reports that an acknowledgement has been received for the value, +// or that the packet carrying the value has been lost. +func (s *sentVal) ackOrLoss(pnum packetNumber, fate packetFate) { + if fate == packetAcked { + *s = sentValReceived + } else if *s == sentVal(pnum)|sentValSent { + *s = sentValUnsent + } +} + +// ackLatestOrLoss reports that an acknowledgement has been received for the value, +// or that the packet carrying the value has been lost. +// The value is set to the acked state only if pnum is the latest packet containing it. +// +// We use this to handle acks for data that varies every time it is sent. +// For example, if we send a MAX_DATA frame followed by an updated MAX_DATA value in a +// second packet, we consider the data sent only upon receiving an ack for the most +// recent value. +func (s *sentVal) ackLatestOrLoss(pnum packetNumber, fate packetFate) { + if fate == packetAcked { + if *s == sentVal(pnum)|sentValSent { + *s = sentValReceived + } + } else { + if *s == sentVal(pnum)|sentValSent { + *s = sentValUnsent + } + } +} + +func (s sentVal) state() uint64 { return uint64(s) & sentValStateMask } diff --git a/internal/quic/sent_val_test.go b/internal/quic/sent_val_test.go new file mode 100644 index 000000000..d253d3a8d --- /dev/null +++ b/internal/quic/sent_val_test.go @@ -0,0 +1,168 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "testing" + +func TestSentVal(t *testing.T) { + for _, test := range []struct { + name string + f func(*sentVal) + wantIsSet bool + wantShouldSend bool + wantIsReceived bool + wantShouldSendPTO bool + }{{ + name: "zero value", + f: func(*sentVal) {}, + wantIsSet: false, + wantShouldSend: false, + wantShouldSendPTO: false, + wantIsReceived: false, + }, { + name: "v.set()", + f: (*sentVal).set, + wantIsSet: true, + wantShouldSend: true, + wantShouldSendPTO: true, + wantIsReceived: false, + }, { + name: "v.setSent(0)", + f: func(v *sentVal) { + v.setSent(0) + }, + wantIsSet: true, + wantShouldSend: false, + wantShouldSendPTO: true, + wantIsReceived: false, + }, { + name: "sent.set()", + f: func(v *sentVal) { + v.setSent(0) + v.set() + }, + wantIsSet: true, + wantShouldSend: false, + wantShouldSendPTO: true, + wantIsReceived: false, + }, { + name: "sent.setUnsent()", + f: func(v *sentVal) { + v.setSent(0) + v.setUnsent() + }, + wantIsSet: true, + wantShouldSend: true, + wantShouldSendPTO: true, + wantIsReceived: false, + }, { + name: "set.clear()", + f: func(v *sentVal) { + v.set() + v.clear() + }, + wantIsSet: false, + wantShouldSend: false, + wantShouldSendPTO: false, + wantIsReceived: false, + }, { + name: "v.setReceived()", + f: (*sentVal).setReceived, + wantIsSet: true, + wantShouldSend: false, + wantShouldSendPTO: false, + wantIsReceived: true, + }, { + name: "v.ackOrLoss(!pnum, true)", + f: func(v *sentVal) { + v.setSent(1) + v.ackOrLoss(0, packetAcked) // ack different packet containing the val + }, + wantIsSet: true, + wantShouldSend: false, + wantShouldSendPTO: false, + wantIsReceived: true, + }, { + name: "v.ackOrLoss(!pnum, packetLost)", + f: func(v *sentVal) { + v.setSent(1) + v.ackOrLoss(0, packetLost) // lose different packet containing the val + }, + wantIsSet: true, + wantShouldSend: false, + wantShouldSendPTO: true, + wantIsReceived: false, + }, { + name: "v.ackOrLoss(pnum, packetLost)", + f: func(v *sentVal) { + v.setSent(1) + v.ackOrLoss(1, packetLost) // lose same packet containing the val + }, + wantIsSet: true, + wantShouldSend: true, + wantShouldSendPTO: true, + wantIsReceived: false, + }, { + name: "v.ackLatestOrLoss(!pnum, packetAcked)", + f: func(v *sentVal) { + v.setSent(1) + v.ackLatestOrLoss(0, packetAcked) // ack different packet containing the val + }, + wantIsSet: true, + wantShouldSend: false, + wantShouldSendPTO: true, + wantIsReceived: false, + }, { + name: "v.ackLatestOrLoss(pnum, packetAcked)", + f: func(v *sentVal) { + v.setSent(1) + v.ackLatestOrLoss(1, packetAcked) // ack same packet containing the val + }, + wantIsSet: true, + wantShouldSend: false, + wantShouldSendPTO: false, + wantIsReceived: true, + }, { + name: "v.ackLatestOrLoss(!pnum, packetLost)", + f: func(v *sentVal) { + v.setSent(1) + v.ackLatestOrLoss(0, packetLost) // lose different packet containing the val + }, + wantIsSet: true, + wantShouldSend: false, + wantShouldSendPTO: true, + wantIsReceived: false, + }, { + name: "v.ackLatestOrLoss(pnum, packetLost)", + f: func(v *sentVal) { + v.setSent(1) + v.ackLatestOrLoss(1, packetLost) // lose same packet containing the val + }, + wantIsSet: true, + wantShouldSend: true, + wantShouldSendPTO: true, + wantIsReceived: false, + }} { + var v sentVal + test.f(&v) + if got, want := v.isSet(), test.wantIsSet; got != want { + t.Errorf("%v: v.isSet() = %v, want %v", test.name, got, want) + } + if got, want := v.shouldSend(), test.wantShouldSend; got != want { + t.Errorf("%v: v.shouldSend() = %v, want %v", test.name, got, want) + } + if got, want := v.shouldSendPTO(false), test.wantShouldSend; got != want { + t.Errorf("%v: v.shouldSendPTO(false) = %v, want %v", test.name, got, want) + } + if got, want := v.shouldSendPTO(true), test.wantShouldSendPTO; got != want { + t.Errorf("%v: v.shouldSendPTO(true) = %v, want %v", test.name, got, want) + } + if got, want := v.isReceived(), test.wantIsReceived; got != want { + t.Errorf("%v: v.isReceived() = %v, want %v", test.name, got, want) + } + } +} diff --git a/internal/quic/stream.go b/internal/quic/stream.go new file mode 100644 index 000000000..89036b19b --- /dev/null +++ b/internal/quic/stream.go @@ -0,0 +1,801 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "errors" + "fmt" + "io" +) + +type Stream struct { + id streamID + conn *Conn + + // ingate's lock guards all receive-related state. + // + // The gate condition is set if a read from the stream will not block, + // either because the stream has available data or because the read will fail. + ingate gate + in pipe // received data + inwin int64 // last MAX_STREAM_DATA sent to the peer + insendmax sentVal // set when we should send MAX_STREAM_DATA to the peer + inmaxbuf int64 // maximum amount of data we will buffer + insize int64 // stream final size; -1 before this is known + inset rangeset[int64] // received ranges + inclosed sentVal // set by CloseRead + inresetcode int64 // RESET_STREAM code received from the peer; -1 if not reset + + // outgate's lock guards all send-related state. + // + // The gate condition is set if a write to the stream will not block, + // either because the stream has available flow control or because + // the write will fail. + outgate gate + out pipe // buffered data to send + outwin int64 // maximum MAX_STREAM_DATA received from the peer + outmaxsent int64 // maximum data offset we've sent to the peer + outmaxbuf int64 // maximum amount of data we will buffer + outunsent rangeset[int64] // ranges buffered but not yet sent + outacked rangeset[int64] // ranges sent and acknowledged + outopened sentVal // set if we should open the stream + outclosed sentVal // set by CloseWrite + outblocked sentVal // set when a write to the stream is blocked by flow control + outreset sentVal // set by Reset + outresetcode uint64 // reset code to send in RESET_STREAM + outdone chan struct{} // closed when all data sent + + // Atomic stream state bits. + // + // These bits provide a fast way to coordinate between the + // send and receive sides of the stream, and the conn's loop. + // + // streamIn* bits must be set with ingate held. + // streamOut* bits must be set with outgate held. + // streamConn* bits are set by the conn's loop. + // streamQueue* bits must be set with streamsState.sendMu held. + state atomicBits[streamState] + + prev, next *Stream // guarded by streamsState.sendMu +} + +type streamState uint32 + +const ( + // streamInSendMeta is set when there are frames to send for the + // inbound side of the stream. For example, MAX_STREAM_DATA. + // Inbound frames are never flow-controlled. + streamInSendMeta = streamState(1 << iota) + + // streamOutSendMeta is set when there are non-flow-controlled frames + // to send for the outbound side of the stream. For example, STREAM_DATA_BLOCKED. + // streamOutSendData is set when there are no non-flow-controlled outbound frames + // and the stream has data to send. + // + // At most one of streamOutSendMeta and streamOutSendData is set at any time. + streamOutSendMeta + streamOutSendData + + // streamInDone and streamOutDone are set when the inbound or outbound + // sides of the stream are finished. When both are set, the stream + // can be removed from the Conn and forgotten. + streamInDone + streamOutDone + + // streamConnRemoved is set when the stream has been removed from the conn. + streamConnRemoved + + // streamQueueMeta and streamQueueData indicate which of the streamsState + // send queues the conn is currently on. + streamQueueMeta + streamQueueData +) + +type streamQueue int + +const ( + noQueue = streamQueue(iota) + metaQueue // streamsState.queueMeta + dataQueue // streamsState.queueData +) + +// wantQueue returns the send queue the stream should be on. +func (s streamState) wantQueue() streamQueue { + switch { + case s&(streamInSendMeta|streamOutSendMeta) != 0: + return metaQueue + case s&(streamInDone|streamOutDone|streamConnRemoved) == streamInDone|streamOutDone: + return metaQueue + case s&streamOutSendData != 0: + // The stream has no non-flow-controlled frames to send, + // but does have data. Put it on the data queue, which is only + // processed when flow control is available. + return dataQueue + } + return noQueue +} + +// inQueue returns the send queue the stream is currently on. +func (s streamState) inQueue() streamQueue { + switch { + case s&streamQueueMeta != 0: + return metaQueue + case s&streamQueueData != 0: + return dataQueue + } + return noQueue +} + +// newStream returns a new stream. +// +// The stream's ingate and outgate are locked. +// (We create the stream with locked gates so after the caller +// initializes the flow control window, +// unlocking outgate will set the stream writability state.) +func newStream(c *Conn, id streamID) *Stream { + s := &Stream{ + conn: c, + id: id, + insize: -1, // -1 indicates the stream size is unknown + inresetcode: -1, // -1 indicates no RESET_STREAM received + ingate: newLockedGate(), + outgate: newLockedGate(), + } + if !s.IsReadOnly() { + s.outdone = make(chan struct{}) + } + return s +} + +// IsReadOnly reports whether the stream is read-only +// (a unidirectional stream created by the peer). +func (s *Stream) IsReadOnly() bool { + return s.id.streamType() == uniStream && s.id.initiator() != s.conn.side +} + +// IsWriteOnly reports whether the stream is write-only +// (a unidirectional stream created locally). +func (s *Stream) IsWriteOnly() bool { + return s.id.streamType() == uniStream && s.id.initiator() == s.conn.side +} + +// Read reads data from the stream. +// See ReadContext for more details. +func (s *Stream) Read(b []byte) (n int, err error) { + return s.ReadContext(context.Background(), b) +} + +// ReadContext reads data from the stream. +// +// ReadContext returns as soon as at least one byte of data is available. +// +// If the peer closes the stream cleanly, ReadContext returns io.EOF after +// returning all data sent by the peer. +// If the peer aborts reads on the stream, ReadContext returns +// an error wrapping StreamResetCode. +func (s *Stream) ReadContext(ctx context.Context, b []byte) (n int, err error) { + if s.IsWriteOnly() { + return 0, errors.New("read from write-only stream") + } + if err := s.ingate.waitAndLock(ctx, s.conn.testHooks); err != nil { + return 0, err + } + defer func() { + s.inUnlock() + s.conn.handleStreamBytesReadOffLoop(int64(n)) // must be done with ingate unlocked + }() + if s.inresetcode != -1 { + return 0, fmt.Errorf("stream reset by peer: %w", StreamErrorCode(s.inresetcode)) + } + if s.inclosed.isSet() { + return 0, errors.New("read from closed stream") + } + if s.insize == s.in.start { + return 0, io.EOF + } + // Getting here indicates the stream contains data to be read. + if len(s.inset) < 1 || s.inset[0].start != 0 || s.inset[0].end <= s.in.start { + panic("BUG: inconsistent input stream state") + } + if size := int(s.inset[0].end - s.in.start); size < len(b) { + b = b[:size] + } + start := s.in.start + end := start + int64(len(b)) + s.in.copy(start, b) + s.in.discardBefore(end) + if s.insize == -1 || s.insize > s.inwin { + if shouldUpdateFlowControl(s.inmaxbuf, s.in.start+s.inmaxbuf-s.inwin) { + // Update stream flow control with a STREAM_MAX_DATA frame. + s.insendmax.setUnsent() + } + } + if end == s.insize { + return len(b), io.EOF + } + return len(b), nil +} + +// shouldUpdateFlowControl determines whether to send a flow control window update. +// +// We want to balance keeping the peer well-supplied with flow control with not sending +// many small updates. +func shouldUpdateFlowControl(maxWindow, addedWindow int64) bool { + return addedWindow >= maxWindow/8 +} + +// Write writes data to the stream. +// See WriteContext for more details. +func (s *Stream) Write(b []byte) (n int, err error) { + return s.WriteContext(context.Background(), b) +} + +// WriteContext writes data to the stream. +// +// WriteContext writes data to the stream write buffer. +// Buffered data is only sent when the buffer is sufficiently full. +// Call the Flush method to ensure buffered data is sent. +// +// TODO: Implement Flush. +func (s *Stream) WriteContext(ctx context.Context, b []byte) (n int, err error) { + if s.IsReadOnly() { + return 0, errors.New("write to read-only stream") + } + canWrite := s.outgate.lock() + for { + // The first time through this loop, we may or may not be write blocked. + // We exit the loop after writing all data, so on subsequent passes through + // the loop we are always write blocked. + if len(b) > 0 && !canWrite { + // Our send buffer is full. Wait for the peer to ack some data. + s.outUnlock() + if err := s.outgate.waitAndLock(ctx, s.conn.testHooks); err != nil { + return n, err + } + // Successfully returning from waitAndLockGate means we are no longer + // write blocked. (Unlike traditional condition variables, gates do not + // have spurious wakeups.) + } + if s.outreset.isSet() { + s.outUnlock() + return n, errors.New("write to reset stream") + } + if s.outclosed.isSet() { + s.outUnlock() + return n, errors.New("write to closed stream") + } + // We set outopened here rather than below, + // so if this is a zero-length write we still + // open the stream despite not writing any data to it. + s.outopened.set() + if len(b) == 0 { + break + } + // Write limit is our send buffer limit. + // This is a stream offset. + lim := s.out.start + s.outmaxbuf + // Amount to write is min(the full buffer, data up to the write limit). + // This is a number of bytes. + nn := min(int64(len(b)), lim-s.out.end) + // Copy the data into the output buffer and mark it as unsent. + if s.out.end <= s.outwin { + s.outunsent.add(s.out.end, min(s.out.end+nn, s.outwin)) + } + s.out.writeAt(b[:nn], s.out.end) + b = b[nn:] + n += int(nn) + if s.out.end > s.outwin { + // We're blocked by flow control. + // Send a STREAM_DATA_BLOCKED frame to let the peer know. + s.outblocked.set() + } + // If we have bytes left to send, we're blocked. + canWrite = false + } + s.outUnlock() + return n, nil +} + +// Close closes the stream. +// See CloseContext for more details. +func (s *Stream) Close() error { + return s.CloseContext(context.Background()) +} + +// CloseContext closes the stream. +// Any blocked stream operations will be unblocked and return errors. +// +// CloseContext flushes any data in the stream write buffer and waits for the peer to +// acknowledge receipt of the data. +// If the stream has been reset, it waits for the peer to acknowledge the reset. +// If the context expires before the peer receives the stream's data, +// CloseContext discards the buffer and returns the context error. +func (s *Stream) CloseContext(ctx context.Context) error { + s.CloseRead() + if s.IsReadOnly() { + return nil + } + s.CloseWrite() + // TODO: Return code from peer's RESET_STREAM frame? + return s.conn.waitOnDone(ctx, s.outdone) +} + +// CloseRead aborts reads on the stream. +// Any blocked reads will be unblocked and return errors. +// +// CloseRead notifies the peer that the stream has been closed for reading. +// It does not wait for the peer to acknowledge the closure. +// Use CloseContext to wait for the peer's acknowledgement. +func (s *Stream) CloseRead() { + if s.IsWriteOnly() { + return + } + s.ingate.lock() + if s.inset.isrange(0, s.insize) || s.inresetcode != -1 { + // We've already received all data from the peer, + // so there's no need to send STOP_SENDING. + // This is the same as saying we sent one and they got it. + s.inclosed.setReceived() + } else { + s.inclosed.set() + } + discarded := s.in.end - s.in.start + s.in.discardBefore(s.in.end) + s.inUnlock() + s.conn.handleStreamBytesReadOffLoop(discarded) // must be done with ingate unlocked +} + +// CloseWrite aborts writes on the stream. +// Any blocked writes will be unblocked and return errors. +// +// CloseWrite sends any data in the stream write buffer to the peer. +// It does not wait for the peer to acknowledge receipt of the data. +// Use CloseContext to wait for the peer's acknowledgement. +func (s *Stream) CloseWrite() { + if s.IsReadOnly() { + return + } + s.outgate.lock() + defer s.outUnlock() + s.outclosed.set() +} + +// Reset aborts writes on the stream and notifies the peer +// that the stream was terminated abruptly. +// Any blocked writes will be unblocked and return errors. +// +// Reset sends the application protocol error code, which must be +// less than 2^62, to the peer. +// It does not wait for the peer to acknowledge receipt of the error. +// Use CloseContext to wait for the peer's acknowledgement. +// +// Reset does not affect reads. +// Use CloseRead to abort reads on the stream. +func (s *Stream) Reset(code uint64) { + const userClosed = true + s.resetInternal(code, userClosed) +} + +// resetInternal resets the send side of the stream. +// +// If userClosed is true, this is s.Reset. +// If userClosed is false, this is a reaction to a STOP_SENDING frame. +func (s *Stream) resetInternal(code uint64, userClosed bool) { + s.outgate.lock() + defer s.outUnlock() + if s.IsReadOnly() { + return + } + if userClosed { + // Mark that the user closed the stream. + s.outclosed.set() + } + if s.outreset.isSet() { + return + } + if code > maxVarint { + code = maxVarint + } + // We could check here to see if the stream is closed and the + // peer has acked all the data and the FIN, but sending an + // extra RESET_STREAM in this case is harmless. + s.outreset.set() + s.outresetcode = code + s.out.discardBefore(s.out.end) + s.outunsent = rangeset[int64]{} + s.outblocked.clear() +} + +// inUnlock unlocks s.ingate. +// It sets the gate condition if reads from s will not block. +// If s has receive-related frames to write or if both directions +// are done and the stream should be removed, it notifies the Conn. +func (s *Stream) inUnlock() { + state := s.inUnlockNoQueue() + s.conn.maybeQueueStreamForSend(s, state) +} + +// inUnlockNoQueue is inUnlock, +// but reports whether s has frames to write rather than notifying the Conn. +func (s *Stream) inUnlockNoQueue() streamState { + canRead := s.inset.contains(s.in.start) || // data available to read + s.insize == s.in.start || // at EOF + s.inresetcode != -1 || // reset by peer + s.inclosed.isSet() // closed locally + defer s.ingate.unlock(canRead) + var state streamState + switch { + case s.IsWriteOnly(): + state = streamInDone + case s.inresetcode != -1: // reset by peer + fallthrough + case s.in.start == s.insize: // all data received and read + // We don't increase MAX_STREAMS until the user calls ReadClose or Close, + // so the receive side is not finished until inclosed is set. + if s.inclosed.isSet() { + state = streamInDone + } + case s.insendmax.shouldSend(): // STREAM_MAX_DATA + state = streamInSendMeta + case s.inclosed.shouldSend(): // STOP_SENDING + state = streamInSendMeta + } + const mask = streamInDone | streamInSendMeta + return s.state.set(state, mask) +} + +// outUnlock unlocks s.outgate. +// It sets the gate condition if writes to s will not block. +// If s has send-related frames to write or if both directions +// are done and the stream should be removed, it notifies the Conn. +func (s *Stream) outUnlock() { + state := s.outUnlockNoQueue() + s.conn.maybeQueueStreamForSend(s, state) +} + +// outUnlockNoQueue is outUnlock, +// but reports whether s has frames to write rather than notifying the Conn. +func (s *Stream) outUnlockNoQueue() streamState { + isDone := s.outclosed.isReceived() && s.outacked.isrange(0, s.out.end) || // all data acked + s.outreset.isSet() // reset locally + if isDone { + select { + case <-s.outdone: + default: + if !s.IsReadOnly() { + close(s.outdone) + } + } + } + lim := s.out.start + s.outmaxbuf + canWrite := lim > s.out.end || // available send buffer + s.outclosed.isSet() || // closed locally + s.outreset.isSet() // reset locally + defer s.outgate.unlock(canWrite) + var state streamState + switch { + case s.IsReadOnly(): + state = streamOutDone + case s.outclosed.isReceived() && s.outacked.isrange(0, s.out.end): // all data sent and acked + fallthrough + case s.outreset.isReceived(): // RESET_STREAM sent and acked + // We don't increase MAX_STREAMS until the user calls WriteClose or Close, + // so the send side is not finished until outclosed is set. + if s.outclosed.isSet() { + state = streamOutDone + } + case s.outreset.shouldSend(): // RESET_STREAM + state = streamOutSendMeta + case s.outreset.isSet(): // RESET_STREAM sent but not acknowledged + case s.outblocked.shouldSend(): // STREAM_DATA_BLOCKED + state = streamOutSendMeta + case len(s.outunsent) > 0: // STREAM frame with data + if s.outunsent.min() < s.outmaxsent { + state = streamOutSendMeta // resent data, will not consume flow control + } else { + state = streamOutSendData // new data, requires flow control + } + case s.outclosed.shouldSend() && s.out.end == s.outmaxsent: // empty STREAM frame with FIN bit + state = streamOutSendMeta + case s.outopened.shouldSend(): // STREAM frame with no data + state = streamOutSendMeta + } + const mask = streamOutDone | streamOutSendMeta | streamOutSendData + return s.state.set(state, mask) +} + +// handleData handles data received in a STREAM frame. +func (s *Stream) handleData(off int64, b []byte, fin bool) error { + s.ingate.lock() + defer s.inUnlock() + end := off + int64(len(b)) + if err := s.checkStreamBounds(end, fin); err != nil { + return err + } + if s.inclosed.isSet() || s.inresetcode != -1 { + // The user read-closed the stream, or the peer reset it. + // Either way, we can discard this frame. + return nil + } + if s.insize == -1 && end > s.in.end { + added := end - s.in.end + if err := s.conn.handleStreamBytesReceived(added); err != nil { + return err + } + } + s.in.writeAt(b, off) + s.inset.add(off, end) + if fin { + s.insize = end + // The peer has enough flow control window to send the entire stream. + s.insendmax.clear() + } + return nil +} + +// handleReset handles a RESET_STREAM frame. +func (s *Stream) handleReset(code uint64, finalSize int64) error { + s.ingate.lock() + defer s.inUnlock() + const fin = true + if err := s.checkStreamBounds(finalSize, fin); err != nil { + return err + } + if s.inresetcode != -1 { + // The stream was already reset. + return nil + } + if s.insize == -1 { + added := finalSize - s.in.end + if err := s.conn.handleStreamBytesReceived(added); err != nil { + return err + } + } + s.conn.handleStreamBytesReadOnLoop(finalSize - s.in.start) + s.in.discardBefore(s.in.end) + s.inresetcode = int64(code) + s.insize = finalSize + return nil +} + +// checkStreamBounds validates the stream offset in a STREAM or RESET_STREAM frame. +func (s *Stream) checkStreamBounds(end int64, fin bool) error { + if end > s.inwin { + // The peer sent us data past the maximum flow control window we gave them. + return localTransportError(errFlowControl) + } + if s.insize != -1 && end > s.insize { + // The peer sent us data past the final size of the stream they previously gave us. + return localTransportError(errFinalSize) + } + if fin && s.insize != -1 && end != s.insize { + // The peer changed the final size of the stream. + return localTransportError(errFinalSize) + } + if fin && end < s.in.end { + // The peer has previously sent us data past the final size. + return localTransportError(errFinalSize) + } + return nil +} + +// handleStopSending handles a STOP_SENDING frame. +func (s *Stream) handleStopSending(code uint64) error { + // Peer requests that we reset this stream. + // https://www.rfc-editor.org/rfc/rfc9000#section-3.5-4 + const userReset = false + s.resetInternal(code, userReset) + return nil +} + +// handleMaxStreamData handles an update received in a MAX_STREAM_DATA frame. +func (s *Stream) handleMaxStreamData(maxStreamData int64) error { + s.outgate.lock() + defer s.outUnlock() + if maxStreamData <= s.outwin { + return nil + } + if s.out.end > s.outwin { + s.outunsent.add(s.outwin, min(maxStreamData, s.out.end)) + } + s.outwin = maxStreamData + if s.out.end > s.outwin { + // We've still got more data than flow control window. + s.outblocked.setUnsent() + } else { + s.outblocked.clear() + } + return nil +} + +// ackOrLoss handles the fate of stream frames other than STREAM. +func (s *Stream) ackOrLoss(pnum packetNumber, ftype byte, fate packetFate) { + // Frames which carry new information each time they are sent + // (MAX_STREAM_DATA, STREAM_DATA_BLOCKED) must only be marked + // as received if the most recent packet carrying this frame is acked. + // + // Frames which are always the same (STOP_SENDING, RESET_STREAM) + // can be marked as received if any packet carrying this frame is acked. + switch ftype { + case frameTypeResetStream: + s.outgate.lock() + s.outreset.ackOrLoss(pnum, fate) + s.outUnlock() + case frameTypeStopSending: + s.ingate.lock() + s.inclosed.ackOrLoss(pnum, fate) + s.inUnlock() + case frameTypeMaxStreamData: + s.ingate.lock() + s.insendmax.ackLatestOrLoss(pnum, fate) + s.inUnlock() + case frameTypeStreamDataBlocked: + s.outgate.lock() + s.outblocked.ackLatestOrLoss(pnum, fate) + s.outUnlock() + default: + panic("unhandled frame type") + } +} + +// ackOrLossData handles the fate of a STREAM frame. +func (s *Stream) ackOrLossData(pnum packetNumber, start, end int64, fin bool, fate packetFate) { + s.outgate.lock() + defer s.outUnlock() + s.outopened.ackOrLoss(pnum, fate) + if fin { + s.outclosed.ackOrLoss(pnum, fate) + } + if s.outreset.isSet() { + // If the stream has been reset, we don't care any more. + return + } + switch fate { + case packetAcked: + s.outacked.add(start, end) + s.outunsent.sub(start, end) + // If this ack is for data at the start of the send buffer, we can now discard it. + if s.outacked.contains(s.out.start) { + s.out.discardBefore(s.outacked[0].end) + } + case packetLost: + // Mark everything lost, but not previously acked, as needing retransmission. + // We do this by adding all the lost bytes to outunsent, and then + // removing everything already acked. + s.outunsent.add(start, end) + for _, a := range s.outacked { + s.outunsent.sub(a.start, a.end) + } + } +} + +// appendInFramesLocked appends STOP_SENDING and MAX_STREAM_DATA frames +// to the current packet. +// +// It returns true if no more frames need appending, +// false if not everything fit in the current packet. +func (s *Stream) appendInFramesLocked(w *packetWriter, pnum packetNumber, pto bool) bool { + if s.inclosed.shouldSendPTO(pto) { + // We don't currently have an API for setting the error code. + // Just send zero. + code := uint64(0) + if !w.appendStopSendingFrame(s.id, code) { + return false + } + s.inclosed.setSent(pnum) + } + // TODO: STOP_SENDING + if s.insendmax.shouldSendPTO(pto) { + // MAX_STREAM_DATA + maxStreamData := s.in.start + s.inmaxbuf + if !w.appendMaxStreamDataFrame(s.id, maxStreamData) { + return false + } + s.inwin = maxStreamData + s.insendmax.setSent(pnum) + } + return true +} + +// appendOutFramesLocked appends RESET_STREAM, STREAM_DATA_BLOCKED, and STREAM frames +// to the current packet. +// +// It returns true if no more frames need appending, +// false if not everything fit in the current packet. +func (s *Stream) appendOutFramesLocked(w *packetWriter, pnum packetNumber, pto bool) bool { + if s.outreset.isSet() { + // RESET_STREAM + if s.outreset.shouldSendPTO(pto) { + if !w.appendResetStreamFrame(s.id, s.outresetcode, min(s.outwin, s.out.end)) { + return false + } + s.outreset.setSent(pnum) + s.frameOpensStream(pnum) + } + return true + } + if s.outblocked.shouldSendPTO(pto) { + // STREAM_DATA_BLOCKED + if !w.appendStreamDataBlockedFrame(s.id, s.outwin) { + return false + } + s.outblocked.setSent(pnum) + s.frameOpensStream(pnum) + } + for { + // STREAM + off, size := dataToSend(min(s.out.start, s.outwin), min(s.out.end, s.outwin), s.outunsent, s.outacked, pto) + if end := off + size; end > s.outmaxsent { + // This will require connection-level flow control to send. + end = min(end, s.outmaxsent+s.conn.streams.outflow.avail()) + size = end - off + } + fin := s.outclosed.isSet() && off+size == s.out.end + shouldSend := size > 0 || // have data to send + s.outopened.shouldSendPTO(pto) || // should open the stream + (fin && s.outclosed.shouldSendPTO(pto)) // should close the stream + if !shouldSend { + return true + } + b, added := w.appendStreamFrame(s.id, off, int(size), fin) + if !added { + return false + } + s.out.copy(off, b) + end := off + int64(len(b)) + if end > s.outmaxsent { + s.conn.streams.outflow.consume(end - s.outmaxsent) + s.outmaxsent = end + } + s.outunsent.sub(off, end) + s.frameOpensStream(pnum) + if fin { + s.outclosed.setSent(pnum) + } + if pto { + return true + } + if int64(len(b)) < size { + return false + } + } +} + +// frameOpensStream records that we're sending a frame that will open the stream. +// +// If we don't have an acknowledgement from the peer for a previous frame opening the stream, +// record this packet as being the latest one to open it. +func (s *Stream) frameOpensStream(pnum packetNumber) { + if !s.outopened.isReceived() { + s.outopened.setSent(pnum) + } +} + +// dataToSend returns the next range of data to send in a STREAM or CRYPTO_STREAM. +func dataToSend(start, end int64, outunsent, outacked rangeset[int64], pto bool) (sendStart, size int64) { + switch { + case pto: + // On PTO, resend unacked data that fits in the probe packet. + // For simplicity, we send the range starting at s.out.start + // (which is definitely unacked, or else we would have discarded it) + // up to the next acked byte (if any). + // + // This may miss unacked data starting after that acked byte, + // but avoids resending data the peer has acked. + for _, r := range outacked { + if r.start > start { + return start, r.start - start + } + } + return start, end - start + case outunsent.numRanges() > 0: + return outunsent.min(), outunsent[0].size() + default: + return end, 0 + } +} diff --git a/internal/quic/stream_limits.go b/internal/quic/stream_limits.go new file mode 100644 index 000000000..6eda7883b --- /dev/null +++ b/internal/quic/stream_limits.go @@ -0,0 +1,113 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" +) + +// Limits on the number of open streams. +// Every connection has separate limits for bidirectional and unidirectional streams. +// +// Note that the MAX_STREAMS limit includes closed as well as open streams. +// Closing a stream doesn't enable an endpoint to open a new one; +// only an increase in the MAX_STREAMS limit does. + +// localStreamLimits are limits on the number of open streams created by us. +type localStreamLimits struct { + gate gate + max int64 // peer-provided MAX_STREAMS + opened int64 // number of streams opened by us +} + +func (lim *localStreamLimits) init() { + lim.gate = newGate() +} + +// open creates a new local stream, blocking until MAX_STREAMS quota is available. +func (lim *localStreamLimits) open(ctx context.Context, c *Conn) (num int64, err error) { + // TODO: Send a STREAMS_BLOCKED when blocked. + if err := lim.gate.waitAndLock(ctx, c.testHooks); err != nil { + return 0, err + } + n := lim.opened + lim.opened++ + lim.gate.unlock(lim.opened < lim.max) + return n, nil +} + +// setMax sets the MAX_STREAMS provided by the peer. +func (lim *localStreamLimits) setMax(maxStreams int64) { + lim.gate.lock() + lim.max = max(lim.max, maxStreams) + lim.gate.unlock(lim.opened < lim.max) +} + +// remoteStreamLimits are limits on the number of open streams created by the peer. +type remoteStreamLimits struct { + max int64 // last MAX_STREAMS sent to the peer + opened int64 // number of streams opened by the peer (including subsequently closed ones) + closed int64 // number of peer streams in the "closed" state + maxOpen int64 // how many streams we want to let the peer simultaneously open + sendMax sentVal // set when we should send MAX_STREAMS +} + +func (lim *remoteStreamLimits) init(maxOpen int64) { + lim.maxOpen = maxOpen + lim.max = min(maxOpen, implicitStreamLimit) // initial limit sent in transport parameters + lim.opened = 0 +} + +// open handles the peer opening a new stream. +func (lim *remoteStreamLimits) open(id streamID) error { + num := id.num() + if num >= lim.max { + return localTransportError(errStreamLimit) + } + if num >= lim.opened { + lim.opened = num + 1 + lim.maybeUpdateMax() + } + return nil +} + +// close handles the peer closing an open stream. +func (lim *remoteStreamLimits) close() { + lim.closed++ + lim.maybeUpdateMax() +} + +// maybeUpdateMax updates the MAX_STREAMS value we will send to the peer. +func (lim *remoteStreamLimits) maybeUpdateMax() { + newMax := min( + // Max streams the peer can have open at once. + lim.closed+lim.maxOpen, + // Max streams the peer can open with a single frame. + lim.opened+implicitStreamLimit, + ) + avail := lim.max - lim.opened + if newMax > lim.max && (avail < 8 || newMax-lim.max >= 2*avail) { + // If the peer has less than 8 streams, or if increasing the peer's + // stream limit would double it, then send a MAX_STREAMS. + lim.max = newMax + lim.sendMax.setUnsent() + } +} + +// appendFrame appends a MAX_STREAMS frame to the current packet, if necessary. +// +// It returns true if no more frames need appending, +// false if not everything fit in the current packet. +func (lim *remoteStreamLimits) appendFrame(w *packetWriter, typ streamType, pnum packetNumber, pto bool) bool { + if lim.sendMax.shouldSendPTO(pto) { + if !w.appendMaxStreamsFrame(typ, lim.max) { + return false + } + lim.sendMax.setSent(pnum) + } + return true +} diff --git a/internal/quic/stream_limits_test.go b/internal/quic/stream_limits_test.go new file mode 100644 index 000000000..3f291e9f4 --- /dev/null +++ b/internal/quic/stream_limits_test.go @@ -0,0 +1,269 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "crypto/tls" + "testing" +) + +func TestStreamLimitNewStreamBlocked(t *testing.T) { + // "An endpoint that receives a frame with a stream ID exceeding the limit + // it has sent MUST treat this as a connection error of type STREAM_LIMIT_ERROR [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-4.6-3 + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + ctx := canceledContext() + tc := newTestConn(t, clientSide, + permissiveTransportParameters, + func(p *transportParameters) { + p.initialMaxStreamsBidi = 0 + p.initialMaxStreamsUni = 0 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + opening := runAsync(tc, func(ctx context.Context) (*Stream, error) { + return tc.conn.newLocalStream(ctx, styp) + }) + if _, err := opening.result(); err != errNotDone { + t.Fatalf("new stream blocked by limit: %v, want errNotDone", err) + } + tc.writeFrames(packetType1RTT, debugFrameMaxStreams{ + streamType: styp, + max: 1, + }) + if _, err := opening.result(); err != nil { + t.Fatalf("new stream not created after limit raised: %v", err) + } + if _, err := tc.conn.newLocalStream(ctx, styp); err == nil { + t.Fatalf("new stream blocked by raised limit: %v, want error", err) + } + }) +} + +func TestStreamLimitMaxStreamsDecreases(t *testing.T) { + // "MAX_STREAMS frames that do not increase the stream limit MUST be ignored." + // https://www.rfc-editor.org/rfc/rfc9000#section-4.6-4 + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + ctx := canceledContext() + tc := newTestConn(t, clientSide, + permissiveTransportParameters, + func(p *transportParameters) { + p.initialMaxStreamsBidi = 0 + p.initialMaxStreamsUni = 0 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + tc.writeFrames(packetType1RTT, debugFrameMaxStreams{ + streamType: styp, + max: 2, + }) + tc.writeFrames(packetType1RTT, debugFrameMaxStreams{ + streamType: styp, + max: 1, + }) + if _, err := tc.conn.newLocalStream(ctx, styp); err != nil { + t.Fatalf("open stream 1, limit 2, got error: %v", err) + } + if _, err := tc.conn.newLocalStream(ctx, styp); err != nil { + t.Fatalf("open stream 2, limit 2, got error: %v", err) + } + if _, err := tc.conn.newLocalStream(ctx, styp); err == nil { + t.Fatalf("open stream 3, limit 2, got error: %v", err) + } + }) +} + +func TestStreamLimitViolated(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + tc := newTestConn(t, serverSide, + func(c *Config) { + if styp == bidiStream { + c.MaxBidiRemoteStreams = 10 + } else { + c.MaxUniRemoteStreams = 10 + } + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, styp, 9), + }) + tc.wantIdle("stream number 9 is within the limit") + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, styp, 10), + }) + tc.wantFrame("stream number 10 is beyond the limit", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errStreamLimit, + }, + ) + }) +} + +func TestStreamLimitImplicitStreams(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + tc := newTestConn(t, serverSide, + func(c *Config) { + c.MaxBidiRemoteStreams = 1 << 60 + c.MaxUniRemoteStreams = 1 << 60 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + if got, want := tc.sentTransportParameters.initialMaxStreamsBidi, int64(implicitStreamLimit); got != want { + t.Errorf("sent initial_max_streams_bidi = %v, want %v", got, want) + } + if got, want := tc.sentTransportParameters.initialMaxStreamsUni, int64(implicitStreamLimit); got != want { + t.Errorf("sent initial_max_streams_uni = %v, want %v", got, want) + } + + // Create stream 0. + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, styp, 0), + }) + tc.wantIdle("max streams not increased enough to send a new frame") + + // Create streams [0, implicitStreamLimit). + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, styp, implicitStreamLimit-1), + }) + tc.wantFrame("max streams increases to implicit stream limit", + packetType1RTT, debugFrameMaxStreams{ + streamType: styp, + max: 2 * implicitStreamLimit, + }) + + // Create a stream past the limit. + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, styp, 2*implicitStreamLimit), + }) + tc.wantFrame("stream is past the limit", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errStreamLimit, + }, + ) + }) +} + +func TestStreamLimitMaxStreamsTransportParameterTooLarge(t *testing.T) { + // "If a max_streams transport parameter [...] is received with + // a value greater than 2^60 [...] the connection MUST be closed + // immediately with a connection error of type TRANSPORT_PARAMETER_ERROR [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-4.6-2 + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + tc := newTestConn(t, serverSide, + func(p *transportParameters) { + if styp == bidiStream { + p.initialMaxStreamsBidi = 1<<60 + 1 + } else { + p.initialMaxStreamsUni = 1<<60 + 1 + } + }) + tc.writeFrames(packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.wantFrame("max streams transport parameter is too large", + packetTypeInitial, debugFrameConnectionCloseTransport{ + code: errTransportParameter, + }, + ) + }) +} + +func TestStreamLimitMaxStreamsFrameTooLarge(t *testing.T) { + // "If [...] a MAX_STREAMS frame is received with a value + // greater than 2^60 [...] the connection MUST be closed immediately + // with a connection error [...] of type FRAME_ENCODING_ERROR [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-4.6-2 + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + tc := newTestConn(t, serverSide) + tc.handshake() + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetType1RTT, debugFrameMaxStreams{ + streamType: styp, + max: 1<<60 + 1, + }) + tc.wantFrame("MAX_STREAMS value is too large", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errFrameEncoding, + }, + ) + }) +} + +func TestStreamLimitSendUpdatesMaxStreams(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + ctx := canceledContext() + tc := newTestConn(t, serverSide, func(c *Config) { + if styp == uniStream { + c.MaxUniRemoteStreams = 4 + c.MaxBidiRemoteStreams = 0 + } else { + c.MaxUniRemoteStreams = 0 + c.MaxBidiRemoteStreams = 4 + } + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + var streams []*Stream + for i := 0; i < 4; i++ { + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, styp, int64(i)), + fin: true, + }) + s, err := tc.conn.AcceptStream(ctx) + if err != nil { + t.Fatalf("AcceptStream = %v", err) + } + streams = append(streams, s) + } + streams[3].CloseContext(ctx) + if styp == bidiStream { + tc.wantFrame("stream is closed", + packetType1RTT, debugFrameStream{ + id: streams[3].id, + fin: true, + data: []byte{}, + }) + tc.writeAckForAll() + } + tc.wantFrame("closing a stream when peer is at limit immediately extends the limit", + packetType1RTT, debugFrameMaxStreams{ + streamType: styp, + max: 5, + }) + }) +} + +func TestStreamLimitStopSendingDoesNotUpdateMaxStreams(t *testing.T) { + tc, s := newTestConnAndRemoteStream(t, serverSide, bidiStream, func(c *Config) { + c.MaxBidiRemoteStreams = 1 + }) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + fin: true, + }) + s.CloseRead() + tc.writeFrames(packetType1RTT, debugFrameStopSending{ + id: s.id, + }) + tc.wantFrame("recieved STOP_SENDING, send RESET_STREAM", + packetType1RTT, debugFrameResetStream{ + id: s.id, + }) + tc.writeAckForAll() + tc.wantIdle("MAX_STREAMS is not extended until the user fully closes the stream") + s.CloseWrite() + tc.wantFrame("user closing the stream triggers MAX_STREAMS update", + packetType1RTT, debugFrameMaxStreams{ + streamType: bidiStream, + max: 2, + }) +} diff --git a/internal/quic/stream_test.go b/internal/quic/stream_test.go new file mode 100644 index 000000000..7c1377fae --- /dev/null +++ b/internal/quic/stream_test.go @@ -0,0 +1,1335 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "context" + "crypto/rand" + "errors" + "fmt" + "io" + "reflect" + "strings" + "testing" +) + +func TestStreamWriteBlockedByOutputBuffer(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + ctx := canceledContext() + want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + const writeBufferSize = 4 + tc := newTestConn(t, clientSide, permissiveTransportParameters, func(c *Config) { + c.MaxStreamWriteBufferSize = writeBufferSize + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + s, err := tc.conn.newLocalStream(ctx, styp) + if err != nil { + t.Fatal(err) + } + + // Non-blocking write. + n, err := s.WriteContext(ctx, want) + if n != writeBufferSize || err != context.Canceled { + t.Fatalf("s.WriteContext() = %v, %v; want %v, context.Canceled", n, err, writeBufferSize) + } + tc.wantFrame("first write buffer of data sent", + packetType1RTT, debugFrameStream{ + id: s.id, + data: want[:writeBufferSize], + }) + off := int64(writeBufferSize) + + // Blocking write, which must wait for buffer space. + w := runAsync(tc, func(ctx context.Context) (int, error) { + return s.WriteContext(ctx, want[writeBufferSize:]) + }) + tc.wantIdle("write buffer is full, no more data can be sent") + + // The peer's ack of the STREAM frame allows progress. + tc.writeAckForAll() + tc.wantFrame("second write buffer of data sent", + packetType1RTT, debugFrameStream{ + id: s.id, + off: off, + data: want[off:][:writeBufferSize], + }) + off += writeBufferSize + tc.wantIdle("write buffer is full, no more data can be sent") + + // The peer's ack of the second STREAM frame allows sending the remaining data. + tc.writeAckForAll() + tc.wantFrame("remaining data sent", + packetType1RTT, debugFrameStream{ + id: s.id, + off: off, + data: want[off:], + }) + + if n, err := w.result(); n != len(want)-writeBufferSize || err != nil { + t.Fatalf("s.WriteContext() = %v, %v; want %v, nil", + len(want)-writeBufferSize, err, writeBufferSize) + } + }) +} + +func TestStreamWriteBlockedByStreamFlowControl(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + ctx := canceledContext() + want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + tc := newTestConn(t, clientSide, func(p *transportParameters) { + p.initialMaxStreamsBidi = 100 + p.initialMaxStreamsUni = 100 + p.initialMaxData = 1 << 20 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + s, err := tc.conn.newLocalStream(ctx, styp) + if err != nil { + t.Fatal(err) + } + + // Data is written to the stream output buffer, but we have no flow control. + _, err = s.WriteContext(ctx, want[:1]) + if err != nil { + t.Fatalf("write with available output buffer: unexpected error: %v", err) + } + tc.wantFrame("write blocked by flow control triggers a STREAM_DATA_BLOCKED frame", + packetType1RTT, debugFrameStreamDataBlocked{ + id: s.id, + max: 0, + }) + + // Write more data. + _, err = s.WriteContext(ctx, want[1:]) + if err != nil { + t.Fatalf("write with available output buffer: unexpected error: %v", err) + } + tc.wantIdle("adding more blocked data does not trigger another STREAM_DATA_BLOCKED") + + // Provide some flow control window. + tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: 4, + }) + tc.wantFrame("stream window extended, but still more data to write", + packetType1RTT, debugFrameStreamDataBlocked{ + id: s.id, + max: 4, + }) + tc.wantFrame("stream window extended to 4, expect blocked write to progress", + packetType1RTT, debugFrameStream{ + id: s.id, + data: want[:4], + }) + + // Provide more flow control window. + tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: int64(len(want)), + }) + tc.wantFrame("stream window extended further, expect blocked write to finish", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 4, + data: want[4:], + }) + }) +} + +func TestStreamIgnoresMaxStreamDataReduction(t *testing.T) { + // "A sender MUST ignore any MAX_STREAM_DATA [...] frames that + // do not increase flow control limits." + // https://www.rfc-editor.org/rfc/rfc9000#section-4.1-9 + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + ctx := canceledContext() + want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + tc := newTestConn(t, clientSide, func(p *transportParameters) { + if styp == uniStream { + p.initialMaxStreamsUni = 1 + p.initialMaxStreamDataUni = 4 + } else { + p.initialMaxStreamsBidi = 1 + p.initialMaxStreamDataBidiRemote = 4 + } + p.initialMaxData = 1 << 20 + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + tc.ignoreFrame(frameTypeStreamDataBlocked) + + // Write [0,1). + s, err := tc.conn.newLocalStream(ctx, styp) + if err != nil { + t.Fatal(err) + } + s.WriteContext(ctx, want[:1]) + tc.wantFrame("sent data (1 byte) fits within flow control limit", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: want[:1], + }) + + // MAX_STREAM_DATA tries to decrease limit, and is ignored. + tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: 2, + }) + + // Write [1,4). + s.WriteContext(ctx, want[1:]) + tc.wantFrame("stream limit is 4 bytes, ignoring decrease in MAX_STREAM_DATA", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 1, + data: want[1:4], + }) + + // MAX_STREAM_DATA increases limit. + // Second MAX_STREAM_DATA decreases it, and is ignored. + tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: 8, + }) + tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: 6, + }) + + // Write [1,4). + s.WriteContext(ctx, want[4:]) + tc.wantFrame("stream limit is 8 bytes, ignoring decrease in MAX_STREAM_DATA", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 4, + data: want[4:8], + }) + }) +} + +func TestStreamWriteBlockedByWriteBufferLimit(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + ctx := canceledContext() + want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + const maxWriteBuffer = 4 + tc := newTestConn(t, clientSide, func(p *transportParameters) { + p.initialMaxStreamsBidi = 100 + p.initialMaxStreamsUni = 100 + p.initialMaxData = 1 << 20 + p.initialMaxStreamDataBidiRemote = 1 << 20 + p.initialMaxStreamDataUni = 1 << 20 + }, func(c *Config) { + c.MaxStreamWriteBufferSize = maxWriteBuffer + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + // Write more data than StreamWriteBufferSize. + // The peer has given us plenty of flow control, + // so we're just blocked by our local limit. + s, err := tc.conn.newLocalStream(ctx, styp) + if err != nil { + t.Fatal(err) + } + w := runAsync(tc, func(ctx context.Context) (int, error) { + return s.WriteContext(ctx, want) + }) + tc.wantFrame("stream write should send as much data as write buffer allows", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: want[:maxWriteBuffer], + }) + tc.wantIdle("no STREAM_DATA_BLOCKED, we're blocked locally not by flow control") + + // ACK for previously-sent data allows making more progress. + tc.writeAckForAll() + tc.wantFrame("ACK for previous data allows making progress", + packetType1RTT, debugFrameStream{ + id: s.id, + off: maxWriteBuffer, + data: want[maxWriteBuffer:][:maxWriteBuffer], + }) + + // Cancel the write with data left to send. + w.cancel() + n, err := w.result() + if n != 2*maxWriteBuffer || err == nil { + t.Fatalf("WriteContext() = %v, %v; want %v bytes, error", n, err, 2*maxWriteBuffer) + } + }) +} + +func TestStreamReceive(t *testing.T) { + // "Endpoints MUST be able to deliver stream data to an application as + // an ordered byte stream." + // https://www.rfc-editor.org/rfc/rfc9000#section-2.2-2 + want := make([]byte, 5000) + for i := range want { + want[i] = byte(i) + } + type frame struct { + start int64 + end int64 + fin bool + want int + wantEOF bool + } + for _, test := range []struct { + name string + frames []frame + }{{ + name: "linear", + frames: []frame{{ + start: 0, + end: 1000, + want: 1000, + }, { + start: 1000, + end: 2000, + want: 2000, + }, { + start: 2000, + end: 3000, + want: 3000, + fin: true, + wantEOF: true, + }}, + }, { + name: "out of order", + frames: []frame{{ + start: 1000, + end: 2000, + }, { + start: 2000, + end: 3000, + }, { + start: 0, + end: 1000, + want: 3000, + }}, + }, { + name: "resent", + frames: []frame{{ + start: 0, + end: 1000, + want: 1000, + }, { + start: 0, + end: 1000, + want: 1000, + }, { + start: 1000, + end: 2000, + want: 2000, + }, { + start: 0, + end: 1000, + want: 2000, + }, { + start: 1000, + end: 2000, + want: 2000, + }}, + }, { + name: "overlapping", + frames: []frame{{ + start: 0, + end: 1000, + want: 1000, + }, { + start: 3000, + end: 4000, + want: 1000, + }, { + start: 2000, + end: 3000, + want: 1000, + }, { + start: 1000, + end: 3000, + want: 4000, + }}, + }, { + name: "early eof", + frames: []frame{{ + start: 3000, + end: 3000, + fin: true, + want: 0, + }, { + start: 1000, + end: 2000, + want: 0, + }, { + start: 0, + end: 1000, + want: 2000, + }, { + start: 2000, + end: 3000, + want: 3000, + wantEOF: true, + }}, + }, { + name: "empty eof", + frames: []frame{{ + start: 0, + end: 1000, + want: 1000, + }, { + start: 1000, + end: 1000, + fin: true, + want: 1000, + wantEOF: true, + }}, + }} { + testStreamTypes(t, test.name, func(t *testing.T, styp streamType) { + ctx := canceledContext() + tc := newTestConn(t, serverSide) + tc.handshake() + sid := newStreamID(clientSide, styp, 0) + var s *Stream + got := make([]byte, len(want)) + var total int + for _, f := range test.frames { + t.Logf("receive [%v,%v)", f.start, f.end) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: sid, + off: f.start, + data: want[f.start:f.end], + fin: f.fin, + }) + if s == nil { + var err error + s, err = tc.conn.AcceptStream(ctx) + if err != nil { + tc.t.Fatalf("conn.AcceptStream() = %v", err) + } + } + for { + n, err := s.ReadContext(ctx, got[total:]) + t.Logf("s.ReadContext() = %v, %v", n, err) + total += n + if f.wantEOF && err != io.EOF { + t.Fatalf("ReadContext() error = %v; want io.EOF", err) + } + if !f.wantEOF && err == io.EOF { + t.Fatalf("ReadContext() error = io.EOF, want something else") + } + if err != nil { + break + } + } + if total != f.want { + t.Fatalf("total bytes read = %v, want %v", total, f.want) + } + for i := 0; i < total; i++ { + if got[i] != want[i] { + t.Fatalf("byte %v differs: got %v, want %v", i, got[i], want[i]) + } + } + } + }) + } + +} + +func TestStreamReceiveExtendsStreamWindow(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + const maxWindowSize = 20 + ctx := canceledContext() + tc := newTestConn(t, serverSide, func(c *Config) { + c.MaxStreamReadBufferSize = maxWindowSize + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + sid := newStreamID(clientSide, styp, 0) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: sid, + off: 0, + data: make([]byte, maxWindowSize), + }) + s, err := tc.conn.AcceptStream(ctx) + if err != nil { + t.Fatalf("AcceptStream: %v", err) + } + tc.wantIdle("stream window is not extended before data is read") + buf := make([]byte, maxWindowSize+1) + if n, err := s.ReadContext(ctx, buf); n != maxWindowSize || err != nil { + t.Fatalf("s.ReadContext() = %v, %v; want %v, nil", n, err, maxWindowSize) + } + tc.wantFrame("stream window is extended after reading data", + packetType1RTT, debugFrameMaxStreamData{ + id: sid, + max: maxWindowSize * 2, + }) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: sid, + off: maxWindowSize, + data: make([]byte, maxWindowSize), + fin: true, + }) + if n, err := s.ReadContext(ctx, buf); n != maxWindowSize || err != io.EOF { + t.Fatalf("s.ReadContext() = %v, %v; want %v, io.EOF", n, err, maxWindowSize) + } + tc.wantIdle("stream window is not extended after FIN") + }) +} + +func TestStreamReceiveViolatesStreamDataLimit(t *testing.T) { + // "A receiver MUST close the connection with an error of type FLOW_CONTROL_ERROR if + // the sender violates the advertised [...] stream data limits [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-4.1-8 + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + const maxStreamData = 10 + for _, test := range []struct { + off int64 + size int64 + }{{ + off: maxStreamData, + size: 1, + }, { + off: 0, + size: maxStreamData + 1, + }, { + off: maxStreamData - 1, + size: 2, + }} { + tc := newTestConn(t, serverSide, func(c *Config) { + c.MaxStreamReadBufferSize = maxStreamData + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, styp, 0), + off: test.off, + data: make([]byte, test.size), + }) + tc.wantFrame( + fmt.Sprintf("data [%v,%v) violates stream data limit and closes connection", + test.off, test.off+test.size), + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errFlowControl, + }, + ) + } + }) +} + +func TestStreamReceiveDuplicateDataDoesNotViolateLimits(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + const maxData = 10 + tc := newTestConn(t, serverSide, func(c *Config) { + // TODO: Add connection-level maximum data here as well. + c.MaxStreamReadBufferSize = maxData + }) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + for i := 0; i < 3; i++ { + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(clientSide, styp, 0), + off: 0, + data: make([]byte, maxData), + }) + tc.wantIdle(fmt.Sprintf("conn sends no frames after receiving data frame %v", i)) + } + }) +} + +func finalSizeTest(t *testing.T, wantErr transportError, f func(tc *testConn, sid streamID) (finalSize int64), opts ...any) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + for _, test := range []struct { + name string + finalFrame func(tc *testConn, sid streamID, finalSize int64) + }{{ + name: "FIN", + finalFrame: func(tc *testConn, sid streamID, finalSize int64) { + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: sid, + off: finalSize, + fin: true, + }) + }, + }, { + name: "RESET_STREAM", + finalFrame: func(tc *testConn, sid streamID, finalSize int64) { + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: sid, + finalSize: finalSize, + }) + }, + }} { + t.Run(test.name, func(t *testing.T) { + tc := newTestConn(t, serverSide, opts...) + tc.handshake() + sid := newStreamID(clientSide, styp, 0) + finalSize := f(tc, sid) + test.finalFrame(tc, sid, finalSize) + tc.wantFrame("change in final size of stream is an error", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: wantErr, + }, + ) + }) + } + }) +} + +func TestStreamFinalSizeChangedAfterFin(t *testing.T) { + // "If a RESET_STREAM or STREAM frame is received indicating a change + // in the final size for the stream, an endpoint SHOULD respond with + // an error of type FINAL_SIZE_ERROR [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-4.5-5 + finalSizeTest(t, errFinalSize, func(tc *testConn, sid streamID) (finalSize int64) { + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: sid, + off: 10, + fin: true, + }) + return 9 + }) +} + +func TestStreamFinalSizeBeforePreviousData(t *testing.T) { + finalSizeTest(t, errFinalSize, func(tc *testConn, sid streamID) (finalSize int64) { + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: sid, + off: 10, + data: []byte{0}, + }) + return 9 + }) +} + +func TestStreamFinalSizePastMaxStreamData(t *testing.T) { + finalSizeTest(t, errFlowControl, func(tc *testConn, sid streamID) (finalSize int64) { + return 11 + }, func(c *Config) { + c.MaxStreamReadBufferSize = 10 + }) +} + +func TestStreamDataBeyondFinalSize(t *testing.T) { + // "A receiver SHOULD treat receipt of data at or beyond + // the final size as an error of type FINAL_SIZE_ERROR [...]" + // https://www.rfc-editor.org/rfc/rfc9000#section-4.5-5 + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + tc := newTestConn(t, serverSide) + tc.handshake() + sid := newStreamID(clientSide, styp, 0) + + const write1size = 4 + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: sid, + off: 0, + data: make([]byte, 16), + fin: true, + }) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: sid, + off: 16, + data: []byte{0}, + }) + tc.wantFrame("received data past final size of stream", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errFinalSize, + }, + ) + }) +} + +func TestStreamReceiveUnblocksReader(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + tc := newTestConn(t, serverSide) + tc.handshake() + want := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + sid := newStreamID(clientSide, styp, 0) + + // AcceptStream blocks until a STREAM frame is received. + accept := runAsync(tc, func(ctx context.Context) (*Stream, error) { + return tc.conn.AcceptStream(ctx) + }) + const write1size = 4 + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: sid, + off: 0, + data: want[:write1size], + }) + s, err := accept.result() + if err != nil { + t.Fatalf("AcceptStream() = %v", err) + } + + // ReadContext succeeds immediately, since we already have data. + got := make([]byte, len(want)) + read := runAsync(tc, func(ctx context.Context) (int, error) { + return s.ReadContext(ctx, got) + }) + if n, err := read.result(); n != write1size || err != nil { + t.Fatalf("ReadContext = %v, %v; want %v, nil", n, err, write1size) + } + + // ReadContext blocks waiting for more data. + read = runAsync(tc, func(ctx context.Context) (int, error) { + return s.ReadContext(ctx, got[write1size:]) + }) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: sid, + off: write1size, + data: want[write1size:], + fin: true, + }) + if n, err := read.result(); n != len(want)-write1size || err != io.EOF { + t.Fatalf("ReadContext = %v, %v; want %v, io.EOF", n, err, len(want)-write1size) + } + if !bytes.Equal(got, want) { + t.Fatalf("read bytes %x, want %x", got, want) + } + }) +} + +// testStreamSendFrameInvalidState calls the test func with a stream ID for: +// +// - a remote bidirectional stream that the peer has not created +// - a remote unidirectional stream +// +// It then sends the returned frame (STREAM, STREAM_DATA_BLOCKED, etc.) +// to the conn and expects a STREAM_STATE_ERROR. +func testStreamSendFrameInvalidState(t *testing.T, f func(sid streamID) debugFrame) { + testSides(t, "stream_not_created", func(t *testing.T, side connSide) { + tc := newTestConn(t, side, permissiveTransportParameters) + tc.handshake() + tc.writeFrames(packetType1RTT, f(newStreamID(side, bidiStream, 0))) + tc.wantFrame("frame for local stream which has not been created", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errStreamState, + }) + }) + testSides(t, "uni_stream", func(t *testing.T, side connSide) { + ctx := canceledContext() + tc := newTestConn(t, side, permissiveTransportParameters) + tc.handshake() + sid := newStreamID(side, uniStream, 0) + s, err := tc.conn.NewSendOnlyStream(ctx) + if err != nil { + t.Fatal(err) + } + s.Write(nil) // open the stream + tc.wantFrame("new stream is opened", + packetType1RTT, debugFrameStream{ + id: sid, + data: []byte{}, + }) + tc.writeFrames(packetType1RTT, f(sid)) + tc.wantFrame("send-oriented frame for send-only stream", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errStreamState, + }) + }) +} + +func TestStreamResetStreamInvalidState(t *testing.T) { + // "An endpoint that receives a RESET_STREAM frame for a send-only + // stream MUST terminate the connection with error STREAM_STATE_ERROR." + // https://www.rfc-editor.org/rfc/rfc9000#section-19.4-3 + testStreamSendFrameInvalidState(t, func(sid streamID) debugFrame { + return debugFrameResetStream{ + id: sid, + code: 0, + finalSize: 0, + } + }) +} + +func TestStreamStreamFrameInvalidState(t *testing.T) { + // "An endpoint MUST terminate the connection with error STREAM_STATE_ERROR + // if it receives a STREAM frame for a locally initiated stream + // that has not yet been created, or for a send-only stream." + // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8-3 + testStreamSendFrameInvalidState(t, func(sid streamID) debugFrame { + return debugFrameStream{ + id: sid, + } + }) +} + +func TestStreamDataBlockedInvalidState(t *testing.T) { + // "An endpoint MUST terminate the connection with error STREAM_STATE_ERROR + // if it receives a STREAM frame for a locally initiated stream + // that has not yet been created, or for a send-only stream." + // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8-3 + testStreamSendFrameInvalidState(t, func(sid streamID) debugFrame { + return debugFrameStream{ + id: sid, + } + }) +} + +// testStreamReceiveFrameInvalidState calls the test func with a stream ID for: +// +// - a remote bidirectional stream that the peer has not created +// - a local unidirectional stream +// +// It then sends the returned frame (MAX_STREAM_DATA, STOP_SENDING, etc.) +// to the conn and expects a STREAM_STATE_ERROR. +func testStreamReceiveFrameInvalidState(t *testing.T, f func(sid streamID) debugFrame) { + testSides(t, "stream_not_created", func(t *testing.T, side connSide) { + tc := newTestConn(t, side) + tc.handshake() + tc.writeFrames(packetType1RTT, f(newStreamID(side, bidiStream, 0))) + tc.wantFrame("frame for local stream which has not been created", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errStreamState, + }) + }) + testSides(t, "uni_stream", func(t *testing.T, side connSide) { + tc := newTestConn(t, side) + tc.handshake() + tc.writeFrames(packetType1RTT, f(newStreamID(side.peer(), uniStream, 0))) + tc.wantFrame("receive-oriented frame for receive-only stream", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errStreamState, + }) + }) +} + +func TestStreamStopSendingInvalidState(t *testing.T) { + // "Receiving a STOP_SENDING frame for a locally initiated stream + // that has not yet been created MUST be treated as a connection error + // of type STREAM_STATE_ERROR. An endpoint that receives a STOP_SENDING + // frame for a receive-only stream MUST terminate the connection with + // error STREAM_STATE_ERROR." + // https://www.rfc-editor.org/rfc/rfc9000#section-19.5-2 + testStreamReceiveFrameInvalidState(t, func(sid streamID) debugFrame { + return debugFrameStopSending{ + id: sid, + } + }) +} + +func TestStreamMaxStreamDataInvalidState(t *testing.T) { + // "Receiving a MAX_STREAM_DATA frame for a locally initiated stream + // that has not yet been created MUST be treated as a connection error + // of type STREAM_STATE_ERROR. An endpoint that receives a MAX_STREAM_DATA + // frame for a receive-only stream MUST terminate the connection + // with error STREAM_STATE_ERROR." + // https://www.rfc-editor.org/rfc/rfc9000#section-19.10-2 + testStreamReceiveFrameInvalidState(t, func(sid streamID) debugFrame { + return debugFrameMaxStreamData{ + id: sid, + max: 1000, + } + }) +} + +func TestStreamOffsetTooLarge(t *testing.T) { + // "Receipt of a frame that exceeds [2^62-1] MUST be treated as a + // connection error of type FRAME_ENCODING_ERROR or FLOW_CONTROL_ERROR." + // https://www.rfc-editor.org/rfc/rfc9000.html#section-19.8-9 + tc := newTestConn(t, serverSide) + tc.handshake() + + tc.writeFrames(packetType1RTT, + debugFrameStream{ + id: newStreamID(clientSide, bidiStream, 0), + off: 1<<62 - 1, + data: []byte{0}, + }) + got, _ := tc.readFrame() + want1 := debugFrameConnectionCloseTransport{code: errFrameEncoding} + want2 := debugFrameConnectionCloseTransport{code: errFlowControl} + if !reflect.DeepEqual(got, want1) && !reflect.DeepEqual(got, want2) { + t.Fatalf("STREAM offset exceeds 2^62-1\ngot: %v\nwant: %v\n or: %v", got, want1, want2) + } +} + +func TestStreamReadFromWriteOnlyStream(t *testing.T) { + _, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters) + buf := make([]byte, 10) + wantErr := "read from write-only stream" + if n, err := s.Read(buf); err == nil || !strings.Contains(err.Error(), wantErr) { + t.Errorf("s.Read() = %v, %v; want error %q", n, err, wantErr) + } +} + +func TestStreamWriteToReadOnlyStream(t *testing.T) { + _, s := newTestConnAndRemoteStream(t, serverSide, uniStream) + buf := make([]byte, 10) + wantErr := "write to read-only stream" + if n, err := s.Write(buf); err == nil || !strings.Contains(err.Error(), wantErr) { + t.Errorf("s.Write() = %v, %v; want error %q", n, err, wantErr) + } +} + +func TestStreamReadFromClosedStream(t *testing.T) { + tc, s := newTestConnAndRemoteStream(t, serverSide, bidiStream, permissiveTransportParameters) + s.CloseRead() + tc.wantFrame("CloseRead sends a STOP_SENDING frame", + packetType1RTT, debugFrameStopSending{ + id: s.id, + }) + wantErr := "read from closed stream" + if n, err := s.Read(make([]byte, 16)); err == nil || !strings.Contains(err.Error(), wantErr) { + t.Errorf("s.Read() = %v, %v; want error %q", n, err, wantErr) + } + // Data which shows up after STOP_SENDING is discarded. + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + data: []byte{1, 2, 3}, + fin: true, + }) + if n, err := s.Read(make([]byte, 16)); err == nil || !strings.Contains(err.Error(), wantErr) { + t.Errorf("s.Read() = %v, %v; want error %q", n, err, wantErr) + } +} + +func TestStreamCloseReadWithAllDataReceived(t *testing.T) { + tc, s := newTestConnAndRemoteStream(t, serverSide, bidiStream, permissiveTransportParameters) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + data: []byte{1, 2, 3}, + fin: true, + }) + s.CloseRead() + tc.wantIdle("CloseRead in Data Recvd state doesn't need to send STOP_SENDING") + // We had all the data for the stream, but CloseRead discarded it. + wantErr := "read from closed stream" + if n, err := s.Read(make([]byte, 16)); err == nil || !strings.Contains(err.Error(), wantErr) { + t.Errorf("s.Read() = %v, %v; want error %q", n, err, wantErr) + } +} + +func TestStreamWriteToClosedStream(t *testing.T) { + tc, s := newTestConnAndLocalStream(t, serverSide, bidiStream, permissiveTransportParameters) + s.CloseWrite() + tc.wantFrame("stream is opened after being closed", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + fin: true, + data: []byte{}, + }) + wantErr := "write to closed stream" + if n, err := s.Write([]byte{}); err == nil || !strings.Contains(err.Error(), wantErr) { + t.Errorf("s.Write() = %v, %v; want error %q", n, err, wantErr) + } +} + +func TestStreamResetBlockedStream(t *testing.T) { + tc, s := newTestConnAndLocalStream(t, serverSide, bidiStream, permissiveTransportParameters, + func(c *Config) { + c.MaxStreamWriteBufferSize = 4 + }) + tc.ignoreFrame(frameTypeStreamDataBlocked) + writing := runAsync(tc, func(ctx context.Context) (int, error) { + return s.WriteContext(ctx, []byte{0, 1, 2, 3, 4, 5, 6, 7}) + }) + tc.wantFrame("stream writes data until write buffer fills", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 0, + data: []byte{0, 1, 2, 3}, + }) + s.Reset(42) + tc.wantFrame("stream is reset", + packetType1RTT, debugFrameResetStream{ + id: s.id, + code: 42, + finalSize: 4, + }) + wantErr := "write to reset stream" + if n, err := writing.result(); n != 4 || !strings.Contains(err.Error(), wantErr) { + t.Errorf("s.Write() interrupted by Reset: %v, %q; want 4, %q", n, err, wantErr) + } + tc.writeAckForAll() + tc.wantIdle("buffer space is available, but stream has been reset") + s.Reset(100) + tc.wantIdle("resetting stream a second time has no effect") + if n, err := s.Write([]byte{}); err == nil || !strings.Contains(err.Error(), wantErr) { + t.Errorf("s.Write() = %v, %v; want error %q", n, err, wantErr) + } +} + +func TestStreamWriteMoreThanOnePacketOfData(t *testing.T) { + tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, func(p *transportParameters) { + p.initialMaxStreamsUni = 1 + p.initialMaxData = 1 << 20 + p.initialMaxStreamDataUni = 1 << 20 + }) + want := make([]byte, 4096) + rand.Read(want) // doesn't need to be crypto/rand, but non-deprecated and harmless + w := runAsync(tc, func(ctx context.Context) (int, error) { + return s.WriteContext(ctx, want) + }) + got := make([]byte, 0, len(want)) + for { + f, _ := tc.readFrame() + if f == nil { + break + } + sf, ok := f.(debugFrameStream) + if !ok { + t.Fatalf("unexpected frame: %v", sf) + } + if len(got) != int(sf.off) { + t.Fatalf("got frame: %v\nwant offset %v", sf, len(got)) + } + got = append(got, sf.data...) + } + if n, err := w.result(); n != len(want) || err != nil { + t.Fatalf("s.WriteContext() = %v, %v; want %v, nil", n, err, len(want)) + } + if !bytes.Equal(got, want) { + t.Fatalf("mismatch in received stream data") + } +} + +func TestStreamCloseWaitsForAcks(t *testing.T) { + ctx := canceledContext() + tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters) + data := make([]byte, 100) + s.WriteContext(ctx, data) + tc.wantFrame("conn sends data for the stream", + packetType1RTT, debugFrameStream{ + id: s.id, + data: data, + }) + if err := s.CloseContext(ctx); err != context.Canceled { + t.Fatalf("s.Close() = %v, want context.Canceled (data not acked yet)", err) + } + tc.wantFrame("conn sends FIN for closed stream", + packetType1RTT, debugFrameStream{ + id: s.id, + off: int64(len(data)), + fin: true, + data: []byte{}, + }) + closing := runAsync(tc, func(ctx context.Context) (struct{}, error) { + return struct{}{}, s.CloseContext(ctx) + }) + if _, err := closing.result(); err != errNotDone { + t.Fatalf("s.CloseContext() = %v, want it to block waiting for acks", err) + } + tc.writeAckForAll() + if _, err := closing.result(); err != nil { + t.Fatalf("s.CloseContext() = %v, want nil (all data acked)", err) + } +} + +func TestStreamCloseReadOnly(t *testing.T) { + tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, permissiveTransportParameters) + if err := s.CloseContext(canceledContext()); err != nil { + t.Errorf("s.CloseContext() = %v, want nil", err) + } + tc.wantFrame("closed stream sends STOP_SENDING", + packetType1RTT, debugFrameStopSending{ + id: s.id, + }) +} + +func TestStreamCloseUnblocked(t *testing.T) { + for _, test := range []struct { + name string + unblock func(tc *testConn, s *Stream) + }{{ + name: "data received", + unblock: func(tc *testConn, s *Stream) { + tc.writeAckForAll() + }, + }, { + name: "stop sending received", + unblock: func(tc *testConn, s *Stream) { + tc.writeFrames(packetType1RTT, debugFrameStopSending{ + id: s.id, + }) + }, + }, { + name: "stream reset", + unblock: func(tc *testConn, s *Stream) { + s.Reset(0) + tc.wait() // wait for test conn to process the Reset + }, + }} { + t.Run(test.name, func(t *testing.T) { + ctx := canceledContext() + tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters) + data := make([]byte, 100) + s.WriteContext(ctx, data) + tc.wantFrame("conn sends data for the stream", + packetType1RTT, debugFrameStream{ + id: s.id, + data: data, + }) + if err := s.CloseContext(ctx); err != context.Canceled { + t.Fatalf("s.Close() = %v, want context.Canceled (data not acked yet)", err) + } + tc.wantFrame("conn sends FIN for closed stream", + packetType1RTT, debugFrameStream{ + id: s.id, + off: int64(len(data)), + fin: true, + data: []byte{}, + }) + closing := runAsync(tc, func(ctx context.Context) (struct{}, error) { + return struct{}{}, s.CloseContext(ctx) + }) + if _, err := closing.result(); err != errNotDone { + t.Fatalf("s.CloseContext() = %v, want it to block waiting for acks", err) + } + test.unblock(tc, s) + if _, err := closing.result(); err != nil { + t.Fatalf("s.CloseContext() = %v, want nil (all data acked)", err) + } + }) + } +} + +func TestStreamCloseWriteWhenBlockedByStreamFlowControl(t *testing.T) { + ctx := canceledContext() + tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters, + func(p *transportParameters) { + //p.initialMaxData = 0 + p.initialMaxStreamDataUni = 0 + }) + tc.ignoreFrame(frameTypeStreamDataBlocked) + if _, err := s.WriteContext(ctx, []byte{0, 1}); err != nil { + t.Fatalf("s.Write = %v", err) + } + s.CloseWrite() + tc.wantIdle("stream write is blocked by flow control") + + tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: 1, + }) + tc.wantFrame("send data up to flow control limit", + packetType1RTT, debugFrameStream{ + id: s.id, + data: []byte{0}, + }) + tc.wantIdle("stream write is again blocked by flow control") + + tc.writeFrames(packetType1RTT, debugFrameMaxStreamData{ + id: s.id, + max: 2, + }) + tc.wantFrame("send remaining data and FIN", + packetType1RTT, debugFrameStream{ + id: s.id, + off: 1, + data: []byte{1}, + fin: true, + }) +} + +func TestStreamPeerResetsWithUnreadAndUnsentData(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + ctx := canceledContext() + tc, s := newTestConnAndRemoteStream(t, serverSide, styp) + data := []byte{0, 1, 2, 3, 4, 5, 6, 7} + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + data: data, + }) + got := make([]byte, 4) + if n, err := s.ReadContext(ctx, got); n != len(got) || err != nil { + t.Fatalf("Read start of stream: got %v, %v; want %v, nil", n, err, len(got)) + } + const sentCode = 42 + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: s.id, + finalSize: 20, + code: sentCode, + }) + wantErr := StreamErrorCode(sentCode) + if n, err := s.ReadContext(ctx, got); n != 0 || !errors.Is(err, wantErr) { + t.Fatalf("Read reset stream: got %v, %v; want 0, %v", n, err, wantErr) + } + }) +} + +func TestStreamPeerResetWakesBlockedRead(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + tc, s := newTestConnAndRemoteStream(t, serverSide, styp) + reader := runAsync(tc, func(ctx context.Context) (int, error) { + got := make([]byte, 4) + return s.ReadContext(ctx, got) + }) + const sentCode = 42 + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: s.id, + finalSize: 20, + code: sentCode, + }) + wantErr := StreamErrorCode(sentCode) + if n, err := reader.result(); n != 0 || !errors.Is(err, wantErr) { + t.Fatalf("Read reset stream: got %v, %v; want 0, %v", n, err, wantErr) + } + }) +} + +func TestStreamPeerResetFollowedByData(t *testing.T) { + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + tc, s := newTestConnAndRemoteStream(t, serverSide, styp) + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: s.id, + finalSize: 4, + code: 1, + }) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: s.id, + data: []byte{0, 1, 2, 3}, + }) + // Another reset with a different code, for good measure. + tc.writeFrames(packetType1RTT, debugFrameResetStream{ + id: s.id, + finalSize: 4, + code: 2, + }) + wantErr := StreamErrorCode(1) + if n, err := s.Read(make([]byte, 16)); n != 0 || !errors.Is(err, wantErr) { + t.Fatalf("Read from reset stream: got %v, %v; want 0, %v", n, err, wantErr) + } + }) +} + +func TestStreamResetInvalidCode(t *testing.T) { + tc, s := newTestConnAndLocalStream(t, serverSide, uniStream, permissiveTransportParameters) + s.Reset(1 << 62) + tc.wantFrame("reset with invalid code sends a RESET_STREAM anyway", + packetType1RTT, debugFrameResetStream{ + id: s.id, + // The code we send here isn't specified, + // so this could really be any value. + code: (1 << 62) - 1, + }) +} + +func TestStreamResetReceiveOnly(t *testing.T) { + tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream) + s.Reset(0) + tc.wantIdle("resetting a receive-only stream has no effect") +} + +func TestStreamPeerStopSendingForActiveStream(t *testing.T) { + // "An endpoint that receives a STOP_SENDING frame MUST send a RESET_STREAM frame if + // the stream is in the "Ready" or "Send" state." + // https://www.rfc-editor.org/rfc/rfc9000#section-3.5-4 + testStreamTypes(t, "", func(t *testing.T, styp streamType) { + tc, s := newTestConnAndLocalStream(t, serverSide, styp, permissiveTransportParameters) + for i := 0; i < 4; i++ { + s.Write([]byte{byte(i)}) + tc.wantFrame("write sends a STREAM frame to peer", + packetType1RTT, debugFrameStream{ + id: s.id, + off: int64(i), + data: []byte{byte(i)}, + }) + } + tc.writeFrames(packetType1RTT, debugFrameStopSending{ + id: s.id, + code: 42, + }) + tc.wantFrame("receiving STOP_SENDING causes stream reset", + packetType1RTT, debugFrameResetStream{ + id: s.id, + code: 42, + finalSize: 4, + }) + if n, err := s.Write([]byte{0}); err == nil { + t.Errorf("s.Write() after STOP_SENDING = %v, %v; want error", n, err) + } + // This ack will result in some of the previous frames being marked as lost. + tc.writeAckForLatest() + tc.wantIdle("lost STREAM frames for reset stream are not resent") + }) +} + +func TestStreamReceiveDataBlocked(t *testing.T) { + tc := newTestConn(t, serverSide, permissiveTransportParameters) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + + // We don't do anything with these frames, + // but should accept them if the peer sends one. + tc.writeFrames(packetType1RTT, debugFrameStreamDataBlocked{ + id: newStreamID(clientSide, bidiStream, 0), + max: 100, + }) + tc.writeFrames(packetType1RTT, debugFrameDataBlocked{ + max: 100, + }) + tc.wantIdle("no response to STREAM_DATA_BLOCKED and DATA_BLOCKED") +} + +type streamSide string + +const ( + localStream = streamSide("local") + remoteStream = streamSide("remote") +) + +func newTestConnAndStream(t *testing.T, side connSide, sside streamSide, styp streamType, opts ...any) (*testConn, *Stream) { + if sside == localStream { + return newTestConnAndLocalStream(t, side, styp, opts...) + } else { + return newTestConnAndRemoteStream(t, side, styp, opts...) + } +} + +func newTestConnAndLocalStream(t *testing.T, side connSide, styp streamType, opts ...any) (*testConn, *Stream) { + t.Helper() + ctx := canceledContext() + tc := newTestConn(t, side, opts...) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + s, err := tc.conn.newLocalStream(ctx, styp) + if err != nil { + t.Fatalf("conn.newLocalStream(%v) = %v", styp, err) + } + return tc, s +} + +func newTestConnAndRemoteStream(t *testing.T, side connSide, styp streamType, opts ...any) (*testConn, *Stream) { + t.Helper() + ctx := canceledContext() + tc := newTestConn(t, side, opts...) + tc.handshake() + tc.ignoreFrame(frameTypeAck) + tc.writeFrames(packetType1RTT, debugFrameStream{ + id: newStreamID(side.peer(), styp, 0), + }) + s, err := tc.conn.AcceptStream(ctx) + if err != nil { + t.Fatalf("conn.AcceptStream() = %v", err) + } + return tc, s +} + +// permissiveTransportParameters may be passed as an option to newTestConn. +func permissiveTransportParameters(p *transportParameters) { + p.initialMaxStreamsBidi = maxStreamsLimit + p.initialMaxStreamsUni = maxStreamsLimit + p.initialMaxData = maxVarint + p.initialMaxStreamDataBidiRemote = maxVarint + p.initialMaxStreamDataBidiLocal = maxVarint + p.initialMaxStreamDataUni = maxVarint +} + +func makeTestData(n int) []byte { + b := make([]byte, n) + for i := 0; i < n; i++ { + b[i] = byte(i) + } + return b +} diff --git a/internal/quic/tls.go b/internal/quic/tls.go new file mode 100644 index 000000000..a37e26fb8 --- /dev/null +++ b/internal/quic/tls.go @@ -0,0 +1,119 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "time" +) + +// startTLS starts the TLS handshake. +func (c *Conn) startTLS(now time.Time, initialConnID []byte, params transportParameters) error { + c.keysInitial = initialKeys(initialConnID, c.side) + + qconfig := &tls.QUICConfig{TLSConfig: c.config.TLSConfig} + if c.side == clientSide { + c.tls = tls.QUICClient(qconfig) + } else { + c.tls = tls.QUICServer(qconfig) + } + c.tls.SetTransportParameters(marshalTransportParameters(params)) + // TODO: We don't need or want a context for cancelation here, + // but users can use a context to plumb values through to hooks defined + // in the tls.Config. Pass through a context. + if err := c.tls.Start(context.TODO()); err != nil { + return err + } + return c.handleTLSEvents(now) +} + +func (c *Conn) handleTLSEvents(now time.Time) error { + for { + e := c.tls.NextEvent() + if c.testHooks != nil { + c.testHooks.handleTLSEvent(e) + } + switch e.Kind { + case tls.QUICNoEvent: + return nil + case tls.QUICSetReadSecret: + if err := checkCipherSuite(e.Suite); err != nil { + return err + } + switch e.Level { + case tls.QUICEncryptionLevelHandshake: + c.keysHandshake.r.init(e.Suite, e.Data) + case tls.QUICEncryptionLevelApplication: + c.keysAppData.r.init(e.Suite, e.Data) + } + case tls.QUICSetWriteSecret: + if err := checkCipherSuite(e.Suite); err != nil { + return err + } + switch e.Level { + case tls.QUICEncryptionLevelHandshake: + c.keysHandshake.w.init(e.Suite, e.Data) + case tls.QUICEncryptionLevelApplication: + c.keysAppData.w.init(e.Suite, e.Data) + } + case tls.QUICWriteData: + var space numberSpace + switch e.Level { + case tls.QUICEncryptionLevelInitial: + space = initialSpace + case tls.QUICEncryptionLevelHandshake: + space = handshakeSpace + case tls.QUICEncryptionLevelApplication: + space = appDataSpace + default: + return fmt.Errorf("quic: internal error: write handshake data at level %v", e.Level) + } + c.crypto[space].write(e.Data) + case tls.QUICHandshakeDone: + if c.side == serverSide { + // "[...] the TLS handshake is considered confirmed + // at the server when the handshake completes." + // https://www.rfc-editor.org/rfc/rfc9001#section-4.1.2-1 + c.confirmHandshake(now) + } + c.handshakeDone() + case tls.QUICTransportParameters: + params, err := unmarshalTransportParams(e.Data) + if err != nil { + return err + } + if err := c.receiveTransportParameters(params); err != nil { + return err + } + } + } +} + +// handleCrypto processes data received in a CRYPTO frame. +func (c *Conn) handleCrypto(now time.Time, space numberSpace, off int64, data []byte) error { + var level tls.QUICEncryptionLevel + switch space { + case initialSpace: + level = tls.QUICEncryptionLevelInitial + case handshakeSpace: + level = tls.QUICEncryptionLevelHandshake + case appDataSpace: + level = tls.QUICEncryptionLevelApplication + default: + return errors.New("quic: internal error: received CRYPTO frame in unexpected number space") + } + err := c.crypto[space].handleCrypto(off, data, func(b []byte) error { + return c.tls.HandleData(level, b) + }) + if err != nil { + return err + } + return c.handleTLSEvents(now) +} diff --git a/internal/quic/tls_test.go b/internal/quic/tls_test.go new file mode 100644 index 000000000..81d17b858 --- /dev/null +++ b/internal/quic/tls_test.go @@ -0,0 +1,603 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "reflect" + "testing" + "time" +) + +// handshake executes the handshake. +func (tc *testConn) handshake() { + tc.t.Helper() + if *testVV { + *testVV = false + defer func() { + tc.t.Helper() + *testVV = true + tc.t.Logf("performed connection handshake") + }() + } + defer func(saved map[byte]bool) { + tc.ignoreFrames = saved + }(tc.ignoreFrames) + tc.ignoreFrames = nil + t := tc.t + dgrams := handshakeDatagrams(tc) + i := 0 + for { + if i == len(dgrams)-1 { + if tc.conn.side == clientSide { + want := tc.now.Add(maxAckDelay - timerGranularity) + if !tc.timer.Equal(want) { + t.Fatalf("want timer = %v (max_ack_delay), got %v", want, tc.timer) + } + if got := tc.readDatagram(); got != nil { + t.Fatalf("client unexpectedly sent: %v", got) + } + } + tc.advance(maxAckDelay) + } + + // Check that we're sending exactly the data we expect. + // Any variation from the norm here should be intentional. + got := tc.readDatagram() + var want *testDatagram + if !(tc.conn.side == serverSide && i == 0) && i < len(dgrams) { + want = dgrams[i] + fillCryptoFrames(want, tc.cryptoDataOut) + i++ + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("dgram %v:\ngot %v\n\nwant %v", i, got, want) + } + if i >= len(dgrams) { + break + } + + fillCryptoFrames(dgrams[i], tc.cryptoDataIn) + tc.write(dgrams[i]) + i++ + } +} + +func handshakeDatagrams(tc *testConn) (dgrams []*testDatagram) { + var ( + clientConnIDs [][]byte + serverConnIDs [][]byte + transientConnID []byte + ) + localConnIDs := [][]byte{ + testLocalConnID(0), + testLocalConnID(1), + } + peerConnIDs := [][]byte{ + testPeerConnID(0), + testPeerConnID(1), + } + if tc.conn.side == clientSide { + clientConnIDs = localConnIDs + serverConnIDs = peerConnIDs + transientConnID = testLocalConnID(-1) + } else { + clientConnIDs = peerConnIDs + serverConnIDs = localConnIDs + transientConnID = []byte{0xde, 0xad, 0xbe, 0xef} + } + return []*testDatagram{{ + // Client Initial + packets: []*testPacket{{ + ptype: packetTypeInitial, + num: 0, + version: quicVersion1, + srcConnID: clientConnIDs[0], + dstConnID: transientConnID, + frames: []debugFrame{ + debugFrameCrypto{}, + }, + }}, + paddedSize: 1200, + }, { + // Server Initial + Handshake + 1-RTT + packets: []*testPacket{{ + ptype: packetTypeInitial, + num: 0, + version: quicVersion1, + srcConnID: serverConnIDs[0], + dstConnID: clientConnIDs[0], + frames: []debugFrame{ + debugFrameAck{ + ranges: []i64range[packetNumber]{{0, 1}}, + }, + debugFrameCrypto{}, + }, + }, { + ptype: packetTypeHandshake, + num: 0, + version: quicVersion1, + srcConnID: serverConnIDs[0], + dstConnID: clientConnIDs[0], + frames: []debugFrame{ + debugFrameCrypto{}, + }, + }, { + ptype: packetType1RTT, + num: 0, + dstConnID: clientConnIDs[0], + frames: []debugFrame{ + debugFrameNewConnectionID{ + seq: 1, + connID: serverConnIDs[1], + }, + }, + }}, + }, { + // Client Initial + Handshake + 1-RTT + packets: []*testPacket{{ + ptype: packetTypeInitial, + num: 1, + version: quicVersion1, + srcConnID: clientConnIDs[0], + dstConnID: serverConnIDs[0], + frames: []debugFrame{ + debugFrameAck{ + ranges: []i64range[packetNumber]{{0, 1}}, + }, + }, + }, { + ptype: packetTypeHandshake, + num: 0, + version: quicVersion1, + srcConnID: clientConnIDs[0], + dstConnID: serverConnIDs[0], + frames: []debugFrame{ + debugFrameAck{ + ranges: []i64range[packetNumber]{{0, 1}}, + }, + debugFrameCrypto{}, + }, + }, { + ptype: packetType1RTT, + num: 0, + dstConnID: serverConnIDs[0], + frames: []debugFrame{ + debugFrameAck{ + ranges: []i64range[packetNumber]{{0, 1}}, + }, + debugFrameNewConnectionID{ + seq: 1, + connID: clientConnIDs[1], + }, + }, + }}, + paddedSize: 1200, + }, { + // Server HANDSHAKE_DONE + packets: []*testPacket{{ + ptype: packetType1RTT, + num: 1, + dstConnID: clientConnIDs[0], + frames: []debugFrame{ + debugFrameAck{ + ranges: []i64range[packetNumber]{{0, 1}}, + }, + debugFrameHandshakeDone{}, + }, + }}, + }, { + // Client ack (after max_ack_delay) + packets: []*testPacket{{ + ptype: packetType1RTT, + num: 1, + dstConnID: serverConnIDs[0], + frames: []debugFrame{ + debugFrameAck{ + ackDelay: unscaledAckDelayFromDuration( + maxAckDelay, ackDelayExponent), + ranges: []i64range[packetNumber]{{0, 2}}, + }, + }, + }}, + }} +} + +func fillCryptoFrames(d *testDatagram, data map[tls.QUICEncryptionLevel][]byte) { + for _, p := range d.packets { + var level tls.QUICEncryptionLevel + switch p.ptype { + case packetTypeInitial: + level = tls.QUICEncryptionLevelInitial + case packetTypeHandshake: + level = tls.QUICEncryptionLevelHandshake + case packetType1RTT: + level = tls.QUICEncryptionLevelApplication + default: + continue + } + for i := range p.frames { + c, ok := p.frames[i].(debugFrameCrypto) + if !ok { + continue + } + c.data = data[level] + data[level] = nil + p.frames[i] = c + } + } +} + +// uncheckedHandshake executes the handshake. +// +// Unlike testConn.handshake, it sends nothing unnecessary +// (in particular, no NEW_CONNECTION_ID frames), +// and does not validate the conn's responses. +// +// Useful for testing scenarios where configuration has +// changed the handshake responses in some way. +func (tc *testConn) uncheckedHandshake() { + tc.t.Helper() + defer func(saved map[byte]bool) { + tc.ignoreFrames = saved + }(tc.ignoreFrames) + tc.ignoreFrames = map[byte]bool{ + frameTypeAck: true, + frameTypeCrypto: true, + frameTypeNewConnectionID: true, + } + if tc.conn.side == serverSide { + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + tc.wantFrame("send HANDSHAKE_DONE after handshake completes", + packetType1RTT, debugFrameHandshakeDone{}) + tc.writeFrames(packetType1RTT, + debugFrameAck{ + ackDelay: unscaledAckDelayFromDuration( + maxAckDelay, ackDelayExponent), + ranges: []i64range[packetNumber]{{0, tc.lastPacket.num + 1}}, + }) + } else { + tc.wantIdle("initial frames are ignored") + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + tc.wantIdle("don't expect any frames we aren't ignoring") + // Send the next two frames in separate packets, so the client sends an + // ack immediately without delay. We want to consume that ack here, rather + // than returning with a delayed ack waiting to be sent. + tc.ignoreFrames = nil + tc.writeFrames(packetType1RTT, + debugFrameHandshakeDone{}) + tc.writeFrames(packetType1RTT, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelApplication], + }) + tc.wantFrame("client ACKs server's first 1-RTT packet", + packetType1RTT, debugFrameAck{ + ranges: []i64range[packetNumber]{{0, 2}}, + }) + + } + tc.wantIdle("handshake is done") +} + +func TestConnClientHandshake(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.handshake() + tc.advance(1 * time.Second) + tc.wantIdle("no packets should be sent by an idle conn after the handshake") +} + +func TestConnServerHandshake(t *testing.T) { + tc := newTestConn(t, serverSide) + tc.handshake() + tc.advance(1 * time.Second) + tc.wantIdle("no packets should be sent by an idle conn after the handshake") +} + +func TestConnKeysDiscardedClient(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.ignoreFrame(frameTypeAck) + + tc.wantFrame("client sends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + tc.wantFrame("client sends Handshake CRYPTO frame", + packetTypeHandshake, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake], + }) + tc.wantFrame("client provides an additional connection ID", + packetType1RTT, debugFrameNewConnectionID{ + seq: 1, + connID: testLocalConnID(1), + }) + + // The client discards Initial keys after sending a Handshake packet. + tc.writeFrames(packetTypeInitial, + debugFrameConnectionCloseTransport{code: errInternal}) + tc.wantIdle("client has discarded Initial keys, cannot read CONNECTION_CLOSE") + + // The client discards Handshake keys after receiving a HANDSHAKE_DONE frame. + tc.writeFrames(packetType1RTT, + debugFrameHandshakeDone{}) + tc.writeFrames(packetTypeHandshake, + debugFrameConnectionCloseTransport{code: errInternal}) + tc.wantIdle("client has discarded Handshake keys, cannot read CONNECTION_CLOSE") + + tc.writeFrames(packetType1RTT, + debugFrameConnectionCloseTransport{code: errInternal}) + tc.conn.Abort(nil) + tc.wantFrame("client closes connection after 1-RTT CONNECTION_CLOSE", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errNo, + }) +} + +func TestConnKeysDiscardedServer(t *testing.T) { + tc := newTestConn(t, serverSide) + tc.ignoreFrame(frameTypeAck) + + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.wantFrame("server sends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + tc.wantFrame("server sends Handshake CRYPTO frame", + packetTypeHandshake, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake], + }) + + // The server discards Initial keys after receiving a Handshake packet. + // The Handshake packet contains only the start of the client's CRYPTO flight here, + // to avoids completing the handshake yet. + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake][:1], + }) + tc.writeFrames(packetTypeInitial, + debugFrameConnectionCloseTransport{code: errInternal}) + tc.wantFrame("server provides an additional connection ID", + packetType1RTT, debugFrameNewConnectionID{ + seq: 1, + connID: testLocalConnID(1), + }) + tc.wantIdle("server has discarded Initial keys, cannot read CONNECTION_CLOSE") + + // The server discards Handshake keys after sending a HANDSHAKE_DONE frame. + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + off: 1, + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake][1:], + }) + tc.wantFrame("server sends HANDSHAKE_DONE after handshake completes", + packetType1RTT, debugFrameHandshakeDone{}) + tc.writeFrames(packetTypeHandshake, + debugFrameConnectionCloseTransport{code: errInternal}) + tc.wantIdle("server has discarded Handshake keys, cannot read CONNECTION_CLOSE") + + tc.writeFrames(packetType1RTT, + debugFrameConnectionCloseTransport{code: errInternal}) + tc.conn.Abort(nil) + tc.wantFrame("server closes connection after 1-RTT CONNECTION_CLOSE", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errNo, + }) +} + +func TestConnInvalidCryptoData(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.ignoreFrame(frameTypeAck) + + tc.wantFrame("client sends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + + // Render the server's response invalid. + // + // The client closes the connection with CRYPTO_ERROR. + // + // Changing the first byte will change the TLS message type, + // so we can reasonably assume that this is an unexpected_message alert (10). + tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake][0] ^= 0x1 + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + tc.wantFrame("client closes connection due to TLS handshake error", + packetTypeInitial, debugFrameConnectionCloseTransport{ + code: errTLSBase + 10, + }) +} + +func TestConnInvalidPeerCertificate(t *testing.T) { + tc := newTestConn(t, clientSide, func(c *tls.Config) { + c.VerifyPeerCertificate = func([][]byte, [][]*x509.Certificate) error { + return errors.New("I will not buy this certificate. It is scratched.") + } + }) + tc.ignoreFrame(frameTypeAck) + + tc.wantFrame("client sends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + tc.wantFrame("client closes connection due to rejecting server certificate", + packetTypeInitial, debugFrameConnectionCloseTransport{ + code: errTLSBase + 42, // 42: bad_certificate + }) +} + +func TestConnHandshakeDoneSentToServer(t *testing.T) { + tc := newTestConn(t, serverSide) + tc.handshake() + + tc.writeFrames(packetType1RTT, + debugFrameHandshakeDone{}) + tc.wantFrame("server closes connection when client sends a HANDSHAKE_DONE frame", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errProtocolViolation, + }) +} + +func TestConnCryptoDataOutOfOrder(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.ignoreFrame(frameTypeAck) + + tc.wantFrame("client sends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.wantIdle("client is idle, server Handshake flight has not arrived") + + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + off: 15, + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake][15:], + }) + tc.wantIdle("client is idle, server Handshake flight is not complete") + + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + off: 1, + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake][1:20], + }) + tc.wantIdle("client is idle, server Handshake flight is still not complete") + + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake][0:1], + }) + tc.wantFrame("client sends Handshake CRYPTO frame", + packetTypeHandshake, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelHandshake], + }) +} + +func TestConnCryptoBufferSizeExceeded(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.ignoreFrame(frameTypeAck) + + tc.wantFrame("client sends Initial CRYPTO frame", + packetTypeInitial, debugFrameCrypto{ + data: tc.cryptoDataOut[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + off: cryptoBufferSize, + data: []byte{0}, + }) + tc.wantFrame("client closes connection after server exceeds CRYPTO buffer", + packetTypeInitial, debugFrameConnectionCloseTransport{ + code: errCryptoBufferExceeded, + }) +} + +func TestConnAEADLimitReached(t *testing.T) { + // "[...] endpoints MUST count the number of received packets that + // fail authentication during the lifetime of a connection. + // If the total number of received packets that fail authentication [...] + // exceeds the integrity limit for the selected AEAD, + // the endpoint MUST immediately close the connection [...]" + // https://www.rfc-editor.org/rfc/rfc9001#section-6.6-6 + tc := newTestConn(t, clientSide) + tc.handshake() + + var limit int64 + switch suite := tc.conn.keysAppData.r.suite; suite { + case tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384: + limit = 1 << 52 + case tls.TLS_CHACHA20_POLY1305_SHA256: + limit = 1 << 36 + default: + t.Fatalf("conn.keysAppData.r.suite = %v, unknown suite", suite) + } + + dstConnID := tc.conn.connIDState.local[0].cid + if tc.conn.connIDState.local[0].seq == -1 { + // Only use the transient connection ID in Initial packets. + dstConnID = tc.conn.connIDState.local[1].cid + } + invalid := tc.encodeTestPacket(&testPacket{ + ptype: packetType1RTT, + num: 1000, + frames: []debugFrame{debugFramePing{}}, + version: quicVersion1, + dstConnID: dstConnID, + srcConnID: tc.peerConnID, + }, 0) + invalid[len(invalid)-1] ^= 1 + sendInvalid := func() { + t.Logf("<- conn under test receives invalid datagram") + tc.conn.sendMsg(&datagram{ + b: invalid, + }) + tc.wait() + } + + // Set the conn's auth failure count to just before the AEAD integrity limit. + tc.conn.keysAppData.authFailures = limit - 1 + + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.advanceToTimer() + tc.wantFrameType("auth failures less than limit: conn ACKs packet", + packetType1RTT, debugFrameAck{}) + + sendInvalid() + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.advanceToTimer() + tc.wantFrameType("auth failures at limit: conn closes", + packetType1RTT, debugFrameConnectionCloseTransport{ + code: errAEADLimitReached, + }) + + tc.writeFrames(packetType1RTT, debugFramePing{}) + tc.advance(1 * time.Second) + tc.wantIdle("auth failures at limit: conn does not process additional packets") +} diff --git a/internal/quic/tlsconfig_test.go b/internal/quic/tlsconfig_test.go new file mode 100644 index 000000000..47bfb0598 --- /dev/null +++ b/internal/quic/tlsconfig_test.go @@ -0,0 +1,62 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "crypto/tls" + "strings" +) + +func newTestTLSConfig(side connSide) *tls.Config { + config := &tls.Config{ + InsecureSkipVerify: true, + CipherSuites: []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + }, + MinVersion: tls.VersionTLS13, + } + if side == serverSide { + config.Certificates = []tls.Certificate{testCert} + } + return config +} + +var testCert = func() tls.Certificate { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + panic(err) + } + return cert +}() + +// localhostCert is a PEM-encoded TLS cert with SAN IPs +// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT. +// generated from src/crypto/tls: +// go run generate_cert.go --ecdsa-curve P256 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var localhostCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBrDCCAVKgAwIBAgIPCvPhO+Hfv+NW76kWxULUMAoGCCqGSM49BAMCMBIxEDAO +BgNVBAoTB0FjbWUgQ28wIBcNNzAwMTAxMDAwMDAwWhgPMjA4NDAxMjkxNjAwMDBa +MBIxEDAOBgNVBAoTB0FjbWUgQ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARh +WRF8p8X9scgW7JjqAwI9nYV8jtkdhqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGms +PyfMPe5Jrha/LmjgR1G9o4GIMIGFMA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAK +BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSOJri/wLQxq6oC +Y6ZImms/STbTljAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAA +AAAAAAAAAAAAATAKBggqhkjOPQQDAgNIADBFAiBUguxsW6TGhixBAdORmVNnkx40 +HjkKwncMSDbUaeL9jQIhAJwQ8zV9JpQvYpsiDuMmqCuW35XXil3cQ6Drz82c+fvE +-----END CERTIFICATE-----`) + +// localhostKey is the private key for localhostCert. +var localhostKey = []byte(testingKey(`-----BEGIN TESTING KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgY1B1eL/Bbwf/MDcs +rnvvWhFNr1aGmJJR59PdCN9lVVqhRANCAARhWRF8p8X9scgW7JjqAwI9nYV8jtkd +hqAXG9gyEgnaFNN5Ze9l3Tp1R9yCDBMNsGmsPyfMPe5Jrha/LmjgR1G9 +-----END TESTING KEY-----`)) + +// testingKey helps keep security scanners from getting excited about a private key in this file. +func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } diff --git a/internal/quic/transport_params.go b/internal/quic/transport_params.go new file mode 100644 index 000000000..dc76d1650 --- /dev/null +++ b/internal/quic/transport_params.go @@ -0,0 +1,283 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "encoding/binary" + "net/netip" + "time" +) + +// transportParameters transferred in the quic_transport_parameters TLS extension. +// https://www.rfc-editor.org/rfc/rfc9000.html#section-18.2 +type transportParameters struct { + originalDstConnID []byte + maxIdleTimeout time.Duration + statelessResetToken []byte + maxUDPPayloadSize int64 + initialMaxData int64 + initialMaxStreamDataBidiLocal int64 + initialMaxStreamDataBidiRemote int64 + initialMaxStreamDataUni int64 + initialMaxStreamsBidi int64 + initialMaxStreamsUni int64 + ackDelayExponent int8 + maxAckDelay time.Duration + disableActiveMigration bool + preferredAddrV4 netip.AddrPort + preferredAddrV6 netip.AddrPort + preferredAddrConnID []byte + preferredAddrResetToken []byte + activeConnIDLimit int64 + initialSrcConnID []byte + retrySrcConnID []byte +} + +const ( + defaultParamMaxUDPPayloadSize = 65527 + defaultParamAckDelayExponent = 3 + defaultParamMaxAckDelayMilliseconds = 25 + defaultParamActiveConnIDLimit = 2 +) + +// defaultTransportParameters is initialized to the RFC 9000 default values. +func defaultTransportParameters() transportParameters { + return transportParameters{ + maxUDPPayloadSize: defaultParamMaxUDPPayloadSize, + ackDelayExponent: defaultParamAckDelayExponent, + maxAckDelay: defaultParamMaxAckDelayMilliseconds * time.Millisecond, + activeConnIDLimit: defaultParamActiveConnIDLimit, + } +} + +const ( + paramOriginalDestinationConnectionID = 0x00 + paramMaxIdleTimeout = 0x01 + paramStatelessResetToken = 0x02 + paramMaxUDPPayloadSize = 0x03 + paramInitialMaxData = 0x04 + paramInitialMaxStreamDataBidiLocal = 0x05 + paramInitialMaxStreamDataBidiRemote = 0x06 + paramInitialMaxStreamDataUni = 0x07 + paramInitialMaxStreamsBidi = 0x08 + paramInitialMaxStreamsUni = 0x09 + paramAckDelayExponent = 0x0a + paramMaxAckDelay = 0x0b + paramDisableActiveMigration = 0x0c + paramPreferredAddress = 0x0d + paramActiveConnectionIDLimit = 0x0e + paramInitialSourceConnectionID = 0x0f + paramRetrySourceConnectionID = 0x10 +) + +func marshalTransportParameters(p transportParameters) []byte { + var b []byte + if v := p.originalDstConnID; v != nil { + b = appendVarint(b, paramOriginalDestinationConnectionID) + b = appendVarintBytes(b, v) + } + if v := uint64(p.maxIdleTimeout / time.Millisecond); v != 0 { + b = appendVarint(b, paramMaxIdleTimeout) + b = appendVarint(b, uint64(sizeVarint(v))) + b = appendVarint(b, uint64(v)) + } + if v := p.statelessResetToken; v != nil { + b = appendVarint(b, paramStatelessResetToken) + b = appendVarintBytes(b, v) + } + if v := p.maxUDPPayloadSize; v != defaultParamMaxUDPPayloadSize { + b = appendVarint(b, paramMaxUDPPayloadSize) + b = appendVarint(b, uint64(sizeVarint(uint64(v)))) + b = appendVarint(b, uint64(v)) + } + if v := p.initialMaxData; v != 0 { + b = appendVarint(b, paramInitialMaxData) + b = appendVarint(b, uint64(sizeVarint(uint64(v)))) + b = appendVarint(b, uint64(v)) + } + if v := p.initialMaxStreamDataBidiLocal; v != 0 { + b = appendVarint(b, paramInitialMaxStreamDataBidiLocal) + b = appendVarint(b, uint64(sizeVarint(uint64(v)))) + b = appendVarint(b, uint64(v)) + } + if v := p.initialMaxStreamDataBidiRemote; v != 0 { + b = appendVarint(b, paramInitialMaxStreamDataBidiRemote) + b = appendVarint(b, uint64(sizeVarint(uint64(v)))) + b = appendVarint(b, uint64(v)) + } + if v := p.initialMaxStreamDataUni; v != 0 { + b = appendVarint(b, paramInitialMaxStreamDataUni) + b = appendVarint(b, uint64(sizeVarint(uint64(v)))) + b = appendVarint(b, uint64(v)) + } + if v := p.initialMaxStreamsBidi; v != 0 { + b = appendVarint(b, paramInitialMaxStreamsBidi) + b = appendVarint(b, uint64(sizeVarint(uint64(v)))) + b = appendVarint(b, uint64(v)) + } + if v := p.initialMaxStreamsUni; v != 0 { + b = appendVarint(b, paramInitialMaxStreamsUni) + b = appendVarint(b, uint64(sizeVarint(uint64(v)))) + b = appendVarint(b, uint64(v)) + } + if v := p.ackDelayExponent; v != defaultParamAckDelayExponent { + b = appendVarint(b, paramAckDelayExponent) + b = appendVarint(b, uint64(sizeVarint(uint64(v)))) + b = appendVarint(b, uint64(v)) + } + if v := uint64(p.maxAckDelay / time.Millisecond); v != defaultParamMaxAckDelayMilliseconds { + b = appendVarint(b, paramMaxAckDelay) + b = appendVarint(b, uint64(sizeVarint(v))) + b = appendVarint(b, v) + } + if p.disableActiveMigration { + b = appendVarint(b, paramDisableActiveMigration) + b = append(b, 0) // 0-length value + } + if p.preferredAddrConnID != nil { + b = append(b, paramPreferredAddress) + b = appendVarint(b, uint64(4+2+16+2+1+len(p.preferredAddrConnID)+16)) + b = append(b, p.preferredAddrV4.Addr().AsSlice()...) // 4 bytes + b = binary.BigEndian.AppendUint16(b, p.preferredAddrV4.Port()) // 2 bytes + b = append(b, p.preferredAddrV6.Addr().AsSlice()...) // 16 bytes + b = binary.BigEndian.AppendUint16(b, p.preferredAddrV6.Port()) // 2 bytes + b = appendUint8Bytes(b, p.preferredAddrConnID) // 1 byte + len(conn_id) + b = append(b, p.preferredAddrResetToken...) // 16 bytes + } + if v := p.activeConnIDLimit; v != defaultParamActiveConnIDLimit { + b = appendVarint(b, paramActiveConnectionIDLimit) + b = appendVarint(b, uint64(sizeVarint(uint64(v)))) + b = appendVarint(b, uint64(v)) + } + if v := p.initialSrcConnID; v != nil { + b = appendVarint(b, paramInitialSourceConnectionID) + b = appendVarintBytes(b, v) + } + if v := p.retrySrcConnID; v != nil { + b = appendVarint(b, paramRetrySourceConnectionID) + b = appendVarintBytes(b, v) + } + return b +} + +func unmarshalTransportParams(params []byte) (transportParameters, error) { + p := defaultTransportParameters() + for len(params) > 0 { + id, n := consumeVarint(params) + if n < 0 { + return p, localTransportError(errTransportParameter) + } + params = params[n:] + val, n := consumeVarintBytes(params) + if n < 0 { + return p, localTransportError(errTransportParameter) + } + params = params[n:] + n = 0 + switch id { + case paramOriginalDestinationConnectionID: + p.originalDstConnID = val + n = len(val) + case paramMaxIdleTimeout: + var v uint64 + v, n = consumeVarint(val) + // If this is unreasonably large, consider it as no timeout to avoid + // time.Duration overflows. + if v > 1<<32 { + v = 0 + } + p.maxIdleTimeout = time.Duration(v) * time.Millisecond + case paramStatelessResetToken: + if len(val) != 16 { + return p, localTransportError(errTransportParameter) + } + p.statelessResetToken = val + n = 16 + case paramMaxUDPPayloadSize: + p.maxUDPPayloadSize, n = consumeVarintInt64(val) + if p.maxUDPPayloadSize < 1200 { + return p, localTransportError(errTransportParameter) + } + case paramInitialMaxData: + p.initialMaxData, n = consumeVarintInt64(val) + case paramInitialMaxStreamDataBidiLocal: + p.initialMaxStreamDataBidiLocal, n = consumeVarintInt64(val) + case paramInitialMaxStreamDataBidiRemote: + p.initialMaxStreamDataBidiRemote, n = consumeVarintInt64(val) + case paramInitialMaxStreamDataUni: + p.initialMaxStreamDataUni, n = consumeVarintInt64(val) + case paramInitialMaxStreamsBidi: + p.initialMaxStreamsBidi, n = consumeVarintInt64(val) + if p.initialMaxStreamsBidi > maxStreamsLimit { + return p, localTransportError(errTransportParameter) + } + case paramInitialMaxStreamsUni: + p.initialMaxStreamsUni, n = consumeVarintInt64(val) + if p.initialMaxStreamsUni > maxStreamsLimit { + return p, localTransportError(errTransportParameter) + } + case paramAckDelayExponent: + var v uint64 + v, n = consumeVarint(val) + if v > 20 { + return p, localTransportError(errTransportParameter) + } + p.ackDelayExponent = int8(v) + case paramMaxAckDelay: + var v uint64 + v, n = consumeVarint(val) + if v >= 1<<14 { + return p, localTransportError(errTransportParameter) + } + p.maxAckDelay = time.Duration(v) * time.Millisecond + case paramDisableActiveMigration: + p.disableActiveMigration = true + case paramPreferredAddress: + if len(val) < 4+2+16+2+1 { + return p, localTransportError(errTransportParameter) + } + p.preferredAddrV4 = netip.AddrPortFrom( + netip.AddrFrom4(*(*[4]byte)(val[:4])), + binary.BigEndian.Uint16(val[4:][:2]), + ) + val = val[4+2:] + p.preferredAddrV6 = netip.AddrPortFrom( + netip.AddrFrom16(*(*[16]byte)(val[:16])), + binary.BigEndian.Uint16(val[16:][:2]), + ) + val = val[16+2:] + var nn int + p.preferredAddrConnID, nn = consumeUint8Bytes(val) + if nn < 0 { + return p, localTransportError(errTransportParameter) + } + val = val[nn:] + if len(val) != 16 { + return p, localTransportError(errTransportParameter) + } + p.preferredAddrResetToken = val + val = nil + case paramActiveConnectionIDLimit: + p.activeConnIDLimit, n = consumeVarintInt64(val) + if p.activeConnIDLimit < 2 { + return p, localTransportError(errTransportParameter) + } + case paramInitialSourceConnectionID: + p.initialSrcConnID = val + n = len(val) + case paramRetrySourceConnectionID: + p.retrySrcConnID = val + n = len(val) + default: + n = len(val) + } + if n != len(val) { + return p, localTransportError(errTransportParameter) + } + } + return p, nil +} diff --git a/internal/quic/transport_params_test.go b/internal/quic/transport_params_test.go new file mode 100644 index 000000000..cc88e83fd --- /dev/null +++ b/internal/quic/transport_params_test.go @@ -0,0 +1,388 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "math" + "net/netip" + "reflect" + "testing" + "time" +) + +func TestTransportParametersMarshalUnmarshal(t *testing.T) { + for _, test := range []struct { + params func(p *transportParameters) + enc []byte + }{{ + params: func(p *transportParameters) { + p.originalDstConnID = []byte("connid") + }, + enc: []byte{ + 0x00, // original_destination_connection_id + byte(len("connid")), + 'c', 'o', 'n', 'n', 'i', 'd', + }, + }, { + params: func(p *transportParameters) { + p.maxIdleTimeout = 10 * time.Millisecond + }, + enc: []byte{ + 0x01, // max_idle_timeout + 1, // length + 10, // varint msecs + }, + }, { + params: func(p *transportParameters) { + p.statelessResetToken = []byte("0123456789abcdef") + }, + enc: []byte{ + 0x02, // stateless_reset_token + 16, // length + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', // reset token + }, + }, { + params: func(p *transportParameters) { + p.maxUDPPayloadSize = 1200 + }, + enc: []byte{ + 0x03, // max_udp_payload_size + 2, // length + 0x44, 0xb0, // varint value + }, + }, { + params: func(p *transportParameters) { + p.initialMaxData = 10 + }, + enc: []byte{ + 0x04, // initial_max_data + 1, // length + 10, // varint value + }, + }, { + params: func(p *transportParameters) { + p.initialMaxStreamDataBidiLocal = 10 + }, + enc: []byte{ + 0x05, // initial_max_stream_data_bidi_local + 1, // length + 10, // varint value + }, + }, { + params: func(p *transportParameters) { + p.initialMaxStreamDataBidiRemote = 10 + }, + enc: []byte{ + 0x06, // initial_max_stream_data_bidi_remote + 1, // length + 10, // varint value + }, + }, { + params: func(p *transportParameters) { + p.initialMaxStreamDataUni = 10 + }, + enc: []byte{ + 0x07, // initial_max_stream_data_uni + 1, // length + 10, // varint value + }, + }, { + params: func(p *transportParameters) { + p.initialMaxStreamsBidi = 10 + }, + enc: []byte{ + 0x08, // initial_max_streams_bidi + 1, // length + 10, // varint value + }, + }, { + params: func(p *transportParameters) { + p.initialMaxStreamsUni = 10 + }, + enc: []byte{ + 0x09, // initial_max_streams_uni + 1, // length + 10, // varint value + }, + }, { + params: func(p *transportParameters) { + p.ackDelayExponent = 4 + }, + enc: []byte{ + 0x0a, // ack_delay_exponent + 1, // length + 4, // varint value + }, + }, { + params: func(p *transportParameters) { + p.maxAckDelay = 10 * time.Millisecond + }, + enc: []byte{ + 0x0b, // max_ack_delay + 1, // length + 10, // varint value + }, + }, { + params: func(p *transportParameters) { + p.disableActiveMigration = true + }, + enc: []byte{ + 0x0c, // disable_active_migration + 0, // length + }, + }, { + params: func(p *transportParameters) { + p.preferredAddrV4 = netip.MustParseAddrPort("127.0.0.1:80") + p.preferredAddrV6 = netip.MustParseAddrPort("[fe80::1]:1024") + p.preferredAddrConnID = []byte("connid") + p.preferredAddrResetToken = []byte("0123456789abcdef") + }, + enc: []byte{ + 0x0d, // preferred_address + byte(4 + 2 + 16 + 2 + 1 + len("connid") + 16), // length + 127, 0, 0, 1, // v4 address + 0, 80, // v4 port + 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // v6 address + 0x04, 0x00, // v6 port, + 6, // connection id length + 'c', 'o', 'n', 'n', 'i', 'd', // connection id + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', // reset token + }, + }, { + params: func(p *transportParameters) { + p.activeConnIDLimit = 10 + }, + enc: []byte{ + 0x0e, // active_connection_id_limit + 1, // length + 10, // varint value + }, + }, { + params: func(p *transportParameters) { + p.initialSrcConnID = []byte("connid") + }, + enc: []byte{ + 0x0f, // initial_source_connection_id + byte(len("connid")), + 'c', 'o', 'n', 'n', 'i', 'd', + }, + }, { + params: func(p *transportParameters) { + p.retrySrcConnID = []byte("connid") + }, + enc: []byte{ + 0x10, // retry_source_connection_id + byte(len("connid")), + 'c', 'o', 'n', 'n', 'i', 'd', + }, + }} { + wantParams := defaultTransportParameters() + test.params(&wantParams) + gotBytes := marshalTransportParameters(wantParams) + if !bytes.Equal(gotBytes, test.enc) { + t.Errorf("marshalTransportParameters(%#v):\n got: %x\nwant: %x", wantParams, gotBytes, test.enc) + } + gotParams, err := unmarshalTransportParams(test.enc) + if err != nil { + t.Errorf("unmarshalTransportParams(%x): unexpected error: %v", test.enc, err) + } else if !reflect.DeepEqual(gotParams, wantParams) { + t.Errorf("unmarshalTransportParams(%x):\n got: %#v\nwant: %#v", test.enc, gotParams, wantParams) + } + } +} + +func TestTransportParametersErrors(t *testing.T) { + for _, test := range []struct { + desc string + enc []byte + }{{ + desc: "invalid id", + enc: []byte{ + 0x40, // too short + }, + }, { + desc: "parameter too short", + enc: []byte{ + 0x00, // original_destination_connection_id + 0x04, // length + 1, 2, 3, // not enough data + }, + }, { + desc: "extra data in parameter", + enc: []byte{ + 0x01, // max_idle_timeout + 2, // length + 10, // varint msecs + 0, // extra junk + }, + }, { + desc: "invalid varint in parameter", + enc: []byte{ + 0x01, // max_idle_timeout + 1, // length + 0x40, // incomplete varint + }, + }, { + desc: "stateless_reset_token not 16 bytes", + enc: []byte{ + 0x02, // stateless_reset_token, + 15, // length + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + }, + }, { + desc: "initial_max_streams_bidi is too large", + enc: []byte{ + 0x08, // initial_max_streams_bidi, + 8, // length, + 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }, + }, { + desc: "initial_max_streams_uni is too large", + enc: []byte{ + 0x08, // initial_max_streams_uni, + 9, // length, + 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + }, + }, { + desc: "preferred_address is too short", + enc: []byte{ + 0x0d, // preferred_address + byte(3), + 127, 0, 0, + }, + }, { + desc: "preferred_address reset token too short", + enc: []byte{ + 0x0d, // preferred_address + byte(4 + 2 + 16 + 2 + 1 + len("connid") + 15), // length + 127, 0, 0, 1, // v4 address + 0, 80, // v4 port + 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // v6 address + 0x04, 0x00, // v6 port, + 6, // connection id length + 'c', 'o', 'n', 'n', 'i', 'd', // connection id + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', // reset token, one byte too short + + }, + }, { + desc: "preferred_address conn id too long", + enc: []byte{ + 0x0d, // preferred_address + byte(4 + 2 + 16 + 2 + 1 + len("connid") + 16), // length + 127, 0, 0, 1, // v4 address + 0, 80, // v4 port + 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // v6 address + 0x04, 0x00, // v6 port, + byte(len("connid")) + 16 + 1, // connection id length, too long + 'c', 'o', 'n', 'n', 'i', 'd', // connection id + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', // reset token + + }, + }} { + _, err := unmarshalTransportParams(test.enc) + if err == nil { + t.Errorf("%v:\nunmarshalTransportParams(%x): unexpectedly succeeded", test.desc, test.enc) + } + } +} + +func TestTransportParametersRangeErrors(t *testing.T) { + for _, test := range []struct { + desc string + params func(p *transportParameters) + }{{ + desc: "max_udp_payload_size < 1200", + params: func(p *transportParameters) { + p.maxUDPPayloadSize = 1199 + }, + }, { + desc: "ack_delay_exponent > 20", + params: func(p *transportParameters) { + p.ackDelayExponent = 21 + }, + }, { + desc: "max_ack_delay > 1^14 ms", + params: func(p *transportParameters) { + p.maxAckDelay = (1 << 14) * time.Millisecond + }, + }, { + desc: "active_connection_id_limit < 2", + params: func(p *transportParameters) { + p.activeConnIDLimit = 1 + }, + }} { + p := defaultTransportParameters() + test.params(&p) + enc := marshalTransportParameters(p) + _, err := unmarshalTransportParams(enc) + if err == nil { + t.Errorf("%v: unmarshalTransportParams unexpectedly succeeded", test.desc) + } + } +} + +func TestTransportParameterMaxIdleTimeoutOverflowsDuration(t *testing.T) { + tooManyMS := 1 + (math.MaxInt64 / uint64(time.Millisecond)) + + var enc []byte + enc = appendVarint(enc, paramMaxIdleTimeout) + enc = appendVarint(enc, uint64(sizeVarint(tooManyMS))) + enc = appendVarint(enc, uint64(tooManyMS)) + + dec, err := unmarshalTransportParams(enc) + if err != nil { + t.Fatalf("unmarshalTransportParameters(enc) = %v", err) + } + if got, want := dec.maxIdleTimeout, time.Duration(0); got != want { + t.Errorf("max_idle_timeout=%v, got maxIdleTimeout=%v; want %v", tooManyMS, got, want) + } +} + +func TestTransportParametersSkipUnknownParameters(t *testing.T) { + enc := []byte{ + 0x20, // unknown transport parameter + 1, // length + 0, // varint value + + 0x04, // initial_max_data + 1, // length + 10, // varint value + + 0x21, // unknown transport parameter + 1, // length + 0, // varint value + } + dec, err := unmarshalTransportParams(enc) + if err != nil { + t.Fatalf("unmarshalTransportParameters(enc) = %v", err) + } + if got, want := dec.initialMaxData, int64(10); got != want { + t.Errorf("got initial_max_data=%v; want %v", got, want) + } +} + +func FuzzTransportParametersMarshalUnmarshal(f *testing.F) { + f.Fuzz(func(t *testing.T, in []byte) { + p1, err := unmarshalTransportParams(in) + if err != nil { + return + } + out := marshalTransportParameters(p1) + p2, err := unmarshalTransportParams(out) + if err != nil { + t.Fatalf("round trip unmarshal/remarshal: unmarshal error: %v\n%x", err, in) + } + if !reflect.DeepEqual(p1, p2) { + t.Fatalf("round trip unmarshal/remarshal: parameters differ:\n%x\n%#v\n%#v", in, p1, p2) + } + }) +} diff --git a/internal/quic/version_test.go b/internal/quic/version_test.go new file mode 100644 index 000000000..cfb7ce4be --- /dev/null +++ b/internal/quic/version_test.go @@ -0,0 +1,110 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "context" + "crypto/tls" + "testing" +) + +func TestVersionNegotiationServerReceivesUnknownVersion(t *testing.T) { + config := &Config{ + TLSConfig: newTestTLSConfig(serverSide), + } + tl := newTestListener(t, config, nil) + + // Packet of unknown contents for some unrecognized QUIC version. + dstConnID := []byte{1, 2, 3, 4} + srcConnID := []byte{5, 6, 7, 8} + pkt := []byte{ + 0b1000_0000, + 0x00, 0x00, 0x00, 0x0f, + } + pkt = append(pkt, byte(len(dstConnID))) + pkt = append(pkt, dstConnID...) + pkt = append(pkt, byte(len(srcConnID))) + pkt = append(pkt, srcConnID...) + for len(pkt) < minimumClientInitialDatagramSize { + pkt = append(pkt, 0) + } + + tl.write(&datagram{ + b: pkt, + }) + gotPkt := tl.read() + if gotPkt == nil { + t.Fatalf("got no response; want Version Negotiaion") + } + if got := getPacketType(gotPkt); got != packetTypeVersionNegotiation { + t.Fatalf("got packet type %v; want Version Negotiaion", got) + } + gotDst, gotSrc, versions := parseVersionNegotiation(gotPkt) + if got, want := gotDst, srcConnID; !bytes.Equal(got, want) { + t.Errorf("got Destination Connection ID %x, want %x", got, want) + } + if got, want := gotSrc, dstConnID; !bytes.Equal(got, want) { + t.Errorf("got Source Connection ID %x, want %x", got, want) + } + if got, want := versions, []byte{0, 0, 0, 1}; !bytes.Equal(got, want) { + t.Errorf("got Supported Version %x, want %x", got, want) + } +} + +func TestVersionNegotiationClientAborts(t *testing.T) { + tc := newTestConn(t, clientSide) + p := tc.readPacket() // client Initial packet + tc.listener.write(&datagram{ + b: appendVersionNegotiation(nil, p.srcConnID, p.dstConnID, 10), + }) + tc.wantIdle("connection does not send a CONNECTION_CLOSE") + if err := tc.conn.waitReady(canceledContext()); err != errVersionNegotiation { + t.Errorf("conn.waitReady() = %v, want errVersionNegotiation", err) + } +} + +func TestVersionNegotiationClientIgnoresAfterProcessingPacket(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.ignoreFrame(frameTypeAck) + p := tc.readPacket() // client Initial packet + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.listener.write(&datagram{ + b: appendVersionNegotiation(nil, p.srcConnID, p.dstConnID, 10), + }) + if err := tc.conn.waitReady(canceledContext()); err != context.Canceled { + t.Errorf("conn.waitReady() = %v, want context.Canceled", err) + } + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + tc.wantFrameType("conn ignores Version Negotiation and continues with handshake", + packetTypeHandshake, debugFrameCrypto{}) +} + +func TestVersionNegotiationClientIgnoresMismatchingSourceConnID(t *testing.T) { + tc := newTestConn(t, clientSide) + tc.ignoreFrame(frameTypeAck) + p := tc.readPacket() // client Initial packet + tc.listener.write(&datagram{ + b: appendVersionNegotiation(nil, p.srcConnID, []byte("mismatch"), 10), + }) + tc.writeFrames(packetTypeInitial, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelInitial], + }) + tc.writeFrames(packetTypeHandshake, + debugFrameCrypto{ + data: tc.cryptoDataIn[tls.QUICEncryptionLevelHandshake], + }) + tc.wantFrameType("conn ignores Version Negotiation and continues with handshake", + packetTypeHandshake, debugFrameCrypto{}) +} diff --git a/internal/quic/wire.go b/internal/quic/wire.go new file mode 100644 index 000000000..848602915 --- /dev/null +++ b/internal/quic/wire.go @@ -0,0 +1,150 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import "encoding/binary" + +const ( + maxVarintSize = 8 // encoded size in bytes + maxVarint = (1 << 62) - 1 +) + +// consumeVarint parses a variable-length integer, reporting its length. +// It returns a negative length upon an error. +// +// https://www.rfc-editor.org/rfc/rfc9000.html#section-16 +func consumeVarint(b []byte) (v uint64, n int) { + if len(b) < 1 { + return 0, -1 + } + b0 := b[0] & 0x3f + switch b[0] >> 6 { + case 0: + return uint64(b0), 1 + case 1: + if len(b) < 2 { + return 0, -1 + } + return uint64(b0)<<8 | uint64(b[1]), 2 + case 2: + if len(b) < 4 { + return 0, -1 + } + return uint64(b0)<<24 | uint64(b[1])<<16 | uint64(b[2])<<8 | uint64(b[3]), 4 + case 3: + if len(b) < 8 { + return 0, -1 + } + return uint64(b0)<<56 | uint64(b[1])<<48 | uint64(b[2])<<40 | uint64(b[3])<<32 | uint64(b[4])<<24 | uint64(b[5])<<16 | uint64(b[6])<<8 | uint64(b[7]), 8 + } + return 0, -1 +} + +// consumeVarint64 parses a variable-length integer as an int64. +func consumeVarintInt64(b []byte) (v int64, n int) { + u, n := consumeVarint(b) + // QUIC varints are 62-bits large, so this conversion can never overflow. + return int64(u), n +} + +// appendVarint appends a variable-length integer to b. +// +// https://www.rfc-editor.org/rfc/rfc9000.html#section-16 +func appendVarint(b []byte, v uint64) []byte { + switch { + case v <= 63: + return append(b, byte(v)) + case v <= 16383: + return append(b, (1<<6)|byte(v>>8), byte(v)) + case v <= 1073741823: + return append(b, (2<<6)|byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) + case v <= 4611686018427387903: + return append(b, (3<<6)|byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) + default: + panic("varint too large") + } +} + +// sizeVarint returns the size of the variable-length integer encoding of f. +func sizeVarint(v uint64) int { + switch { + case v <= 63: + return 1 + case v <= 16383: + return 2 + case v <= 1073741823: + return 4 + case v <= 4611686018427387903: + return 8 + default: + panic("varint too large") + } +} + +// consumeUint32 parses a 32-bit fixed-length, big-endian integer, reporting its length. +// It returns a negative length upon an error. +func consumeUint32(b []byte) (uint32, int) { + if len(b) < 4 { + return 0, -1 + } + return binary.BigEndian.Uint32(b), 4 +} + +// consumeUint64 parses a 64-bit fixed-length, big-endian integer, reporting its length. +// It returns a negative length upon an error. +func consumeUint64(b []byte) (uint64, int) { + if len(b) < 8 { + return 0, -1 + } + return binary.BigEndian.Uint64(b), 8 +} + +// consumeUint8Bytes parses a sequence of bytes prefixed with an 8-bit length, +// reporting the total number of bytes consumed. +// It returns a negative length upon an error. +func consumeUint8Bytes(b []byte) ([]byte, int) { + if len(b) < 1 { + return nil, -1 + } + size := int(b[0]) + const n = 1 + if size > len(b[n:]) { + return nil, -1 + } + return b[n:][:size], size + n +} + +// appendUint8Bytes appends a sequence of bytes prefixed by an 8-bit length. +func appendUint8Bytes(b, v []byte) []byte { + if len(v) > 0xff { + panic("uint8-prefixed bytes too large") + } + b = append(b, uint8(len(v))) + b = append(b, v...) + return b +} + +// consumeVarintBytes parses a sequence of bytes preceded by a variable-length integer length, +// reporting the total number of bytes consumed. +// It returns a negative length upon an error. +func consumeVarintBytes(b []byte) ([]byte, int) { + size, n := consumeVarint(b) + if n < 0 { + return nil, -1 + } + if size > uint64(len(b[n:])) { + return nil, -1 + } + return b[n:][:size], int(size) + n +} + +// appendVarintBytes appends a sequence of bytes prefixed by a variable-length integer length. +func appendVarintBytes(b, v []byte) []byte { + b = appendVarint(b, uint64(len(v))) + b = append(b, v...) + return b +} diff --git a/internal/quic/wire_test.go b/internal/quic/wire_test.go new file mode 100644 index 000000000..379da0d34 --- /dev/null +++ b/internal/quic/wire_test.go @@ -0,0 +1,225 @@ +// Copyright 2023 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. + +//go:build go1.21 + +package quic + +import ( + "bytes" + "testing" +) + +func TestConsumeVarint(t *testing.T) { + for _, test := range []struct { + b []byte + want uint64 + wantLen int + }{ + {[]byte{0x00}, 0, 1}, + {[]byte{0x3f}, 63, 1}, + {[]byte{0x40, 0x00}, 0, 2}, + {[]byte{0x7f, 0xff}, 16383, 2}, + {[]byte{0x80, 0x00, 0x00, 0x00}, 0, 4}, + {[]byte{0xbf, 0xff, 0xff, 0xff}, 1073741823, 4}, + {[]byte{0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 0, 8}, + {[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 4611686018427387903, 8}, + // Example cases from https://www.rfc-editor.org/rfc/rfc9000.html#section-a.1 + {[]byte{0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c}, 151288809941952652, 8}, + {[]byte{0x9d, 0x7f, 0x3e, 0x7d}, 494878333, 4}, + {[]byte{0x7b, 0xbd}, 15293, 2}, + {[]byte{0x25}, 37, 1}, + {[]byte{0x40, 0x25}, 37, 2}, + } { + got, gotLen := consumeVarint(test.b) + if got != test.want || gotLen != test.wantLen { + t.Errorf("consumeVarint(%x) = %v, %v; want %v, %v", test.b, got, gotLen, test.want, test.wantLen) + } + // Extra data in the buffer is ignored. + b := append(test.b, 0) + got, gotLen = consumeVarint(b) + if got != test.want || gotLen != test.wantLen { + t.Errorf("consumeVarint(%x) = %v, %v; want %v, %v", b, got, gotLen, test.want, test.wantLen) + } + // Short buffer results in an error. + for i := 1; i <= len(test.b); i++ { + b = test.b[:len(test.b)-i] + got, gotLen = consumeVarint(b) + if got != 0 || gotLen >= 0 { + t.Errorf("consumeVarint(%x) = %v, %v; want 0, -1", b, got, gotLen) + } + } + } +} + +func TestAppendVarint(t *testing.T) { + for _, test := range []struct { + v uint64 + want []byte + }{ + {0, []byte{0x00}}, + {63, []byte{0x3f}}, + {16383, []byte{0x7f, 0xff}}, + {1073741823, []byte{0xbf, 0xff, 0xff, 0xff}}, + {4611686018427387903, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}, + // Example cases from https://www.rfc-editor.org/rfc/rfc9000.html#section-a.1 + {151288809941952652, []byte{0xc2, 0x19, 0x7c, 0x5e, 0xff, 0x14, 0xe8, 0x8c}}, + {494878333, []byte{0x9d, 0x7f, 0x3e, 0x7d}}, + {15293, []byte{0x7b, 0xbd}}, + {37, []byte{0x25}}, + } { + got := appendVarint([]byte{}, test.v) + if !bytes.Equal(got, test.want) { + t.Errorf("AppendVarint(nil, %v) = %x, want %x", test.v, got, test.want) + } + if gotLen, wantLen := sizeVarint(test.v), len(got); gotLen != wantLen { + t.Errorf("SizeVarint(%v) = %v, want %v", test.v, gotLen, wantLen) + } + } +} + +func TestConsumeUint32(t *testing.T) { + for _, test := range []struct { + b []byte + want uint32 + wantLen int + }{ + {[]byte{0x01, 0x02, 0x03, 0x04}, 0x01020304, 4}, + {[]byte{0x01, 0x02, 0x03}, 0, -1}, + } { + if got, n := consumeUint32(test.b); got != test.want || n != test.wantLen { + t.Errorf("consumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen) + } + } +} + +func TestConsumeUint64(t *testing.T) { + for _, test := range []struct { + b []byte + want uint64 + wantLen int + }{ + {[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, 0x0102030405060708, 8}, + {[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, 0, -1}, + } { + if got, n := consumeUint64(test.b); got != test.want || n != test.wantLen { + t.Errorf("consumeUint32(%x) = %v, %v; want %v, %v", test.b, got, n, test.want, test.wantLen) + } + } +} + +func TestConsumeVarintBytes(t *testing.T) { + for _, test := range []struct { + b []byte + want []byte + wantLen int + }{ + {[]byte{0x00}, []byte{}, 1}, + {[]byte{0x40, 0x00}, []byte{}, 2}, + {[]byte{0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 5}, + {[]byte{0x40, 0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 6}, + } { + got, gotLen := consumeVarintBytes(test.b) + if !bytes.Equal(got, test.want) || gotLen != test.wantLen { + t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen) + } + // Extra data in the buffer is ignored. + b := append(test.b, 0) + got, gotLen = consumeVarintBytes(b) + if !bytes.Equal(got, test.want) || gotLen != test.wantLen { + t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen) + } + // Short buffer results in an error. + for i := 1; i <= len(test.b); i++ { + b = test.b[:len(test.b)-i] + got, gotLen := consumeVarintBytes(b) + if len(got) > 0 || gotLen > 0 { + t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen) + } + } + + } +} + +func TestConsumeVarintBytesErrors(t *testing.T) { + for _, b := range [][]byte{ + {0x01}, + {0x40, 0x01}, + } { + got, gotLen := consumeVarintBytes(b) + if len(got) > 0 || gotLen > 0 { + t.Errorf("consumeVarintBytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen) + } + } +} + +func TestConsumeUint8Bytes(t *testing.T) { + for _, test := range []struct { + b []byte + want []byte + wantLen int + }{ + {[]byte{0x00}, []byte{}, 1}, + {[]byte{0x01, 0x00}, []byte{0x00}, 2}, + {[]byte{0x04, 0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}, 5}, + } { + got, gotLen := consumeUint8Bytes(test.b) + if !bytes.Equal(got, test.want) || gotLen != test.wantLen { + t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", test.b, got, gotLen, test.want, test.wantLen) + } + // Extra data in the buffer is ignored. + b := append(test.b, 0) + got, gotLen = consumeUint8Bytes(b) + if !bytes.Equal(got, test.want) || gotLen != test.wantLen { + t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {%x}, %v", b, got, gotLen, test.want, test.wantLen) + } + // Short buffer results in an error. + for i := 1; i <= len(test.b); i++ { + b = test.b[:len(test.b)-i] + got, gotLen := consumeUint8Bytes(b) + if len(got) > 0 || gotLen > 0 { + t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen) + } + } + + } +} + +func TestConsumeUint8BytesErrors(t *testing.T) { + for _, b := range [][]byte{ + {0x01}, + {0x04, 0x01, 0x02, 0x03}, + } { + got, gotLen := consumeUint8Bytes(b) + if len(got) > 0 || gotLen > 0 { + t.Errorf("consumeUint8Bytes(%x) = {%x}, %v; want {}, -1", b, got, gotLen) + } + } +} + +func TestAppendUint8Bytes(t *testing.T) { + var got []byte + got = appendUint8Bytes(got, []byte{}) + got = appendUint8Bytes(got, []byte{0xaa, 0xbb}) + want := []byte{ + 0x00, + 0x02, 0xaa, 0xbb, + } + if !bytes.Equal(got, want) { + t.Errorf("appendUint8Bytes {}, {aabb} = {%x}; want {%x}", got, want) + } +} + +func TestAppendVarintBytes(t *testing.T) { + var got []byte + got = appendVarintBytes(got, []byte{}) + got = appendVarintBytes(got, []byte{0xaa, 0xbb}) + want := []byte{ + 0x00, + 0x02, 0xaa, 0xbb, + } + if !bytes.Equal(got, want) { + t.Errorf("appendVarintBytes {}, {aabb} = {%x}; want {%x}", got, want) + } +} diff --git a/internal/socks/socks.go b/internal/socks/socks.go index 97db2340e..84fcc32b6 100644 --- a/internal/socks/socks.go +++ b/internal/socks/socks.go @@ -289,7 +289,7 @@ func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, case AuthMethodNotRequired: return nil case AuthMethodUsernamePassword: - if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) == 0 || len(up.Password) > 255 { + if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) > 255 { return errors.New("invalid username/password") } b := []byte{authUsernamePasswordVersion} diff --git a/ipv4/export_test.go b/ipv4/export_test.go new file mode 100644 index 000000000..c2229e732 --- /dev/null +++ b/ipv4/export_test.go @@ -0,0 +1,7 @@ +// Copyright 2023 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 ipv4 + +var ErrNotImplemented = errNotImplemented diff --git a/ipv4/helper_posix_test.go b/ipv4/helper_posix_test.go index 40f432c51..4f6ecc0fd 100644 --- a/ipv4/helper_posix_test.go +++ b/ipv4/helper_posix_test.go @@ -8,8 +8,11 @@ package ipv4_test import ( + "errors" "os" "syscall" + + "golang.org/x/net/ipv4" ) func protocolNotSupported(err error) bool { @@ -28,5 +31,5 @@ func protocolNotSupported(err error) bool { } } } - return false + return errors.Is(err, ipv4.ErrNotImplemented) } diff --git a/ipv4/multicast_test.go b/ipv4/multicast_test.go index 09463bff0..ddd85def6 100644 --- a/ipv4/multicast_test.go +++ b/ipv4/multicast_test.go @@ -11,7 +11,6 @@ import ( "os" "runtime" "testing" - "time" "golang.org/x/net/icmp" "golang.org/x/net/internal/iana" @@ -30,7 +29,7 @@ var packetConnReadWriteMulticastUDPTests = []struct { func TestPacketConnReadWriteMulticastUDP(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "illumos", "js", "nacl", "plan9", "solaris", "windows", "zos": + case "fuchsia", "hurd", "illumos", "js", "nacl", "plan9", "solaris", "wasip1", "windows", "zos": t.Skipf("not supported on %s", runtime.GOOS) } ifi, err := nettest.RoutedInterface("ip4", net.FlagUp|net.FlagMulticast|net.FlagLoopback) @@ -100,9 +99,6 @@ func TestPacketConnReadWriteMulticastUDP(t *testing.T) { } t.Fatal(err) } - if err := p.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil { - t.Fatal(err) - } if err := p.SetMulticastTTL(i + 1); err != nil { t.Fatal(err) } @@ -131,10 +127,6 @@ var packetConnReadWriteMulticastICMPTests = []struct { } func TestPacketConnReadWriteMulticastICMP(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "illumos", "js", "nacl", "plan9", "solaris", "windows": - t.Skipf("not supported on %s", runtime.GOOS) - } if !nettest.SupportsRawSocket() { t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH) } @@ -221,9 +213,6 @@ func TestPacketConnReadWriteMulticastICMP(t *testing.T) { } t.Fatal(err) } - if err := p.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil { - t.Fatal(err) - } if err := p.SetMulticastTTL(i + 1); err != nil { t.Fatal(err) } @@ -261,10 +250,6 @@ var rawConnReadWriteMulticastICMPTests = []struct { } func TestRawConnReadWriteMulticastICMP(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "illumos", "js", "nacl", "plan9", "solaris", "windows": - t.Skipf("not supported on %s", runtime.GOOS) - } if testing.Short() { t.Skip("to avoid external network") } @@ -345,9 +330,6 @@ func TestRawConnReadWriteMulticastICMP(t *testing.T) { } t.Fatal(err) } - if err := r.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil { - t.Fatal(err) - } r.SetMulticastTTL(i + 1) if err := r.WriteTo(wh, wb, nil); err != nil { t.Fatal(err) diff --git a/ipv4/multicastlistener_test.go b/ipv4/multicastlistener_test.go index 77bad6676..906964682 100644 --- a/ipv4/multicastlistener_test.go +++ b/ipv4/multicastlistener_test.go @@ -21,7 +21,7 @@ var udpMultipleGroupListenerTests = []net.Addr{ func TestUDPSinglePacketConnWithMultipleGroupListeners(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows", "zos": t.Skipf("not supported on %s", runtime.GOOS) } if testing.Short() { @@ -61,7 +61,7 @@ func TestUDPSinglePacketConnWithMultipleGroupListeners(t *testing.T) { func TestUDPMultiplePacketConnWithMultipleGroupListeners(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows", "zos": t.Skipf("not supported on %s", runtime.GOOS) } if testing.Short() { @@ -116,7 +116,7 @@ func TestUDPMultiplePacketConnWithMultipleGroupListeners(t *testing.T) { func TestUDPPerInterfaceSinglePacketConnWithSingleGroupListener(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows", "zos": t.Skipf("not supported on %s", runtime.GOOS) } if testing.Short() { @@ -171,10 +171,6 @@ func TestUDPPerInterfaceSinglePacketConnWithSingleGroupListener(t *testing.T) { } func TestIPSingleRawConnWithSingleGroupListener(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": - t.Skipf("not supported on %s", runtime.GOOS) - } if testing.Short() { t.Skip("to avoid external network") } @@ -216,10 +212,6 @@ func TestIPSingleRawConnWithSingleGroupListener(t *testing.T) { } func TestIPPerInterfaceSingleRawConnWithSingleGroupListener(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": - t.Skipf("not supported on %s", runtime.GOOS) - } if testing.Short() { t.Skip("to avoid external network") } diff --git a/ipv4/multicastsockopt_test.go b/ipv4/multicastsockopt_test.go index b0ddd0716..ddf9e6023 100644 --- a/ipv4/multicastsockopt_test.go +++ b/ipv4/multicastsockopt_test.go @@ -26,7 +26,7 @@ var packetConnMulticastSocketOptionTests = []struct { func TestPacketConnMulticastSocketOptions(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "zos": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "zos": t.Skipf("not supported on %s", runtime.GOOS) } ifi, err := nettest.RoutedInterface("ip4", net.FlagUp|net.FlagMulticast|net.FlagLoopback) @@ -65,10 +65,6 @@ var rawConnMulticastSocketOptionTests = []struct { } func TestRawConnMulticastSocketOptions(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "zos": - t.Skipf("not supported on %s", runtime.GOOS) - } if !nettest.SupportsRawSocket() { t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH) } diff --git a/ipv4/readwrite_test.go b/ipv4/readwrite_test.go index 27aaa7bcc..28bd22d45 100644 --- a/ipv4/readwrite_test.go +++ b/ipv4/readwrite_test.go @@ -21,7 +21,7 @@ import ( func BenchmarkReadWriteUnicast(b *testing.B) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": b.Skipf("not supported on %s", runtime.GOOS) } @@ -69,7 +69,7 @@ func BenchmarkReadWriteUnicast(b *testing.B) { func BenchmarkPacketConnReadWriteUnicast(b *testing.B) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": b.Skipf("not supported on %s", runtime.GOOS) } @@ -220,7 +220,7 @@ func BenchmarkPacketConnReadWriteUnicast(b *testing.B) { func TestPacketConnConcurrentReadWriteUnicastUDP(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } @@ -324,7 +324,7 @@ func TestPacketConnConcurrentReadWriteUnicastUDP(t *testing.T) { func TestPacketConnConcurrentReadWriteUnicast(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } diff --git a/ipv4/unicast_test.go b/ipv4/unicast_test.go index 12bbdb41f..2e55f2e5b 100644 --- a/ipv4/unicast_test.go +++ b/ipv4/unicast_test.go @@ -20,7 +20,7 @@ import ( func TestPacketConnReadWriteUnicastUDP(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } // Skip this check on z/OS since net.Interfaces() does not return loopback, however @@ -50,9 +50,6 @@ func TestPacketConnReadWriteUnicastUDP(t *testing.T) { t.Fatal(err) } p.SetTTL(i + 1) - if err := p.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - t.Fatal(err) - } backoff := time.Millisecond for { @@ -72,9 +69,6 @@ func TestPacketConnReadWriteUnicastUDP(t *testing.T) { } rb := make([]byte, 128) - if err := p.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - t.Fatal(err) - } if n, _, _, err := p.ReadFrom(rb); err != nil { t.Fatal(err) } else if !bytes.Equal(rb[:n], wb) { @@ -84,10 +78,6 @@ func TestPacketConnReadWriteUnicastUDP(t *testing.T) { } func TestPacketConnReadWriteUnicastICMP(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": - t.Skipf("not supported on %s", runtime.GOOS) - } if !nettest.SupportsRawSocket() { t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH) } @@ -134,9 +124,6 @@ func TestPacketConnReadWriteUnicastICMP(t *testing.T) { t.Fatal(err) } p.SetTTL(i + 1) - if err := p.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - t.Fatal(err) - } backoff := time.Millisecond for { @@ -157,9 +144,6 @@ func TestPacketConnReadWriteUnicastICMP(t *testing.T) { rb := make([]byte, 128) loop: - if err := p.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - t.Fatal(err) - } if n, _, _, err := p.ReadFrom(rb); err != nil { t.Fatal(err) } else { @@ -179,10 +163,6 @@ func TestPacketConnReadWriteUnicastICMP(t *testing.T) { } func TestRawConnReadWriteUnicastICMP(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": - t.Skipf("not supported on %s", runtime.GOOS) - } if !nettest.SupportsRawSocket() { t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH) } @@ -234,17 +214,11 @@ func TestRawConnReadWriteUnicastICMP(t *testing.T) { } t.Fatal(err) } - if err := r.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - t.Fatal(err) - } if err := r.WriteTo(wh, wb, nil); err != nil { t.Fatal(err) } rb := make([]byte, ipv4.HeaderLen+128) loop: - if err := r.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - t.Fatal(err) - } if _, b, _, err := r.ReadFrom(rb); err != nil { t.Fatal(err) } else { diff --git a/ipv4/unicastsockopt_test.go b/ipv4/unicastsockopt_test.go index 58d653eac..837baba74 100644 --- a/ipv4/unicastsockopt_test.go +++ b/ipv4/unicastsockopt_test.go @@ -16,7 +16,7 @@ import ( func TestConnUnicastSocketOptions(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows", "zos": t.Skipf("not supported on %s", runtime.GOOS) } if _, err := nettest.RoutedInterface("ip4", net.FlagUp|net.FlagLoopback); err != nil { @@ -61,7 +61,7 @@ var packetConnUnicastSocketOptionTests = []struct { func TestPacketConnUnicastSocketOptions(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows", "zos": t.Skipf("not supported on %s", runtime.GOOS) } if _, err := nettest.RoutedInterface("ip4", net.FlagUp|net.FlagLoopback); err != nil { @@ -85,10 +85,6 @@ func TestPacketConnUnicastSocketOptions(t *testing.T) { } func TestRawConnUnicastSocketOptions(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": - t.Skipf("not supported on %s", runtime.GOOS) - } if !nettest.SupportsRawSocket() { t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH) } diff --git a/ipv6/export_test.go b/ipv6/export_test.go new file mode 100644 index 000000000..a506cb38d --- /dev/null +++ b/ipv6/export_test.go @@ -0,0 +1,7 @@ +// Copyright 2023 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 ipv6 + +var ErrNotImplemented = errNotImplemented diff --git a/ipv6/helper_posix_test.go b/ipv6/helper_posix_test.go index ab561f34f..8ca6a3c3c 100644 --- a/ipv6/helper_posix_test.go +++ b/ipv6/helper_posix_test.go @@ -8,8 +8,11 @@ package ipv6_test import ( + "errors" "os" "syscall" + + "golang.org/x/net/ipv6" ) func protocolNotSupported(err error) bool { @@ -28,5 +31,5 @@ func protocolNotSupported(err error) bool { } } } - return false + return errors.Is(err, ipv6.ErrNotImplemented) } diff --git a/ipv6/icmp_test.go b/ipv6/icmp_test.go index 3652067d8..9d8d68127 100644 --- a/ipv6/icmp_test.go +++ b/ipv6/icmp_test.go @@ -5,6 +5,7 @@ package ipv6_test import ( + "errors" "net" "reflect" "runtime" @@ -34,7 +35,7 @@ func TestICMPString(t *testing.T) { func TestICMPFilter(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } @@ -60,10 +61,6 @@ func TestICMPFilter(t *testing.T) { } func TestSetICMPFilter(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": - t.Skipf("not supported on %s", runtime.GOOS) - } if !nettest.SupportsIPv6() { t.Skip("ipv6 is not supported") } @@ -83,9 +80,12 @@ func TestSetICMPFilter(t *testing.T) { f.SetAll(true) f.Accept(ipv6.ICMPTypeEchoRequest) f.Accept(ipv6.ICMPTypeEchoReply) - if err := p.SetICMPFilter(&f); err != nil { + if err := p.SetICMPFilter(&f); errors.Is(err, ipv6.ErrNotImplemented) { + t.Skipf("setting ICMP filter not supported: %v", err) + } else if err != nil { t.Fatal(err) } + kf, err := p.ICMPFilter() if err != nil { t.Fatal(err) diff --git a/ipv6/multicast_test.go b/ipv6/multicast_test.go index 267098a10..0cd1ac4e3 100644 --- a/ipv6/multicast_test.go +++ b/ipv6/multicast_test.go @@ -29,7 +29,7 @@ var packetConnReadWriteMulticastUDPTests = []struct { func TestPacketConnReadWriteMulticastUDP(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } if !nettest.SupportsIPv6() { @@ -144,7 +144,7 @@ func TestPacketConnReadWriteMulticastICMP(t *testing.T) { `and needs investigation, see golang.org/issue/42064`) } switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } if !nettest.SupportsIPv6() { diff --git a/ipv6/multicastlistener_test.go b/ipv6/multicastlistener_test.go index a4dc86342..3daa19838 100644 --- a/ipv6/multicastlistener_test.go +++ b/ipv6/multicastlistener_test.go @@ -21,7 +21,7 @@ var udpMultipleGroupListenerTests = []net.Addr{ func TestUDPSinglePacketConnWithMultipleGroupListeners(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } if !nettest.SupportsIPv6() { @@ -61,7 +61,7 @@ func TestUDPSinglePacketConnWithMultipleGroupListeners(t *testing.T) { func TestUDPMultiplePacketConnWithMultipleGroupListeners(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows", "zos": t.Skipf("not supported on %s", runtime.GOOS) } if !nettest.SupportsIPv6() { @@ -116,7 +116,7 @@ func TestUDPMultiplePacketConnWithMultipleGroupListeners(t *testing.T) { func TestUDPPerInterfaceSinglePacketConnWithSingleGroupListener(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } if !nettest.SupportsIPv6() { @@ -171,10 +171,6 @@ func TestUDPPerInterfaceSinglePacketConnWithSingleGroupListener(t *testing.T) { } func TestIPSinglePacketConnWithSingleGroupListener(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": - t.Skipf("not supported on %s", runtime.GOOS) - } if !nettest.SupportsIPv6() { t.Skip("ipv6 is not supported") } @@ -216,8 +212,6 @@ func TestIPPerInterfaceSinglePacketConnWithSingleGroupListener(t *testing.T) { switch runtime.GOOS { case "darwin", "ios", "dragonfly", "openbsd": // platforms that return fe80::1%lo0: bind: can't assign requested address t.Skipf("not supported on %s", runtime.GOOS) - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": - t.Skipf("not supported on %s", runtime.GOOS) } if !nettest.SupportsIPv6() { t.Skip("ipv6 is not supported") diff --git a/ipv6/multicastsockopt_test.go b/ipv6/multicastsockopt_test.go index d598d08d0..9d4de24c2 100644 --- a/ipv6/multicastsockopt_test.go +++ b/ipv6/multicastsockopt_test.go @@ -26,7 +26,7 @@ var packetConnMulticastSocketOptionTests = []struct { func TestPacketConnMulticastSocketOptions(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } if !nettest.SupportsIPv6() { diff --git a/ipv6/readwrite_test.go b/ipv6/readwrite_test.go index 131b1904c..51c1b8bc4 100644 --- a/ipv6/readwrite_test.go +++ b/ipv6/readwrite_test.go @@ -21,7 +21,7 @@ import ( func BenchmarkReadWriteUnicast(b *testing.B) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": b.Skipf("not supported on %s", runtime.GOOS) } @@ -72,7 +72,7 @@ func BenchmarkReadWriteUnicast(b *testing.B) { func BenchmarkPacketConnReadWriteUnicast(b *testing.B) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": b.Skipf("not supported on %s", runtime.GOOS) } @@ -220,7 +220,7 @@ func BenchmarkPacketConnReadWriteUnicast(b *testing.B) { func TestPacketConnConcurrentReadWriteUnicastUDP(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } ifi, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback) @@ -327,7 +327,7 @@ func TestPacketConnConcurrentReadWriteUnicastUDP(t *testing.T) { func TestPacketConnConcurrentReadWriteUnicast(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } diff --git a/ipv6/sockopt_test.go b/ipv6/sockopt_test.go index ab0d2e4e5..3dc42c139 100644 --- a/ipv6/sockopt_test.go +++ b/ipv6/sockopt_test.go @@ -5,6 +5,7 @@ package ipv6_test import ( + "errors" "fmt" "net" "runtime" @@ -17,7 +18,7 @@ import ( func TestConnInitiatorPathMTU(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": + case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "wasip1", "zos": t.Skipf("not supported on %s", runtime.GOOS) } @@ -51,7 +52,7 @@ func TestConnInitiatorPathMTU(t *testing.T) { func TestConnResponderPathMTU(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": + case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "wasip1", "zos": t.Skipf("not supported on %s", runtime.GOOS) } if _, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback); err != nil { @@ -83,10 +84,6 @@ func TestConnResponderPathMTU(t *testing.T) { } func TestPacketConnChecksum(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": - t.Skipf("not supported on %s", runtime.GOOS) - } if !nettest.SupportsIPv6() { t.Skip("ipv6 is not supported") } @@ -104,7 +101,9 @@ func TestPacketConnChecksum(t *testing.T) { offset := 12 // see RFC 5340 for _, toggle := range []bool{false, true} { - if err := p.SetChecksum(toggle, offset); err != nil { + if err := p.SetChecksum(toggle, offset); errors.Is(err, ipv6.ErrNotImplemented) { + t.Skipf("setting checksum not supported: %v", err) + } else if err != nil { if toggle { t.Fatalf("ipv6.PacketConn.SetChecksum(%v, %v) failed: %v", toggle, offset, err) } else { diff --git a/ipv6/unicast_test.go b/ipv6/unicast_test.go index e03c2cd33..79de14c5a 100644 --- a/ipv6/unicast_test.go +++ b/ipv6/unicast_test.go @@ -6,6 +6,7 @@ package ipv6_test import ( "bytes" + "errors" "net" "os" "runtime" @@ -20,7 +21,7 @@ import ( func TestPacketConnReadWriteUnicastUDP(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } if _, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback); err != nil { @@ -56,9 +57,6 @@ func TestPacketConnReadWriteUnicastUDP(t *testing.T) { t.Fatal(err) } cm.HopLimit = i + 1 - if err := p.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - t.Fatal(err) - } backoff := time.Millisecond for { @@ -78,9 +76,6 @@ func TestPacketConnReadWriteUnicastUDP(t *testing.T) { } rb := make([]byte, 128) - if err := p.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - t.Fatal(err) - } if n, _, _, err := p.ReadFrom(rb); err != nil { t.Fatal(err) } else if !bytes.Equal(rb[:n], wb) { @@ -90,10 +85,6 @@ func TestPacketConnReadWriteUnicastUDP(t *testing.T) { } func TestPacketConnReadWriteUnicastICMP(t *testing.T) { - switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows", "zos": - t.Skipf("not supported on %s", runtime.GOOS) - } if !nettest.SupportsIPv6() { t.Skip("ipv6 is not supported") } @@ -128,7 +119,9 @@ func TestPacketConnReadWriteUnicastICMP(t *testing.T) { var f ipv6.ICMPFilter f.SetAll(true) f.Accept(ipv6.ICMPTypeEchoReply) - if err := p.SetICMPFilter(&f); err != nil { + if err := p.SetICMPFilter(&f); errors.Is(err, ipv6.ErrNotImplemented) { + t.Skipf("setting ICMP filter not supported: %v", err) + } else if err != nil { t.Fatal(err) } @@ -169,9 +162,6 @@ func TestPacketConnReadWriteUnicastICMP(t *testing.T) { t.Fatal(err) } cm.HopLimit = i + 1 - if err := p.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - t.Fatal(err) - } backoff := time.Millisecond for { @@ -191,9 +181,6 @@ func TestPacketConnReadWriteUnicastICMP(t *testing.T) { } rb := make([]byte, 128) - if err := p.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil { - t.Fatal(err) - } if n, _, _, err := p.ReadFrom(rb); err != nil { t.Fatal(err) } else { diff --git a/ipv6/unicastsockopt_test.go b/ipv6/unicastsockopt_test.go index c3abe2d14..bb477ea6a 100644 --- a/ipv6/unicastsockopt_test.go +++ b/ipv6/unicastsockopt_test.go @@ -16,7 +16,7 @@ import ( func TestConnUnicastSocketOptions(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } if _, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback); err != nil { @@ -61,7 +61,7 @@ var packetConnUnicastSocketOptionTests = []struct { func TestPacketConnUnicastSocketOptions(t *testing.T) { switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9", "windows": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1", "windows": t.Skipf("not supported on %s", runtime.GOOS) } if _, err := nettest.RoutedInterface("ip6", net.FlagUp|net.FlagLoopback); err != nil { diff --git a/nettest/nettest.go b/nettest/nettest.go index 510555ac2..3656c3c54 100644 --- a/nettest/nettest.go +++ b/nettest/nettest.go @@ -103,12 +103,12 @@ func TestableNetwork(network string) bool { // This is an internal network name for testing on the // package net of the standard library. switch runtime.GOOS { - case "android", "fuchsia", "hurd", "ios", "js", "nacl", "plan9", "windows": + case "android", "fuchsia", "hurd", "ios", "js", "nacl", "plan9", "wasip1", "windows": return false } case "ip", "ip4", "ip6": switch runtime.GOOS { - case "fuchsia", "hurd", "js", "nacl", "plan9": + case "fuchsia", "hurd", "js", "nacl", "plan9", "wasip1": return false default: if os.Getuid() != 0 { @@ -117,21 +117,15 @@ func TestableNetwork(network string) bool { } case "unix", "unixgram": switch runtime.GOOS { - case "android", "fuchsia", "hurd", "ios", "js", "nacl", "plan9", "windows": + case "android", "fuchsia", "hurd", "ios", "js", "nacl", "plan9", "wasip1", "windows": return false case "aix": return unixStrmDgramEnabled() } case "unixpacket": switch runtime.GOOS { - case "aix", "android", "fuchsia", "hurd", "darwin", "ios", "js", "nacl", "plan9", "windows", "zos": + case "aix", "android", "fuchsia", "hurd", "darwin", "ios", "js", "nacl", "plan9", "wasip1", "windows", "zos": return false - case "netbsd": - // It passes on amd64 at least. 386 fails - // (Issue 22927). arm is unknown. - if runtime.GOARCH == "386" { - return false - } } } switch ss[0] { diff --git a/publicsuffix/data/children b/publicsuffix/data/children index 1038c561a..08261bffd 100644 Binary files a/publicsuffix/data/children and b/publicsuffix/data/children differ diff --git a/publicsuffix/data/nodes b/publicsuffix/data/nodes index 34751cd5b..1dae6ede8 100644 Binary files a/publicsuffix/data/nodes and b/publicsuffix/data/nodes differ diff --git a/publicsuffix/data/text b/publicsuffix/data/text index 124dcd61f..7e516413f 100644 --- a/publicsuffix/data/text +++ b/publicsuffix/data/text @@ -1 +1 @@ -billustrationionjukudoyamakeupowiathletajimageandsoundandvision-riopretobishimagentositecnologiabiocelotenkawabipanasonicatfoodnetworkinggroupperbirdartcenterprisecloudaccesscamdvrcampaniabirkenesoddtangenovarahkkeravjuegoshikikiraraholtalenishikatakazakindependent-revieweirbirthplaceu-1bitbucketrzynishikatsuragirlyuzawabitternidiscoverybjarkoybjerkreimdbaltimore-og-romsdalp1bjugnishikawazukamishihoronobeautydalwaysdatabaseballangenkainanaejrietisalatinabenogatabitorderblackfridaybloombergbauernishimerabloxcms3-website-us-west-2blushakotanishinomiyashironocparachutingjovikarateu-2bmoattachmentsalangenishinoomotegovtattoolforgerockartuzybmsalon-1bmwellbeingzoneu-3bnrwesteuropenairbusantiquesaltdalomzaporizhzhedmarkaratsuginamikatagamilanotairesistanceu-4bondigitaloceanspacesaludishangrilanciabonnishinoshimatsusakahoginankokubunjindianapolis-a-bloggerbookonlinewjerseyboomlahppiacenzachpomorskienishiokoppegardiskussionsbereichattanooganordkapparaglidinglassassinationalheritageu-north-1boschaefflerdalondonetskarelianceu-south-1bostik-serveronagasukevje-og-hornnesalvadordalibabalatinord-aurdalipaywhirlondrinaplesknsalzburgleezextraspace-to-rentalstomakomaibarabostonakijinsekikogentappssejnyaarparalleluxembourglitcheltenham-radio-opensocialorenskogliwicebotanicalgardeno-staginglobodoes-itcouldbeworldisrechtranakamurataiwanairforcechireadthedocsxeroxfinitybotanicgardenishitosashimizunaminamiawajikindianmarketinglogowestfalenishiwakindielddanuorrindigenamsskoganeindustriabotanyanagawallonieruchomoscienceandindustrynissandiegoddabouncemerckmsdnipropetrovskjervoyageorgeorgiabounty-fullensakerrypropertiesamegawaboutiquebecommerce-shopselectaxihuanissayokkaichintaifun-dnsaliasamnangerboutireservditchyouriparasiteboyfriendoftheinternetflixjavaldaostathellevangerbozen-sudtirolottokorozawabozen-suedtirolouvreisenissedalovepoparisor-fronisshingucciprianiigataipeidsvollovesickariyakumodumeloyalistoragebplaceducatorprojectcmembersampalermomahaccapooguybrandywinevalleybrasiliadboxosascoli-picenorddalpusercontentcp4bresciaokinawashirosatobamagazineuesamsclubartowestus2brindisibenikitagataikikuchikumagayagawalmartgorybristoloseyouriparliamentjeldsundivtasvuodnakaniikawatanagurabritishcolumbialowiezaganiyodogawabroadcastlebtimnetzlgloomy-routerbroadwaybroke-itvedestrandivttasvuotnakanojohanamakindlefrakkestadiybrokerbrothermesaverdeatnulmemergencyachtsamsungloppennebrowsersafetymarketsandnessjoenl-ams-1brumunddalublindesnesandoybrunelastxn--0trq7p7nnbrusselsandvikcoromantovalle-daostavangerbruxellesanfranciscofreakunekobayashikaoirmemorialucaniabryanskodjedugit-pagespeedmobilizeroticagliaricoharuovatlassian-dev-builderscbglugsjcbnpparibashkiriabrynewmexicoacharterbuzzwfarmerseinebwhalingmbhartiffany-2bzhitomirbzzcodyn-vpndnsantacruzsantafedjeffersoncoffeedbackdropocznordlandrudupontariobranconavstackasaokamikoaniikappudownloadurbanamexhibitioncogretakamatsukawacollectioncolognewyorkshirebungoonordre-landurhamburgrimstadynamisches-dnsantamariakecolonialwilliamsburgripeeweeklylotterycoloradoplateaudnedalncolumbusheycommunexus-3community-prochowicecomobaravendbambleborkapsicilyonagoyauthgear-stagingivestbyglandroverhallair-traffic-controlleyombomloabaths-heilbronnoysunddnslivegarsheiheijibigawaustraliaustinnfshostrolekamisatokaizukameyamatotakadaustevollivornowtv-infolldalolipopmcdircompanychipstmncomparemarkerryhotelsantoandrepbodynaliasnesoddenmarkhangelskjakdnepropetrovskiervaapsteigenflfannefrankfurtjxn--12cfi8ixb8lutskashibatakashimarshallstatebankashiharacomsecaaskimitsubatamibuildingriwatarailwaycondoshichinohealth-carereformemsettlersanukindustriesteamfamberlevagangaviikanonjinfinitigotembaixadaconferenceconstructionconsuladogadollsaobernardomniweatherchanneluxuryconsultanthropologyconsultingroks-thisayamanobeokakegawacontactkmaxxn--12co0c3b4evalled-aostamayukinsuregruhostingrondarcontagematsubaravennaharimalborkashiwaracontemporaryarteducationalchikugodonnakaiwamizawashtenawsmppl-wawdev-myqnapcloudcontrolledogawarabikomaezakirunoopschlesischesaogoncartoonartdecologiacontractorskenconventureshinodearthickashiwazakiyosatokamachilloutsystemscloudsitecookingchannelsdvrdnsdojogaszkolancashirecifedexetercoolblogdnsfor-better-thanawassamukawatarikuzentakatairavpagecooperativano-frankivskygearapparochernigovernmentksatxn--1ck2e1bananarepublic-inquiryggeebinatsukigatajimidsundevelopmentatarantours3-external-1copenhagencyclopedichiropracticatholicaxiashorokanaiecoproductionsaotomeinforumzcorporationcorsicahcesuoloanswatch-and-clockercorvettenrissagaeroclubmedecincinnativeamericanantiquest-le-patron-k3sapporomuracosenzamamidorittoeigersundynathomebuiltwithdarkasserverrankoshigayaltakasugaintelligencecosidnshome-webservercellikescandypoppdaluzerncostumedicallynxn--1ctwolominamatargets-itlon-2couchpotatofriesardegnarutomobegetmyiparsardiniacouncilvivanovoldacouponsarlcozoracq-acranbrookuwanalyticsarpsborgrongausdalcrankyowariasahikawatchandclockasukabeauxartsandcraftsarufutsunomiyawakasaikaitabashijonawatecrdyndns-at-homedepotaruinterhostsolutionsasayamatta-varjjatmpartinternationalfirearmsaseboknowsitallcreditcardyndns-at-workshoppingrossetouchigasakitahiroshimansionsaskatchewancreditunioncremonashgabadaddjaguarqcxn--1lqs03ncrewhmessinarashinomutashinaintuitoyosatoyokawacricketnedalcrimeast-kazakhstanangercrotonecrownipartsassarinuyamashinazawacrsaudacruisesauheradyndns-blogsitextilegnicapetownnews-stagingroundhandlingroznycuisinellancasterculturalcentertainmentoyotapartysvardocuneocupcakecuritibabymilk3curvallee-d-aosteinkjerusalempresashibetsurugashimaringatlantajirinvestmentsavannahgacutegirlfriendyndns-freeboxoslocalzonecymrulvikasumigaurawa-mazowszexnetlifyinzairtrafficplexus-1cyonabarumesswithdnsaveincloudyndns-homednsaves-the-whalessandria-trani-barletta-andriatranibarlettaandriacyouthruherecipescaracaltanissettaishinomakilovecollegefantasyleaguernseyfembetsukumiyamazonawsglobalacceleratorahimeshimabaridagawatchesciencecentersciencehistoryfermockasuyamegurownproviderferraraferraris-a-catererferrerotikagoshimalopolskanlandyndns-picsaxofetsundyndns-remotewdyndns-ipasadenaroyfgujoinvilleitungsenfhvalerfidontexistmein-iservschulegallocalhostrodawarafieldyndns-serverdalfigueresindevicenzaolkuszczytnoipirangalsaceofilateliafilegear-augustowhoswholdingsmall-webthingscientistordalfilegear-debianfilegear-gbizfilegear-iefilegear-jpmorganfilegear-sg-1filminamiechizenfinalfinancefineartscrapper-sitefinlandyndns-weblikes-piedmonticellocus-4finnoyfirebaseappaviancarrdyndns-wikinkobearalvahkijoetsuldalvdalaskanittedallasalleasecuritytacticschoenbrunnfirenetoystre-slidrettozawafirenzefirestonefirewebpaascrappingulenfirmdaleikangerfishingoldpoint2thisamitsukefitjarvodkafjordyndns-workangerfitnessettlementozsdellogliastradingunmanxn--1qqw23afjalerfldrvalleeaosteflekkefjordyndns1flesberguovdageaidnunjargaflickragerogerscrysecretrosnubar0flierneflirfloginlinefloppythonanywhereggio-calabriafloraflorencefloridatsunangojomedicinakamagayahabackplaneapplinzis-a-celticsfanfloripadoval-daostavalleyfloristanohatakahamalselvendrellflorokunohealthcareerscwienflowerservehalflifeinsurancefltrani-andria-barletta-trani-andriaflynnhosting-clusterfnchiryukyuragifuchungbukharanzanfndynnschokokekschokoladenfnwkaszubytemarkatowicefoolfor-ourfor-somedio-campidano-mediocampidanomediofor-theaterforexrothachijolsterforgotdnservehttpbin-butterforli-cesena-forlicesenaforlillesandefjordynservebbscholarshipschoolbusinessebyforsaleirfjordynuniversityforsandasuolodingenfortalfortefortmissoulangevagrigentomologyeonggiehtavuoatnagahamaroygardencowayfortworthachinoheavyfosneservehumourfotraniandriabarlettatraniandriafoxfordecampobassociatest-iserveblogsytemp-dnserveirchitachinakagawashingtondchernivtsiciliafozfr-par-1fr-par-2franamizuhobby-sitefrancaiseharafranziskanerimalvikatsushikabedzin-addrammenuorochesterfredrikstadtvserveminecraftranoyfreeddnsfreebox-oservemp3freedesktopfizerfreemasonryfreemyiphosteurovisionfreesitefreetlservep2pgfoggiafreiburgushikamifuranorfolkebibleksvikatsuyamarugame-hostyhostingxn--2m4a15efrenchkisshikirkeneservepicservequakefreseniuscultureggio-emilia-romagnakasatsunairguardiannakadomarinebraskaunicommbankaufentigerfribourgfriuli-v-giuliafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfroganservesarcasmatartanddesignfrognfrolandynv6from-akrehamnfrom-alfrom-arfrom-azurewebsiteshikagamiishibukawakepnoorfrom-capitalonewportransipharmacienservicesevastopolefrom-coalfrom-ctranslatedynvpnpluscountryestateofdelawareclaimschoolsztynsettsupportoyotomiyazakis-a-candidatefrom-dchitosetodayfrom-dediboxafrom-flandersevenassisienarvikautokeinoticeablewismillerfrom-gaulardalfrom-hichisochikuzenfrom-iafrom-idyroyrvikingruenoharafrom-ilfrom-in-berlindasewiiheyaizuwakamatsubushikusakadogawafrom-ksharpharmacyshawaiijimarcheapartmentshellaspeziafrom-kyfrom-lanshimokawafrom-mamurogawatsonfrom-mdfrom-medizinhistorischeshimokitayamattelekommunikationfrom-mifunefrom-mnfrom-modalenfrom-mshimonitayanagit-reposts-and-telecommunicationshimonosekikawafrom-mtnfrom-nchofunatoriginstantcloudfrontdoorfrom-ndfrom-nefrom-nhktistoryfrom-njshimosuwalkis-a-chefarsundyndns-mailfrom-nminamifuranofrom-nvalleedaostefrom-nynysagamiharafrom-ohdattorelayfrom-oketogolffanshimotsukefrom-orfrom-padualstackazoologicalfrom-pratogurafrom-ris-a-conservativegashimotsumayfirstockholmestrandfrom-schmidtre-gauldalfrom-sdscloudfrom-tnfrom-txn--2scrj9chonanbunkyonanaoshimakanegasakikugawaltervistailscaleforcefrom-utsiracusaikirovogradoyfrom-vald-aostarostwodzislawildlifestylefrom-vtransportefrom-wafrom-wiardwebview-assetshinichinanfrom-wvanylvenneslaskerrylogisticshinjournalismartlabelingfrom-wyfrosinonefrostalowa-wolawafroyal-commissionfruskydivingfujiiderafujikawaguchikonefujiminokamoenairkitapps-auction-rancherkasydneyfujinomiyadattowebhoptogakushimotoganefujiokayamandalfujisatoshonairlinedre-eikerfujisawafujishiroishidakabiratoridedyn-berlincolnfujitsuruokazakiryuohkurafujiyoshidavvenjargap-east-1fukayabeardubaiduckdnsncfdfukuchiyamadavvesiidappnodebalancertmgrazimutheworkpccwilliamhillfukudomigawafukuis-a-cpalacefukumitsubishigakisarazure-mobileirvikazteleportlligatransurlfukuokakamigaharafukuroishikarikaturindalfukusakishiwadazaifudaigokaseljordfukuyamagatakaharunusualpersonfunabashiriuchinadafunagatakahashimamakisofukushimangonnakatombetsumy-gatewayfunahashikamiamakusatsumasendaisenergyfundaciofunkfeuerfuoiskujukuriyamangyshlakasamatsudoomdnstracefuosskoczowinbar1furubirafurudonostiaafurukawajimaniwakuratefusodegaurafussaintlouis-a-anarchistoireggiocalabriafutabayamaguchinomihachimanagementrapaniizafutboldlygoingnowhere-for-morenakatsugawafuttsurutaharafuturecmshinjukumamotoyamashikefuturehostingfuturemailingfvghamurakamigoris-a-designerhandcraftedhandsonyhangglidinghangoutwentehannanmokuizumodenaklodzkochikuseihidorahannorthwesternmutualhanyuzenhapmircloudletshintokushimahappounzenharvestcelebrationhasamap-northeast-3hasaminami-alpshintomikasaharahashbangryhasudahasura-apphiladelphiaareadmyblogspotrdhasvikfh-muensterhatogayahoooshikamaishimofusartshinyoshitomiokamisunagawahatoyamazakitakatakanabeatshiojirishirifujiedahatsukaichikaiseiyoichimkentrendhostinghattfjelldalhayashimamotobusellfylkesbiblackbaudcdn-edgestackhero-networkisboringhazuminobushistoryhelplfinancialhelsinkitakyushuaiahembygdsforbundhemneshioyanaizuerichardlimanowarudahemsedalhepforgeblockshirahamatonbetsurgeonshalloffameiwamasoyheroyhetemlbfanhgtvaohigashiagatsumagoianiahigashichichibuskerudhigashihiroshimanehigashiizumozakitamigrationhigashikagawahigashikagurasoedahigashikawakitaaikitamotosunndalhigashikurumeeresinstaginghigashimatsushimarburghigashimatsuyamakitaakitadaitoigawahigashimurayamamotorcycleshirakokonoehigashinarusells-for-lesshiranukamitondabayashiogamagoriziahigashinehigashiomitamanortonsberghigashiosakasayamanakakogawahigashishirakawamatakanezawahigashisumiyoshikawaminamiaikitanakagusukumodernhigashitsunosegawahigashiurausukitashiobarahigashiyamatokoriyamanashifteditorxn--30rr7yhigashiyodogawahigashiyoshinogaris-a-doctorhippyhiraizumisatohnoshoohirakatashinagawahiranairportland-4-salernogiessennanjobojis-a-financialadvisor-aurdalhirarahiratsukaerusrcfastlylbanzaicloudappspotagerhirayaitakaokalmykiahistorichouseshiraois-a-geekhakassiahitachiomiyagildeskaliszhitachiotagonohejis-a-greenhitraeumtgeradegreehjartdalhjelmelandholeckodairaholidayholyhomegoodshiraokamitsuehomeiphilatelyhomelinkyard-cloudjiffyresdalhomelinuxn--32vp30hachiojiyahikobierzycehomeofficehomesecuritymacaparecidahomesecuritypchoseikarugamvikarlsoyhomesenseeringhomesklepphilipsynology-diskstationhomeunixn--3bst00minamiiserniahondahongooglecodebergentinghonjyoitakarazukaluganskharkivaporcloudhornindalhorsells-for-ustkanmakiwielunnerhortendofinternet-dnshiratakahagitapphoenixn--3ds443ghospitalhoteleshishikuis-a-guruhotelwithflightshisognehotmailhoyangerhoylandetakasagophonefosshisuifuettertdasnetzhumanitieshitaramahungryhurdalhurumajis-a-hard-workershizukuishimogosenhyllestadhyogoris-a-hunterhyugawarahyundaiwafuneis-into-carsiiitesilkharkovaresearchaeologicalvinklein-the-bandairtelebitbridgestoneenebakkeshibechambagricultureadymadealstahaugesunderseaportsinfolionetworkdalaheadjudygarlandis-into-cartoonsimple-urlis-into-gamesserlillyis-leetrentin-suedtirolis-lostre-toteneis-a-lawyeris-not-certifiedis-savedis-slickhersonis-uberleetrentino-a-adigeis-very-badajozis-a-liberalis-very-evillageis-very-goodyearis-very-niceis-very-sweetpepperugiais-with-thebandovre-eikerisleofmanaustdaljellybeanjenv-arubahccavuotnagaragusabaerobaticketsirdaljeonnamerikawauejetztrentino-aadigejevnakershusdecorativeartslupskhmelnytskyivarggatrentino-alto-adigejewelryjewishartgalleryjfkhplaystation-cloudyclusterjgorajlljls-sto1jls-sto2jls-sto3jmphotographysiojnjaworznospamproxyjoyentrentino-altoadigejoyokaichibajddarchitecturealtorlandjpnjprslzjurkotohiradomainstitutekotourakouhokutamamurakounosupabasembokukizunokunimilitarykouyamarylhurstjordalshalsenkouzushimasfjordenkozagawakozakis-a-llamarnardalkozowindowskrakowinnersnoasakatakkokamiminersokndalkpnkppspbarcelonagawakkanaibetsubamericanfamilyds3-fips-us-gov-west-1krasnikahokutokashikis-a-musiciankrasnodarkredstonekrelliankristiansandcatsolarssonkristiansundkrodsheradkrokstadelvalle-aostatic-accessolognekryminamiizukaminokawanishiaizubangekumanotteroykumatorinovecoregontrailroadkumejimashikis-a-nascarfankumenantokonamegatakatoris-a-nursells-itrentin-sud-tirolkunisakis-a-painteractivelvetrentin-sudtirolkunitachiaraindropilotsolundbecknx-serversellsyourhomeftphxn--3e0b707ekunitomigusukuleuvenetokigawakunneppuboliviajessheimpertrixcdn77-secureggioemiliaromagnamsosnowiechristiansburgminakamichiharakunstsammlungkunstunddesignkuokgroupimientaketomisatoolsomakurehabmerkurgankurobeeldengeluidkurogimimatakatsukis-a-patsfankuroisoftwarezzoologykuromatsunais-a-personaltrainerkuronkurotakikawasakis-a-photographerokussldkushirogawakustanais-a-playershiftcryptonomichigangwonkusupersalezajskomakiyosemitekutchanelkutnowruzhgorodeokuzumakis-a-republicanonoichinomiyakekvafjordkvalsundkvamscompute-1kvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspdnsomnatalkzmisakis-a-soxfanmisasaguris-a-studentalmisawamisconfusedmishimasudamissilemisugitokuyamatsumaebashikshacknetrentino-sued-tirolmitakeharamitourismilemitoyoakemiuramiyazurecontainerdpolicemiyotamatsukuris-a-teacherkassyno-dshowamjondalenmonstermontrealestatefarmequipmentrentino-suedtirolmonza-brianzapposor-odalmonza-e-della-brianzaptokyotangotpantheonsitemonzabrianzaramonzaebrianzamonzaedellabrianzamoonscalebookinghostedpictetrentinoa-adigemordoviamoriyamatsumotofukemoriyoshiminamiashigaramormonmouthachirogatakamoriokakudamatsuemoroyamatsunomortgagemoscowiosor-varangermoseushimodatemosjoenmoskenesorfoldmossorocabalena-devicesorreisahayakawakamiichikawamisatottoris-a-techietis-a-landscaperspectakasakitchenmosvikomatsushimarylandmoteginowaniihamatamakinoharamoviemovimientolgamozilla-iotrentinoaadigemtranbytomaritimekeepingmuginozawaonsensiositemuikaminoyamaxunispacemukoebenhavnmulhouseoullensvanguardmunakatanemuncienciamuosattemupinbarclaycards3-sa-east-1murmanskomforbar2murotorcraftrentinoalto-adigemusashinoharamuseetrentinoaltoadigemuseumverenigingmusicargodaddyn-o-saurlandesortlandmutsuzawamy-wanggoupilemyactivedirectorymyamazeplaymyasustor-elvdalmycdmycloudnsoruminamimakis-a-rockstarachowicemydattolocalcertificationmyddnsgeekgalaxymydissentrentinos-tirolmydobissmarterthanyoumydrobofageologymydsoundcastronomy-vigorlicemyeffectrentinostirolmyfastly-terrariuminamiminowamyfirewalledreplittlestargardmyforuminamioguni5myfritzmyftpaccessouthcarolinaturalhistorymuseumcentermyhome-servermyjinomykolaivencloud66mymailermymediapchristmasakillucernemyokohamamatsudamypepinkommunalforbundmypetsouthwest1-uslivinghistorymyphotoshibalashovhadanorth-kazakhstanmypicturestaurantrentinosud-tirolmypsxn--3pxu8kommunemysecuritycamerakermyshopblocksowamyshopifymyspreadshopwarendalenugmythic-beastspectruminamisanrikubetsuppliesoomytis-a-bookkeepermaritimodspeedpartnermytuleap-partnersphinxn--41amyvnchromediatechnologymywirepaircraftingvollohmusashimurayamashikokuchuoplantationplantspjelkavikomorotsukagawaplatformsharis-a-therapistoiaplatter-appinokofuefukihaboromskogplatterpioneerplazaplcube-serversicherungplumbingoplurinacionalpodhalepodlasiellaktyubinskiptveterinairealmpmnpodzonepohlpoivronpokerpokrovskomvuxn--3hcrj9choyodobashichikashukujitawaraumalatvuopmicrosoftbankarmoypoliticarrierpolitiendapolkowicepoltavalle-d-aostaticspydebergpomorzeszowitdkongsbergponpesaro-urbino-pesarourbinopesaromasvuotnarusawapordenonepornporsangerporsangugeporsgrunnanyokoshibahikariwanumatakinouepoznanpraxis-a-bruinsfanprdpreservationpresidioprgmrprimetelemarkongsvingerprincipeprivatizehealthinsuranceprofesionalprogressivestfoldpromombetsupplypropertyprotectionprotonetrentinosued-tirolprudentialpruszkowithgoogleapiszprvcyberprzeworskogpulawypunyufuelveruminamiuonumassa-carrara-massacarraramassabuyshousesopotrentino-sud-tirolpupugliapussycateringebuzentsujiiepvhadselfiphdfcbankazunoticiashinkamigototalpvtrentinosuedtirolpwchungnamdalseidsbergmodellingmxn--11b4c3dray-dnsupdaterpzqhaebaruericssongdalenviknakayamaoris-a-cubicle-slavellinodeobjectshinshinotsurfashionstorebaselburguidefinimamateramochizukimobetsumidatlantichirurgiens-dentistes-en-franceqldqotoyohashimotoshimatsuzakis-an-accountantshowtimelbourneqponiatowadaqslgbtrentinsud-tirolqualifioappippueblockbusternopilawaquickconnectrentinsudtirolquicksytesrhtrentinsued-tirolquipelementsrltunestuff-4-saletunkonsulatrobeebyteappigboatsmolaquilanxessmushcdn77-sslingturystykaniepcetuscanytushuissier-justicetuvalleaostaverntuxfamilytwmailvestvagoyvevelstadvibo-valentiavibovalentiavideovillastufftoread-booksnestorfjordvinnicasadelamonedagestangevinnytsiavipsinaappiwatevirginiavirtual-uservecounterstrikevirtualcloudvirtualservervirtualuserveexchangevirtuelvisakuhokksundviterbolognagasakikonaikawagoevivianvivolkenkundenvixn--42c2d9avlaanderennesoyvladikavkazimierz-dolnyvladimirvlogintoyonezawavminanovologdanskonyveloftrentino-stirolvolvolkswagentstuttgartrentinsuedtirolvolyngdalvoorlopervossevangenvotevotingvotoyonovps-hostrowiecircustomer-ocimmobilienwixsitewloclawekoobindalwmcloudwmflabsurnadalwoodsidelmenhorstabackyardsurreyworse-thandawowithyoutuberspacekitagawawpdevcloudwpenginepoweredwphostedmailwpmucdnpixolinodeusercontentrentinosudtirolwpmudevcdnaccessokanagawawritesthisblogoipizzawroclawiwatsukiyonoshiroomgwtcirclerkstagewtfastvps-serverisignwuozuwzmiuwajimaxn--4gbriminingxn--4it168dxn--4it797kooris-a-libertarianxn--4pvxs4allxn--54b7fta0ccivilaviationredumbrellajollamericanexpressexyxn--55qw42gxn--55qx5dxn--5dbhl8dxn--5js045dxn--5rtp49civilisationrenderxn--5rtq34koperviklabudhabikinokawachinaganoharamcocottempurlxn--5su34j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264civilizationthewifiatmallorcafederation-webspacexn--80aaa0cvacationsusonoxn--80adxhksuzakananiimiharuxn--80ao21axn--80aqecdr1axn--80asehdbarclays3-us-east-2xn--80aswgxn--80aukraanghkembuchikujobservableusercontentrevisohughestripperxn--8dbq2axn--8ltr62koryokamikawanehonbetsuwanouchijiwadeliveryxn--8pvr4uxn--8y0a063axn--90a1affinitylotterybnikeisenbahnxn--90a3academiamicable-modemoneyxn--90aeroportalabamagasakishimabaraffleentry-snowplowiczeladzxn--90aishobarakawaharaoxn--90amckinseyxn--90azhytomyrxn--9dbhblg6dietritonxn--9dbq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byandexcloudxn--asky-iraxn--aurskog-hland-jnbarefootballooningjerstadgcapebretonamicrolightingjesdalombardiadembroideryonagunicloudiherokuappanamasteiermarkaracoldwarszawauthgearappspacehosted-by-previderxn--avery-yuasakuragawaxn--b-5gaxn--b4w605ferdxn--balsan-sdtirol-nsbsuzukanazawaxn--bck1b9a5dre4civilwarmiasadoesntexisteingeekarpaczest-a-la-maisondre-landrayddns5yxn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2xn--bjarky-fyaotsurgeryxn--bjddar-ptargithubpreviewsaitohmannore-og-uvdalxn--blt-elabourxn--bmlo-graingerxn--bod-2naturalsciencesnaturellesuzukis-an-actorxn--bozen-sdtirol-2obanazawaxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investigation-acornxn--brum-voagatroandinosaureportrentoyonakagyokutoyakomaganexn--btsfjord-9zaxn--bulsan-sdtirol-nsbaremetalpha-myqnapcloud9guacuiababia-goracleaningitpagexlimoldell-ogliastraderxn--c1avgxn--c2br7gxn--c3s14mincomcastreserve-onlinexn--cck2b3bargainstances3-us-gov-west-1xn--cckwcxetdxn--cesena-forl-mcbremangerxn--cesenaforl-i8axn--cg4bkis-an-actresshwindmillxn--ciqpnxn--clchc0ea0b2g2a9gcdxn--comunicaes-v6a2oxn--correios-e-telecomunicaes-ghc29axn--czr694barreaudiblebesbydgoszczecinemagnethnologyoriikaragandauthordalandroiddnss3-ap-southeast-2ix4432-balsan-suedtirolimiteddnskinggfakefurniturecreationavuotnaritakoelnayorovigotsukisosakitahatakahatakaishimoichinosekigaharaurskog-holandingitlaborxn--czrs0trogstadxn--czru2dxn--czrw28barrel-of-knowledgeappgafanquanpachicappacificurussiautomotivelandds3-ca-central-16-balsan-sudtirollagdenesnaaseinet-freaks3-ap-southeast-123websiteleaf-south-123webseiteckidsmynasushiobarackmazerbaijan-mayen-rootaribeiraogakibichuobiramusementdllpages3-ap-south-123sitewebhareidfjordvagsoyerhcloudd-dnsiskinkyolasiteastcoastaldefenceastus2038xn--d1acj3barrell-of-knowledgecomputerhistoryofscience-fictionfabricafjs3-us-west-1xn--d1alfaromeoxn--d1atromsakegawaxn--d5qv7z876clanbibaidarmeniaxn--davvenjrga-y4axn--djrs72d6uyxn--djty4kosaigawaxn--dnna-grajewolterskluwerxn--drbak-wuaxn--dyry-iraxn--e1a4cldmailukowhitesnow-dnsangohtawaramotoineppubtlsanjotelulubin-brbambinagisobetsuitagajoburgjerdrumcprequalifymein-vigorgebetsukuibmdeveloperauniteroizumizakinderoyomitanobninskanzakiyokawaraustrheimatunduhrennebulsan-suedtirololitapunk123kotisivultrobjectselinogradimo-siemenscaledekaascolipiceno-ipifony-1337xn--eckvdtc9dxn--efvn9svalbardunloppaderbornxn--efvy88hagakhanamigawaxn--ehqz56nxn--elqq16hagebostadxn--eveni-0qa01gaxn--f6qx53axn--fct429kosakaerodromegallupaasdaburxn--fhbeiarnxn--finny-yuaxn--fiq228c5hsvchurchaseljeepsondriodejaneirockyotobetsuliguriaxn--fiq64barsycenterprisesakievennodesadistcgrouplidlugolekagaminord-frontierxn--fiqs8sveioxn--fiqz9svelvikoninjambylxn--fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbssvizzeraxn--forlcesena-c8axn--fpcrj9c3dxn--frde-grandrapidsvn-repostorjcloud-ver-jpchowderxn--frna-woaraisaijosoyroroswedenxn--frya-hraxn--fzc2c9e2cleverappsannanxn--fzys8d69uvgmailxn--g2xx48clicketcloudcontrolapparmatsuuraxn--gckr3f0fauskedsmokorsetagayaseralingenoamishirasatogliattipschulserverxn--gecrj9clickrisinglesannohekinannestadraydnsanokaruizawaxn--ggaviika-8ya47haibarakitakamiizumisanofidelitysfjordxn--gildeskl-g0axn--givuotna-8yasakaiminatoyookaneyamazoexn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-an-anarchistoricalsocietysnesigdalxn--gmqw5axn--gnstigbestellen-zvbrplsbxn--45br5cylxn--gnstigliefern-wobihirosakikamijimatsushigexn--h-2failxn--h1aeghair-surveillancexn--h1ahnxn--h1alizxn--h2breg3eveneswidnicasacampinagrandebungotakadaemongolianxn--h2brj9c8clinichippubetsuikilatironporterxn--h3cuzk1digickoseis-a-linux-usershoujis-a-knightpointtohoboleslawieconomiastalbanshizuokamogawaxn--hbmer-xqaxn--hcesuolo-7ya35barsyonlinewhampshirealtychyattorneyagawakuyabukihokumakogeniwaizumiotsurugimbalsfjordeportexaskoyabeagleboardetroitskypecorivneatonoshoes3-eu-west-3utilitiesquare7xn--hebda8basicserversaillesjabbottateshinanomachildrensgardenhlfanhsbc66xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-prevention-aptibleangaviikadenaamesjevuemielnoboribetsuckswidnikkolobrzegersundxn--hnefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyasugithubusercontentromsojamisonxn--io0a7is-an-artistgstagexn--j1adpkomonotogawaxn--j1aefbsbxn--1lqs71dyndns-office-on-the-webhostingrpassagensavonarviikamiokameokamakurazakiwakunigamihamadaxn--j1ael8basilicataniautoscanadaeguambulancentralus-2xn--j1amhakatanorthflankddiamondshinshiroxn--j6w193gxn--jlq480n2rgxn--jlq61u9w7basketballfinanzgorzeleccodespotenzakopanewspaperxn--jlster-byasuokannamihokkaidopaaskvollxn--jrpeland-54axn--jvr189miniserversusakis-a-socialistg-builderxn--k7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--45brj9cistrondheimperiaxn--koluokta-7ya57hakodatexn--kprw13dxn--kpry57dxn--kput3is-an-engineeringxn--krager-gyatominamibosogndalxn--kranghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdevcloudfunctionsimplesitexn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyatsukanoyakagexn--kvnangen-k0axn--l-1fairwindswiebodzin-dslattuminamiyamashirokawanabeepilepsykkylvenicexn--l1accentureklamborghinikolaeventswinoujscienceandhistoryxn--laheadju-7yatsushiroxn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52batochigifts3-us-west-2xn--lesund-huaxn--lgbbat1ad8jdfaststackschulplattformetacentrumeteorappassenger-associationxn--lgrd-poacctrusteexn--lhppi-xqaxn--linds-pramericanartrvestnestudioxn--lns-qlavagiskexn--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liacliniquedapliexn--lten-granexn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddeswisstpetersburgxn--mgb9awbfbx-ostrowwlkpmguitarschwarzgwangjuifminamidaitomanchesterxn--mgba3a3ejtrycloudflarevistaplestudynamic-dnsrvaroyxn--mgba3a4f16axn--mgba3a4fra1-deloittevaksdalxn--mgba7c0bbn0axn--mgbaakc7dvfstdlibestadxn--mgbaam7a8hakonexn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00batsfjordiscordsays3-website-ap-northeast-1xn--mgbai9azgqp6jejuniperxn--mgbayh7gpalmaseratis-an-entertainerxn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbcpq6gpa1axn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexposedxn--mgbpl2fhskosherbrookegawaxn--mgbqly7c0a67fbclintonkotsukubankarumaifarmsteadrobaknoluoktachikawakayamadridvallee-aosteroyxn--mgbqly7cvafr-1xn--mgbt3dhdxn--mgbtf8flapymntrysiljanxn--mgbtx2bauhauspostman-echocolatemasekd1xn--mgbx4cd0abbvieeexn--mix082fbxoschweizxn--mix891fedorainfraclouderaxn--mjndalen-64axn--mk0axin-vpnclothingdustdatadetectjmaxxxn--12c1fe0bradescotlandrrxn--mk1bu44cn-northwest-1xn--mkru45is-bykleclerchoshibuyachiyodancexn--mlatvuopmi-s4axn--mli-tlavangenxn--mlselv-iuaxn--moreke-juaxn--mori-qsakurais-certifiedxn--mosjen-eyawaraxn--mot-tlazioxn--mre-og-romsdal-qqbuseranishiaritakurashikis-foundationxn--msy-ula0hakubaghdadultravelchannelxn--mtta-vrjjat-k7aflakstadaokagakicks-assnasaarlandxn--muost-0qaxn--mxtq1minisitexn--ngbc5azdxn--ngbe9e0axn--ngbrxn--45q11citadelhicampinashikiminohostfoldnavyxn--nit225koshimizumakiyosunnydayxn--nmesjevuemie-tcbalestrandabergamoarekeymachineustarnbergxn--nnx388axn--nodessakyotanabelaudiopsysynology-dstreamlitappittsburghofficialxn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nttery-byaeserveftplanetariuminamitanexn--nvuotna-hwaxn--nyqy26axn--o1achernihivgubsxn--o3cw4hakuis-a-democratravelersinsurancexn--o3cyx2axn--od0algxn--od0aq3belementorayoshiokanumazuryukuhashimojibxos3-website-ap-southeast-1xn--ogbpf8flatangerxn--oppegrd-ixaxn--ostery-fyawatahamaxn--osyro-wuaxn--otu796dxn--p1acfedorapeoplegoismailillehammerfeste-ipatriaxn--p1ais-gonexn--pgbs0dhlx3xn--porsgu-sta26fedoraprojectoyotsukaidoxn--pssu33lxn--pssy2uxn--q7ce6axn--q9jyb4cngreaterxn--qcka1pmcpenzaporizhzhiaxn--qqqt11minnesotaketakayamassivegridxn--qxa6axn--qxamsterdamnserverbaniaxn--rady-iraxn--rdal-poaxn--rde-ulaxn--rdy-0nabaris-into-animeetrentin-sued-tirolxn--rennesy-v1axn--rhkkervju-01afeiraquarelleasingujaratoyouraxn--rholt-mragowoltlab-democraciaxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5naturbruksgymnxn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byaxn--rny31hakusanagochihayaakasakawaiishopitsitexn--rovu88bellevuelosangeles3-website-ap-southeast-2xn--rros-granvindafjordxn--rskog-uuaxn--rst-0naturhistorischesxn--rsta-framercanvasxn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byaxn--s-1faithaldenxn--s9brj9cnpyatigorskolecznagatorodoyxn--sandnessjen-ogbellunord-odalombardyn53xn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-graphoxn--4dbgdty6citichernovtsyncloudrangedaluccarbonia-iglesias-carboniaiglesiascarboniaxn--skierv-utazasxn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5natuurwetenschappenginexn--slt-elabcieszynh-servebeero-stageiseiroumuenchencoreapigeelvinckoshunantankmpspawnextdirectrentino-s-tirolxn--smla-hraxn--smna-gratangentlentapisa-geekosugexn--snase-nraxn--sndre-land-0cbeneventochiokinoshimaintenancebinordreisa-hockeynutazurestaticappspaceusercontentateyamaveroykenglandeltaitogitsumitakagiizeasypanelblagrarchaeologyeongbuk0emmafann-arboretumbriamallamaceiobbcg123homepagefrontappchizip61123minsidaarborteaches-yogasawaracingroks-theatree123hjemmesidealerimo-i-rana4u2-localhistorybolzano-altoadigeometre-experts-comptables3-ap-northeast-123miwebcambridgehirn4t3l3p0rtarumizusawabogadobeaemcloud-fr123paginaweberkeleyokosukanrabruzzombieidskoguchikushinonsenasakuchinotsuchiurakawafaicloudineat-url-o-g-i-naval-d-aosta-valleyokote164-b-datacentermezproxyzgoraetnabudejjudaicadaquest-mon-blogueurodirumaceratabuseating-organicbcn-north-123saitamakawabartheshopencraftrainingdyniajuedischesapeakebayernavigationavoi234lima-cityeats3-ap-northeast-20001wwwedeployokozeastasiamunemurorangecloudplatform0xn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbentleyurihonjournalistjohnikonanporovnobserverxn--srfold-byaxn--srreisa-q1axn--srum-gratis-a-bulls-fanxn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbeppublishproxyusuharavocatanzarowegroweiboltashkentatamotorsitestingivingjemnes3-eu-central-1kappleadpages-12hpalmspringsakerxn--stre-toten-zcbeskidyn-ip24xn--t60b56axn--tckweddingxn--tiq49xqyjelasticbeanstalkhmelnitskiyamarumorimachidaxn--tjme-hraxn--tn0agrocerydxn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbestbuyshoparenagareyamaizurugbyenvironmentalconservationflashdrivefsnillfjordiscordsezjampaleoceanographics3-website-eu-west-1xn--trentin-sdtirol-7vbetainaboxfuseekloges3-website-sa-east-1xn--trentino-sd-tirol-c3bhzcasertainaioirasebastopologyeongnamegawafflecellclstagemologicaliforniavoues3-eu-west-1xn--trentino-sdtirol-szbielawalbrzycharitypedreamhostersvp4xn--trentinosd-tirol-rzbiellaakesvuemieleccebizenakanotoddeninoheguriitatebayashiibahcavuotnagaivuotnagaokakyotambabybluebitelevisioncilla-speziaxarnetbank8s3-eu-west-2xn--trentinosdtirol-7vbieszczadygeyachimataijiiyamanouchikuhokuryugasakitaurayasudaxn--trentinsd-tirol-6vbievat-band-campaignieznombrendlyngengerdalces3-website-us-east-1xn--trentinsdtirol-nsbifukagawalesundiscountypeformelhusgardeninomiyakonojorpelandiscourses3-website-us-west-1xn--trgstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atvestre-slidrexn--uc0ay4axn--uist22halsakakinokiaxn--uisz3gxn--unjrga-rtarnobrzegyptianxn--unup4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbtularvikonskowolayangroupiemontexn--valle-d-aoste-ehboehringerikexn--valleaoste-e7axn--valledaoste-ebbvadsoccerxn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctb-hostingxn--vermgensberatung-pwbigvalledaostaobaomoriguchiharag-cloud-championshiphoplixboxenirasakincheonishiazaindependent-commissionishigouvicasinordeste-idclkarasjohkamikitayamatsurindependent-inquest-a-la-masionishiharaxn--vestvgy-ixa6oxn--vg-yiabkhaziaxn--vgan-qoaxn--vgsy-qoa0jelenia-goraxn--vgu402cnsantabarbaraxn--vhquvestre-totennishiawakuraxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861biharstadotsubetsugaruhrxn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1cntjomeldaluroyxn--wgbl6axn--xhq521bihorologyusuisservegame-serverxn--xkc2al3hye2axn--xkc2dl3a5ee0hammarfeastafricaravantaaxn--y9a3aquariumintereitrentino-sudtirolxn--yer-znaumburgxn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn--4dbrk0cexn--ystre-slidre-ujbikedaejeonbukarasjokarasuyamarriottatsunoceanographiquehimejindependent-inquiryuufcfanishiizunazukindependent-panelomoliseminemrxn--zbx025dxn--zf0ao64axn--zf0avxlxn--zfr164bilbaogashimadachicagoboavistanbulsan-sudtirolbia-tempio-olbiatempioolbialystokkeliwebredirectme-south-1xnbayxz \ No newline at end of file +birkenesoddtangentinglogoweirbitbucketrzynishikatakayamatta-varjjatjomembersaltdalovepopartysfjordiskussionsbereichatinhlfanishikatsuragitappassenger-associationishikawazukamiokameokamakurazakitaurayasudabitternidisrechtrainingloomy-routerbjarkoybjerkreimdbalsan-suedtirololitapunkapsienamsskoganeibmdeveloperauniteroirmemorialombardiadempresashibetsukumiyamagasakinderoyonagunicloudevelopmentaxiijimarriottayninhaccanthobby-siteval-d-aosta-valleyoriikaracolognebinatsukigataiwanumatajimidsundgcahcesuolocustomer-ocimperiautoscanalytics-gatewayonagoyaveroykenflfanpachihayaakasakawaiishopitsitemasekd1kappenginedre-eikerimo-siemenscaledekaascolipicenoboribetsucks3-eu-west-3utilities-16-balestrandabergentappsseekloges3-eu-west-123paginawebcamauction-acornfshostrodawaraktyubinskaunicommbank123kotisivultrobjectselinogradimo-i-rana4u2-localhostrolekanieruchomoscientistordal-o-g-i-nikolaevents3-ap-northeast-2-ddnsking123homepagefrontappchizip61123saitamakawababia-goracleaningheannakadomarineat-urlimanowarudakuneustarostwodzislawdev-myqnapcloudcontrolledgesuite-stagingdyniamusementdllclstagehirnikonantomobelementorayokosukanoyakumoliserniaurland-4-salernord-aurdalipaywhirlimiteddnslivelanddnss3-ap-south-123siteweberlevagangaviikanonji234lima-cityeats3-ap-southeast-123webseiteambulancechireadmyblogspotaribeiraogakicks-assurfakefurniturealmpmninoheguribigawaurskog-holandinggfarsundds3-ap-southeast-20001wwwedeployokote123hjemmesidealerdalaheadjuegoshikibichuobiraustevollimombetsupplyokoze164-balena-devices3-ca-central-123websiteleaf-south-12hparliamentatsunobninsk8s3-eu-central-1337bjugnishimerablackfridaynightjxn--11b4c3ditchyouripatriabloombergretaijindustriesteinkjerbloxcmsaludivtasvuodnakaiwanairlinekobayashimodatecnologiablushakotanishinomiyashironomniwebview-assetsalvadorbmoattachmentsamegawabmsamnangerbmwellbeingzonebnrweatherchannelsdvrdnsamparalleluxenishinoomotegotsukishiwadavvenjargamvikarpaczest-a-la-maisondre-landivttasvuotnakamai-stagingloppennebomlocalzonebonavstackartuzybondigitaloceanspacesamsclubartowest1-usamsunglugsmall-webspacebookonlineboomlaakesvuemielecceboschristmasakilatiron-riopretoeidsvollovesickaruizawabostik-serverrankoshigayachtsandvikcoromantovalle-d-aostakinouebostonakijinsekikogentlentapisa-geekarumaifmemsetkmaxxn--12c1fe0bradescotksatmpaviancapitalonebouncemerckmsdscloudiybounty-fullensakerrypropertiesangovtoyosatoyokawaboutiquebecologialaichaugiangmbhartiengiangminakamichiharaboutireservdrangedalpusercontentoyotapfizerboyfriendoftheinternetflixn--12cfi8ixb8lublindesnesanjosoyrovnoticiasannanishinoshimattelemarkasaokamikitayamatsurinfinitigopocznore-og-uvdalucaniabozen-sudtiroluccanva-appstmnishiokoppegardray-dnsupdaterbozen-suedtirolukowesteuropencraftoyotomiyazakinsurealtypeformesswithdnsannohekinanporovigonohejinternationaluroybplacedogawarabikomaezakirunordkappgfoggiabrandrayddns5ybrasiliadboxoslockerbresciaogashimadachicappadovaapstemp-dnswatchest-mon-blogueurodirumagazinebrindisiciliabroadwaybroke-itvedestrandraydnsanokashibatakashimashikiyosatokigawabrokerbrothermesserlifestylebtimnetzpisdnpharmaciensantamariakebrowsersafetymarketingmodumetacentrumeteorappharmacymruovatlassian-dev-builderschaefflerbrumunddalutskashiharabrusselsantoandreclaimsanukintlon-2bryanskiptveterinaireadthedocsaobernardovre-eikerbrynebwestus2bzhitomirbzzwhitesnowflakecommunity-prochowicecomodalenissandoycompanyaarphdfcbankasumigaurawa-mazowszexn--1ck2e1bambinagisobetsuldalpha-myqnapcloudaccess3-us-east-2ixboxeroxfinityolasiteastus2comparemarkerryhotelsaves-the-whalessandria-trani-barletta-andriatranibarlettaandriacomsecaasnesoddeno-stagingrondarcondoshifteditorxn--1ctwolominamatarnobrzegrongrossetouchijiwadedyn-berlincolnissayokoshibahikariyaltakazakinzais-a-bookkeepermarshallstatebankasuyalibabahccavuotnagaraholtaleniwaizumiotsurugashimaintenanceomutazasavonarviikaminoyamaxunispaceconferenceconstructionflashdrivefsncf-ipfsaxoconsuladobeio-static-accesscamdvrcampaniaconsultantranoyconsultingroundhandlingroznysaitohnoshookuwanakayamangyshlakdnepropetrovskanlandyndns-freeboxostrowwlkpmgrphilipsyno-dschokokekscholarshipschoolbusinessebycontactivetrailcontagematsubaravendbambleborkdalvdalcest-le-patron-rancherkasydneyukuhashimokawavoues3-sa-east-1contractorskenissedalcookingruecoolblogdnsfor-better-thanhhoarairforcentralus-1cooperativano-frankivskodjeephonefosschoolsztynsetransiphotographysiocoproductionschulplattforminamiechizenisshingucciprianiigatairaumalatvuopmicrolightinguidefinimaringatlancastercorsicafjschulservercosenzakopanecosidnshome-webservercellikescandypopensocialcouchpotatofrieschwarzgwangjuh-ohtawaramotoineppueblockbusternopilawacouncilcouponscrapper-sitecozoravennaharimalborkaszubytemarketscrappinguitarscrysecretrosnubananarepublic-inquiryurihonjoyenthickaragandaxarnetbankanzakiwielunnerepairbusanagochigasakishimabarakawaharaolbia-tempio-olbiatempioolbialowiezachpomorskiengiangjesdalolipopmcdirepbodyn53cqcxn--1lqs03niyodogawacrankyotobetsumidaknongujaratmallcrdyndns-homednscwhminamifuranocreditcardyndns-iphutholdingservehttpbincheonl-ams-1creditunionionjukujitawaravpagecremonashorokanaiecrewhoswholidaycricketnedalcrimeast-kazakhstanangercrotonecrowniphuyencrsvp4cruiseservehumourcuisinellair-traffic-controllagdenesnaaseinet-freakserveircasertainaircraftingvolloansnasaarlanduponthewifidelitypedreamhostersaotomeldaluxurycuneocupcakecuritibacgiangiangryggeecurvalled-aostargets-itranslatedyndns-mailcutegirlfriendyndns-office-on-the-webhoptogurafedoraprojectransurlfeirafembetsukuis-a-bruinsfanfermodenakasatsunairportrapaniizaferraraferraris-a-bulls-fanferrerotikagoshimalopolskanittedalfetsundyndns-wikimobetsumitakagildeskaliszkolamericanfamilydservemp3fgunmaniwamannorth-kazakhstanfhvalerfilegear-augustowiiheyakagefilegear-deatnuniversitysvardofilegear-gbizfilegear-iefilegear-jpmorgangwonporterfilegear-sg-1filminamiizukamiminefinalchikugokasellfyis-a-candidatefinancefinnoyfirebaseappiemontefirenetlifylkesbiblackbaudcdn-edgestackhero-networkinggroupowiathletajimabaria-vungtaudiopsysharpigboatshawilliamhillfirenzefirestonefireweblikes-piedmontravelersinsurancefirmdalegalleryfishingoldpoint2thisamitsukefitjarfitnessettsurugiminamimakis-a-catererfjalerfkatsushikabeebyteappilottonsberguovdageaidnunjargausdalflekkefjordyndns-workservep2phxn--1lqs71dyndns-remotewdyndns-picserveminecraftransporteflesbergushikamifuranorthflankatsuyamashikokuchuoflickragerokunohealthcareershellflierneflirfloginlinefloppythonanywherealtorfloraflorencefloripalmasfjordenfloristanohatajiris-a-celticsfanfloromskogxn--2m4a15eflowershimokitayamafltravinhlonganflynnhosting-clusterfncashgabadaddjabbottoyourafndyndns1fnwkzfolldalfoolfor-ourfor-somegurownproviderfor-theaterfordebianforexrotheworkpccwinbar0emmafann-arborlandd-dnsiskinkyowariasahikawarszawashtenawsmppl-wawsglobalacceleratorahimeshimakanegasakievennodebalancern4t3l3p0rtatarantours3-ap-northeast-123minsidaarborteaches-yogano-ipifony-123miwebaccelastx4432-b-datacenterprisesakijobservableusercontentateshinanomachintaifun-dnsdojournalistoloseyouriparisor-fronavuotnarashinoharaetnabudejjunipereggio-emilia-romagnaroyboltateyamajureggiocalabriakrehamnayoro0o0forgotdnshimonitayanagithubpreviewsaikisarazure-mobileirfjordynnservepicservequakeforli-cesena-forlicesenaforlillehammerfeste-ipimientaketomisatoolshimonosekikawaforsalegoismailillesandefjordynservebbservesarcasmileforsandasuolodingenfortalfortefosneshimosuwalkis-a-chefashionstorebaseljordyndns-serverisignfotrdynulvikatowicefoxn--2scrj9casinordlandurbanamexnetgamersapporomurafozfr-1fr-par-1fr-par-2franamizuhoboleslawiecommerce-shoppingyeongnamdinhachijohanamakisofukushimaoris-a-conservativegarsheiheijis-a-cparachutingfredrikstadynv6freedesktopazimuthaibinhphuocelotenkawakayamagnetcieszynh-servebeero-stageiseiroumugifuchungbukharag-cloud-championshiphoplixn--30rr7yfreemyiphosteurovisionredumbrellangevagrigentobishimadridvagsoygardenebakkeshibechambagricoharugbydgoszczecin-berlindasdaburfreesitefreetlshimotsukefreisennankokubunjis-a-cubicle-slavellinodeobjectshimotsumafrenchkisshikindleikangerfreseniushinichinanfriuli-v-giuliafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfroganshinjotelulubin-vpncateringebunkyonanaoshimamateramockashiwarafrognfrolandynvpnpluservicesevastopolitiendafrom-akamaized-stagingfrom-alfrom-arfrom-azurewebsiteshikagamiishibuyabukihokuizumobaragusabaerobaticketshinjukuleuvenicefrom-campobassociatest-iserveblogsytenrissadistdlibestadultrentin-sudtirolfrom-coachaseljeducationcillahppiacenzaganfrom-ctrentin-sued-tirolfrom-dcatfooddagestangefrom-decagliarikuzentakataikillfrom-flapymntrentin-suedtirolfrom-gap-east-1from-higashiagatsumagoianiafrom-iafrom-idyroyrvikingulenfrom-ilfrom-in-the-bandairtelebitbridgestonemurorangecloudplatform0from-kshinkamigototalfrom-kyfrom-langsonyantakahamalselveruminamiminowafrom-malvikaufentigerfrom-mdfrom-mein-vigorlicefrom-mifunefrom-mnfrom-modshinshinotsurgeryfrom-mshinshirofrom-mtnfrom-ncatholicurus-4from-ndfrom-nefrom-nhs-heilbronnoysundfrom-njshintokushimafrom-nminamioguni5from-nvalledaostargithubusercontentrentino-a-adigefrom-nycaxiaskvollpagesardegnarutolgaulardalvivanovoldafrom-ohdancefrom-okegawassamukawataris-a-democratrentino-aadigefrom-orfrom-panasonichernovtsykkylvenneslaskerrylogisticsardiniafrom-pratohmamurogawatsonrenderfrom-ris-a-designerimarugame-hostyhostingfrom-schmidtre-gauldalfrom-sdfrom-tnfrom-txn--32vp30hachinoheavyfrom-utsiracusagaeroclubmedecin-addrammenuorodoyerfrom-val-daostavalleyfrom-vtrentino-alto-adigefrom-wafrom-wiardwebthingsjcbnpparibashkiriafrom-wvallee-aosteroyfrom-wyfrosinonefrostabackplaneapplebesbyengerdalp1froyal-commissionfruskydivingfujiiderafujikawaguchikonefujiminokamoenairtrafficplexus-2fujinomiyadapliefujiokazakinkobearalvahkikonaibetsubame-south-1fujisatoshoeshintomikasaharafujisawafujishiroishidakabiratoridediboxn--3bst00minamisanrikubetsupportrentino-altoadigefujitsuruokakamigaharafujiyoshidappnodearthainguyenfukayabeardubaikawagoefukuchiyamadatsunanjoburgfukudomigawafukuis-a-doctorfukumitsubishigakirkeneshinyoshitomiokamisatokamachippubetsuikitchenfukuokakegawafukuroishikariwakunigamigrationfukusakirovogradoyfukuyamagatakaharunusualpersonfunabashiriuchinadattorelayfunagatakahashimamakiryuohkurafunahashikamiamakusatsumasendaisenergyeongginowaniihamatamakinoharafundfunkfeuerfuoiskujukuriyamandalfuosskoczowindowskrakowinefurubirafurudonordreisa-hockeynutwentertainmentrentino-s-tirolfurukawajimangolffanshiojirishirifujiedafusoctrangfussagamiharafutabayamaguchinomihachimanagementrentino-stirolfutboldlygoingnowhere-for-more-og-romsdalfuttsurutashinais-a-financialadvisor-aurdalfuturecmshioyamelhushirahamatonbetsurnadalfuturehostingfuturemailingfvghakuis-a-gurunzenhakusandnessjoenhaldenhalfmoonscalebookinghostedpictetrentino-sud-tirolhalsakakinokiaham-radio-opinbar1hamburghammarfeastasiahamurakamigoris-a-hard-workershiraokamisunagawahanamigawahanawahandavvesiidanangodaddyn-o-saurealestatefarmerseinehandcrafteducatorprojectrentino-sudtirolhangglidinghangoutrentino-sued-tirolhannannestadhannosegawahanoipinkazohanyuzenhappouzshiratakahagianghasamap-northeast-3hasaminami-alpshishikuis-a-hunterhashbanghasudazaifudaigodogadobeioruntimedio-campidano-mediocampidanomediohasura-appinokokamikoaniikappudopaashisogndalhasvikazteleportrentino-suedtirolhatogayahoooshikamagayaitakamoriokakudamatsuehatoyamazakitahiroshimarcheapartmentshisuifuettertdasnetzhatsukaichikaiseiyoichipshitaramahattfjelldalhayashimamotobusells-for-lesshizukuishimoichilloutsystemscloudsitehazuminobushibukawahelplfinancialhelsinkitakamiizumisanofidonnakamurataitogliattinnhemneshizuokamitondabayashiogamagoriziahemsedalhepforgeblockshoujis-a-knightpointtokaizukamaishikshacknetrentinoa-adigehetemlbfanhigashichichibuzentsujiiehigashihiroshimanehigashiizumozakitakatakanabeautychyattorneyagawakkanaioirasebastopoleangaviikadenagahamaroyhigashikagawahigashikagurasoedahigashikawakitaaikitakyushunantankazunovecorebungoonow-dnshowahigashikurumeinforumzhigashimatsushimarnardalhigashimatsuyamakitaakitadaitoigawahigashimurayamamotorcycleshowtimeloyhigashinarusells-for-uhigashinehigashiomitamanoshiroomghigashiosakasayamanakakogawahigashishirakawamatakanezawahigashisumiyoshikawaminamiaikitamihamadahigashitsunospamproxyhigashiurausukitamotosunnydayhigashiyamatokoriyamanashiibaclieu-1higashiyodogawahigashiyoshinogaris-a-landscaperspectakasakitanakagusukumoldeliveryhippyhiraizumisatohokkaidontexistmein-iservschulecznakaniikawatanagurahirakatashinagawahiranais-a-lawyerhirarahiratsukaeruhirayaizuwakamatsubushikusakadogawahitachiomiyaginozawaonsensiositehitachiotaketakaokalmykiahitraeumtgeradegreehjartdalhjelmelandholyhomegoodshwinnersiiitesilkddiamondsimple-urlhomeipioneerhomelinkyard-cloudjiffyresdalhomelinuxn--3ds443ghomeofficehomesecuritymacaparecidahomesecuritypchiryukyuragiizehomesenseeringhomeskleppippugliahomeunixn--3e0b707ehondahonjyoitakarazukaluganskfh-muensterhornindalhorsells-itrentinoaadigehortendofinternet-dnsimplesitehospitalhotelwithflightsirdalhotmailhoyangerhoylandetakasagooglecodespotrentinoalto-adigehungyenhurdalhurumajis-a-liberalhyllestadhyogoris-a-libertarianhyugawarahyundaiwafuneis-very-evillasalleitungsenis-very-goodyearis-very-niceis-very-sweetpepperugiais-with-thebandoomdnstraceisk01isk02jenv-arubacninhbinhdinhktistoryjeonnamegawajetztrentinostiroljevnakerjewelryjgorajlljls-sto1jls-sto2jls-sto3jmpixolinodeusercontentrentinosud-tiroljnjcloud-ver-jpchitosetogitsuliguriajoyokaichibahcavuotnagaivuotnagaokakyotambabymilk3jozis-a-musicianjpnjprsolarvikhersonlanxessolundbeckhmelnitskiyamasoykosaigawakosakaerodromegalloabatobamaceratachikawafaicloudineencoreapigeekoseis-a-painterhostsolutionslupskhakassiakosheroykoshimizumakis-a-patsfankoshughesomakosugekotohiradomainstitutekotourakouhokumakogenkounosupersalevangerkouyamasudakouzushimatrixn--3pxu8khplaystation-cloudyclusterkozagawakozakis-a-personaltrainerkozowiosomnarviklabudhabikinokawachinaganoharamcocottekpnkppspbarcelonagawakepnord-odalwaysdatabaseballangenkainanaejrietisalatinabenogiehtavuoatnaamesjevuemielnombrendlyngen-rootaruibxos3-us-gov-west-1krasnikahokutokonamegatakatoris-a-photographerokussldkrasnodarkredstonekrelliankristiansandcatsoowitdkmpspawnextdirectrentinosudtirolkristiansundkrodsheradkrokstadelvaldaostavangerkropyvnytskyis-a-playershiftcryptonomichinomiyakekryminamiyamashirokawanabelaudnedalnkumamotoyamatsumaebashimofusakatakatsukis-a-republicanonoichinosekigaharakumanowtvaokumatorinokumejimatsumotofukekumenanyokkaichirurgiens-dentistes-en-francekundenkunisakis-a-rockstarachowicekunitachiaraisaijolsterkunitomigusukukis-a-socialistgstagekunneppubtlsopotrentinosued-tirolkuokgroupizzakurgankurobegetmyipirangalluplidlugolekagaminorddalkurogimimozaokinawashirosatochiokinoshimagentositempurlkuroisodegaurakuromatsunais-a-soxfankuronkurotakikawasakis-a-studentalkushirogawakustanais-a-teacherkassyncloudkusuppliesor-odalkutchanelkutnokuzumakis-a-techietipslzkvafjordkvalsundkvamsterdamnserverbaniakvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspdnsor-varangermishimatsusakahogirlymisugitokorozawamitakeharamitourismartlabelingmitoyoakemiuramiyazurecontainerdpoliticaobangmiyotamatsukuris-an-actormjondalenmonzabrianzaramonzaebrianzamonzaedellabrianzamordoviamorenapolicemoriyamatsuuramoriyoshiminamiashigaramormonstermoroyamatsuzakis-an-actressmushcdn77-sslingmortgagemoscowithgoogleapiszmoseushimogosenmosjoenmoskenesorreisahayakawakamiichikawamisatottoris-an-anarchistjordalshalsenmossortlandmosviknx-serversusakiyosupabaseminemotegit-reposoruminanomoviemovimientokyotangotembaixadattowebhareidsbergmozilla-iotrentinosuedtirolmtranbytomaridagawalmartrentinsud-tirolmuikaminokawanishiaizubangemukoelnmunakatanemuosattemupkomatsushimassa-carrara-massacarraramassabuzzmurmanskomforbar2murotorcraftranakatombetsumy-gatewaymusashinodesakegawamuseumincomcastoripressorfoldmusicapetownnews-stagingmutsuzawamy-vigormy-wanggoupilemyactivedirectorymyamazeplaymyasustor-elvdalmycdmycloudnsoundcastorjdevcloudfunctionsokndalmydattolocalcertificationmyddnsgeekgalaxymydissentrentinsudtirolmydobissmarterthanyoumydrobofageometre-experts-comptablesowamydspectruminisitemyeffectrentinsued-tirolmyfastly-edgekey-stagingmyfirewalledreplittlestargardmyforuminterecifedextraspace-to-rentalstomakomaibaramyfritzmyftpaccesspeedpartnermyhome-servermyjinomykolaivencloud66mymailermymediapchoseikarugalsacemyokohamamatsudamypeplatformsharis-an-artistockholmestrandmypetsphinxn--41amyphotoshibajddarvodkafjordvaporcloudmypictureshinomypsxn--42c2d9amysecuritycamerakermyshopblockspjelkavikommunalforbundmyshopifymyspreadshopselectrentinsuedtirolmytabitordermythic-beastspydebergmytis-a-anarchistg-buildermytuleap-partnersquaresindevicenzamyvnchoshichikashukudoyamakeuppermywirecipescaracallypoivronpokerpokrovskommunepolkowicepoltavalle-aostavernpomorzeszowithyoutuberspacekitagawaponpesaro-urbino-pesarourbinopesaromasvuotnaritakurashikis-bykleclerchitachinakagawaltervistaipeigersundynamic-dnsarlpordenonepornporsangerporsangugeporsgrunnanpoznanpraxihuanprdprgmrprimetelprincipeprivatelinkomonowruzhgorodeoprivatizehealthinsuranceprofesionalprogressivegasrlpromonza-e-della-brianzaptokuyamatsushigepropertysnesrvarggatrevisogneprotectionprotonetroandindependent-inquest-a-la-masionprudentialpruszkowiwatsukiyonotaireserve-onlineprvcyonabarumbriaprzeworskogpunyufuelpupulawypussycatanzarowixsitepvhachirogatakahatakaishimojis-a-geekautokeinotteroypvtrogstadpwchowderpzqhadanorthwesternmutualqldqotoyohashimotoshimaqponiatowadaqslgbtroitskomorotsukagawaqualifioapplatter-applatterplcube-serverquangngais-certifiedugit-pagespeedmobilizeroticaltanissettailscaleforcequangninhthuanquangtritonoshonais-foundationquickconnectromsakuragawaquicksytestreamlitapplumbingouvaresearchitectesrhtrentoyonakagyokutoyakomakizunokunimimatakasugais-an-engineeringquipelementstrippertuscanytushungrytuvalle-daostamayukis-into-animeiwamizawatuxfamilytuyenquangbinhthuantwmailvestnesuzukis-gonevestre-slidreggio-calabriavestre-totennishiawakuravestvagoyvevelstadvibo-valentiaavibovalentiavideovinhphuchromedicinagatorogerssarufutsunomiyawakasaikaitakokonoevinnicarbonia-iglesias-carboniaiglesiascarboniavinnytsiavipsinaapplurinacionalvirginanmokurennebuvirtual-userveexchangevirtualservervirtualuserveftpodhalevisakurais-into-carsnoasakuholeckodairaviterboliviajessheimmobilienvivianvivoryvixn--45br5cylvlaanderennesoyvladikavkazimierz-dolnyvladimirvlogintoyonezawavmintsorocabalashovhachiojiyahikobierzycevologdanskoninjambylvolvolkswagencyouvolyngdalvoorlopervossevangenvotevotingvotoyonovps-hostrowiechungnamdalseidfjordynathomebuiltwithdarkhangelskypecorittogojomeetoystre-slidrettozawawmemergencyahabackdropalermochizukikirarahkkeravjuwmflabsvalbardunloppadualstackomvuxn--3hcrj9chonanbuskerudynamisches-dnsarpsborgripeeweeklylotterywoodsidellogliastradingworse-thanhphohochiminhadselbuyshouseshirakolobrzegersundongthapmircloudletshiranukamishihorowowloclawekonskowolawawpdevcloudwpenginepoweredwphostedmailwpmucdnipropetrovskygearappodlasiellaknoluoktagajobojis-an-entertainerwpmudevcdnaccessojamparaglidingwritesthisblogoipodzonewroclawmcloudwsseoullensvanguardianwtcp4wtfastlylbanzaicloudappspotagereporthruherecreationinomiyakonojorpelandigickarasjohkameyamatotakadawuozuerichardlillywzmiuwajimaxn--4it797konsulatrobeepsondriobranconagareyamaizuruhrxn--4pvxs4allxn--54b7fta0ccistrondheimpertrixcdn77-secureadymadealstahaugesunderxn--55qw42gxn--55qx5dxn--5dbhl8dxn--5js045dxn--5rtp49citadelhichisochimkentozsdell-ogliastraderxn--5rtq34kontuminamiuonumatsunoxn--5su34j936bgsgxn--5tzm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264citicarrdrobakamaiorigin-stagingmxn--12co0c3b4evalleaostaobaomoriguchiharaffleentrycloudflare-ipfstcgroupaaskimitsubatamibulsan-suedtirolkuszczytnoopscbgrimstadrrxn--80aaa0cvacationsvchoyodobashichinohealth-carereforminamidaitomanaustdalxn--80adxhksveioxn--80ao21axn--80aqecdr1axn--80asehdbarclaycards3-us-west-1xn--80aswgxn--80aukraanghkeliwebpaaskoyabeagleboardxn--8dbq2axn--8ltr62konyvelohmusashimurayamassivegridxn--8pvr4uxn--8y0a063axn--90a1affinitylotterybnikeisencowayxn--90a3academiamicable-modemoneyxn--90aeroportsinfolionetworkangerxn--90aishobaraxn--90amckinseyxn--90azhytomyrxn--9dbq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byanagawaxn--asky-iraxn--aurskog-hland-jnbarclays3-us-west-2xn--avery-yuasakurastoragexn--b-5gaxn--b4w605ferdxn--balsan-sdtirol-nsbsvelvikongsbergxn--bck1b9a5dre4civilaviationfabricafederation-webredirectmediatechnologyeongbukashiwazakiyosembokutamamuraxn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2xn--bjarky-fyanaizuxn--bjddar-ptarumizusawaxn--blt-elabcienciamallamaceiobbcn-north-1xn--bmlo-graingerxn--bod-2natalxn--bozen-sdtirol-2obanazawaxn--brnny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investigation-aptibleadpagesquare7xn--brum-voagatrustkanazawaxn--btsfjord-9zaxn--bulsan-sdtirol-nsbarefootballooningjovikarasjoketokashikiyokawaraxn--c1avgxn--c2br7gxn--c3s14misakis-a-therapistoiaxn--cck2b3baremetalombardyn-vpndns3-website-ap-northeast-1xn--cckwcxetdxn--cesena-forl-mcbremangerxn--cesenaforl-i8axn--cg4bkis-into-cartoonsokamitsuexn--ciqpnxn--clchc0ea0b2g2a9gcdxn--czr694bargainstantcloudfrontdoorestauranthuathienhuebinordre-landiherokuapparochernigovernmentjeldsundiscordsays3-website-ap-southeast-1xn--czrs0trvaroyxn--czru2dxn--czrw28barrel-of-knowledgeapplinziitatebayashijonawatebizenakanojoetsumomodellinglassnillfjordiscordsezgoraxn--d1acj3barrell-of-knowledgecomputermezproxyzgorzeleccoffeedbackanagawarmiastalowa-wolayangroupars3-website-ap-southeast-2xn--d1alfaststacksevenassigdalxn--d1atrysiljanxn--d5qv7z876clanbibaiduckdnsaseboknowsitallxn--davvenjrga-y4axn--djrs72d6uyxn--djty4koobindalxn--dnna-grajewolterskluwerxn--drbak-wuaxn--dyry-iraxn--e1a4cldmail-boxaxn--eckvdtc9dxn--efvn9svn-repostuff-4-salexn--efvy88haebaruericssongdalenviknaklodzkochikushinonsenasakuchinotsuchiurakawaxn--ehqz56nxn--elqq16hagakhanhhoabinhduongxn--eveni-0qa01gaxn--f6qx53axn--fct429kooris-a-nascarfanxn--fhbeiarnxn--finny-yuaxn--fiq228c5hsbcleverappsassarinuyamashinazawaxn--fiq64barsycenterprisecloudcontrolappgafanquangnamasteigenoamishirasatochigifts3-website-eu-west-1xn--fiqs8swidnicaravanylvenetogakushimotoganexn--fiqz9swidnikitagatakkomaganexn--fjord-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesena-fcbsswiebodzindependent-commissionxn--forlcesena-c8axn--fpcrj9c3dxn--frde-granexn--frna-woaxn--frya-hraxn--fzc2c9e2clickrisinglesjaguarxn--fzys8d69uvgmailxn--g2xx48clinicasacampinagrandebungotakadaemongolianishitosashimizunaminamiawajikintuitoyotsukaidownloadrudtvsaogoncapooguyxn--gckr3f0fastvps-serveronakanotoddenxn--gecrj9cliniquedaklakasamatsudoesntexisteingeekasserversicherungroks-theatrentin-sud-tirolxn--ggaviika-8ya47hagebostadxn--gildeskl-g0axn--givuotna-8yandexcloudxn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-into-gamessinamsosnowieconomiasadojin-dslattuminamitanexn--gmqw5axn--gnstigbestellen-zvbrplsbxn--45brj9churcharterxn--gnstigliefern-wobihirosakikamijimayfirstorfjordxn--h-2failxn--h1ahnxn--h1alizxn--h2breg3eveneswinoujsciencexn--h2brj9c8clothingdustdatadetectrani-andria-barletta-trani-andriaxn--h3cuzk1dienbienxn--hbmer-xqaxn--hcesuolo-7ya35barsyonlinehimejiiyamanouchikujoinvilleirvikarasuyamashikemrevistathellequipmentjmaxxxjavald-aostatics3-website-sa-east-1xn--hebda8basicserversejny-2xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-prevention-k3swisstufftoread-booksnestudioxn--hnefoss-q1axn--hobl-iraxn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyaotsusonoxn--io0a7is-leetrentinoaltoadigexn--j1adpohlxn--j1aefauskedsmokorsetagayaseralingenovaraxn--j1ael8basilicataniaxn--j1amhaibarakisosakitahatakamatsukawaxn--j6w193gxn--jlq480n2rgxn--jlster-byasakaiminatoyookananiimiharuxn--jrpeland-54axn--jvr189misasaguris-an-accountantsmolaquilaocais-a-linux-useranishiaritabashikaoizumizakitashiobaraxn--k7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--45q11circlerkstagentsasayamaxn--koluokta-7ya57haiduongxn--kprw13dxn--kpry57dxn--kput3is-lostre-toteneis-a-llamarumorimachidaxn--krager-gyasugitlabbvieeexn--kranghke-b0axn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdfastly-terrariuminamiiseharaxn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyasuokanmakiwakuratexn--kvnangen-k0axn--l-1fairwindsynology-diskstationxn--l1accentureklamborghinikkofuefukihabororosynology-dsuzakadnsaliastudynaliastrynxn--laheadju-7yatominamibosoftwarendalenugxn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52basketballfinanzjaworznoticeableksvikaratsuginamikatagamilanotogawaxn--lesund-huaxn--lgbbat1ad8jejuxn--lgrd-poacctulaspeziaxn--lhppi-xqaxn--linds-pramericanexpresservegame-serverxn--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liacn-northwest-1xn--lten-granvindafjordxn--lury-iraxn--m3ch0j3axn--mely-iraxn--merker-kuaxn--mgb2ddesxn--mgb9awbfbsbxn--1qqw23axn--mgba3a3ejtunesuzukamogawaxn--mgba3a4f16axn--mgba3a4fra1-deloittexn--mgba7c0bbn0axn--mgbaakc7dvfsxn--mgbaam7a8haiphongonnakatsugawaxn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00batsfjordiscountry-snowplowiczeladzlgleezeu-2xn--mgbai9azgqp6jelasticbeanstalkharkovalleeaostexn--mgbayh7gparasitexn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgbcpq6gpa1axn--mgberp4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexposedxn--mgbpl2fhskopervikhmelnytskyivalleedaostexn--mgbqly7c0a67fbcngroks-thisayamanobeatsaudaxn--mgbqly7cvafricargoboavistanbulsan-sudtirolxn--mgbt3dhdxn--mgbtf8flatangerxn--mgbtx2bauhauspostman-echofunatoriginstances3-website-us-east-1xn--mgbx4cd0abkhaziaxn--mix082fbx-osewienxn--mix891fbxosexyxn--mjndalen-64axn--mk0axindependent-inquiryxn--mk1bu44cnpyatigorskjervoyagexn--mkru45is-not-certifiedxn--mlatvuopmi-s4axn--mli-tlavagiskexn--mlselv-iuaxn--moreke-juaxn--mori-qsakuratanxn--mosjen-eyatsukannamihokksundxn--mot-tlavangenxn--mre-og-romsdal-qqbuservecounterstrikexn--msy-ula0hair-surveillancexn--mtta-vrjjat-k7aflakstadaokayamazonaws-cloud9guacuiababybluebiteckidsmynasushiobaracingrok-freeddnsfreebox-osascoli-picenogatabuseating-organicbcgjerdrumcprequalifymelbourneasypanelblagrarq-authgear-stagingjerstadeltaishinomakilovecollegefantasyleaguenoharauthgearappspacehosted-by-previderehabmereitattoolforgerockyombolzano-altoadigeorgeorgiauthordalandroideporteatonamidorivnebetsukubankanumazuryomitanocparmautocodebergamoarekembuchikumagayagawafflecelloisirs3-external-180reggioemiliaromagnarusawaustrheimbalsan-sudtirolivingitpagexlivornobserveregruhostingivestbyglandroverhalladeskjakamaiedge-stagingivingjemnes3-eu-west-2038xn--muost-0qaxn--mxtq1misawaxn--ngbc5azdxn--ngbe9e0axn--ngbrxn--4dbgdty6ciscofreakamaihd-stagingriwataraindroppdalxn--nit225koryokamikawanehonbetsuwanouchikuhokuryugasakis-a-nursellsyourhomeftpiwatexn--nmesjevuemie-tcbalatinord-frontierxn--nnx388axn--nodessakurawebsozais-savedxn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn--ntsq17gxn--nttery-byaeservehalflifeinsurancexn--nvuotna-hwaxn--nyqy26axn--o1achernivtsicilynxn--4dbrk0cexn--o3cw4hakatanortonkotsunndalxn--o3cyx2axn--od0algardxn--od0aq3beneventodayusuharaxn--ogbpf8fldrvelvetromsohuissier-justicexn--oppegrd-ixaxn--ostery-fyatsushiroxn--osyro-wuaxn--otu796dxn--p1acfedjeezxn--p1ais-slickharkivallee-d-aostexn--pgbs0dhlx3xn--porsgu-sta26fedorainfraclouderaxn--pssu33lxn--pssy2uxn--q7ce6axn--q9jyb4cnsauheradyndns-at-homedepotenzamamicrosoftbankasukabedzin-brbalsfjordietgoryoshiokanravocats3-fips-us-gov-west-1xn--qcka1pmcpenzapposxn--qqqt11misconfusedxn--qxa6axn--qxamunexus-3xn--rady-iraxn--rdal-poaxn--rde-ulazioxn--rdy-0nabaris-uberleetrentinos-tirolxn--rennesy-v1axn--rhkkervju-01afedorapeoplefrakkestadyndns-webhostingujogaszxn--rholt-mragowoltlab-democraciaxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--risa-5naturalxn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byawaraxn--rny31hakodatexn--rovu88bentleyusuitatamotorsitestinglitchernihivgubs3-website-us-west-1xn--rros-graphicsxn--rskog-uuaxn--rst-0naturbruksgymnxn--rsta-framercanvasxn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byawatahamaxn--s-1faitheshopwarezzoxn--s9brj9cntraniandriabarlettatraniandriaxn--sandnessjen-ogbentrendhostingliwiceu-3xn--sandy-yuaxn--sdtirol-n2axn--seral-lraxn--ses554gxn--sgne-graphoxn--4gbriminiserverxn--skierv-utazurestaticappspaceusercontentunkongsvingerxn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5navigationxn--slt-elabogadobeaemcloud-fr1xn--smla-hraxn--smna-gratangenxn--snase-nraxn--sndre-land-0cbeppublishproxyuufcfanirasakindependent-panelomonza-brianzaporizhzhedmarkarelianceu-4xn--snes-poaxn--snsa-roaxn--sr-aurdal-l8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbeskidyn-ip24xn--srfold-byaxn--srreisa-q1axn--srum-gratis-a-bloggerxn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbestbuyshoparenagasakikuchikuseihicampinashikiminohostfoldnavyuzawaxn--stre-toten-zcbetainaboxfuselfipartindependent-reviewegroweibolognagasukeu-north-1xn--t60b56axn--tckweddingxn--tiq49xqyjelenia-goraxn--tjme-hraxn--tn0agrocerydxn--tnsberg-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbhzc66xn--trentin-sdtirol-7vbialystokkeymachineu-south-1xn--trentino-sd-tirol-c3bielawakuyachimataharanzanishiazaindielddanuorrindigenamerikawauevje-og-hornnes3-website-us-west-2xn--trentino-sdtirol-szbiella-speziaxn--trentinosd-tirol-rzbieszczadygeyachiyodaeguamfamscompute-1xn--trentinosdtirol-7vbievat-band-campaignieznoorstaplesakyotanabellunordeste-idclkarlsoyxn--trentinsd-tirol-6vbifukagawalbrzycharitydalomzaporizhzhiaxn--trentinsdtirol-nsbigv-infolkebiblegnicalvinklein-butterhcloudiscoursesalangenishigotpantheonsitexn--trgstad-r1axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atventuresinstagingxn--uc0ay4axn--uist22hakonexn--uisz3gxn--unjrga-rtashkenturindalxn--unup4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbturystykaneyamazoexn--valle-d-aoste-ehboehringerikexn--valleaoste-e7axn--valledaoste-ebbvadsoccertmgreaterxn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctb-hostingxn--vermgensberatung-pwbiharstadotsubetsugarulezajskiervaksdalondonetskarmoyxn--vestvgy-ixa6oxn--vg-yiabruzzombieidskogasawarackmazerbaijan-mayenbaidarmeniaxn--vgan-qoaxn--vgsy-qoa0jellybeanxn--vgu402coguchikuzenishiwakinvestmentsaveincloudyndns-at-workisboringsakershusrcfdyndns-blogsitexn--vhquvestfoldxn--vler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861bihoronobeokagakikugawalesundiscoverdalondrinaplesknsalon-1xn--w4r85el8fhu5dnraxn--w4rs40lxn--wcvs22dxn--wgbh1communexn--wgbl6axn--xhq521bikedaejeonbuk0xn--xkc2al3hye2axn--xkc2dl3a5ee0hakubackyardshiraois-a-greenxn--y9a3aquarelleasingxn--yer-znavois-very-badxn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn--4it168dxn--ystre-slidre-ujbiofficialorenskoglobodoes-itcouldbeworldishangrilamdongnairkitapps-audibleasecuritytacticsxn--0trq7p7nnishiharaxn--zbx025dxn--zf0ao64axn--zf0avxlxn--zfr164bipartsaloonishiizunazukindustriaxnbayernxz \ No newline at end of file diff --git a/publicsuffix/example_test.go b/publicsuffix/example_test.go index 3f44dcfe7..c051dac8e 100644 --- a/publicsuffix/example_test.go +++ b/publicsuffix/example_test.go @@ -77,7 +77,7 @@ func ExamplePublicSuffix_manager() { // > golang.dev dev is ICANN Managed // > golang.net net is ICANN Managed // > play.golang.org org is ICANN Managed - // > gophers.in.space.museum space.museum is ICANN Managed + // > gophers.in.space.museum museum is ICANN Managed // > // > 0emm.com com is ICANN Managed // > a.0emm.com a.0emm.com is Privately Managed diff --git a/publicsuffix/table.go b/publicsuffix/table.go index 6bdadcc44..78d400fa6 100644 --- a/publicsuffix/table.go +++ b/publicsuffix/table.go @@ -4,7 +4,7 @@ package publicsuffix import _ "embed" -const version = "publicsuffix.org's public_suffix_list.dat, git revision e248cbc92a527a166454afe9914c4c1b4253893f (2022-11-15T18:02:38Z)" +const version = "publicsuffix.org's public_suffix_list.dat, git revision 63cbc63d470d7b52c35266aa96c4c98c96ec499c (2023-08-03T10:01:25Z)" const ( nodesBits = 40 @@ -26,7 +26,7 @@ const ( ) // numTLD is the number of top level domains. -const numTLD = 1494 +const numTLD = 1474 // text is the combined text of all labels. // @@ -63,8 +63,8 @@ var nodes uint40String //go:embed data/children var children uint32String -// max children 718 (capacity 1023) -// max text offset 32976 (capacity 65535) -// max text length 36 (capacity 63) -// max hi 9656 (capacity 16383) -// max lo 9651 (capacity 16383) +// max children 743 (capacity 1023) +// max text offset 30876 (capacity 65535) +// max text length 31 (capacity 63) +// max hi 9322 (capacity 16383) +// max lo 9317 (capacity 16383) diff --git a/publicsuffix/table_test.go b/publicsuffix/table_test.go index 99698271a..a297b3b0d 100644 --- a/publicsuffix/table_test.go +++ b/publicsuffix/table_test.go @@ -2,7 +2,7 @@ package publicsuffix -const numICANNRules = 7367 +const numICANNRules = 6893 var rules = [...]string{ "ac", @@ -302,9 +302,26 @@ var rules = [...]string{ "org.bi", "biz", "bj", - "asso.bj", - "barreau.bj", - "gouv.bj", + "africa.bj", + "agro.bj", + "architectes.bj", + "assur.bj", + "avocats.bj", + "co.bj", + "com.bj", + "eco.bj", + "econo.bj", + "edu.bj", + "info.bj", + "loisirs.bj", + "money.bj", + "net.bj", + "org.bj", + "ote.bj", + "resto.bj", + "restaurant.bj", + "tourism.bj", + "univ.bj", "bm", "com.bm", "edu.bm", @@ -3596,552 +3613,6 @@ var rules = [...]string{ "co.mu", "or.mu", "museum", - "academy.museum", - "agriculture.museum", - "air.museum", - "airguard.museum", - "alabama.museum", - "alaska.museum", - "amber.museum", - "ambulance.museum", - "american.museum", - "americana.museum", - "americanantiques.museum", - "americanart.museum", - "amsterdam.museum", - "and.museum", - "annefrank.museum", - "anthro.museum", - "anthropology.museum", - "antiques.museum", - "aquarium.museum", - "arboretum.museum", - "archaeological.museum", - "archaeology.museum", - "architecture.museum", - "art.museum", - "artanddesign.museum", - "artcenter.museum", - "artdeco.museum", - "arteducation.museum", - "artgallery.museum", - "arts.museum", - "artsandcrafts.museum", - "asmatart.museum", - "assassination.museum", - "assisi.museum", - "association.museum", - "astronomy.museum", - "atlanta.museum", - "austin.museum", - "australia.museum", - "automotive.museum", - "aviation.museum", - "axis.museum", - "badajoz.museum", - "baghdad.museum", - "bahn.museum", - "bale.museum", - "baltimore.museum", - "barcelona.museum", - "baseball.museum", - "basel.museum", - "baths.museum", - "bauern.museum", - "beauxarts.museum", - "beeldengeluid.museum", - "bellevue.museum", - "bergbau.museum", - "berkeley.museum", - "berlin.museum", - "bern.museum", - "bible.museum", - "bilbao.museum", - "bill.museum", - "birdart.museum", - "birthplace.museum", - "bonn.museum", - "boston.museum", - "botanical.museum", - "botanicalgarden.museum", - "botanicgarden.museum", - "botany.museum", - "brandywinevalley.museum", - "brasil.museum", - "bristol.museum", - "british.museum", - "britishcolumbia.museum", - "broadcast.museum", - "brunel.museum", - "brussel.museum", - "brussels.museum", - "bruxelles.museum", - "building.museum", - "burghof.museum", - "bus.museum", - "bushey.museum", - "cadaques.museum", - "california.museum", - "cambridge.museum", - "can.museum", - "canada.museum", - "capebreton.museum", - "carrier.museum", - "cartoonart.museum", - "casadelamoneda.museum", - "castle.museum", - "castres.museum", - "celtic.museum", - "center.museum", - "chattanooga.museum", - "cheltenham.museum", - "chesapeakebay.museum", - "chicago.museum", - "children.museum", - "childrens.museum", - "childrensgarden.museum", - "chiropractic.museum", - "chocolate.museum", - "christiansburg.museum", - "cincinnati.museum", - "cinema.museum", - "circus.museum", - "civilisation.museum", - "civilization.museum", - "civilwar.museum", - "clinton.museum", - "clock.museum", - "coal.museum", - "coastaldefence.museum", - "cody.museum", - "coldwar.museum", - "collection.museum", - "colonialwilliamsburg.museum", - "coloradoplateau.museum", - "columbia.museum", - "columbus.museum", - "communication.museum", - "communications.museum", - "community.museum", - "computer.museum", - "computerhistory.museum", - "xn--comunicaes-v6a2o.museum", - "contemporary.museum", - "contemporaryart.museum", - "convent.museum", - "copenhagen.museum", - "corporation.museum", - "xn--correios-e-telecomunicaes-ghc29a.museum", - "corvette.museum", - "costume.museum", - "countryestate.museum", - "county.museum", - "crafts.museum", - "cranbrook.museum", - "creation.museum", - "cultural.museum", - "culturalcenter.museum", - "culture.museum", - "cyber.museum", - "cymru.museum", - "dali.museum", - "dallas.museum", - "database.museum", - "ddr.museum", - "decorativearts.museum", - "delaware.museum", - "delmenhorst.museum", - "denmark.museum", - "depot.museum", - "design.museum", - "detroit.museum", - "dinosaur.museum", - "discovery.museum", - "dolls.museum", - "donostia.museum", - "durham.museum", - "eastafrica.museum", - "eastcoast.museum", - "education.museum", - "educational.museum", - "egyptian.museum", - "eisenbahn.museum", - "elburg.museum", - "elvendrell.museum", - "embroidery.museum", - "encyclopedic.museum", - "england.museum", - "entomology.museum", - "environment.museum", - "environmentalconservation.museum", - "epilepsy.museum", - "essex.museum", - "estate.museum", - "ethnology.museum", - "exeter.museum", - "exhibition.museum", - "family.museum", - "farm.museum", - "farmequipment.museum", - "farmers.museum", - "farmstead.museum", - "field.museum", - "figueres.museum", - "filatelia.museum", - "film.museum", - "fineart.museum", - "finearts.museum", - "finland.museum", - "flanders.museum", - "florida.museum", - "force.museum", - "fortmissoula.museum", - "fortworth.museum", - "foundation.museum", - "francaise.museum", - "frankfurt.museum", - "franziskaner.museum", - "freemasonry.museum", - "freiburg.museum", - "fribourg.museum", - "frog.museum", - "fundacio.museum", - "furniture.museum", - "gallery.museum", - "garden.museum", - "gateway.museum", - "geelvinck.museum", - "gemological.museum", - "geology.museum", - "georgia.museum", - "giessen.museum", - "glas.museum", - "glass.museum", - "gorge.museum", - "grandrapids.museum", - "graz.museum", - "guernsey.museum", - "halloffame.museum", - "hamburg.museum", - "handson.museum", - "harvestcelebration.museum", - "hawaii.museum", - "health.museum", - "heimatunduhren.museum", - "hellas.museum", - "helsinki.museum", - "hembygdsforbund.museum", - "heritage.museum", - "histoire.museum", - "historical.museum", - "historicalsociety.museum", - "historichouses.museum", - "historisch.museum", - "historisches.museum", - "history.museum", - "historyofscience.museum", - "horology.museum", - "house.museum", - "humanities.museum", - "illustration.museum", - "imageandsound.museum", - "indian.museum", - "indiana.museum", - "indianapolis.museum", - "indianmarket.museum", - "intelligence.museum", - "interactive.museum", - "iraq.museum", - "iron.museum", - "isleofman.museum", - "jamison.museum", - "jefferson.museum", - "jerusalem.museum", - "jewelry.museum", - "jewish.museum", - "jewishart.museum", - "jfk.museum", - "journalism.museum", - "judaica.museum", - "judygarland.museum", - "juedisches.museum", - "juif.museum", - "karate.museum", - "karikatur.museum", - "kids.museum", - "koebenhavn.museum", - "koeln.museum", - "kunst.museum", - "kunstsammlung.museum", - "kunstunddesign.museum", - "labor.museum", - "labour.museum", - "lajolla.museum", - "lancashire.museum", - "landes.museum", - "lans.museum", - "xn--lns-qla.museum", - "larsson.museum", - "lewismiller.museum", - "lincoln.museum", - "linz.museum", - "living.museum", - "livinghistory.museum", - "localhistory.museum", - "london.museum", - "losangeles.museum", - "louvre.museum", - "loyalist.museum", - "lucerne.museum", - "luxembourg.museum", - "luzern.museum", - "mad.museum", - "madrid.museum", - "mallorca.museum", - "manchester.museum", - "mansion.museum", - "mansions.museum", - "manx.museum", - "marburg.museum", - "maritime.museum", - "maritimo.museum", - "maryland.museum", - "marylhurst.museum", - "media.museum", - "medical.museum", - "medizinhistorisches.museum", - "meeres.museum", - "memorial.museum", - "mesaverde.museum", - "michigan.museum", - "midatlantic.museum", - "military.museum", - "mill.museum", - "miners.museum", - "mining.museum", - "minnesota.museum", - "missile.museum", - "missoula.museum", - "modern.museum", - "moma.museum", - "money.museum", - "monmouth.museum", - "monticello.museum", - "montreal.museum", - "moscow.museum", - "motorcycle.museum", - "muenchen.museum", - "muenster.museum", - "mulhouse.museum", - "muncie.museum", - "museet.museum", - "museumcenter.museum", - "museumvereniging.museum", - "music.museum", - "national.museum", - "nationalfirearms.museum", - "nationalheritage.museum", - "nativeamerican.museum", - "naturalhistory.museum", - "naturalhistorymuseum.museum", - "naturalsciences.museum", - "nature.museum", - "naturhistorisches.museum", - "natuurwetenschappen.museum", - "naumburg.museum", - "naval.museum", - "nebraska.museum", - "neues.museum", - "newhampshire.museum", - "newjersey.museum", - "newmexico.museum", - "newport.museum", - "newspaper.museum", - "newyork.museum", - "niepce.museum", - "norfolk.museum", - "north.museum", - "nrw.museum", - "nyc.museum", - "nyny.museum", - "oceanographic.museum", - "oceanographique.museum", - "omaha.museum", - "online.museum", - "ontario.museum", - "openair.museum", - "oregon.museum", - "oregontrail.museum", - "otago.museum", - "oxford.museum", - "pacific.museum", - "paderborn.museum", - "palace.museum", - "paleo.museum", - "palmsprings.museum", - "panama.museum", - "paris.museum", - "pasadena.museum", - "pharmacy.museum", - "philadelphia.museum", - "philadelphiaarea.museum", - "philately.museum", - "phoenix.museum", - "photography.museum", - "pilots.museum", - "pittsburgh.museum", - "planetarium.museum", - "plantation.museum", - "plants.museum", - "plaza.museum", - "portal.museum", - "portland.museum", - "portlligat.museum", - "posts-and-telecommunications.museum", - "preservation.museum", - "presidio.museum", - "press.museum", - "project.museum", - "public.museum", - "pubol.museum", - "quebec.museum", - "railroad.museum", - "railway.museum", - "research.museum", - "resistance.museum", - "riodejaneiro.museum", - "rochester.museum", - "rockart.museum", - "roma.museum", - "russia.museum", - "saintlouis.museum", - "salem.museum", - "salvadordali.museum", - "salzburg.museum", - "sandiego.museum", - "sanfrancisco.museum", - "santabarbara.museum", - "santacruz.museum", - "santafe.museum", - "saskatchewan.museum", - "satx.museum", - "savannahga.museum", - "schlesisches.museum", - "schoenbrunn.museum", - "schokoladen.museum", - "school.museum", - "schweiz.museum", - "science.museum", - "scienceandhistory.museum", - "scienceandindustry.museum", - "sciencecenter.museum", - "sciencecenters.museum", - "science-fiction.museum", - "sciencehistory.museum", - "sciences.museum", - "sciencesnaturelles.museum", - "scotland.museum", - "seaport.museum", - "settlement.museum", - "settlers.museum", - "shell.museum", - "sherbrooke.museum", - "sibenik.museum", - "silk.museum", - "ski.museum", - "skole.museum", - "society.museum", - "sologne.museum", - "soundandvision.museum", - "southcarolina.museum", - "southwest.museum", - "space.museum", - "spy.museum", - "square.museum", - "stadt.museum", - "stalbans.museum", - "starnberg.museum", - "state.museum", - "stateofdelaware.museum", - "station.museum", - "steam.museum", - "steiermark.museum", - "stjohn.museum", - "stockholm.museum", - "stpetersburg.museum", - "stuttgart.museum", - "suisse.museum", - "surgeonshall.museum", - "surrey.museum", - "svizzera.museum", - "sweden.museum", - "sydney.museum", - "tank.museum", - "tcm.museum", - "technology.museum", - "telekommunikation.museum", - "television.museum", - "texas.museum", - "textile.museum", - "theater.museum", - "time.museum", - "timekeeping.museum", - "topology.museum", - "torino.museum", - "touch.museum", - "town.museum", - "transport.museum", - "tree.museum", - "trolley.museum", - "trust.museum", - "trustee.museum", - "uhren.museum", - "ulm.museum", - "undersea.museum", - "university.museum", - "usa.museum", - "usantiques.museum", - "usarts.museum", - "uscountryestate.museum", - "usculture.museum", - "usdecorativearts.museum", - "usgarden.museum", - "ushistory.museum", - "ushuaia.museum", - "uslivinghistory.museum", - "utah.museum", - "uvic.museum", - "valley.museum", - "vantaa.museum", - "versailles.museum", - "viking.museum", - "village.museum", - "virginia.museum", - "virtual.museum", - "virtuel.museum", - "vlaanderen.museum", - "volkenkunde.museum", - "wales.museum", - "wallonie.museum", - "war.museum", - "washingtondc.museum", - "watchandclock.museum", - "watch-and-clock.museum", - "western.museum", - "westfalen.museum", - "whaling.museum", - "wildlife.museum", - "williamsburg.museum", - "windmill.museum", - "workshop.museum", - "york.museum", - "yorkshire.museum", - "yosemite.museum", - "youth.museum", - "zoological.museum", - "zoology.museum", - "xn--9dbhblg6di.museum", - "xn--h1aegh.museum", "mv", "aero.mv", "biz.mv", @@ -5133,52 +4604,60 @@ var rules = [...]string{ "turystyka.pl", "gov.pl", "ap.gov.pl", + "griw.gov.pl", "ic.gov.pl", "is.gov.pl", - "us.gov.pl", "kmpsp.gov.pl", + "konsulat.gov.pl", "kppsp.gov.pl", - "kwpsp.gov.pl", - "psp.gov.pl", - "wskr.gov.pl", "kwp.gov.pl", + "kwpsp.gov.pl", + "mup.gov.pl", "mw.gov.pl", - "ug.gov.pl", - "um.gov.pl", - "umig.gov.pl", - "ugim.gov.pl", - "upow.gov.pl", - "uw.gov.pl", - "starostwo.gov.pl", + "oia.gov.pl", + "oirm.gov.pl", + "oke.gov.pl", + "oow.gov.pl", + "oschr.gov.pl", + "oum.gov.pl", "pa.gov.pl", + "pinb.gov.pl", + "piw.gov.pl", "po.gov.pl", + "pr.gov.pl", + "psp.gov.pl", "psse.gov.pl", "pup.gov.pl", "rzgw.gov.pl", "sa.gov.pl", + "sdn.gov.pl", + "sko.gov.pl", "so.gov.pl", "sr.gov.pl", - "wsa.gov.pl", - "sko.gov.pl", + "starostwo.gov.pl", + "ug.gov.pl", + "ugim.gov.pl", + "um.gov.pl", + "umig.gov.pl", + "upow.gov.pl", + "uppo.gov.pl", + "us.gov.pl", + "uw.gov.pl", "uzs.gov.pl", + "wif.gov.pl", "wiih.gov.pl", "winb.gov.pl", - "pinb.gov.pl", "wios.gov.pl", "witd.gov.pl", - "wzmiuw.gov.pl", - "piw.gov.pl", "wiw.gov.pl", - "griw.gov.pl", - "wif.gov.pl", - "oum.gov.pl", - "sdn.gov.pl", - "zp.gov.pl", - "uppo.gov.pl", - "mup.gov.pl", + "wkz.gov.pl", + "wsa.gov.pl", + "wskr.gov.pl", + "wsse.gov.pl", "wuoz.gov.pl", - "konsulat.gov.pl", - "oirm.gov.pl", + "wzmiuw.gov.pl", + "zp.gov.pl", + "zpisdn.gov.pl", "augustow.pl", "babia-gora.pl", "bedzin.pl", @@ -5722,6 +5201,7 @@ var rules = [...]string{ "kirovograd.ua", "km.ua", "kr.ua", + "kropyvnytskyi.ua", "krym.ua", "ks.ua", "kv.ua", @@ -6063,18 +5543,84 @@ var rules = [...]string{ "net.vi", "org.vi", "vn", + "ac.vn", + "ai.vn", + "biz.vn", "com.vn", - "net.vn", - "org.vn", "edu.vn", "gov.vn", - "int.vn", - "ac.vn", - "biz.vn", + "health.vn", + "id.vn", "info.vn", + "int.vn", + "io.vn", "name.vn", + "net.vn", + "org.vn", "pro.vn", - "health.vn", + "angiang.vn", + "bacgiang.vn", + "backan.vn", + "baclieu.vn", + "bacninh.vn", + "baria-vungtau.vn", + "bentre.vn", + "binhdinh.vn", + "binhduong.vn", + "binhphuoc.vn", + "binhthuan.vn", + "camau.vn", + "cantho.vn", + "caobang.vn", + "daklak.vn", + "daknong.vn", + "danang.vn", + "dienbien.vn", + "dongnai.vn", + "dongthap.vn", + "gialai.vn", + "hagiang.vn", + "haiduong.vn", + "haiphong.vn", + "hanam.vn", + "hanoi.vn", + "hatinh.vn", + "haugiang.vn", + "hoabinh.vn", + "hungyen.vn", + "khanhhoa.vn", + "kiengiang.vn", + "kontum.vn", + "laichau.vn", + "lamdong.vn", + "langson.vn", + "laocai.vn", + "longan.vn", + "namdinh.vn", + "nghean.vn", + "ninhbinh.vn", + "ninhthuan.vn", + "phutho.vn", + "phuyen.vn", + "quangbinh.vn", + "quangnam.vn", + "quangngai.vn", + "quangninh.vn", + "quangtri.vn", + "soctrang.vn", + "sonla.vn", + "tayninh.vn", + "thaibinh.vn", + "thainguyen.vn", + "thanhhoa.vn", + "thanhphohochiminh.vn", + "thuathienhue.vn", + "tiengiang.vn", + "travinh.vn", + "tuyenquang.vn", + "vinhlong.vn", + "vinhphuc.vn", + "yenbai.vn", "vu", "com.vu", "edu.vu", @@ -6221,7 +5767,6 @@ var rules = [...]string{ "org.zw", "aaa", "aarp", - "abarth", "abb", "abbott", "abbvie", @@ -6235,7 +5780,6 @@ var rules = [...]string{ "accountants", "aco", "actor", - "adac", "ads", "adult", "aeg", @@ -6249,7 +5793,6 @@ var rules = [...]string{ "airforce", "airtel", "akdn", - "alfaromeo", "alibaba", "alipay", "allfinanz", @@ -6445,7 +5988,6 @@ var rules = [...]string{ "contact", "contractors", "cooking", - "cookingchannel", "cool", "corsica", "country", @@ -6554,7 +6096,6 @@ var rules = [...]string{ "feedback", "ferrari", "ferrero", - "fiat", "fidelity", "fido", "film", @@ -6576,7 +6117,6 @@ var rules = [...]string{ "fly", "foo", "food", - "foodnetwork", "football", "ford", "forex", @@ -6661,7 +6201,6 @@ var rules = [...]string{ "helsinki", "here", "hermes", - "hgtv", "hiphop", "hisamitsu", "hitachi", @@ -6680,7 +6219,6 @@ var rules = [...]string{ "host", "hosting", "hot", - "hoteles", "hotels", "hotmail", "house", @@ -6761,7 +6299,6 @@ var rules = [...]string{ "lamborghini", "lamer", "lancaster", - "lancia", "land", "landrover", "lanxess", @@ -6789,7 +6326,6 @@ var rules = [...]string{ "limited", "limo", "lincoln", - "linde", "link", "lipsy", "live", @@ -6800,7 +6336,6 @@ var rules = [...]string{ "loans", "locker", "locus", - "loft", "lol", "london", "lotte", @@ -6813,7 +6348,6 @@ var rules = [...]string{ "lundbeck", "luxe", "luxury", - "macys", "madrid", "maif", "maison", @@ -6827,7 +6361,6 @@ var rules = [...]string{ "markets", "marriott", "marshalls", - "maserati", "mattel", "mba", "mckinsey", @@ -6868,7 +6401,6 @@ var rules = [...]string{ "mtn", "mtr", "music", - "mutual", "nab", "nagoya", "natura", @@ -6933,7 +6465,6 @@ var rules = [...]string{ "partners", "parts", "party", - "passagens", "pay", "pccw", "pet", @@ -7063,7 +6594,6 @@ var rules = [...]string{ "select", "sener", "services", - "ses", "seven", "sew", "sex", @@ -7157,7 +6687,6 @@ var rules = [...]string{ "tiaa", "tickets", "tienda", - "tiffany", "tips", "tires", "tirol", @@ -7180,7 +6709,6 @@ var rules = [...]string{ "trading", "training", "travel", - "travelchannel", "travelers", "travelersinsurance", "trust", @@ -7225,7 +6753,6 @@ var rules = [...]string{ "voting", "voto", "voyage", - "vuelos", "wales", "walmart", "walter", @@ -7316,7 +6843,6 @@ var rules = [...]string{ "xn--io0a7i", "xn--j1aef", "xn--jlq480n2rg", - "xn--jlq61u9w7b", "xn--jvr189m", "xn--kcrx77d1x4a", "xn--kput3i", @@ -7379,17 +6905,35 @@ var rules = [...]string{ "graphox.us", "*.devcdnaccesso.com", "*.on-acorn.io", + "activetrail.biz", "adobeaemcloud.com", "*.dev.adobeaemcloud.com", "hlx.live", "adobeaemcloud.net", "hlx.page", "hlx3.page", + "adobeio-static.net", + "adobeioruntime.net", "beep.pl", "airkitapps.com", "airkitapps-au.com", "airkitapps.eu", "aivencloud.com", + "akadns.net", + "akamai.net", + "akamai-staging.net", + "akamaiedge.net", + "akamaiedge-staging.net", + "akamaihd.net", + "akamaihd-staging.net", + "akamaiorigin.net", + "akamaiorigin-staging.net", + "akamaized.net", + "akamaized-staging.net", + "edgekey.net", + "edgekey-staging.net", + "edgesuite.net", + "edgesuite-staging.net", "barsy.ca", "*.compute.estate", "*.alces.network", @@ -7456,46 +7000,72 @@ var rules = [...]string{ "s3.dualstack.us-east-2.amazonaws.com", "s3.us-east-2.amazonaws.com", "s3-website.us-east-2.amazonaws.com", + "analytics-gateway.ap-northeast-1.amazonaws.com", + "analytics-gateway.eu-west-1.amazonaws.com", + "analytics-gateway.us-east-1.amazonaws.com", + "analytics-gateway.us-east-2.amazonaws.com", + "analytics-gateway.us-west-2.amazonaws.com", + "webview-assets.aws-cloud9.af-south-1.amazonaws.com", "vfs.cloud9.af-south-1.amazonaws.com", "webview-assets.cloud9.af-south-1.amazonaws.com", + "webview-assets.aws-cloud9.ap-east-1.amazonaws.com", "vfs.cloud9.ap-east-1.amazonaws.com", "webview-assets.cloud9.ap-east-1.amazonaws.com", + "webview-assets.aws-cloud9.ap-northeast-1.amazonaws.com", "vfs.cloud9.ap-northeast-1.amazonaws.com", "webview-assets.cloud9.ap-northeast-1.amazonaws.com", + "webview-assets.aws-cloud9.ap-northeast-2.amazonaws.com", "vfs.cloud9.ap-northeast-2.amazonaws.com", "webview-assets.cloud9.ap-northeast-2.amazonaws.com", + "webview-assets.aws-cloud9.ap-northeast-3.amazonaws.com", "vfs.cloud9.ap-northeast-3.amazonaws.com", "webview-assets.cloud9.ap-northeast-3.amazonaws.com", + "webview-assets.aws-cloud9.ap-south-1.amazonaws.com", "vfs.cloud9.ap-south-1.amazonaws.com", "webview-assets.cloud9.ap-south-1.amazonaws.com", + "webview-assets.aws-cloud9.ap-southeast-1.amazonaws.com", "vfs.cloud9.ap-southeast-1.amazonaws.com", "webview-assets.cloud9.ap-southeast-1.amazonaws.com", + "webview-assets.aws-cloud9.ap-southeast-2.amazonaws.com", "vfs.cloud9.ap-southeast-2.amazonaws.com", "webview-assets.cloud9.ap-southeast-2.amazonaws.com", + "webview-assets.aws-cloud9.ca-central-1.amazonaws.com", "vfs.cloud9.ca-central-1.amazonaws.com", "webview-assets.cloud9.ca-central-1.amazonaws.com", + "webview-assets.aws-cloud9.eu-central-1.amazonaws.com", "vfs.cloud9.eu-central-1.amazonaws.com", "webview-assets.cloud9.eu-central-1.amazonaws.com", + "webview-assets.aws-cloud9.eu-north-1.amazonaws.com", "vfs.cloud9.eu-north-1.amazonaws.com", "webview-assets.cloud9.eu-north-1.amazonaws.com", + "webview-assets.aws-cloud9.eu-south-1.amazonaws.com", "vfs.cloud9.eu-south-1.amazonaws.com", "webview-assets.cloud9.eu-south-1.amazonaws.com", + "webview-assets.aws-cloud9.eu-west-1.amazonaws.com", "vfs.cloud9.eu-west-1.amazonaws.com", "webview-assets.cloud9.eu-west-1.amazonaws.com", + "webview-assets.aws-cloud9.eu-west-2.amazonaws.com", "vfs.cloud9.eu-west-2.amazonaws.com", "webview-assets.cloud9.eu-west-2.amazonaws.com", + "webview-assets.aws-cloud9.eu-west-3.amazonaws.com", "vfs.cloud9.eu-west-3.amazonaws.com", "webview-assets.cloud9.eu-west-3.amazonaws.com", + "webview-assets.aws-cloud9.me-south-1.amazonaws.com", "vfs.cloud9.me-south-1.amazonaws.com", "webview-assets.cloud9.me-south-1.amazonaws.com", + "webview-assets.aws-cloud9.sa-east-1.amazonaws.com", "vfs.cloud9.sa-east-1.amazonaws.com", "webview-assets.cloud9.sa-east-1.amazonaws.com", + "webview-assets.aws-cloud9.us-east-1.amazonaws.com", "vfs.cloud9.us-east-1.amazonaws.com", "webview-assets.cloud9.us-east-1.amazonaws.com", + "webview-assets.aws-cloud9.us-east-2.amazonaws.com", "vfs.cloud9.us-east-2.amazonaws.com", "webview-assets.cloud9.us-east-2.amazonaws.com", + "webview-assets.aws-cloud9.us-west-1.amazonaws.com", "vfs.cloud9.us-west-1.amazonaws.com", "webview-assets.cloud9.us-west-1.amazonaws.com", + "webview-assets.aws-cloud9.us-west-2.amazonaws.com", "vfs.cloud9.us-west-2.amazonaws.com", "webview-assets.cloud9.us-west-2.amazonaws.com", "cn-north-1.eb.amazonaws.com.cn", @@ -7542,6 +7112,7 @@ var rules = [...]string{ "myasustor.com", "cdn.prod.atlassian-dev.net", "translated.page", + "autocode.dev", "myfritz.net", "onavstack.net", "*.awdev.ca", @@ -7588,6 +7159,8 @@ var rules = [...]string{ "vm.bytemark.co.uk", "cafjs.com", "mycd.eu", + "canva-apps.cn", + "canva-apps.com", "drr.ac", "uwu.ai", "carrd.co", @@ -7653,8 +7226,11 @@ var rules = [...]string{ "cloudcontrolled.com", "cloudcontrolapp.com", "*.cloudera.site", - "pages.dev", + "cf-ipfs.com", + "cloudflare-ipfs.com", "trycloudflare.com", + "pages.dev", + "r2.dev", "workers.dev", "wnext.app", "co.ca", @@ -8227,6 +7803,7 @@ var rules = [...]string{ "channelsdvr.net", "u.channelsdvr.net", "edgecompute.app", + "fastly-edge.com", "fastly-terrarium.com", "fastlylb.net", "map.fastlylb.net", @@ -8566,6 +8143,7 @@ var rules = [...]string{ "ngo.ng", "edu.scot", "sch.so", + "ie.ua", "hostyhosting.io", "xn--hkkinen-5wa.fi", "*.moonscale.io", @@ -8633,7 +8211,6 @@ var rules = [...]string{ "iobb.net", "mel.cloudlets.com.au", "cloud.interhostsolutions.be", - "users.scale.virtualcloud.com.br", "mycloud.by", "alp1.ae.flow.ch", "appengine.flow.ch", @@ -8657,9 +8234,7 @@ var rules = [...]string{ "de.trendhosting.cloud", "jele.club", "amscompute.com", - "clicketcloud.com", "dopaas.com", - "hidora.com", "paas.hosted-by-previder.com", "rag-cloud.hosteur.com", "rag-cloud-ch.hosteur.com", @@ -8834,6 +8409,7 @@ var rules = [...]string{ "azurestaticapps.net", "1.azurestaticapps.net", "2.azurestaticapps.net", + "3.azurestaticapps.net", "centralus.azurestaticapps.net", "eastasia.azurestaticapps.net", "eastus2.azurestaticapps.net", @@ -8864,7 +8440,19 @@ var rules = [...]string{ "cloud.nospamproxy.com", "netlify.app", "4u.com", + "ngrok.app", + "ngrok-free.app", + "ngrok.dev", + "ngrok-free.dev", "ngrok.io", + "ap.ngrok.io", + "au.ngrok.io", + "eu.ngrok.io", + "in.ngrok.io", + "jp.ngrok.io", + "sa.ngrok.io", + "us.ngrok.io", + "ngrok.pizza", "nh-serv.co.uk", "nfshost.com", "*.developer.app", @@ -9084,6 +8672,7 @@ var rules = [...]string{ "eu.pythonanywhere.com", "qoto.io", "qualifioapp.com", + "ladesk.com", "qbuser.com", "cloudsite.builders", "instances.spawn.cc", @@ -9132,6 +8721,53 @@ var rules = [...]string{ "xn--h1aliz.xn--p1acf", "xn--90a1af.xn--p1acf", "xn--41a.xn--p1acf", + "180r.com", + "dojin.com", + "sakuratan.com", + "sakuraweb.com", + "x0.com", + "2-d.jp", + "bona.jp", + "crap.jp", + "daynight.jp", + "eek.jp", + "flop.jp", + "halfmoon.jp", + "jeez.jp", + "matrix.jp", + "mimoza.jp", + "ivory.ne.jp", + "mail-box.ne.jp", + "mints.ne.jp", + "mokuren.ne.jp", + "opal.ne.jp", + "sakura.ne.jp", + "sumomo.ne.jp", + "topaz.ne.jp", + "netgamers.jp", + "nyanta.jp", + "o0o0.jp", + "rdy.jp", + "rgr.jp", + "rulez.jp", + "s3.isk01.sakurastorage.jp", + "s3.isk02.sakurastorage.jp", + "saloon.jp", + "sblo.jp", + "skr.jp", + "tank.jp", + "uh-oh.jp", + "undo.jp", + "rs.webaccel.jp", + "user.webaccel.jp", + "websozai.jp", + "xii.jp", + "squares.net", + "jpn.org", + "kirara.st", + "x0.to", + "from.tv", + "sakura.tv", "*.builder.code.com", "*.dev-builder.code.com", "*.stg-builder.code.com", @@ -9204,6 +8840,9 @@ var rules = [...]string{ "beta.bounty-full.com", "small-web.org", "vp4.me", + "snowflake.app", + "privatelink.snowflake.app", + "streamlit.app", "streamlitapp.com", "try-snowplow.com", "srht.site", @@ -9243,6 +8882,7 @@ var rules = [...]string{ "myspreadshop.se", "myspreadshop.co.uk", "api.stdlib.com", + "storipress.app", "storj.farm", "utwente.io", "soc.srcf.net", @@ -9272,6 +8912,8 @@ var rules = [...]string{ "vpnplus.to", "direct.quickconnect.to", "tabitorder.co.il", + "mytabit.co.il", + "mytabit.com", "taifun-dns.de", "beta.tailscale.net", "ts.net", @@ -9350,6 +8992,7 @@ var rules = [...]string{ "hk.org", "ltd.hk", "inc.hk", + "it.com", "name.pm", "sch.tf", "biz.wf", @@ -9472,7 +9115,6 @@ var rules = [...]string{ var nodeLabels = [...]string{ "aaa", "aarp", - "abarth", "abb", "abbott", "abbvie", @@ -9488,7 +9130,6 @@ var nodeLabels = [...]string{ "aco", "actor", "ad", - "adac", "ads", "adult", "ae", @@ -9508,7 +9149,6 @@ var nodeLabels = [...]string{ "airtel", "akdn", "al", - "alfaromeo", "alibaba", "alipay", "allfinanz", @@ -9750,7 +9390,6 @@ var nodeLabels = [...]string{ "contact", "contractors", "cooking", - "cookingchannel", "cool", "coop", "corsica", @@ -9882,7 +9521,6 @@ var nodeLabels = [...]string{ "ferrari", "ferrero", "fi", - "fiat", "fidelity", "fido", "film", @@ -9908,7 +9546,6 @@ var nodeLabels = [...]string{ "fo", "foo", "food", - "foodnetwork", "football", "ford", "forex", @@ -10014,7 +9651,6 @@ var nodeLabels = [...]string{ "helsinki", "here", "hermes", - "hgtv", "hiphop", "hisamitsu", "hitachi", @@ -10036,7 +9672,6 @@ var nodeLabels = [...]string{ "host", "hosting", "hot", - "hoteles", "hotels", "hotmail", "house", @@ -10149,7 +9784,6 @@ var nodeLabels = [...]string{ "lamborghini", "lamer", "lancaster", - "lancia", "land", "landrover", "lanxess", @@ -10180,7 +9814,6 @@ var nodeLabels = [...]string{ "limited", "limo", "lincoln", - "linde", "link", "lipsy", "live", @@ -10192,7 +9825,6 @@ var nodeLabels = [...]string{ "loans", "locker", "locus", - "loft", "lol", "london", "lotte", @@ -10212,7 +9844,6 @@ var nodeLabels = [...]string{ "lv", "ly", "ma", - "macys", "madrid", "maif", "maison", @@ -10226,7 +9857,6 @@ var nodeLabels = [...]string{ "markets", "marriott", "marshalls", - "maserati", "mattel", "mba", "mc", @@ -10286,7 +9916,6 @@ var nodeLabels = [...]string{ "mu", "museum", "music", - "mutual", "mv", "mw", "mx", @@ -10374,7 +10003,6 @@ var nodeLabels = [...]string{ "partners", "parts", "party", - "passagens", "pay", "pccw", "pe", @@ -10530,7 +10158,6 @@ var nodeLabels = [...]string{ "select", "sener", "services", - "ses", "seven", "sew", "sex", @@ -10647,7 +10274,6 @@ var nodeLabels = [...]string{ "tiaa", "tickets", "tienda", - "tiffany", "tips", "tires", "tirol", @@ -10677,7 +10303,6 @@ var nodeLabels = [...]string{ "trading", "training", "travel", - "travelchannel", "travelers", "travelersinsurance", "trust", @@ -10739,7 +10364,6 @@ var nodeLabels = [...]string{ "voto", "voyage", "vu", - "vuelos", "wales", "walmart", "walter", @@ -10856,7 +10480,6 @@ var nodeLabels = [...]string{ "xn--j1amh", "xn--j6w193g", "xn--jlq480n2rg", - "xn--jlq61u9w7b", "xn--jvr189m", "xn--kcrx77d1x4a", "xn--kprw13d", @@ -11119,18 +10742,24 @@ var nodeLabels = [...]string{ "loginline", "messerli", "netlify", + "ngrok", + "ngrok-free", "noop", "northflank", "ondigitalocean", "onflashdrive", "platform0", "run", + "snowflake", + "storipress", + "streamlit", "telebit", "typedream", "vercel", "web", "wnext", "a", + "privatelink", "bet", "com", "coop", @@ -11316,6 +10945,7 @@ var nodeLabels = [...]string{ "edu", "or", "org", + "activetrail", "cloudns", "dscloud", "dyndns", @@ -11330,10 +10960,27 @@ var nodeLabels = [...]string{ "orx", "selfip", "webhop", - "asso", - "barreau", + "africa", + "agro", + "architectes", + "assur", + "avocats", "blogspot", - "gouv", + "co", + "com", + "eco", + "econo", + "edu", + "info", + "loisirs", + "money", + "net", + "org", + "ote", + "restaurant", + "resto", + "tourism", + "univ", "com", "edu", "gov", @@ -11529,9 +11176,6 @@ var nodeLabels = [...]string{ "zlg", "blogspot", "simplesite", - "virtualcloud", - "scale", - "users", "ac", "al", "am", @@ -11772,6 +11416,7 @@ var nodeLabels = [...]string{ "ac", "ah", "bj", + "canva-apps", "com", "cq", "edu", @@ -11853,6 +11498,7 @@ var nodeLabels = [...]string{ "owo", "001www", "0emm", + "180r", "1kapp", "3utilities", "4u", @@ -11888,11 +11534,13 @@ var nodeLabels = [...]string{ "br", "builtwithdark", "cafjs", + "canva-apps", "cechire", + "cf-ipfs", "ciscofreak", - "clicketcloud", "cloudcontrolapp", "cloudcontrolled", + "cloudflare-ipfs", "cn", "co", "code", @@ -11919,6 +11567,7 @@ var nodeLabels = [...]string{ "dnsdojo", "dnsiskinky", "doesntexist", + "dojin", "dontexist", "doomdns", "dopaas", @@ -11951,6 +11600,7 @@ var nodeLabels = [...]string{ "eu", "evennode", "familyds", + "fastly-edge", "fastly-terrarium", "fastvps-server", "fbsbx", @@ -12024,7 +11674,6 @@ var nodeLabels = [...]string{ "health-carereform", "herokuapp", "herokussl", - "hidora", "hk", "hobby-site", "homelinux", @@ -12098,6 +11747,7 @@ var nodeLabels = [...]string{ "isa-geek", "isa-hockeynut", "issmarterthanyou", + "it", "jdevcloud", "jelastic", "joyent", @@ -12107,6 +11757,7 @@ var nodeLabels = [...]string{ "kozow", "kr", "ktistory", + "ladesk", "likes-pie", "likescandy", "linode", @@ -12133,6 +11784,7 @@ var nodeLabels = [...]string{ "myshopblocks", "myshopify", "myspreadshop", + "mytabit", "mythic-beasts", "mytuleap", "myvnc", @@ -12179,6 +11831,8 @@ var nodeLabels = [...]string{ "rhcloud", "ru", "sa", + "sakuratan", + "sakuraweb", "saves-the-whales", "scrysec", "securitytactics", @@ -12241,6 +11895,7 @@ var nodeLabels = [...]string{ "wphostedmail", "wpmucdn", "writesthisblog", + "x0", "xnbay", "yolasite", "za", @@ -12295,107 +11950,154 @@ var nodeLabels = [...]string{ "us-east-2", "us-west-1", "us-west-2", + "aws-cloud9", "cloud9", + "webview-assets", "vfs", "webview-assets", + "aws-cloud9", "cloud9", + "webview-assets", "vfs", "webview-assets", + "analytics-gateway", + "aws-cloud9", "cloud9", "dualstack", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", "dualstack", "s3", "s3-website", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", + "webview-assets", "vfs", "webview-assets", + "aws-cloud9", "cloud9", "dualstack", "s3", "s3-website", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", "dualstack", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", "dualstack", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", "dualstack", "s3", "s3-website", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", "dualstack", "s3", "s3-website", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", + "webview-assets", "vfs", "webview-assets", + "aws-cloud9", "cloud9", + "webview-assets", "vfs", "webview-assets", + "analytics-gateway", + "aws-cloud9", "cloud9", "dualstack", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", "dualstack", "s3", "s3-website", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", "dualstack", "s3", "s3-website", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", + "webview-assets", "vfs", "webview-assets", + "aws-cloud9", "cloud9", "dualstack", + "webview-assets", "vfs", "webview-assets", "s3", + "analytics-gateway", + "aws-cloud9", "cloud9", "dualstack", + "webview-assets", "vfs", "webview-assets", "s3", + "analytics-gateway", + "aws-cloud9", "cloud9", "dualstack", "s3", "s3-website", + "webview-assets", "vfs", "webview-assets", "s3", + "aws-cloud9", "cloud9", + "webview-assets", "vfs", "webview-assets", + "analytics-gateway", + "aws-cloud9", "cloud9", + "webview-assets", "vfs", "webview-assets", "r", @@ -12610,6 +12312,7 @@ var nodeLabels = [...]string{ "pages", "customer", "bss", + "autocode", "curv", "deno", "deno-staging", @@ -12623,8 +12326,11 @@ var nodeLabels = [...]string{ "localcert", "loginline", "mediatech", + "ngrok", + "ngrok-free", "pages", "platter-app", + "r2", "shiftcrypto", "stg", "stgstage", @@ -13016,6 +12722,7 @@ var nodeLabels = [...]string{ "net", "org", "blogspot", + "mytabit", "ravpage", "tabitorder", "ac", @@ -13176,6 +12883,13 @@ var nodeLabels = [...]string{ "dyndns", "id", "apps", + "ap", + "au", + "eu", + "in", + "jp", + "sa", + "us", "stage", "mock", "sys", @@ -13649,6 +13363,7 @@ var nodeLabels = [...]string{ "net", "org", "sch", + "2-d", "ac", "ad", "aichi", @@ -13662,6 +13377,7 @@ var nodeLabels = [...]string{ "bitter", "blogspot", "blush", + "bona", "boo", "boy", "boyfriend", @@ -13682,18 +13398,22 @@ var nodeLabels = [...]string{ "cocotte", "coolblog", "cranky", + "crap", "cutegirl", "daa", + "daynight", "deca", "deci", "digick", "ed", + "eek", "egoism", "ehime", "fakefur", "fashionstore", "fem", "flier", + "flop", "floppy", "fool", "frenchkiss", @@ -13710,6 +13430,7 @@ var nodeLabels = [...]string{ "greater", "gunma", "hacca", + "halfmoon", "handcrafted", "heavy", "her", @@ -13725,6 +13446,7 @@ var nodeLabels = [...]string{ "ishikawa", "itigo", "iwate", + "jeez", "jellybean", "kagawa", "kagoshima", @@ -13748,7 +13470,9 @@ var nodeLabels = [...]string{ "lovepop", "lovesick", "main", + "matrix", "mie", + "mimoza", "miyagi", "miyazaki", "mods", @@ -13761,10 +13485,13 @@ var nodeLabels = [...]string{ "namaste", "nara", "ne", + "netgamers", "niigata", "nikita", "nobushi", "noor", + "nyanta", + "o0o0", "oita", "okayama", "okinawa", @@ -13785,22 +13512,30 @@ var nodeLabels = [...]string{ "pussycat", "pya", "raindrop", + "rdy", "readymade", + "rgr", + "rulez", "sadist", "saga", "saitama", + "sakurastorage", + "saloon", "sapporo", + "sblo", "schoolbus", "secret", "sendai", "shiga", "shimane", "shizuoka", + "skr", "staba", "stripper", "sub", "sunnyday", "supersale", + "tank", "theshop", "thick", "tochigi", @@ -13809,7 +13544,9 @@ var nodeLabels = [...]string{ "tonkotsu", "tottori", "toyama", + "uh-oh", "under", + "undo", "upper", "usercontent", "velvet", @@ -13818,8 +13555,11 @@ var nodeLabels = [...]string{ "vivian", "wakayama", "watson", + "webaccel", "weblike", + "websozai", "whitesnow", + "xii", "xn--0trq7p7nn", "xn--1ctwo", "xn--1lqs03n", @@ -14954,6 +14694,14 @@ var nodeLabels = [...]string{ "yoshino", "aseinet", "gehirn", + "ivory", + "mail-box", + "mints", + "mokuren", + "opal", + "sakura", + "sumomo", + "topaz", "user", "aga", "agano", @@ -15221,6 +14969,10 @@ var nodeLabels = [...]string{ "yoshida", "yoshikawa", "yoshimi", + "isk01", + "isk02", + "s3", + "s3", "city", "city", "aisho", @@ -15476,6 +15228,8 @@ var nodeLabels = [...]string{ "wakayama", "yuasa", "yura", + "rs", + "user", "asahi", "funagata", "higashine", @@ -15865,552 +15619,6 @@ var nodeLabels = [...]string{ "net", "or", "org", - "academy", - "agriculture", - "air", - "airguard", - "alabama", - "alaska", - "amber", - "ambulance", - "american", - "americana", - "americanantiques", - "americanart", - "amsterdam", - "and", - "annefrank", - "anthro", - "anthropology", - "antiques", - "aquarium", - "arboretum", - "archaeological", - "archaeology", - "architecture", - "art", - "artanddesign", - "artcenter", - "artdeco", - "arteducation", - "artgallery", - "arts", - "artsandcrafts", - "asmatart", - "assassination", - "assisi", - "association", - "astronomy", - "atlanta", - "austin", - "australia", - "automotive", - "aviation", - "axis", - "badajoz", - "baghdad", - "bahn", - "bale", - "baltimore", - "barcelona", - "baseball", - "basel", - "baths", - "bauern", - "beauxarts", - "beeldengeluid", - "bellevue", - "bergbau", - "berkeley", - "berlin", - "bern", - "bible", - "bilbao", - "bill", - "birdart", - "birthplace", - "bonn", - "boston", - "botanical", - "botanicalgarden", - "botanicgarden", - "botany", - "brandywinevalley", - "brasil", - "bristol", - "british", - "britishcolumbia", - "broadcast", - "brunel", - "brussel", - "brussels", - "bruxelles", - "building", - "burghof", - "bus", - "bushey", - "cadaques", - "california", - "cambridge", - "can", - "canada", - "capebreton", - "carrier", - "cartoonart", - "casadelamoneda", - "castle", - "castres", - "celtic", - "center", - "chattanooga", - "cheltenham", - "chesapeakebay", - "chicago", - "children", - "childrens", - "childrensgarden", - "chiropractic", - "chocolate", - "christiansburg", - "cincinnati", - "cinema", - "circus", - "civilisation", - "civilization", - "civilwar", - "clinton", - "clock", - "coal", - "coastaldefence", - "cody", - "coldwar", - "collection", - "colonialwilliamsburg", - "coloradoplateau", - "columbia", - "columbus", - "communication", - "communications", - "community", - "computer", - "computerhistory", - "contemporary", - "contemporaryart", - "convent", - "copenhagen", - "corporation", - "corvette", - "costume", - "countryestate", - "county", - "crafts", - "cranbrook", - "creation", - "cultural", - "culturalcenter", - "culture", - "cyber", - "cymru", - "dali", - "dallas", - "database", - "ddr", - "decorativearts", - "delaware", - "delmenhorst", - "denmark", - "depot", - "design", - "detroit", - "dinosaur", - "discovery", - "dolls", - "donostia", - "durham", - "eastafrica", - "eastcoast", - "education", - "educational", - "egyptian", - "eisenbahn", - "elburg", - "elvendrell", - "embroidery", - "encyclopedic", - "england", - "entomology", - "environment", - "environmentalconservation", - "epilepsy", - "essex", - "estate", - "ethnology", - "exeter", - "exhibition", - "family", - "farm", - "farmequipment", - "farmers", - "farmstead", - "field", - "figueres", - "filatelia", - "film", - "fineart", - "finearts", - "finland", - "flanders", - "florida", - "force", - "fortmissoula", - "fortworth", - "foundation", - "francaise", - "frankfurt", - "franziskaner", - "freemasonry", - "freiburg", - "fribourg", - "frog", - "fundacio", - "furniture", - "gallery", - "garden", - "gateway", - "geelvinck", - "gemological", - "geology", - "georgia", - "giessen", - "glas", - "glass", - "gorge", - "grandrapids", - "graz", - "guernsey", - "halloffame", - "hamburg", - "handson", - "harvestcelebration", - "hawaii", - "health", - "heimatunduhren", - "hellas", - "helsinki", - "hembygdsforbund", - "heritage", - "histoire", - "historical", - "historicalsociety", - "historichouses", - "historisch", - "historisches", - "history", - "historyofscience", - "horology", - "house", - "humanities", - "illustration", - "imageandsound", - "indian", - "indiana", - "indianapolis", - "indianmarket", - "intelligence", - "interactive", - "iraq", - "iron", - "isleofman", - "jamison", - "jefferson", - "jerusalem", - "jewelry", - "jewish", - "jewishart", - "jfk", - "journalism", - "judaica", - "judygarland", - "juedisches", - "juif", - "karate", - "karikatur", - "kids", - "koebenhavn", - "koeln", - "kunst", - "kunstsammlung", - "kunstunddesign", - "labor", - "labour", - "lajolla", - "lancashire", - "landes", - "lans", - "larsson", - "lewismiller", - "lincoln", - "linz", - "living", - "livinghistory", - "localhistory", - "london", - "losangeles", - "louvre", - "loyalist", - "lucerne", - "luxembourg", - "luzern", - "mad", - "madrid", - "mallorca", - "manchester", - "mansion", - "mansions", - "manx", - "marburg", - "maritime", - "maritimo", - "maryland", - "marylhurst", - "media", - "medical", - "medizinhistorisches", - "meeres", - "memorial", - "mesaverde", - "michigan", - "midatlantic", - "military", - "mill", - "miners", - "mining", - "minnesota", - "missile", - "missoula", - "modern", - "moma", - "money", - "monmouth", - "monticello", - "montreal", - "moscow", - "motorcycle", - "muenchen", - "muenster", - "mulhouse", - "muncie", - "museet", - "museumcenter", - "museumvereniging", - "music", - "national", - "nationalfirearms", - "nationalheritage", - "nativeamerican", - "naturalhistory", - "naturalhistorymuseum", - "naturalsciences", - "nature", - "naturhistorisches", - "natuurwetenschappen", - "naumburg", - "naval", - "nebraska", - "neues", - "newhampshire", - "newjersey", - "newmexico", - "newport", - "newspaper", - "newyork", - "niepce", - "norfolk", - "north", - "nrw", - "nyc", - "nyny", - "oceanographic", - "oceanographique", - "omaha", - "online", - "ontario", - "openair", - "oregon", - "oregontrail", - "otago", - "oxford", - "pacific", - "paderborn", - "palace", - "paleo", - "palmsprings", - "panama", - "paris", - "pasadena", - "pharmacy", - "philadelphia", - "philadelphiaarea", - "philately", - "phoenix", - "photography", - "pilots", - "pittsburgh", - "planetarium", - "plantation", - "plants", - "plaza", - "portal", - "portland", - "portlligat", - "posts-and-telecommunications", - "preservation", - "presidio", - "press", - "project", - "public", - "pubol", - "quebec", - "railroad", - "railway", - "research", - "resistance", - "riodejaneiro", - "rochester", - "rockart", - "roma", - "russia", - "saintlouis", - "salem", - "salvadordali", - "salzburg", - "sandiego", - "sanfrancisco", - "santabarbara", - "santacruz", - "santafe", - "saskatchewan", - "satx", - "savannahga", - "schlesisches", - "schoenbrunn", - "schokoladen", - "school", - "schweiz", - "science", - "science-fiction", - "scienceandhistory", - "scienceandindustry", - "sciencecenter", - "sciencecenters", - "sciencehistory", - "sciences", - "sciencesnaturelles", - "scotland", - "seaport", - "settlement", - "settlers", - "shell", - "sherbrooke", - "sibenik", - "silk", - "ski", - "skole", - "society", - "sologne", - "soundandvision", - "southcarolina", - "southwest", - "space", - "spy", - "square", - "stadt", - "stalbans", - "starnberg", - "state", - "stateofdelaware", - "station", - "steam", - "steiermark", - "stjohn", - "stockholm", - "stpetersburg", - "stuttgart", - "suisse", - "surgeonshall", - "surrey", - "svizzera", - "sweden", - "sydney", - "tank", - "tcm", - "technology", - "telekommunikation", - "television", - "texas", - "textile", - "theater", - "time", - "timekeeping", - "topology", - "torino", - "touch", - "town", - "transport", - "tree", - "trolley", - "trust", - "trustee", - "uhren", - "ulm", - "undersea", - "university", - "usa", - "usantiques", - "usarts", - "uscountryestate", - "usculture", - "usdecorativearts", - "usgarden", - "ushistory", - "ushuaia", - "uslivinghistory", - "utah", - "uvic", - "valley", - "vantaa", - "versailles", - "viking", - "village", - "virginia", - "virtual", - "virtuel", - "vlaanderen", - "volkenkunde", - "wales", - "wallonie", - "war", - "washingtondc", - "watch-and-clock", - "watchandclock", - "western", - "westfalen", - "whaling", - "wildlife", - "williamsburg", - "windmill", - "workshop", - "xn--9dbhblg6di", - "xn--comunicaes-v6a2o", - "xn--correios-e-telecomunicaes-ghc29a", - "xn--h1aegh", - "xn--lns-qla", - "york", - "yorkshire", - "yosemite", - "youth", - "zoological", - "zoology", "aero", "biz", "com", @@ -16483,6 +15691,19 @@ var nodeLabels = [...]string{ "asso", "nom", "adobeaemcloud", + "adobeio-static", + "adobeioruntime", + "akadns", + "akamai", + "akamai-staging", + "akamaiedge", + "akamaiedge-staging", + "akamaihd", + "akamaihd-staging", + "akamaiorigin", + "akamaiorigin-staging", + "akamaized", + "akamaized-staging", "alwaysdata", "appudo", "at-band-camp", @@ -16532,6 +15753,10 @@ var nodeLabels = [...]string{ "dynv6", "eating-organic", "edgeapp", + "edgekey", + "edgekey-staging", + "edgesuite", + "edgesuite-staging", "elastx", "endofinternet", "familyds", @@ -16612,6 +15837,7 @@ var nodeLabels = [...]string{ "shopselect", "siteleaf", "square7", + "squares", "srcf", "static-access", "supabase", @@ -16634,6 +15860,7 @@ var nodeLabels = [...]string{ "cdn", "1", "2", + "3", "centralus", "eastasia", "eastus2", @@ -17619,6 +16846,7 @@ var nodeLabels = [...]string{ "is-very-nice", "is-very-sweet", "isa-geek", + "jpn", "js", "kicks-ass", "mayfirst", @@ -17774,6 +17002,7 @@ var nodeLabels = [...]string{ "org", "framer", "1337", + "ngrok", "biz", "com", "edu", @@ -17978,12 +17207,17 @@ var nodeLabels = [...]string{ "kwpsp", "mup", "mw", + "oia", "oirm", + "oke", + "oow", + "oschr", "oum", "pa", "pinb", "piw", "po", + "pr", "psp", "psse", "pup", @@ -18009,11 +17243,14 @@ var nodeLabels = [...]string{ "wios", "witd", "wiw", + "wkz", "wsa", "wskr", + "wsse", "wuoz", "wzmiuw", "zp", + "zpisdn", "co", "name", "own", @@ -18355,6 +17592,7 @@ var nodeLabels = [...]string{ "consulado", "edu", "embaixada", + "kirara", "mil", "net", "noho", @@ -18501,6 +17739,7 @@ var nodeLabels = [...]string{ "quickconnect", "rdv", "vpnplus", + "x0", "direct", "prequalifyme", "now-dns", @@ -18549,7 +17788,9 @@ var nodeLabels = [...]string{ "travel", "better-than", "dyndns", + "from", "on-the-web", + "sakura", "worse-than", "blogspot", "club", @@ -18602,6 +17843,7 @@ var nodeLabels = [...]string{ "dp", "edu", "gov", + "ie", "if", "in", "inf", @@ -18616,6 +17858,7 @@ var nodeLabels = [...]string{ "kirovograd", "km", "kr", + "kropyvnytskyi", "krym", "ks", "kv", @@ -19010,18 +18253,84 @@ var nodeLabels = [...]string{ "net", "org", "ac", + "ai", + "angiang", + "bacgiang", + "backan", + "baclieu", + "bacninh", + "baria-vungtau", + "bentre", + "binhdinh", + "binhduong", + "binhphuoc", + "binhthuan", "biz", "blogspot", + "camau", + "cantho", + "caobang", "com", + "daklak", + "daknong", + "danang", + "dienbien", + "dongnai", + "dongthap", "edu", + "gialai", "gov", + "hagiang", + "haiduong", + "haiphong", + "hanam", + "hanoi", + "hatinh", + "haugiang", "health", + "hoabinh", + "hungyen", + "id", "info", "int", + "io", + "khanhhoa", + "kiengiang", + "kontum", + "laichau", + "lamdong", + "langson", + "laocai", + "longan", + "namdinh", "name", "net", + "nghean", + "ninhbinh", + "ninhthuan", "org", + "phutho", + "phuyen", "pro", + "quangbinh", + "quangnam", + "quangngai", + "quangninh", + "quangtri", + "soctrang", + "sonla", + "tayninh", + "thaibinh", + "thainguyen", + "thanhhoa", + "thanhphohochiminh", + "thuathienhue", + "tiengiang", + "travinh", + "tuyenquang", + "vinhlong", + "vinhphuc", + "yenbai", "blog", "cn", "com", diff --git a/webdav/if.go b/webdav/if.go index 416e81cdf..e646570bb 100644 --- a/webdav/if.go +++ b/webdav/if.go @@ -24,7 +24,7 @@ type ifList struct { // parseIfHeader parses the "If: foo bar" HTTP header. The httpHeader string // should omit the "If:" prefix and have any "\r\n"s collapsed to a " ", as is -// returned by req.Header.Get("If") for a http.Request req. +// returned by req.Header.Get("If") for an http.Request req. func parseIfHeader(httpHeader string) (h ifHeader, ok bool) { s := strings.TrimSpace(httpHeader) switch tokenType, _, _ := lex(s); tokenType {