diff --git a/connection/quic.go b/connection/quic.go index 745a1593..6b10e5d1 100644 --- a/connection/quic.go +++ b/connection/quic.go @@ -9,6 +9,7 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/google/uuid" "github.com/lucas-clemente/quic-go" @@ -167,7 +168,7 @@ func (q *QUICConnection) handleRPCStream(rpcStream *quicpogs.RPCServerStream) er return rpcStream.Serve(q, q.logger) } -func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16) error { +func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration) error { // Each session is a series of datagram from an eyeball to a dstIP:dstPort. // (src port, dst IP, dst port) uniquely identifies a session, so it needs a dedicated connected socket. originProxy, err := ingress.DialUDP(dstIP, dstPort) @@ -182,7 +183,7 @@ func (q *QUICConnection) RegisterUdpSession(ctx context.Context, sessionID uuid. } go func() { defer q.sessionManager.UnregisterSession(q.session.Context(), sessionID) - if err := session.Serve(q.session.Context()); err != nil { + if err := session.Serve(q.session.Context(), closeAfterIdleHint); err != nil { q.logger.Debug().Err(err).Str("sessionID", sessionID.String()).Msg("session terminated") } }() diff --git a/datagramsession/manager.go b/datagramsession/manager.go index 32156079..59193799 100644 --- a/datagramsession/manager.go +++ b/datagramsession/manager.go @@ -127,7 +127,7 @@ func (m *manager) sendToSession(datagram *newDatagram) { } // session writes to destination over a connected UDP socket, which should not be blocking, so this call doesn't // need to run in another go routine - _, err := session.writeToDst(datagram.payload) + _, err := session.transportToDst(datagram.payload) if err != nil { m.log.Err(err).Str("sessionID", datagram.sessionID.String()).Msg("Failed to write payload to session") } diff --git a/datagramsession/manager_test.go b/datagramsession/manager_test.go index 1fe11cec..7385eaf1 100644 --- a/datagramsession/manager_test.go +++ b/datagramsession/manager_test.go @@ -7,6 +7,7 @@ import ( "io" "net" "testing" + "time" "github.com/google/uuid" "github.com/rs/zerolog" @@ -21,15 +22,15 @@ func TestManagerServe(t *testing.T) { ) log := zerolog.Nop() transport := &mockQUICTransport{ - reqChan: newDatagramChannel(), - respChan: newDatagramChannel(), + reqChan: newDatagramChannel(1), + respChan: newDatagramChannel(1), } mg := NewManager(transport, &log) eyeballTracker := make(map[uuid.UUID]*datagramChannel) for i := 0; i < sessions; i++ { sessionID := uuid.New() - eyeballTracker[sessionID] = newDatagramChannel() + eyeballTracker[sessionID] = newDatagramChannel(1) } ctx, cancel := context.WithCancel(context.Background()) @@ -88,7 +89,7 @@ func TestManagerServe(t *testing.T) { sessionDone := make(chan struct{}) go func() { - session.Serve(ctx) + session.Serve(ctx, time.Minute*2) close(sessionDone) }() @@ -179,9 +180,9 @@ type datagramChannel struct { closedChan chan struct{} } -func newDatagramChannel() *datagramChannel { +func newDatagramChannel(capacity uint) *datagramChannel { return &datagramChannel{ - datagramChan: make(chan *newDatagram, 1), + datagramChan: make(chan *newDatagram, capacity), closedChan: make(chan struct{}), } } diff --git a/datagramsession/session.go b/datagramsession/session.go index acd4056d..f387d303 100644 --- a/datagramsession/session.go +++ b/datagramsession/session.go @@ -3,10 +3,15 @@ package datagramsession import ( "context" "io" + "time" "github.com/google/uuid" ) +const ( + defaultCloseIdleAfter = time.Second * 210 +) + // Each Session is a bidirectional pipe of datagrams between transport and dstConn // Currently the only implementation of transport is quic DatagramMuxer // Destination can be a connection with origin or with eyeball @@ -22,7 +27,9 @@ type Session struct { id uuid.UUID transport transport dstConn io.ReadWriteCloser - doneChan chan struct{} + // activeAtChan is used to communicate the last read/write time + activeAtChan chan time.Time + doneChan chan struct{} } func newSession(id uuid.UUID, transport transport, dstConn io.ReadWriteCloser) *Session { @@ -30,41 +37,81 @@ func newSession(id uuid.UUID, transport transport, dstConn io.ReadWriteCloser) * id: id, transport: transport, dstConn: dstConn, - doneChan: make(chan struct{}), + // activeAtChan has low capacity. It can be full when there are many concurrent read/write. markActive() will + // drop instead of blocking because last active time only needs to be an approximation + activeAtChan: make(chan time.Time, 2), + doneChan: make(chan struct{}), } } -func (s *Session) Serve(ctx context.Context) error { +func (s *Session) Serve(ctx context.Context, closeAfterIdle time.Duration) error { serveCtx, cancel := context.WithCancel(ctx) defer cancel() - go func() { - select { - case <-serveCtx.Done(): - case <-s.doneChan: - } - s.dstConn.Close() - }() + go s.waitForCloseCondition(serveCtx, closeAfterIdle) // QUIC implementation copies data to another buffer before returning https://github.com/lucas-clemente/quic-go/blob/v0.24.0/session.go#L1967-L1975 // This makes it safe to share readBuffer between iterations - readBuffer := make([]byte, 1280) + readBuffer := make([]byte, s.transport.MTU()) for { - // TODO: TUN-5303: origin proxy should determine the buffer size - n, err := s.dstConn.Read(readBuffer) - if n > 0 { - if err := s.transport.SendTo(s.id, readBuffer[:n]); err != nil { - return err - } - } - if err != nil { + if err := s.dstToTransport(readBuffer); err != nil { return err } } } -func (s *Session) writeToDst(payload []byte) (int, error) { +func (s *Session) waitForCloseCondition(ctx context.Context, closeAfterIdle time.Duration) { + if closeAfterIdle == 0 { + // provide deafult is caller doesn't specify one + closeAfterIdle = defaultCloseIdleAfter + } + // Closing dstConn cancels read so Serve function can return + defer s.dstConn.Close() + + checkIdleFreq := closeAfterIdle / 8 + checkIdleTicker := time.NewTicker(checkIdleFreq) + defer checkIdleTicker.Stop() + + activeAt := time.Now() + for { + select { + case <-ctx.Done(): + return + case <-s.doneChan: + return + case <-checkIdleTicker.C: + // The session is considered inactive if current time is after (last active time + allowed idle time) + if time.Now().After(activeAt.Add(closeAfterIdle)) { + return + } + case activeAt = <-s.activeAtChan: // Update last active time + } + } +} + +func (s *Session) dstToTransport(buffer []byte) error { + n, err := s.dstConn.Read(buffer) + s.markActive() + if n > 0 { + if err := s.transport.SendTo(s.id, buffer[:n]); err != nil { + return err + } + } + return err +} + +func (s *Session) transportToDst(payload []byte) (int, error) { + s.markActive() return s.dstConn.Write(payload) } +// Sends the last active time to the idle checker loop without blocking. activeAtChan will only be full when there +// are many concurrent read/write. It is fine to lose some precision +func (s *Session) markActive() { + select { + case s.activeAtChan <- time.Now(): + default: + } +} + func (s *Session) close() { close(s.doneChan) } diff --git a/datagramsession/session_test.go b/datagramsession/session_test.go index 6fc25b6e..b55393b6 100644 --- a/datagramsession/session_test.go +++ b/datagramsession/session_test.go @@ -1,43 +1,54 @@ package datagramsession import ( + "bytes" "context" + "fmt" + "io" "net" + "sync" "testing" + "time" "github.com/google/uuid" "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" ) // TestCloseSession makes sure a session will stop after context is done func TestSessionCtxDone(t *testing.T) { - testSessionReturns(t, true) + testSessionReturns(t, closeByContext, time.Minute*2) } // TestCloseSession makes sure a session will stop after close method is called func TestCloseSession(t *testing.T) { - testSessionReturns(t, false) + testSessionReturns(t, closeByCallingClose, time.Minute*2) } -func testSessionReturns(t *testing.T, closeByContext bool) { +// TestCloseIdle makess sure a session will stop after there is no read/write for a period defined by closeAfterIdle +func TestCloseIdle(t *testing.T) { + testSessionReturns(t, closeByTimeout, time.Millisecond*100) +} + +func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.Duration) { sessionID := uuid.New() cfdConn, originConn := net.Pipe() payload := testPayload(sessionID) transport := &mockQUICTransport{ - reqChan: newDatagramChannel(), - respChan: newDatagramChannel(), + reqChan: newDatagramChannel(1), + respChan: newDatagramChannel(1), } session := newSession(sessionID, transport, cfdConn) ctx, cancel := context.WithCancel(context.Background()) sessionDone := make(chan struct{}) go func() { - session.Serve(ctx) + session.Serve(ctx, closeAfterIdle) close(sessionDone) }() go func() { - n, err := session.writeToDst(payload) + n, err := session.transportToDst(payload) require.NoError(t, err) require.Equal(t, len(payload), n) }() @@ -47,13 +58,120 @@ func testSessionReturns(t *testing.T, closeByContext bool) { require.NoError(t, err) require.Equal(t, len(payload), n) - if closeByContext { + lastRead := time.Now() + + switch closeBy { + case closeByContext: cancel() - } else { + case closeByCallingClose: session.close() } <-sessionDone + if closeBy == closeByTimeout { + require.True(t, time.Now().After(lastRead.Add(closeAfterIdle))) + } // call cancelled again otherwise the linter will warn about possible context leak cancel() } + +type closeMethod int + +const ( + closeByContext closeMethod = iota + closeByCallingClose + closeByTimeout +) + +func TestWriteToDstSessionPreventClosed(t *testing.T) { + testActiveSessionNotClosed(t, false, true) +} + +func TestReadFromDstSessionPreventClosed(t *testing.T) { + testActiveSessionNotClosed(t, true, false) +} + +func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool) { + const closeAfterIdle = time.Millisecond * 100 + const activeTime = time.Millisecond * 500 + + sessionID := uuid.New() + cfdConn, originConn := net.Pipe() + payload := testPayload(sessionID) + transport := &mockQUICTransport{ + reqChan: newDatagramChannel(100), + respChan: newDatagramChannel(100), + } + session := newSession(sessionID, transport, cfdConn) + + startTime := time.Now() + activeUntil := startTime.Add(activeTime) + ctx, cancel := context.WithCancel(context.Background()) + errGroup, ctx := errgroup.WithContext(ctx) + errGroup.Go(func() error { + session.Serve(ctx, closeAfterIdle) + if time.Now().Before(startTime.Add(activeTime)) { + return fmt.Errorf("session closed while it's still active") + } + return nil + }) + + if readFromDst { + errGroup.Go(func() error { + for { + if time.Now().After(activeUntil) { + return nil + } + if _, err := originConn.Write(payload); err != nil { + return err + } + time.Sleep(closeAfterIdle / 2) + } + }) + } + if writeToDst { + errGroup.Go(func() error { + readBuffer := make([]byte, len(payload)) + for { + n, err := originConn.Read(readBuffer) + if err != nil { + if err == io.EOF || err == io.ErrClosedPipe { + return nil + } + return err + } + if !bytes.Equal(payload, readBuffer[:n]) { + return fmt.Errorf("payload %v is not equal to %v", readBuffer[:n], payload) + } + } + }) + errGroup.Go(func() error { + for { + if time.Now().After(activeUntil) { + return nil + } + if _, err := session.transportToDst(payload); err != nil { + return err + } + time.Sleep(closeAfterIdle / 2) + } + }) + } + + require.NoError(t, errGroup.Wait()) + cancel() +} + +func TestMarkActiveNotBlocking(t *testing.T) { + const concurrentCalls = 50 + session := newSession(uuid.New(), nil, nil) + var wg sync.WaitGroup + wg.Add(concurrentCalls) + for i := 0; i < concurrentCalls; i++ { + go func() { + session.markActive() + wg.Done() + }() + } + wg.Wait() +} diff --git a/datagramsession/transport.go b/datagramsession/transport.go index 4d078ac3..e2b73bf0 100644 --- a/datagramsession/transport.go +++ b/datagramsession/transport.go @@ -8,4 +8,6 @@ type transport interface { SendTo(sessionID uuid.UUID, payload []byte) error // ReceiveFrom reads the next datagram from the transport ReceiveFrom() (uuid.UUID, []byte, error) + // Max transmission unit of the transport + MTU() uint } diff --git a/datagramsession/transport_test.go b/datagramsession/transport_test.go index 6d2fa91e..f8c67895 100644 --- a/datagramsession/transport_test.go +++ b/datagramsession/transport_test.go @@ -22,6 +22,10 @@ func (mt *mockQUICTransport) ReceiveFrom() (uuid.UUID, []byte, error) { return mt.reqChan.Receive(context.Background()) } +func (mt *mockQUICTransport) MTU() uint { + return 1220 +} + func (mt *mockQUICTransport) newRequest(ctx context.Context, sessionID uuid.UUID, payload []byte) error { return mt.reqChan.Send(ctx, sessionID, payload) } diff --git a/quic/datagram.go b/quic/datagram.go index d32056dd..be3d52e3 100644 --- a/quic/datagram.go +++ b/quic/datagram.go @@ -57,6 +57,10 @@ func (dm *DatagramMuxer) ReceiveFrom() (uuid.UUID, []byte, error) { return ExtractSessionID(msg) } +func (dm *DatagramMuxer) MTU() uint { + return MaxDatagramFrameSize +} + // Each QUIC datagram should be suffixed with session ID. // ExtractSessionID extracts the session ID and a slice with only the payload func ExtractSessionID(b []byte) (uuid.UUID, []byte, error) { diff --git a/quic/quic_protocol.go b/quic/quic_protocol.go index 78c3c33d..bd69c3d6 100644 --- a/quic/quic_protocol.go +++ b/quic/quic_protocol.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net" + "time" capnp "zombiezen.com/go/capnproto2" "zombiezen.com/go/capnproto2/rpc" @@ -239,8 +240,8 @@ func NewRPCClientStream(ctx context.Context, stream io.ReadWriteCloser, logger * }, nil } -func (rcs *RPCClientStream) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16) error { - resp, err := rcs.client.RegisterUdpSession(ctx, sessionID, dstIP, dstPort) +func (rcs *RPCClientStream) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfterHint time.Duration) error { + resp, err := rcs.client.RegisterUdpSession(ctx, sessionID, dstIP, dstPort, closeIdleAfterHint) if err != nil { return err } diff --git a/quic/quic_protocol_test.go b/quic/quic_protocol_test.go index 93998d0b..adfdcddb 100644 --- a/quic/quic_protocol_test.go +++ b/quic/quic_protocol_test.go @@ -8,6 +8,7 @@ import ( "io" "net" "testing" + "time" "github.com/google/uuid" "github.com/rs/zerolog" @@ -15,6 +16,10 @@ import ( "github.com/stretchr/testify/require" ) +const ( + testCloseIdleAfterHint = time.Minute * 2 +) + func TestConnectRequestData(t *testing.T) { var tests = []struct { name string @@ -110,9 +115,10 @@ func TestRegisterUdpSession(t *testing.T) { serverStream := mockRPCStream{serverReader, serverWriter} rpcServer := mockRPCServer{ - sessionID: uuid.New(), - dstIP: net.IP{172, 16, 0, 1}, - dstPort: 8000, + sessionID: uuid.New(), + dstIP: net.IP{172, 16, 0, 1}, + dstPort: 8000, + closeIdleAfter: testCloseIdleAfterHint, } logger := zerolog.Nop() sessionRegisteredChan := make(chan struct{}) @@ -131,10 +137,10 @@ func TestRegisterUdpSession(t *testing.T) { rpcClientStream, err := NewRPCClientStream(context.Background(), clientStream, &logger) assert.NoError(t, err) - assert.NoError(t, rpcClientStream.RegisterUdpSession(context.Background(), rpcServer.sessionID, rpcServer.dstIP, rpcServer.dstPort)) + assert.NoError(t, rpcClientStream.RegisterUdpSession(context.Background(), rpcServer.sessionID, rpcServer.dstIP, rpcServer.dstPort, testCloseIdleAfterHint)) // Different sessionID, the RPC server should reject the registraion - assert.Error(t, rpcClientStream.RegisterUdpSession(context.Background(), uuid.New(), rpcServer.dstIP, rpcServer.dstPort)) + assert.Error(t, rpcClientStream.RegisterUdpSession(context.Background(), uuid.New(), rpcServer.dstIP, rpcServer.dstPort, testCloseIdleAfterHint)) assert.NoError(t, rpcClientStream.UnregisterUdpSession(context.Background(), rpcServer.sessionID)) @@ -146,12 +152,13 @@ func TestRegisterUdpSession(t *testing.T) { } type mockRPCServer struct { - sessionID uuid.UUID - dstIP net.IP - dstPort uint16 + sessionID uuid.UUID + dstIP net.IP + dstPort uint16 + closeIdleAfter time.Duration } -func (s mockRPCServer) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16) error { +func (s mockRPCServer) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeIdleAfter time.Duration) error { if s.sessionID != sessionID { return fmt.Errorf("expect session ID %s, got %s", s.sessionID, sessionID) } @@ -159,7 +166,10 @@ func (s mockRPCServer) RegisterUdpSession(ctx context.Context, sessionID uuid.UU return fmt.Errorf("expect destination IP %s, got %s", s.dstIP, dstIP) } if s.dstPort != dstPort { - return fmt.Errorf("expect session ID %d, got %d", s.dstPort, dstPort) + return fmt.Errorf("expect destination port %d, got %d", s.dstPort, dstPort) + } + if s.closeIdleAfter != closeIdleAfter { + return fmt.Errorf("expect closeIdleAfter %d, got %d", s.closeIdleAfter, closeIdleAfter) } return nil } diff --git a/tunnelrpc/pogs/sessionrpc.go b/tunnelrpc/pogs/sessionrpc.go index ce8a06c0..8b41b6ea 100644 --- a/tunnelrpc/pogs/sessionrpc.go +++ b/tunnelrpc/pogs/sessionrpc.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "time" "github.com/cloudflare/cloudflared/tunnelrpc" "github.com/google/uuid" @@ -13,7 +14,7 @@ import ( ) type SessionManager interface { - RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16) error + RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration) error UnregisterUdpSession(ctx context.Context, sessionID uuid.UUID) error } @@ -47,8 +48,10 @@ func (i SessionManager_PogsImpl) RegisterUdpSession(p tunnelrpc.SessionManager_r } dstPort := p.Params.DstPort() + closeIdleAfterHint := time.Duration(p.Params.CloseAfterIdleHint()) + resp := RegisterUdpSessionResponse{} - registrationErr := i.impl.RegisterUdpSession(p.Ctx, sessionID, dstIP, dstPort) + registrationErr := i.impl.RegisterUdpSession(p.Ctx, sessionID, dstIP, dstPort, closeIdleAfterHint) if registrationErr != nil { resp.Err = registrationErr } @@ -108,7 +111,7 @@ func (c SessionManager_PogsClient) Close() error { return c.Conn.Close() } -func (c SessionManager_PogsClient) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16) (*RegisterUdpSessionResponse, error) { +func (c SessionManager_PogsClient) RegisterUdpSession(ctx context.Context, sessionID uuid.UUID, dstIP net.IP, dstPort uint16, closeAfterIdleHint time.Duration) (*RegisterUdpSessionResponse, error) { client := tunnelrpc.SessionManager{Client: c.Client} promise := client.RegisterUdpSession(ctx, func(p tunnelrpc.SessionManager_registerUdpSession_Params) error { if err := p.SetSessionId(sessionID[:]); err != nil { @@ -118,6 +121,7 @@ func (c SessionManager_PogsClient) RegisterUdpSession(ctx context.Context, sessi return err } p.SetDstPort(dstPort) + p.SetCloseAfterIdleHint(int64(closeAfterIdleHint)) return nil }) result, err := promise.Result().Struct() diff --git a/tunnelrpc/tunnelrpc.capnp b/tunnelrpc/tunnelrpc.capnp index 916a81b4..2c578ff8 100644 --- a/tunnelrpc/tunnelrpc.capnp +++ b/tunnelrpc/tunnelrpc.capnp @@ -148,6 +148,7 @@ struct RegisterUdpSessionResponse { } interface SessionManager { - registerUdpSession @0 (sessionId :Data, dstIp :Data, dstPort: UInt16) -> (result :RegisterUdpSessionResponse); + # Let the edge decide closeAfterIdle to make sure cloudflared doesn't close session before the edge closes its side + registerUdpSession @0 (sessionId :Data, dstIp :Data, dstPort: UInt16, closeAfterIdleHint: Int64) -> (result :RegisterUdpSessionResponse); unregisterUdpSession @1 (sessionId :Data) -> (); } \ No newline at end of file diff --git a/tunnelrpc/tunnelrpc.capnp.go b/tunnelrpc/tunnelrpc.capnp.go index aea215a3..e8df05a0 100644 --- a/tunnelrpc/tunnelrpc.capnp.go +++ b/tunnelrpc/tunnelrpc.capnp.go @@ -3465,7 +3465,7 @@ func (c SessionManager) RegisterUdpSession(ctx context.Context, params func(Sess Options: capnp.NewCallOptions(opts), } if params != nil { - call.ParamsSize = capnp.ObjectSize{DataSize: 8, PointerCount: 2} + call.ParamsSize = capnp.ObjectSize{DataSize: 16, PointerCount: 2} call.ParamsFunc = func(s capnp.Struct) error { return params(SessionManager_registerUdpSession_Params{Struct: s}) } } return SessionManager_registerUdpSession_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))} @@ -3560,12 +3560,12 @@ type SessionManager_registerUdpSession_Params struct{ capnp.Struct } const SessionManager_registerUdpSession_Params_TypeID = 0x904e297b87fbecea func NewSessionManager_registerUdpSession_Params(s *capnp.Segment) (SessionManager_registerUdpSession_Params, error) { - st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2}) + st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 2}) return SessionManager_registerUdpSession_Params{st}, err } func NewRootSessionManager_registerUdpSession_Params(s *capnp.Segment) (SessionManager_registerUdpSession_Params, error) { - st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2}) + st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 16, PointerCount: 2}) return SessionManager_registerUdpSession_Params{st}, err } @@ -3615,12 +3615,20 @@ func (s SessionManager_registerUdpSession_Params) SetDstPort(v uint16) { s.Struct.SetUint16(0, v) } +func (s SessionManager_registerUdpSession_Params) CloseAfterIdleHint() int64 { + return int64(s.Struct.Uint64(8)) +} + +func (s SessionManager_registerUdpSession_Params) SetCloseAfterIdleHint(v int64) { + s.Struct.SetUint64(8, uint64(v)) +} + // SessionManager_registerUdpSession_Params_List is a list of SessionManager_registerUdpSession_Params. type SessionManager_registerUdpSession_Params_List struct{ capnp.List } // NewSessionManager_registerUdpSession_Params creates a new list of SessionManager_registerUdpSession_Params. func NewSessionManager_registerUdpSession_Params_List(s *capnp.Segment, sz int32) (SessionManager_registerUdpSession_Params_List, error) { - l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2}, sz) + l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 16, PointerCount: 2}, sz) return SessionManager_registerUdpSession_Params_List{l}, err } @@ -3853,201 +3861,203 @@ func (p SessionManager_unregisterUdpSession_Results_Promise) Struct() (SessionMa return SessionManager_unregisterUdpSession_Results{s}, err } -const schema_db8274f9144abc7e = "x\xda\xccY}p\x15\xe5\xd5?g\xf7\xdelB>" + - "nv\xf6BHF\xdf\xbc/\x03\xe3K\x14\x14(\x1d" + - "\xa0\xda\x04L\xa8\x89|d\xef\x85\x8e\x05t\xdc\xdc\xfb" + - "\x106\xbdw\xf7\xb2\xbb7\x12\x04\xf9\x10D\x1c\xbf@" + - "PD\xa9\x88\xd3vDm\xa1j\xad\x1d\x9dJ\xeb\xe7" + - "(*\x0etPqZD\xfa\xc1`\xad\x88uh\xd5" + - "\xed\x9c\xdd\xbb\x1f\xb9\x09I\x90\xfe\xd1\xff\x92\xb3\xcfs" + - "\x9es~\xe7\xf7\x9cs\x9es/\x9bY\xda\xc4M\x88" + - "\xd6T\x02\xc8\xdb\xa2%6kxg\xf9\xce1\xbf[" + - "\x0br\x1d\xa2}\xd3sm\xf13\xd6\xda\xf7!\xca\x0b" + - "\x00\x93\x96\x96,Gi}\x89\x00 \xad)\xf93\xa0" + - "}\xcb\x88=\x0f\xfd\xb8e\xcb\xcd \xd6\xf1\xc1b\xc0" + - "ILhC\xa9G\xa0\x95ya\x83t\x88\xfe\xb2\xaf" + - "\x16/]\x18\x7f\xfbMZ\x1dV\x1d!\xd5\xcf\x0b\x0d" + - "(\xedw6\xbc&\x90\xea\xcb\xb3o\xed\xfa\xf6\xd6\xd7" + - "\xd7\x81X\xc7\xf5R\xfdt\xe9r\x94^+\xa5\x95/" + - "\x95\xce\x05\xb4?\xdb2\xf2\xb1\x87\xdf|u=\x88\x17" + - "!\x14,\xfd\xa0\xf4=\x04\x94>-\xfd9\xa0\xbd\xff" + - "\xf3\x85\xa7\x9fzy\xf2- \x8e\xa5\x05H\x0b6\x95" + - "\x8d\xe2\x00\xa5G\xcb\x1a\x01\xed\x13'\xff\xb5\xe1\xc6\xb1" + - "s\xee\x06y,\xd2\x0a\x8eV\xbcVVG+\x8e\x96" + - "\x91\x8a\xc6\x99\xfb\x9f\xad\x9bt\xef\x96\"\xd3\x9d\x85+" + - "\x875\xa0t\xc702h\xe3\xb0\x1b\x00\xed\x7fT=" + - "\xf0f\xfe\xcag\xee\x0d\x9f\xf7\xf1\xb0\x06\xd2\x16-\xa7" + - "\xf3Fu\x8f\xb9\xfe\xb7/=y\x1f\xc8\xe3\x10\xed#" + - "\x1d\x17\x1f\xe2w\xec~\x1f\xe6\xa3\x80\x1c\xc0\xa4\xb1\xe5" + - "\xbb\xc8\xf8\xa9\xe5\xa4\xec\xadK\x9e\xfb\xf5\xddOnx" + - "\x00\xe4\x8b\xc86\x07\xac\x1d\xe5\xff\xa4\x05{\x1de[" + - "\x0e??'\xbbi\xfb.\xd7}\xe7\xfb\xbb\xe5\x1c\x07" + - "\x11{]\xeb\x17\xd9\xf9\x8f$\x1f)\x00\x13\xa5O\xfb" + - "\xcbO!\xe0\xa4\xa3\xe5\xf5\x08hO~\xef\xf8\xdc\xd9" + - "\xbfX\xfc\xd3\xd0\xde\xaf*\x96\xd3\xde\x0d\x8bO\xed\xab" + - "Nd\x1f+r\xd8\xf1\xe5L\xc5n\x94\xc4Jr\xb8" + - "\xb2\x92Lx\xe2\x7f\xae.[v|\xe6\x1e\x10\xc7y" + - "j\xc6U&HM\xe4Z\xfeke\xdbo\x9e*\xa6" + - "\x93\x03\xdc\xd8\xca\x0e\x94\xae =\x93\xa6V:\xf6\xdc" + - "\xb6o\xfb\xc5\xa5\x0f}\xf6t\x7f0+U\x1d(\xe5" + - "\xab\xe8\xd4\xa5U\x84\xcc\xf0V<\xf2\xc2\x84\xc83\xe1" + - "\xb8\x1f\xa8:A\xc8\x1c\xaf\xa2\xa0]\xf8\xf1\x8cJ\xed" + - "\x93\xb5/\x14is\x16\xae\x8f\xb5\xa1t\x7f\x8c\xb4m" + - "\x8d\xd1\xe2\xb6\x85\xf7l\x8e\x1e\xbf\xe7\x15\xb24D\xb8" + - "(\x11m\xd2\x84j\x03\xa5\x96j\xfaszu\x0d\x0f" + - "h\xd7\xed\xf9\xce\xcff\xa4\xdf}\xbd\x1fK\xa5}\xd2" + - ")i\xbf\xe4PY\"C\x8f\x8d\xdb{\xe3_\xef8" + - "p\xb0`\xa8\x83\xe1\x98\xb8\x13\xc2\xa9q\xc2\xcfg@" + - "\x11J\xce\xca\x1f\xc4\xbbP\xca\xc6I\x9d\xea\xac\xe6\x8e" + - "+\xb5\xab\x7f\xff\xdd#\xa1\xa0e\xe3\x1f\"D\xec9" + - "\xdf_\xd8U\xb6\xf2\xd8\xb1\xf0AJ\xdcA$\xefl" + - "\xfd\xdbON\xdcu2\x9b\xfe\x93C<\x0f\xb3\xad\xf1" + - "iD\xcd'\xe2t\xedj\xea+[F\x1dn?\xe1" + - "\x86\xd2U\xb1q\xf8\x0cZ\xf0\xf0pR1\xf9\xfa\xe9" + - "l\xd1\x94kN\xf4\xb9\xf2\xfb\x86OC\xe9\xc0p\x87" + - "d\xc37\xa0t|D\x0d\x80\xdd\xfd\xcbM\xd7<\xf6" + - "\xe2\x9cS\xee]p\x8c=4b\"Q\xe3\xce\x9b\x9a" + - "\xe7N\x1d\xb5\xefT\xd8\xd8\xfd#\x88\x9d\xd2\xd1\x11t" + - "\xd2\xe2)'\xbf7\xe6\xce\x97O\xf5GA\xaci@" + - "I\xacq(X\xd3\x08\xf8\xc9\xcc\x1f\x1d\xac\x8b\xd5\x9d" + - ".\x02\xb0\xc4\x09^M\x17J-5N\xf0j^!" + - "\x9a\xdd\xf2\xfeu\xcb\xde\xb9\xf9\xb3\xcf\x8bc\xed\xa8\x9e" + - "\\\x9b@\xa9\xb5\x96T\xb7\xd4\x123\xee\x9b\xf7\x97U" + - "'\xb7\x8e\xf8\xa2\x8f\xc7\xc7k\xbbP:\xe3\xac\xfc\xbc" + - "v\x834\xbf\x8e\x92\xdc\xdb\xc2#\x13\x9aW\xbd~&" + - "t\x17\xae\xa8k#\x87\xef\x15\x1e<\xb6\xfa\x0f\xd7}" + - "\x19vxj\xdd\x87\xe4\xf0\xec:rx\xc5'\xf7_" + - "u\xd7\xa2\xc7\xbf\x0e\x07\xb6n-m\xb5\xf2\x9a\xc62" + - "F.\x92\xba\xd4\xfb35>\xa5\xe4\xb4\xdc\xb4\xe9y" + - "k\x09\xd3,5\xa5X,\xc1\x1a\xcd\x9c\xae\x99\xac\x1d" + - "Q\xae\xe6#\x00\x11\x04\x10\x95.\x00\xf9z\x1e\xe5\x0c" + - "\x87\"b\x9cB/\xaa$\\\xc2\xa3lq(r\\" + - "\x9c2\x8f\xb8t\x14\x80\x9c\xe1Q^\xc6!\xf2q\xe4" + - "\x01\xc4\xfcf\x00y\x19\x8f\xf2:\x0e\xed\x1c3\xb2\x8a" + - "\xc64\x88Y-\x86\x81\x15\xc0a\x05\xa0m0\xcb\xe8" + - "Q:2\x10c!\xb1\xd0u\x83\x85\x95\xc0a%\xa0" + - "\xbdD\xcf\x1b\xe6|\xcdB5\x93`\x8b\x0df\xe2\x12" + - ",\x01\x0eK\x00\x07r/\xc9LS\xd5\xb5\xd9\x8a\xa6" + - "t2\x03\x80<+\xe5\xa3\x00~\xd2F/\xbd\x8b\x13" + - "\xb6\x03'\x8e\x130\xc8\xc0\xe8\xd1O\xfc\xbf\xdd\xc0\x89" + - "\x17\x0a\xb6\xc1:U\xd3b\x06\xceO\xe7\x1c\xdd\xbc\xae" + - "5\xa1\x9d\xd7\xdc\x0f\xc8\x0c\xf7C\x8cNm\xc2v\x0c" + - "\xac\xe3\xfbZweFe\x9a\x15k\xd5\x16\xebE\x90" + - "\xb7\xf5\x07y[\x01\xf2u!\xc8\xd7\xcc\x00\x90W\xf0" + - "(\xdf\xca\xa1\xc8\x170_\xdf\x00 \xaf\xe6Q\xbe\x9d" + - "C;\xe5\x1c\xd2\x9a\x06\x00\x1f\xcd\xc5L\xb1\xf2\x063" + - "IV\x05\xd8\xce\xa3\x03z\x15\xe0\xaanf\x90\xed^" + - "\x10b\x8a\x91Z\xe2\x07j\x00\xa4[\x96\xa9\xa6\xa5j" + - "\x9d\xf3\x1cyc\xbb\x9eQS=\xe4U\x85c\xe7\x85" + - "\xd3\x00\x10\xc5\xe1\x0b\x00\x90\x13\xc5\x19\x00\x8dj\xa7\xa6" + - "\x1b\xccN\xabfJ\xd74\x06|\xcaZ\xd5\xa1d\x14" + - "-\xc5\xfc\x83J\xfa\x1e\xe4\x1e\x90dF73\xc6+" + - "!\xfa\x8enW\x0c\x85\xcf\x9ar\x85\x8fc\xcb\x02\x00" + - "\xb9\x99G\xb9=\x84\xe3l\xc2q\x16\x8f\xf25!\x1c" + - "\xe7\x13\x8e\xed<\xca\x8b8\xb4uC\xedT\xb5+\x19" + - "\xf0F\x98\x81\xa6\xa5)YF\x98\x15\xf0X\xa5\xe7," + - "U\xd7L\xac\x0e\xf2? V\x87\x90\x12\x06\xe3\xe4x" + - "\x8fR\x1e\xa3tmt\x82\x99y!c\x99r\xc4\xf7" + - "\xa4r\x1a\x80\\\xca\xa3\x1c\xe7\xb0\xd1`f>ca" + - "uPf\xff\x13\xa7\xf6\x03_\xa2?\xf8&\x02\xc8W" + - "\xf1(\xcf\xe3\x10\x0b\xe8\xc93\x02Hm\xd3\xd5\xd7\x0a" + - "\x98\xf6\xc0\xabO\x9bVk\xce\xfboU\xda\xb4\xdau" + - "\xc3B\x018\x14`\xc0+\xe2F;Fi\xca\xbd\xbb" + - "\x9emc)\xb4\xff\xcf\xa3\xfc\xad\x90m\x13(+]" + - "\xc6\xa3|9\x87\xb6\x92J\xe9y\xcd\x9a\x07\xbc\xd2Y" + - "\xc4\xe0$\x83X\xca`Ap\x87\x0e\x9cw\xd5\x8b\xa0" + - "\x8b\x19J\xb6W\xbc\x08\xba\x0a\x1e\xe5\x91\xfd#\xe2\x9f" + - "\x18\xed'-\xd0uH\x11\xb3\x12\xccM\xc8\xe3\x0df" + - "\x0a\xf9\x8cE\xfeW\xd8\xb6\x0b\x00\xc5a4\x8f\xf2e" + - "\x1cV\xe2\xd7\xb6\x8b\xc0\xb8\xcd\x01\x02\xf5\xcc0t\x03" + - "\xab\x83\x82U\xa0I\xaap\x00\xeaZ3\xb3\x145\x83" + - "Da\xbf{*\"\xd3`w0@\xc4\x15\x8fn$" + - "&\xf5Fc;\x80\\\xcd\xa3|\x01\x87v\xa7\xa1\xa4" + - "X;3P\xd5\xd3s\x14MO\xf2,\x85Q\xe00" + - "\x1a:\xb4\xea\\\x0fM8w\xc2\x04\x7f\xd7\xc0\xfb\x0d" + - "V\x00\xa1\xb0\xbd\xbd\xde\xb59\xee\xdb\xbcrTP\xb8" + - "|\x82\xad\xe9\x082\xab\x9f;6\x12\x15o\xe5Q\xde" + - "\x12\xca\xc1\x9b(\xcb\xdc\xcd\xa3\xfc \x87b$\x12\xc7" + - "\x08\x80x?\xdd\x93-<\xca;\xb9\xde\xe5\x8du3" + - "\xcdjV;A`f %\x13\x9b\xd5N\x06\xbcy" + - "\xbey\xa8t\x10<\xf4\x0eS\xcf0\x8b5\xb3TF" + - "1\x14K\xedf\xee\xf7\x02\x19\xbd\xa0\x0e\xc4\xdbD\x9f" + - "\x8bA\xfc\x8dy\x1dE\x88\x0e\xa3\x82d&\xb0P#" + - "0\x80\xb5\xaer\xb2L\xd7\xfap \xb81\x05\x1e\xa0" + - "9P\xad\x0a\x96\xcf\xcdY\xaa\xa0k&\xd9\x17\x0a\xfd" + - "\xb4\xfeBo\x04\xa1\xf7\xf2\xde\xc6\xb5\xe1\xc8c!\xf2" + - "\xdb\x83 \x8b\x11\xce\x8d\xfc\x8e]\x00\xf2N\x1e\xe5\xc7" + - "9ltK2V\x07O\xdaB\xb4\xdc\xc23K\x87" + - "\xfa\x94\x92\x09\x12\xa6m\xb0\\FI\xb1\x16,\x14Y" + - "@\x04\x0e\xd1\xa1H6g0\xd3DU\xd7\xe4\xbc\x92" + - "Qy\xab\xc7o\x8c\xb4|\xb6\xdd`\xdd*\xeays" + - "\xbae\xb1\xac\x90\xb3\xcc\xa1\xb4M\x01@\x94\x1f\x045" + - "c\x16%\xdf\x86 \xf7\xf8\x00\x8d\xa3\xe4{\x09\x8f\xf2" + - "\x14\x0ec\xf9\xbc\x1a\xe4\xba\x8c\x9er\xe2\x06\xb19J" + - "\x96\xf5\x89v\xc9\xa0w\xb5\xd7M\xf7\x92\xed\x7fS\x99" + - "\x1f\xb8\xb3&\xd7\x9d\xd63d2]\x81&\x1e\xe5Y" + - "!\x93['\x86\xfc\xf0L\x9e\xdd\x11\xf8!\xfc\x90\xf5" + - "xV\xd5\xb3,en\x0f\xcc\x823\xd3A\xb8:X" + - "3\x90}\xe1\x0b57W\xefxH6N\xf1l\x94" + - "z\xb0\x0d \xb9\x0cyL\xae\xc3\xc0Li\x0d\xce\x00" + - "H\xae \xf9\xad\x18X*\xad\xc7:\x80\xe4j\x92\xdf" + - "\x8e\xfe\x0b@\xda\x88\xbb\x01\x92\xb7\x93x\x1b-\x8f\xf0" + - "\xce\x95\x90\xb6:\xea\xb7\x90|'\xc9\xa3\x918F\x01" + - "\xa4\x1d\xd8\x00\x90\xdcF\xf2\xa7H^\xc2\xc5\xb1\x04@" + - "\xda\x8b]\x00\xc9=$\x7f\x8e\xe4B4N\x8f \xe9" + - "Y4\x00\x92\xbf\"\xf9\x8b$/\x1d\x19\xc7RzG" + - ";\xf2\x17H\xfe\x06\xc9\xcbj\xe3XF\xafj\\\x0b" + - "\x90|\x95\xe4\x07I>\x0c\xe38\x0c@:\x80\xdb\x01" + - "\x92\x07I\xfeG\x92\x97\x97\xc4\xb1\x1c@\xfa\xc0\xb1\xe7" + - "0\xc9?\"yE$\x8e\x15\x00\xd2Q\xdc\x05\x90\xfc" + - "\x88\xe4\x7f'y\xa5\x10\xc7J\x00\xe9c\xc7\xaf\x93$" + - "/\xe5\x8a\x1ap\x8fQE]6\xaf\x9b~\xc8X\xe1" + - "\x8e\xa3K\xf7v=F\x9d4\xc6\x82\x91\x16 \xc6\x00" + - "\xed\x9c\xaeg\xe6\xf4fj\xccR:M\xaf\xa3\xaf\x0e" + - "\xa6\x0c\x80$\xf4\xeb>\xc4t\xad5\xed'\x82\xe2\xac" + - "\xe3Y\xa2\x9a\xd3\xf3\x96\x9e\xcfA}Z\xb1X\xda\xcf" + - "9F^\x9bi\xe8\xd9y\xc8\x8c\xac\xaa)\x99A\xb2" + - "Q\x19pX\x06\x85\x94\xe0\xe9\x1e85\x9d\xfd}\xe2" + - "3\x9a+ft}n\xda<\xa5s(yjb\xd0" + - "9\xc6\xb4PB\xaa\xefV2\xf9o\x92\x9ez\xb7\x12" + - "\x89F\xb7\x15\x19\xac{\xf7\x86\x0e\x83\xa7\x92\xde\x0da" + - "\xef\x82\x8a\xa1y \x9d\xc3\x15\xf4\x0f\xd9\xfcNf\xb9" + - "\x7f\xd13\x94\x1e\x01B\xb8\xcc\x9f\xdb\xee\x043cC" + - "q=\x18\xce\x0c\xfep\xe9\xa7\xf0\xf7S\xf6\xbd\x9e3" + - "\xf4\x86\xa6\xd8/\xe2Q^\x12\x8a=\xa3\xa2\x90\xe6Q" + - "\xce\x05E<\x9b\x08\xa6\x16\"\xcf\x15\xc6\x16T(r" + - "<\xca+8\x8c\xd1+\x13\xab\x83)n/\xa3{\xbf" + - "\xac\x89\x0a\xadZ\x9a\x01.\xf3\xd8\x1c*\x1f\xfe" + - "\xa3^\xf7\x83\xc3\xd1\xde\xa3\xde\xf3x\xbe\xbae,\x94" + - "1\xcei\x02:\xe4\xc1\xa1\xff;m\xd1M/;\xdf" + - "1\x81W\x90\xfe\x1d\x00\x00\xff\xff#\xafZ\xc1" +const schema_db8274f9144abc7e = "x\xda\xccY{p\x14e\xb6?\xa7{&\x9d@\x86" + + "IW\x0f\x04\xa6\xe4\xe6^\x0a\xcaK\x14\x14\xb8\xdeB" + + "\xae\xde\x04L\xb8&\xf2H\xcf\x90\xbb\x96\xa0eg\xe6" + + "#tv\xa6{\xe8\xee\x89\x04A\x1e\x82\x88\xe5\x0b\x04" + + "E\x94\x95\xc5r\xb7D\xdd\x85U\xd7eKke\xd7" + + "\x17\xa5\xa8X\xb8\x05\x8a\xb5\xab\xc8>(\\W\xc4\xb5" + + "\xdcu\xed\xad\xd3=\xfd\xc8$$A\xf6\x8f\xfdor" + + "\xfa\xfb\xcew\xce\xef\xfc\xbes\xcewr\xe9w*\x1b" + + "\xb9)\xd1\xda\x18\x80\xbc-Za\xb3\xfaw\x96\xef\x9c" + + "\xf0\xab\xb5 '\x11\xed[\x9eoM|e\xad}\x1f" + + "\xa2\xbc\x000mi\xc5r\x94\xd6W\x08\x00\xd2\x9a\x8a" + + "\xdf\x03\xda\xb7\x8d\xda\xf3\xc8c\xcd[n\x051\xc9\x07" + + "\x8b\x01\xa71\xa1\x15\xa5\x1e\x81V\x16\x85\x0d\xd2\xbb\xf4" + + "\xcb\xbeF\xbcda\xe2\xed7iuXu\x84T\xbf" + + " \xd4\xa3t\xd0\xd9p@ \xd5W\xe4\xdf\xda\xf5\xdf" + + "[__\x07b\x92\xeb\xa5\xfa\xd9\xca\xe5(\x1d\xa8\xa4" + + "\x95/W\xce\x07\xb4?\xdf2\xfa\x89\xef\xbf\xf9\xdaz" + + "\x10/D(Y\xfaA\xe5{\x08(}V\xf9c@" + + "\xfb\xe0\x17\x0b\xcf<\xf3\xcae\xb7\x818\x91\x16 -" + + "\xd8T5\x8e\x03\x94\x1e\xafj\x00\xb4O\x9e\xfa\xdb\x86" + + "\x9b'\xce\xbb\x17\xe4\x89\xc8\x01D9Zq\xa0*I" + + "+>\xac\"k\x1af\x1f\xdc\x97\x9cv\xff\x962\xd3" + + "\x9d\x85\xfb\x87\xd5\xa3th\x18\x19tp\xd8M\x80\xf6" + + "_F<\xf4f\xf1\xaa\xe7\xee\x0f\x9f7ex=i" + + "k\x19N\xe7\x8d\xeb\x9ep\xe3/_~\xfa\x01\x90'" + + "!\xda\xc7:.z\x97\xdf\xb1\xfb}hG\x81\x8e\x9f" + + "\x96\x1f\xbe\x8b\x8c_9\x9c\x94\xbdu\xf1\xf3?\xbf\xf7" + + "\xe9\x0d\x0f\x81|!\"\x80\x03\xd6\x87\xc3\xffJ\x0b\xbe" + + "p\x94m9\xf2\xc2\xbc\xfc\xa6\xed\xbb\\\xf7\x9d\xefc" + + "\xab9\x0e\"\xf6\xba\x96/\xf3\xed\x8f\xa6\x1f-\x01\x13" + + "\xa5Ob\xf5i\x04\x9c6\xa1\xba\x0e\x01\xed\xcb\xde;" + + "1\x7f\xeeO\x16\xff0\xb4wfl9\xed\xdd\xb0\xf8" + + "\xf4\xfe\x9aT\xfe\x892\x87\x1d_\xae\x8c\xedF\xa9=" + + "F\x0e\xcb12\xe1\xa9\x7f\xbb\xa6j\xd9\x89\xd9{@" + + "\x9c\xe4\xa9Y\x1aK\x91\x9a\xc8\xf5\xfc7\xca\xb6_<" + + "SN'\x07\xb8|\xac\x03\xa55\xa4g\xda\xca\x98c" + + "\xcf\x1d\xfb\xb7_T\xf9\xc8\xe7\xcf\xf6\x07\xf3c#:" + + "P\xda7\x82N}v\x04!3\xb2\x05\x8f\xbd8%" + + "\xf2\\8\xee#\xe3'\x09\x99\x89q\x8a\xfb\xd8Of" + + "\xc5\xb4O\xd7\xbeX\xa6\xcdYx \xde\x8a\xd2\x07q" + + "\xd2v\xd4Y\xdc\xba\xf0\xbe\xcd\xd1\x13\xf7\xbdJ\x96\x86" + + "\x08\x17%\xa2M+\xd6\x18(m\xac\xa1\x9f\xebkj" + + "y@;\xb9\xe7\x7f~4+{\xf4\xf5~,\x95\xa2" + + "\x89\xd3\x92\x98\xa0_\xb1\x04\x19z|\xd2\xde\x9b\xffx" + + "\xd7\xa1\xc3%C\x1d\x0c\xd5\x84\x13\xc2\x95\x09\xc2\xcfg" + + "@\x19J\xce\xca\x1d\x89.\x94\xf6:\xea\x9erVs" + + "'\x941\xab\x7f\xfd\xbf\xc7BA\xdb\x9b\xf8\x08!b" + + "\xcf\xfb\xff\x85]U+\x8f\x1f\x0f\x1f\xf4X\xc2Ad" + + "\x9f\xb3\xf5O?8y\xcf\xa9|\xf6w\x0e\xf1<\xcc" + + "\x8e&f\x105?K\x10\xd1k\xebb\xcd\xe3\x8e\xb4" + + "\x9dtC\xe9\xaa88r\x16-81\x92T\\v" + + "\xe3L\xb6h\xfa\xb5'\xfb\\\xf9\xe8\xa8\x19(\x8d\x1c" + + "\xe5\x90l\xd4\x06\x94&\xd6\xd6\x02\xd8\xdd?\xddt\xed" + + "\x13/\xcd;\xed\xde\x05\xc7\xd81\xb5S\x89\x1aw\xdf" + + "\xd24\xff\xf2q\xfbO\x87\x8d\x15k\x89\x9d\xd2\x84Z" + + ":i\xf1\xf4S\xff7\xe1\xeeWN\xf7G\xc1\xe6\xda" + + "z\x94\xdak\x1d\x0a\xd2\xe2Og\x7f\xefp2\x9e<" + + "S\x06`\x85\x13\xbc\xda.\x946\xd6:\xc1\xab}\x95" + + "hv\xdb\xfb7,{\xe7\xd6\xcf\xbf(\x8f\xb5\xa3\xba" + + "gL\x0a\xa5\xbb\xc6\x90\xea\x8dc\x88\x19\x0f,\xf8\xc3" + + "\xaaS[G}\xd9\xc7\xe3\x89\xc9.\x94\xaeL\xd2\xca" + + "\xcb\x93\x1b\xa4\x07\xe9\x97\xfd\xb6\xf0\xe8\x94\xa6U\xaf\x7f" + + "\x15\xba\x0bk\x92\xad\xe4\xf0\xfd\xc2\xc3\xc7W\xff\xe6\x86" + + "\xaf\xc3\x0e\xafL~D\x0eoJ\x92\xc3+>}\xf0" + + "\xea{\x16=\xf9M8\xb0\xc9\xb5\xb4\xd5*j\x1a\xcb" + + "\x19\x85H\xe6\x12\xefgfrF)h\x85\x193\x8b" + + "\xd6\x12\xa6YjF\xb1X\x8a5\x98\x05]3Y\x1b" + + "\xa2\\\xc3G\x00\"\x08 *]\x00\xf2\x8d<\xca9" + + "\x0eE\xc4\x04\x85^TI\xb8\x84G\xd9\xe2P\xe4\xb8" + + "\x04e\x1eq\xe98\x009\xc7\xa3\xbc\x8cC\xe4\x13\xc8" + + "\x03\x88\xc5\xcd\x00\xf22\x1e\xe5u\x1c\xda\x05f\xe4\x15" + + "\x8di\x10\xb7\x9a\x0d\x03\xab\x81\xc3j@\xdb`\x96\xd1" + + "\xa3t\xe4 \xceBb\xa1\xeb&\x0bc\xc0a\x0c\xd0" + + "^\xa2\x17\x0d\xb3]\xb3P\xcd\xa5\xd8b\x83\x99\xb8\x04" + + "+\x80\xc3\x0a\xc0\x81\xdcK3\xd3Tum\xae\xa2)" + + "\x9d\xcc\x00 \xcf*\xf9(\x80\x9f\xb4\xd1K\xef\xe2\x94" + + "\xed\xc0\x89\x93\x04\x0c20z\xf4\x13\xffc7p\xe2" + + "X\xc16X\xa7jZ\xcc\xc0\xf6l\xc1\xd1\xcd\xebZ" + + "#\xdaE\xcd\xfd\x80\xccp?\xc4\xe9\xd4Fl\xc3\xc0" + + ":\xbe\xafuW\xe5T\xa6Y\xf1\x16m\xb1^\x06y" + + "k\x7f\x90\xb7\x96 _\x17\x82|\xcd,\x00y\x05\x8f" + + "\xf2\xed\x1c\x8a|\x09\xf3\xf5\xf5\x00\xf2j\x1e\xe5;9" + + "\xb43\xce!-Y\x00\xf0\xd1\\\xcc\x14\xabh0\x93" + + "d#\x00\xdbxt@\x1f\x01\xb8\xaa\x9b\x19d\xbb\x17" + + "\x84\xb8bd\x96\xf8\x81\x1a\x00\xe9\xe6e\xaai\xa9Z" + + "\xe7\x02G\xde\xd0\xa6\xe7\xd4L\x0fyU\xed\xd89v" + + "\x06\x00\xa28\xf2:\x00\xe4Dq\x16@\x83\xda\xa9\xe9" + + "\x06\xb3\xb3\xaa\x99\xd15\x8d\x01\x9f\xb1Vu(9E" + + "\xcb0\xff\xa0\x8a\xbe\x07\xb9\x07\xa4\x99\xd1\xcd\x8c\xc9J" + + "\x88\xbe\xe3\xdb\x14C\xe1\xf3\xa6\\\xed\xe3\xd8|\x1d\x80" + + "\xdc\xc4\xa3\xdc\x16\xc2q.\xe18\x87G\xf9\xda\x10\x8e" + + "\xed\x84c\x1b\x8f\xf2\"\x0em\xddP;U\xed*\x06" + + "\xbc\x11f\xa0iiJ\x9e\x11f%\xcd \x9e1X@\x87\xa1C\xed%" + + "\x872\xb0\xe3\x86\x92\xef\x15a\x02\xbb\x9aGyt\xff" + + "p\xf9'F\xfbI$t\x812\xc4\xc5\x14sS\xf8" + + "d\x83\x99B1g\x91\xff\xd5\xb6\xed\x02@\x91\x1b\xcf" + + "\xa3|)\x871\xfc\xc6v\x11\x98\xb49@\xa0\x8e\x19" + + "\x86n`MP\xe2J\xc4\xca\x94\x0e@]kb\x96" + + "\xa2\xe6\x90H\xef\xf7[e\xf4\x1b\xec\xd6\x06\x88\xb8\xe2" + + "\xf1\x0d\xc4\xbd\xdeh\x10yjx\x94/\xe0\xd0\xee4" + + "\x94\x0ckc\x06\xaazv\x9e\xa2\xe9i\x9ee\xfaP" + + "a\xc4\xb9\x1e\x9arn\x91\x09\xfe\xae\x81\xf7\x1b\xac\x04" + + "Bi{[\x9dks\xc2\xb7y\xe5\xb8\xa0\xd4\xf9\x04" + + "[\xd3\x11\xe4b?\xdbl$*\xde\xce\xa3\xbc%\x94" + + "\xb57Q^\xba\x97G\xf9a\x0e\xc5H$\x81\x11\x00" + + "\xf1A\xbaY[x\x94wr\xbd\x0b\"\xebf\x9a\xd5" + + "\xa4v\x82\xc0\xcc@J&6\xa9\x9d\x0cx\xf3|3" + + "W\xe5 x\xe8\x1d\xa6\x9ec\x16kb\x99\x9cb(" + + "\x96\xda\xcd\xdc\xef%2zA\x1d\x88\xb7\xa9>\x17\x83" + + "\xf8\x1b\xf7z\x90\x10\x1d\xc6\x05\xe9O`\xa1\xd6a\x00" + + "k]\xe5d\x99\xae\xf5\xe1@pcJ<@s\xa0" + + "\xea\x16,\x9f_\xb0TA\xd7L\xb2/\x14\xfa\x19\xfd" + + "\x85\xde\x08B\xefe\xca\x8dk\xc3\x91/e\xcaM\xdb" + + "\x83 \x8b\x11\xce\x8d\xfc\x8e]\x00\xf2N\x1e\xe5'9" + + "lp\x8b8\xd6\x04\x8f\xe0R\xb4\xdcR5G\x87\xba" + + "\x8c\x92\x0b\xb2\xa9m\xb0BN\xc9\xb0f,\x95e@" + + "\x04\x0e\xd1\xa1H\xbe`0\xd3DU\xd7\xe4\xa2\x92S" + + "y\xab\xc7o\xa5\xb4b\xbe\xcd`\xdd*\xeaEs\xa6" + + "e\xb1\xbcP\xb0\xcc\xa14Z\x01@\x94\x1f\x045g" + + "\x96%\xdf\xfa \xf7\xf8\x00M\xa2\xe4{1\x8f\xf2t" + + "\x0e\xe3\xc5\xa2\x1a\xe4\xba\x9c\x9eq\xe2\x06\xf1yJ\x9e" + + "\xf5\x89v\xc5\xa0w\xb5\xd7M\xf7\x92\xed\xbfRc0" + + "p/N\xae;\xcdj\xc8d\xba\x02\x8d<\xcasB" + + "&\xb7L\x0d\xf9\xe1\x99<\xb7#\xf0C\xf8.\xeb\xf1" + + "\xac\xaacy\xca\xdc\x1e\x98%gf\x82pM\xb0f" + + " \xfb\xc2\x17j~\xa1\xce\xf1\x90l\x9c\xee\xd9(\xf5" + + "`+@z\x19\xf2\x98^\x87\x81\x99\xd2\x1a\x9c\x05\x90" + + "^A\xf2\xdb1\xb0TZ\x8fI\x80\xf4j\x92\xdf\x89" + + "\xfe\x9bA\xda\x88\xbb\x01\xd2w\x92x\x1b-\x8f\xf0\xce" + + "\x95\x90\xb6:\xea\xb7\x90|'\xc9\xa3\x91\x04F\x01\xa4" + + "\x1dX\x0f\x90\xdeF\xf2gH^\xc1%\xb0\x02@\xda" + + "\x8b]\x00\xe9=$\x7f\x9e\xe4B4A\xcf&i\x1f" + + "\x1a\x00\xe9\x9f\x91\xfc%\x92W\x8eN`%\x80\xb4\xdf" + + "\x91\xbfH\xf27H^5&\x81U\x00\xd2\x01\\\x0b" + + "\x90~\x8d\xe4\x87I>\x0c\x138\x0c@:\x84\xdb\x01" + + "\xd2\x87I\xfe[\x92\x0f\xafH\xe0p\x00\xe9\x03\xc7\x9e" + + "#$\xff\x98\xe4\xd5\x91\x04V\x03H\x1f\xe2.\x80\xf4" + + "\xc7$\xff3\xc9cB\x02c\x00\xd2'\x8e_\xa7H" + + "^\xc9\x95\xb5\xec\x1e\xa3\xca\xfar^7\xfd\x90\xb1\xd2" + + "\x1dG\x97\xeemz\x9czo\x8c\x07C0@\x8c\x03" + + "\xda\x05]\xcf\xcd\xeb\xcd\xd4\xb8\xa5t\x9a\xde\x1b\xa0&" + + "\x98K\x00\x92\xd0\xaf\xfb\x10\xd7\xb5\x96\xac\x9f\x08\xca\xb3" + + "\x8eg\x89j\xce,Zz\xb1\x00uY\xc5bY?" + + "\xe7\x18Em\xb6\xa1\xe7\x17 3\xf2\xaa\xa6\xe4\x06\xc9" + + "FU\xc0a\x15\x94R\x82\xa7{\xe0\xd4t\xf6\x17\x8d" + + "\xcfh\xae\x9c\xd1u\x85\x19\x0b\x94\xce\xa1\xe4\xa9\xa9A" + + "\xe7\x18\xd7B\x09\xa9\xae[\xc9\x15\xbfMz\xea\xddJ" + + "\xa4\x1a\xdcVd\xb0~\xdf\x1bS\x0c\x9eJz7\x84" + + "\xbd\x0b*\x86&\x88t\x0eW\xd2?d\xf3;\x99\xe5" + + "\xfe\xa2\x87+=\x1b\x84p\x99?\xb7\xdd)f\xc6\x87" + + "\xe2z0\xce\x19\xfc\xa9\xd3O\xe1\xef\xa7\xec{=g" + + "\xe8\xb9C\xb1_\xc4\xa3\xbc$\x14{\xd6\xda\xcfs'" + + "\x15\xcc9D\x9e+\x0d:\xa8P\x14x\x94Wp\x18" + + "\xa7w)\xd6\x04s\xdf^F\xf7~\x8b\x13\x15Z\xb4" + + ",\x03\\\xe6\xb19T>\xfc\x09\xe8\xe0\xdd\xd9\xd0\xdc" + + "\xf6\xba\xdeA\x01\xf7\xa7\x8ae'\x9f\xf5\xc9\xd5\xe0\x1e" + + "J<\x1b\xed\x8cX\xbc\x09+z\xb3:q\xefr\xe0" + + "\xc4\xc7\x05\x0c\xa6\x90\xe8\x0d\x1d\xc5\x1d\x06p\xe2V\x01" + + "9\x7ff\x8d\xdelZ\xdcx\x07p\xe2z\x01y\x7f" + + "\xe4\x8c\xde\xb4kJ\xcf0\x04N\\)`\xc4\x1f\xe5" + + "\xa37+\x13\x97v\x01'\xaa\x02F\xfdi6z\xe3" + + "T\xf1\xfa\xb5\xc0\x89\xed\xc1L\x07\x1a\\?\x1a\xd1\xf6" + + "8\x0au\x0eK{Ox\xdcU\x00\x8dh{=0" + + "\x7f\xb6&\xd8Y\xe5\x0d) \x9eQ,\xd6H\xcd\x99" + + "{\xff\xb1\x94\x00\xa0\x11\xe5\x08\x86F\x85\x00\xe7\xfb\xbe" + + "L\xb1:'\xce\xdf\xb6e\xf2\xf6\x7f\xcb\x94\xc4\xf7g" + + "5\x9d\xe3\x0f\xbbBz\xbbB\x0f\xdfA\x1a\xbf\xc8\xd9" + + "\xbc\xf0\xc8\x1f\xa7\xcd\xa4\xff\xdf}\xfd\x87\xa8qz\x83" + + "G\xf9H\xe8Z\xbfK\xc2\xb7y\x94\x8f\x85\x1a\xa7\xa3" + + "t\xd7\x8f\xf0(\x9f\x09\xe6\x97\x9f\xdd\x01 \x9f\xe11" + + "\x15jD\xc4\xbf\xd3\xc2\xaf\xa9\\;m\x08\xbamH" + + "\x147\x03\xa4+\xa9\x8c'\x9c6$\xe2\xb6!\"v" + + "\x00\xa4kH~A\xb8\x0d\x19\x83\xd7\x01\xa4G\x93|" + + "<\xf6~\xd7\x08E#h\xd4rz\xe7\x1cU\xeb\xb7" + + "\xb6y\x03U\xb4f+j\xaeh0\x08Jk)\xd9" + + "4\x85\xaa\xbd;iu\x87*i\"a\x16M\x7f\xe0" + + "r\x0e/\xca!U\x9ef\xc3\xd0\xd1(kb\xa7\x06" + + "M\xac\xdf\xc3R/~5\x8f\xf2\x02\x0aE\xa3\x1b\x0a" + + "\xb9#h\xbb\xeb2J\xd1d}|\x00\x9e\x19\xfe\x14" + + "\xc0\\\xa2\x17s\xd9\x14\x03\xc12z\xca \x18\xb4\x99" + + "M\xb3\xb8\x97\xb9\xdc\xe1\xb0\xf7\x8f\x0e\xf4\xfe\x9f\x11\x1a" + + "\x0e{\x13z\xf4\xfem\xd5w8\xeca\xd0g8\xec" + + "~p8\xda{8|\x1e\xcfW\xb7\x8c\x852\xc69" + + "\xcdL\x87