From 25b0c40d05599ef4bcf3de93ae553d061457a51f Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:12 +0200 Subject: [PATCH 01/13] chanfitness: fix line wrapping Original PR was written with 4 spaces instead of 8, do a once off fix here rather than fixing bit-by bit in the subsequent commits and cluttering them for review. --- chanfitness/chanevent.go | 52 ++++++----- chanfitness/chanevent_test.go | 49 +++++----- chanfitness/chaneventstore.go | 87 +++++++++-------- chanfitness/chaneventstore_test.go | 145 ++++++++++++++++++----------- 4 files changed, 197 insertions(+), 136 deletions(-) diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index 53048ee3..c749cf1c 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -51,12 +51,12 @@ type chanEventLog struct { now func() time.Time // openedAt tracks the first time this channel was seen. This is not - // necessarily the time that it confirmed on chain because channel events - // are not persisted at present. + // necessarily the time that it confirmed on chain because channel + // events are not persisted at present. openedAt time.Time - // closedAt is the time that the channel was closed. If the channel has not - // been closed yet, it is zero. + // closedAt is the time that the channel was closed. If the channel has + // not been closed yet, it is zero. closedAt time.Time } @@ -83,7 +83,8 @@ func (e *chanEventLog) close() { // The open time for the eventLog will be set to the event's timestamp if it is // not set yet. func (e *chanEventLog) add(eventType eventType) { - // If the channel is already closed, return early without adding an event. + // If the channel is already closed, return early without adding an + // event. if !e.closedAt.IsZero() { return } @@ -136,14 +137,16 @@ func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { case peerOfflineEvent: offline = true - // Do not add to uptime if there is no previous online timestamp, - // the event log has started with an offline event + // Do not add to uptime if there is no previous online + // timestamp, the event log has started with an offline + // event if lastOnline.IsZero() { continue } - // The eventLog has recorded an offline event, having previously - // been online so we add an online period to to set of online periods. + // The eventLog has recorded an offline event, having + // previously been online so we add an online period to + // set of online periods. onlinePeriods = append(onlinePeriods, &onlinePeriod{ start: lastOnline, end: event.timestamp, @@ -151,15 +154,15 @@ func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { } } - // If the last event was an peer offline event, we do not need to calculate - // a final online period and can return online periods as is. + // If the last event was an peer offline event, we do not need to + // calculate a final online period and can return online periods as is. if offline { return onlinePeriods } - // The log ended on an online event, so we need to add a final online event. - // If the channel is closed, this period is until channel closure. It it is - // still open, we calculate it until the present. + // The log ended on an online event, so we need to add a final online + // event. If the channel is closed, this period is until channel + // closure. It it is still open, we calculate it until the present. endTime := e.closedAt if endTime.IsZero() { endTime = e.now() @@ -176,7 +179,8 @@ func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { // inclusive range specified. An error is returned if the end of the range is // before the start or a zero end time is returned. func (e *chanEventLog) uptime(start, end time.Time) (time.Duration, error) { - // Error if we are provided with an invalid range to calculate uptime for. + // Error if we are provided with an invalid range to calculate uptime + // for. if end.Before(start) { return 0, fmt.Errorf("end time: %v before start time: %v", end, start) @@ -188,25 +192,27 @@ func (e *chanEventLog) uptime(start, end time.Time) (time.Duration, error) { var uptime time.Duration for _, p := range e.getOnlinePeriods() { - // The online period ends before the range we're looking at, so we can - // skip over it. + // The online period ends before the range we're looking at, so + // we can skip over it. if p.end.Before(start) { continue } - // The online period starts after the range we're looking at, so can - // stop calculating uptime. + // The online period starts after the range we're looking at, so + // can stop calculating uptime. if p.start.After(end) { break } - // If the online period starts before our range, shift the start time up - // so that we only calculate uptime from the start of our range. + // If the online period starts before our range, shift the start + // time up so that we only calculate uptime from the start of + // our range. if p.start.Before(start) { p.start = start } - // If the online period ends before our range, shift the end time - // forward so that we only calculate uptime until the end of the range. + // If the online period ends before our range, shift the end + // time forward so that we only calculate uptime until the end + // of the range. if p.end.After(end) { p.end = end } diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 72733dbe..1fec1d95 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -40,9 +40,11 @@ func TestAdd(t *testing.T) { test.eventLog.add(test.event) for i, e := range test.expected { - if test.eventLog.events[i].eventType != e { - t.Fatalf("Expected event type: %v, got: %v", - e, test.eventLog.events[i].eventType) + eventType := test.eventLog.events[i].eventType + if eventType != e { + t.Fatalf("Expected event type: %v, "+ + "got: %v", e, eventType, + ) } } }) @@ -154,13 +156,13 @@ func TestGetOnlinePeriod(t *testing.T) { for i, o := range test.expectedOnline { if online[i].start != o.start { - t.Errorf("Expected start: %v, got %v", o.start, - online[i].start) + t.Errorf("Expected start: %v, got "+ + "%v", o.start, online[i].start) } if online[i].end != o.end { - t.Errorf("Expected end: %v, got %v", o.end, - online[i].end) + t.Errorf("Expected end: %v, got %v", + o.end, online[i].end) } } }) @@ -181,31 +183,34 @@ func TestUptime(t *testing.T) { tests := []struct { name string - // opened at is the time the channel was recorded as being open, and is - // never expected to be zero. + // opened at is the time the channel was recorded as being open, + // and is never expected to be zero. openedAt time.Time - // closed at is the tim the channel was recorded as being closed, and - // can have a zero value if the. + // closed at is the time the channel was recorded as being + // closed, and can have a zero value if the channel is not + // closed. closedAt time.Time - // events is the set of event log that we are calculating uptime for. + // events is the set of event log that we are calculating uptime + // for. events []*channelEvent - // startTime is the beginning of the period that we are calculating - // uptime for, it cannot have a zero value. + // startTime is the beginning of the period that we are + // calculating uptime for, it cannot have a zero value. startTime time.Time - // endTime is the end of the period that we are calculating uptime for, - // it cannot have a zero value. + // endTime is the end of the period that we are calculating + // uptime for, it cannot have a zero value. endTime time.Time - // expectedUptime is the amount of uptime we expect to be calculated - // over the period specified by startTime and endTime. + // expectedUptime is the amount of uptime we expect to be + // calculated over the period specified by startTime and + // endTime. expectedUptime time.Duration - // expectErr is set to true if we expect an error to be returned when - // calling the uptime function + // expectErr is set to true if we expect an error to be returned + // when calling the uptime function. expectErr bool }{ { @@ -378,7 +383,9 @@ func TestUptime(t *testing.T) { closedAt: test.closedAt, } - uptime, err := score.uptime(test.startTime, test.endTime) + uptime, err := score.uptime( + test.startTime, test.endTime, + ) if test.expectErr && err == nil { t.Fatal("Expected an error, got nil") } diff --git a/chanfitness/chaneventstore.go b/chanfitness/chaneventstore.go index b54409bf..1fa846ae 100644 --- a/chanfitness/chaneventstore.go +++ b/chanfitness/chaneventstore.go @@ -24,12 +24,12 @@ import ( ) var ( - // errShuttingDown is returned when the store cannot respond to a query because - // it has received the shutdown signal. + // errShuttingDown is returned when the store cannot respond to a query + // because it has received the shutdown signal. errShuttingDown = errors.New("channel event store shutting down") - // ErrChannelNotFound is returned when a query is made for a channel that - // the event store does not have knowledge of. + // ErrChannelNotFound is returned when a query is made for a channel + // that the event store does not have knowledge of. ErrChannelNotFound = errors.New("channel not found in event store") ) @@ -41,8 +41,8 @@ type ChannelEventStore struct { // channels maps channel points to event logs. channels map[wire.OutPoint]*chanEventLog - // peers tracks the current online status of peers based on online/offline - // events. + // peers tracks the current online status of peers based on online + // and offline events. peers map[route.Vertex]bool // lifespanRequests serves requests for the lifespan of channels. @@ -60,16 +60,17 @@ type ChannelEventStore struct { // activity. All elements of the config must be non-nil for the event store to // operate. type Config struct { - // SubscribeChannelEvents provides a subscription client which provides a - // stream of channel events. + // SubscribeChannelEvents provides a subscription client which provides + // a stream of channel events. SubscribeChannelEvents func() (*subscribe.Client, error) // SubscribePeerEvents provides a subscription client which provides a // stream of peer online/offline events. SubscribePeerEvents func() (*subscribe.Client, error) - // GetOpenChannels provides a list of existing open channels which is used - // to populate the ChannelEventStore with a set of channels on startup. + // GetOpenChannels provides a list of existing open channels which is + // used to populate the ChannelEventStore with a set of channels on + // startup. GetOpenChannels func() ([]*channeldb.OpenChannel, error) } @@ -140,7 +141,8 @@ func (c *ChannelEventStore) Start() error { return err } - // cancel should be called to cancel all subscriptions if an error occurs. + // cancel should be called to cancel all subscriptions if an error + // occurs. cancel := func() { channelClient.Cancel() peerClient.Cancel() @@ -166,8 +168,8 @@ func (c *ChannelEventStore) Start() error { return err } - // Add existing channels to the channel store with an initial peer - // online or offline event. + // Add existing channels to the channel store with an initial + // peer online or offline event. c.addChannel(ch.FundingOutpoint, peerKey) } @@ -200,10 +202,12 @@ func (c *ChannelEventStore) Stop() { func (c *ChannelEventStore) addChannel(channelPoint wire.OutPoint, peer route.Vertex) { - // Check for the unexpected case where the channel is already in the store. + // Check for the unexpected case where the channel is already in the + // store. _, ok := c.channels[channelPoint] if ok { - log.Errorf("Channel %v duplicated in channel store", channelPoint) + log.Errorf("Channel %v duplicated in channel store", + channelPoint) return } @@ -222,7 +226,8 @@ func (c *ChannelEventStore) addChannel(channelPoint wire.OutPoint, // closeChannel records a closed time for a channel, and returns early is the // channel is not known to the event store. func (c *ChannelEventStore) closeChannel(channelPoint wire.OutPoint) { - // Check for the unexpected case where the channel is unknown to the store. + // Check for the unexpected case where the channel is unknown to the + // store. eventLog, ok := c.channels[channelPoint] if !ok { log.Errorf("Close channel %v unknown to store", channelPoint) @@ -265,21 +270,24 @@ func (c *ChannelEventStore) consume(subscriptions *subscriptions) { // Process channel opened and closed events. case e := <-subscriptions.channelUpdates: switch event := e.(type) { - // A new channel has been opened, we must add the channel to the - // store and record a channel open event. + // A new channel has been opened, we must add the + // channel to the store and record a channel open event. case channelnotifier.OpenChannelEvent: + compressed := event.Channel.IdentityPub.SerializeCompressed() peerKey, err := route.NewVertexFromBytes( - event.Channel.IdentityPub.SerializeCompressed(), + compressed, ) if err != nil { - log.Errorf("Could not get vertex from: %v", - event.Channel.IdentityPub.SerializeCompressed()) + log.Errorf("Could not get vertex "+ + "from: %v", compressed) } - c.addChannel(event.Channel.FundingOutpoint, peerKey) + c.addChannel( + event.Channel.FundingOutpoint, peerKey, + ) - // A channel has been closed, we must remove the channel from the - // store and record a channel closed event. + // A channel has been closed, we must remove the channel + // from the store and record a channel closed event. case channelnotifier.ClosedChannelEvent: c.closeChannel(event.CloseSummary.ChanPoint) } @@ -287,13 +295,15 @@ func (c *ChannelEventStore) consume(subscriptions *subscriptions) { // Process peer online and offline events. case e := <-subscriptions.peerUpdates: switch event := e.(type) { - // We have reestablished a connection with our peer, and should - // record an online event for any channels with that peer. + // We have reestablished a connection with our peer, + // and should record an online event for any channels + // with that peer. case peernotifier.PeerOnlineEvent: c.peerEvent(event.PubKey, peerOnlineEvent) - // We have lost a connection with our peer, and should record an - // offline event for any channels with that peer. + // We have lost a connection with our peer, and should + // record an offline event for any channels with that + // peer. case peernotifier.PeerOfflineEvent: c.peerEvent(event.PubKey, peerOfflineEvent) } @@ -320,7 +330,10 @@ func (c *ChannelEventStore) consume(subscriptions *subscriptions) { if !ok { resp.err = ErrChannelNotFound } else { - uptime, err := channel.uptime(req.startTime, req.endTime) + uptime, err := channel.uptime( + req.startTime, req.endTime, + ) + resp.uptime = uptime resp.err = err } @@ -346,16 +359,16 @@ func (c *ChannelEventStore) GetLifespan( } // Send a request for the channel's lifespan to the main event loop, or - // return early with an error if the store has already received a shutdown - // signal. + // return early with an error if the store has already received a + // shutdown signal. select { case c.lifespanRequests <- request: case <-c.quit: return time.Time{}, time.Time{}, errShuttingDown } - // Return the response we receive on the response channel or exit early if - // the store is instructed to exit. + // Return the response we receive on the response channel or exit early + // if the store is instructed to exit. select { case resp := <-request.responseChan: return resp.start, resp.end, resp.err @@ -378,16 +391,16 @@ func (c *ChannelEventStore) GetUptime(channelPoint wire.OutPoint, startTime, } // Send a request for the channel's uptime to the main event loop, or - // return early with an error if the store has already received a shutdown - // signal. + // return early with an error if the store has already received a + // shutdown signal. select { case c.uptimeRequests <- request: case <-c.quit: return 0, errShuttingDown } - // Return the response we receive on the response channel or exit early if - // the store is instructed to exit. + // Return the response we receive on the response channel or exit early + // if the store is instructed to exit. select { case resp := <-request.responseChan: return resp.uptime, resp.err diff --git a/chanfitness/chaneventstore_test.go b/chanfitness/chaneventstore_test.go index 5a785884..793fbb66 100644 --- a/chanfitness/chaneventstore_test.go +++ b/chanfitness/chaneventstore_test.go @@ -19,14 +19,15 @@ import ( // functions fail. It does not test the mechanics of consuming events because // these are covered in a separate set of tests. func TestStartStoreError(t *testing.T) { - // Ok and erroring subscribe functions are defined here to de-clutter tests. + // Ok and erroring subscribe functions are defined here to de-clutter + // tests. okSubscribeFunc := func() (*subscribe.Client, error) { return &subscribe.Client{ Cancel: func() {}, }, nil } - errSubscribeFunc := func() (client *subscribe.Client, e error) { + errSubscribeFunc := func() (*subscribe.Client, error) { return nil, errors.New("intentional test err") } @@ -49,7 +50,7 @@ func TestStartStoreError(t *testing.T) { name: "Get open channels fails", ChannelEvents: okSubscribeFunc, PeerEvents: okSubscribeFunc, - GetChannels: func() (channels []*channeldb.OpenChannel, e error) { + GetChannels: func() ([]*channeldb.OpenChannel, error) { return nil, errors.New("intentional test err") }, }, @@ -66,8 +67,8 @@ func TestStartStoreError(t *testing.T) { }) err := store.Start() - // Check that we receive an error, because the test only checks for - // error cases. + // Check that we receive an error, because the test only + // checks for error cases. if err == nil { t.Fatalf("Expected error on startup, got: nil") } @@ -110,19 +111,22 @@ func TestMonitorChannelEvents(t *testing.T) { tests := []struct { name string - // generateEvents takes channels which represent the updates channels - // for subscription clients and passes events in the desired order. - // This function is intended to be blocking so that the test does not - // have a data race with event consumption, so the channels should not - // be buffered. - generateEvents func(channelEvents, peerEvents chan<- interface{}) + // generateEvents takes channels which represent the updates + // channels for subscription clients and passes events in the + // desired order. This function is intended to be blocking so + // that the test does not have a data race with event + // consumption, so the channels should not be buffered. + generateEvents func(channelEvents, + peerEvents chan<- interface{}) // expectedEvents is the expected set of event types in the store. expectedEvents []eventType }{ { name: "Channel opened, peer comes online", - generateEvents: func(channelEvents, peerEvents chan<- interface{}) { + generateEvents: func(channelEvents, + peerEvents chan<- interface{}) { + // Add an open channel event channelEvents <- channelnotifier.OpenChannelEvent{ Channel: &channeldb.OpenChannel{ @@ -132,13 +136,17 @@ func TestMonitorChannelEvents(t *testing.T) { } // Add a peer online event. - peerEvents <- peernotifier.PeerOnlineEvent{PubKey: vertex} + peerEvents <- peernotifier.PeerOnlineEvent{ + PubKey: vertex, + } }, expectedEvents: []eventType{peerOnlineEvent}, }, { name: "Duplicate channel open events", - generateEvents: func(channelEvents, peerEvents chan<- interface{}) { + generateEvents: func(channelEvents, + peerEvents chan<- interface{}) { + // Add an open channel event channelEvents <- channelnotifier.OpenChannelEvent{ Channel: &channeldb.OpenChannel{ @@ -148,7 +156,9 @@ func TestMonitorChannelEvents(t *testing.T) { } // Add a peer online event. - peerEvents <- peernotifier.PeerOnlineEvent{PubKey: vertex} + peerEvents <- peernotifier.PeerOnlineEvent{ + PubKey: vertex, + } // Add a duplicate channel open event. channelEvents <- channelnotifier.OpenChannelEvent{ @@ -162,9 +172,13 @@ func TestMonitorChannelEvents(t *testing.T) { }, { name: "Channel opened, peer already online", - generateEvents: func(channelEvents, peerEvents chan<- interface{}) { + generateEvents: func(channelEvents, + peerEvents chan<- interface{}) { + // Add a peer online event. - peerEvents <- peernotifier.PeerOnlineEvent{PubKey: vertex} + peerEvents <- peernotifier.PeerOnlineEvent{ + PubKey: vertex, + } // Add an open channel event channelEvents <- channelnotifier.OpenChannelEvent{ @@ -179,7 +193,9 @@ func TestMonitorChannelEvents(t *testing.T) { { name: "Channel opened, peer offline, closed", - generateEvents: func(channelEvents, peerEvents chan<- interface{}) { + generateEvents: func(channelEvents, + peerEvents chan<- interface{}) { + // Add an open channel event channelEvents <- channelnotifier.OpenChannelEvent{ Channel: &channeldb.OpenChannel{ @@ -189,7 +205,9 @@ func TestMonitorChannelEvents(t *testing.T) { } // Add a peer online event. - peerEvents <- peernotifier.PeerOfflineEvent{PubKey: vertex} + peerEvents <- peernotifier.PeerOfflineEvent{ + PubKey: vertex, + } // Add a close channel event. channelEvents <- channelnotifier.ClosedChannelEvent{ @@ -202,7 +220,9 @@ func TestMonitorChannelEvents(t *testing.T) { }, { name: "Event after channel close not recorded", - generateEvents: func(channelEvents, peerEvents chan<- interface{}) { + generateEvents: func(channelEvents, + peerEvents chan<- interface{}) { + // Add an open channel event channelEvents <- channelnotifier.OpenChannelEvent{ Channel: &channeldb.OpenChannel{ @@ -219,7 +239,9 @@ func TestMonitorChannelEvents(t *testing.T) { } // Add a peer online event. - peerEvents <- peernotifier.PeerOfflineEvent{PubKey: vertex} + peerEvents <- peernotifier.PeerOfflineEvent{ + PubKey: vertex, + } }, }, } @@ -228,12 +250,12 @@ func TestMonitorChannelEvents(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { - // Create a store with the channels and online peers specified - // by the test. + // Create a store with the channels and online peers + // specified by the test. store := NewChannelEventStore(&Config{}) - // Create channels which represent the subscriptions we have to peer - // and client events. + // Create channels which represent the subscriptions + // we have to peer and client events. channelEvents := make(chan interface{}) peerEvents := make(chan interface{}) @@ -244,21 +266,23 @@ func TestMonitorChannelEvents(t *testing.T) { cancel: func() {}, }) - // Add events to the store then kill the goroutine using store.Stop. + // Add events to the store then kill the goroutine using + // store.Stop. test.generateEvents(channelEvents, peerEvents) store.Stop() - // Retrieve the eventLog for the channel and check that its - // contents are as expected. + // Retrieve the eventLog for the channel and check that + // its contents are as expected. eventLog, ok := store.channels[chanPoint] if !ok { t.Fatalf("Expected to find event store") } for i, e := range eventLog.events { - if test.expectedEvents[i] != e.eventType { + expectedType := test.expectedEvents[i] + if expectedType != e.eventType { t.Fatalf("Expected type: %v, got: %v", - test.expectedEvents[i], e.eventType) + expectedType, e.eventType) } } }) @@ -309,26 +333,30 @@ func TestGetLifetime(t *testing.T) { // Stop the store's go routine. defer store.Stop() - // Add channel to eventStore if the test indicates that it should - // be present. + // Add channel to eventStore if the test indicates that + // it should be present. if test.channelFound { - store.channels[test.channelPoint] = &chanEventLog{ - openedAt: test.opened, - closedAt: test.closed, - } + store.channels[test.channelPoint] = + &chanEventLog{ + openedAt: test.opened, + closedAt: test.closed, + } } open, close, err := store.GetLifespan(test.channelPoint) if test.expectedError != err { - t.Fatalf("Expected: %v, got: %v", test.expectedError, err) + t.Fatalf("Expected: %v, got: %v", + test.expectedError, err) } if open != test.opened { - t.Errorf("Expected: %v, got %v", test.opened, open) + t.Errorf("Expected: %v, got %v", + test.opened, open) } if close != test.closed { - t.Errorf("Expected: %v, got %v", test.closed, close) + t.Errorf("Expected: %v, got %v", + test.closed, close) } }) } @@ -351,24 +379,28 @@ func TestGetUptime(t *testing.T) { channelPoint wire.OutPoint - // events is the set of events we expect to find in the channel store. + // events is the set of events we expect to find in the channel + // store. events []*channelEvent - // openedAt is the time the channel is recorded as open by the store. + // openedAt is the time the channel is recorded as open by the + // store. openedAt time.Time - // closedAt is the time the channel is recorded as closed by the store. - // If the channel is still open, this value is zero. + // closedAt is the time the channel is recorded as closed by the + // store. If the channel is still open, this value is zero. closedAt time.Time - // channelFound is true if we expect to find the channel in the store. + // channelFound is true if we expect to find the channel in the + // store. channelFound bool - // startTime specifies the beginning of the uptime range we want to - // calculate. + // startTime specifies the beginning of the uptime range we want + // to calculate. startTime time.Time - // endTime specified the end of the uptime range we want to calculate. + // endTime specified the end of the uptime range we want to + // calculate. endTime time.Time expectedUptime time.Duration @@ -428,8 +460,8 @@ func TestGetUptime(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { - // Set up event store with the events specified for the test and - // mocked time. + // Set up event store with the events specified for the + // test and mocked time. store := NewChannelEventStore(&Config{}) // Start goroutine which consumes GetUptime requests. @@ -443,7 +475,8 @@ func TestGetUptime(t *testing.T) { // Stop the store's goroutine. defer store.Stop() - // Add the channel to the store if it is intended to be found. + // Add the channel to the store if it is intended to be + // found. if test.channelFound { store.channels[test.channelPoint] = &chanEventLog{ events: test.events, @@ -453,16 +486,18 @@ func TestGetUptime(t *testing.T) { } } - uptime, err := store.GetUptime(test.channelPoint, test.startTime, test.endTime) + uptime, err := store.GetUptime( + test.channelPoint, test.startTime, test.endTime, + ) if test.expectedError != err { - t.Fatalf("Expected: %v, got: %v", test.expectedError, err) + t.Fatalf("Expected: %v, got: %v", + test.expectedError, err) } if uptime != test.expectedUptime { - t.Fatalf("Expected uptime percentage: %v, got %v", - test.expectedUptime, uptime) + t.Fatalf("Expected uptime percentage: %v, "+ + "got %v", test.expectedUptime, uptime) } - }) } } From 3aa008ab043856e39e66b2248084847c5d678132 Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:13 +0200 Subject: [PATCH 02/13] multi: add interface for subscribe client so it can be mocked The current implementation of subscribe is difficult to mock because the queue that we send updates on in unexported, so you cannot create a subscribe.Client object and then add your own updates. While it is possible to run a subscribe server in tests, subscribe servers will shutdown before dispatching their udpates to all clients, which can be flakey (and is difficult to workaround). In this commit, we add a subscription interface so that these testing struggles can be addressed with a mock. --- chanfitness/chaneventstore.go | 4 ++-- server.go | 11 ++++++++--- subscribe/interface.go | 17 +++++++++++++++++ subscribe/subscribe.go | 12 +++++++++--- 4 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 subscribe/interface.go diff --git a/chanfitness/chaneventstore.go b/chanfitness/chaneventstore.go index 1fa846ae..3bf7c324 100644 --- a/chanfitness/chaneventstore.go +++ b/chanfitness/chaneventstore.go @@ -62,11 +62,11 @@ type ChannelEventStore struct { type Config struct { // SubscribeChannelEvents provides a subscription client which provides // a stream of channel events. - SubscribeChannelEvents func() (*subscribe.Client, error) + SubscribeChannelEvents func() (subscribe.Subscription, error) // SubscribePeerEvents provides a subscription client which provides a // stream of peer online/offline events. - SubscribePeerEvents func() (*subscribe.Client, error) + SubscribePeerEvents func() (subscribe.Subscription, error) // GetOpenChannels provides a list of existing open channels which is // used to populate the ChannelEventStore with a set of channels on diff --git a/server.go b/server.go index cba47e39..91bbf211 100644 --- a/server.go +++ b/server.go @@ -60,6 +60,7 @@ import ( "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing/localchans" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/subscribe" "github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/ticker" "github.com/lightningnetwork/lnd/tor" @@ -1203,9 +1204,13 @@ func newServer(cfg *Config, listenAddrs []net.Addr, // Create a channel event store which monitors all open channels. s.chanEventStore = chanfitness.NewChannelEventStore(&chanfitness.Config{ - SubscribeChannelEvents: s.channelNotifier.SubscribeChannelEvents, - SubscribePeerEvents: s.peerNotifier.SubscribePeerEvents, - GetOpenChannels: s.remoteChanDB.FetchAllOpenChannels, + SubscribeChannelEvents: func() (subscribe.Subscription, error) { + return s.channelNotifier.SubscribeChannelEvents() + }, + SubscribePeerEvents: func() (subscribe.Subscription, error) { + return s.peerNotifier.SubscribePeerEvents() + }, + GetOpenChannels: s.remoteChanDB.FetchAllOpenChannels, }) if cfg.WtClient.Active { diff --git a/subscribe/interface.go b/subscribe/interface.go new file mode 100644 index 00000000..f28429df --- /dev/null +++ b/subscribe/interface.go @@ -0,0 +1,17 @@ +package subscribe + +// Subscription is an interface implemented by subscriptions to a server +// providing updates. +type Subscription interface { + // Updates returns a read-only channel where the updates the client has + // subscribed to will be delivered. + Updates() <-chan interface{} + + // Quit is a channel that will be closed in case the server decides to + // no longer deliver updates to this client. + Quit() <-chan struct{} + + // Cancel should be called in case the client no longer wants to + // subscribe for updates from the server. + Cancel() +} diff --git a/subscribe/subscribe.go b/subscribe/subscribe.go index 1d413031..f95283cc 100644 --- a/subscribe/subscribe.go +++ b/subscribe/subscribe.go @@ -14,9 +14,9 @@ var ErrServerShuttingDown = errors.New("subscription server shutting down") // Client is used to get notified about updates the caller has subscribed to, type Client struct { - // Cancel should be called in case the client no longer wants to + // cancel should be called in case the client no longer wants to // subscribe for updates from the server. - Cancel func() + cancel func() updates *queue.ConcurrentQueue quit chan struct{} @@ -34,6 +34,12 @@ func (c *Client) Quit() <-chan struct{} { return c.quit } +// Cancel should be called in case the client no longer wants to +// subscribe for updates from the server. +func (c *Client) Cancel() { + c.cancel() +} + // Server is a struct that manages a set of subscriptions and their // corresponding clients. Any update will be delivered to all active clients. type Server struct { @@ -118,7 +124,7 @@ func (s *Server) Subscribe() (*Client, error) { client := &Client{ updates: queue.NewConcurrentQueue(20), quit: make(chan struct{}), - Cancel: func() { + cancel: func() { select { case s.clientUpdates <- &clientUpdate{ cancel: true, From 7afd113b9f343f8e7ebac24d6f8a9ab3ae636e18 Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:14 +0200 Subject: [PATCH 03/13] chanfitness: add test context for better testing As we add more elements to the chanfitness subsystem, we will require more complex testing. The current tests are built around the inability to mock subscriptions, which is remedied by addition of our own mock. This context allows us to run the full store in a test, rather than having to manually spin up the main goroutine. Mocking our subscriptions is required so that we can block our subscribe updates on consumption, using the real package provides us with no guarantee that the client receives the update before shutdown, which produces test flakes. This change also makes a move towards separating out the testing of our event store from testing the underlying event logs to prepare for further refactoring. --- chanfitness/chaneventstore_test.go | 446 +++++++-------------- chanfitness/chaneventstore_testctx_test.go | 215 ++++++++++ 2 files changed, 350 insertions(+), 311 deletions(-) create mode 100644 chanfitness/chaneventstore_testctx_test.go diff --git a/chanfitness/chaneventstore_test.go b/chanfitness/chaneventstore_test.go index 793fbb66..df7b34d3 100644 --- a/chanfitness/chaneventstore_test.go +++ b/chanfitness/chaneventstore_test.go @@ -2,17 +2,16 @@ package chanfitness import ( "errors" + "math/big" "testing" "time" "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/channelnotifier" - "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" + "github.com/stretchr/testify/require" ) // TestStartStoreError tests the starting of the store in cases where the setup @@ -21,20 +20,18 @@ import ( func TestStartStoreError(t *testing.T) { // Ok and erroring subscribe functions are defined here to de-clutter // tests. - okSubscribeFunc := func() (*subscribe.Client, error) { - return &subscribe.Client{ - Cancel: func() {}, - }, nil + okSubscribeFunc := func() (subscribe.Subscription, error) { + return newMockSubscription(t), nil } - errSubscribeFunc := func() (*subscribe.Client, error) { + errSubscribeFunc := func() (subscribe.Subscription, error) { return nil, errors.New("intentional test err") } tests := []struct { name string - ChannelEvents func() (*subscribe.Client, error) - PeerEvents func() (*subscribe.Client, error) + ChannelEvents func() (subscribe.Subscription, error) + PeerEvents func() (subscribe.Subscription, error) GetChannels func() ([]*channeldb.OpenChannel, error) }{ { @@ -76,29 +73,6 @@ func TestStartStoreError(t *testing.T) { } } -// getTestChannel returns a non-zero peer pubKey, serialized pubKey and channel -// outpoint for testing. -func getTestChannel(t *testing.T) (*btcec.PublicKey, route.Vertex, - wire.OutPoint) { - - privKey, err := btcec.NewPrivateKey(btcec.S256()) - if err != nil { - t.Fatalf("Error getting pubkey: %v", err) - } - - pubKey, err := route.NewVertexFromBytes( - privKey.PubKey().SerializeCompressed(), - ) - if err != nil { - t.Fatalf("Could not create vertex: %v", err) - } - - return privKey.PubKey(), pubKey, wire.OutPoint{ - Hash: [chainhash.HashSize]byte{1, 2, 3}, - Index: 0, - } -} - // TestMonitorChannelEvents tests the store's handling of channel and peer // events. It tests for the unexpected cases where we receive a channel open for // an already known channel and but does not test for closing an unknown channel @@ -106,187 +80,103 @@ func getTestChannel(t *testing.T) (*btcec.PublicKey, route.Vertex, // through an eventLog which does not exist. This test does not test handling // of uptime and lifespan requests, as they are tested in their own tests. func TestMonitorChannelEvents(t *testing.T) { - pubKey, vertex, chanPoint := getTestChannel(t) + var ( + pubKey = &btcec.PublicKey{ + X: big.NewInt(0), + Y: big.NewInt(1), + Curve: btcec.S256(), + } - tests := []struct { - name string + chan1 = wire.OutPoint{Index: 1} + chan2 = wire.OutPoint{Index: 2} + ) - // generateEvents takes channels which represent the updates - // channels for subscription clients and passes events in the - // desired order. This function is intended to be blocking so - // that the test does not have a data race with event - // consumption, so the channels should not be buffered. - generateEvents func(channelEvents, - peerEvents chan<- interface{}) + peer1, err := route.NewVertexFromBytes(pubKey.SerializeCompressed()) + require.NoError(t, err) - // expectedEvents is the expected set of event types in the store. - expectedEvents []eventType - }{ - { - name: "Channel opened, peer comes online", - generateEvents: func(channelEvents, - peerEvents chan<- interface{}) { + t.Run("peer comes online after channel open", func(t *testing.T) { + gen := func(ctx *chanEventStoreTestCtx) { + ctx.sendChannelOpenedUpdate(pubKey, chan1) + ctx.peerEvent(peer1, true) + } - // Add an open channel event - channelEvents <- channelnotifier.OpenChannelEvent{ - Channel: &channeldb.OpenChannel{ - FundingOutpoint: chanPoint, - IdentityPub: pubKey, - }, - } - - // Add a peer online event. - peerEvents <- peernotifier.PeerOnlineEvent{ - PubKey: vertex, - } - }, - expectedEvents: []eventType{peerOnlineEvent}, - }, - { - name: "Duplicate channel open events", - generateEvents: func(channelEvents, - peerEvents chan<- interface{}) { - - // Add an open channel event - channelEvents <- channelnotifier.OpenChannelEvent{ - Channel: &channeldb.OpenChannel{ - FundingOutpoint: chanPoint, - IdentityPub: pubKey, - }, - } - - // Add a peer online event. - peerEvents <- peernotifier.PeerOnlineEvent{ - PubKey: vertex, - } - - // Add a duplicate channel open event. - channelEvents <- channelnotifier.OpenChannelEvent{ - Channel: &channeldb.OpenChannel{ - FundingOutpoint: chanPoint, - IdentityPub: pubKey, - }, - } - }, - expectedEvents: []eventType{peerOnlineEvent}, - }, - { - name: "Channel opened, peer already online", - generateEvents: func(channelEvents, - peerEvents chan<- interface{}) { - - // Add a peer online event. - peerEvents <- peernotifier.PeerOnlineEvent{ - PubKey: vertex, - } - - // Add an open channel event - channelEvents <- channelnotifier.OpenChannelEvent{ - Channel: &channeldb.OpenChannel{ - FundingOutpoint: chanPoint, - IdentityPub: pubKey, - }, - } - }, - expectedEvents: []eventType{peerOnlineEvent}, - }, - - { - name: "Channel opened, peer offline, closed", - generateEvents: func(channelEvents, - peerEvents chan<- interface{}) { - - // Add an open channel event - channelEvents <- channelnotifier.OpenChannelEvent{ - Channel: &channeldb.OpenChannel{ - FundingOutpoint: chanPoint, - IdentityPub: pubKey, - }, - } - - // Add a peer online event. - peerEvents <- peernotifier.PeerOfflineEvent{ - PubKey: vertex, - } - - // Add a close channel event. - channelEvents <- channelnotifier.ClosedChannelEvent{ - CloseSummary: &channeldb.ChannelCloseSummary{ - ChanPoint: chanPoint, - }, - } - }, - expectedEvents: []eventType{peerOfflineEvent}, - }, - { - name: "Event after channel close not recorded", - generateEvents: func(channelEvents, - peerEvents chan<- interface{}) { - - // Add an open channel event - channelEvents <- channelnotifier.OpenChannelEvent{ - Channel: &channeldb.OpenChannel{ - FundingOutpoint: chanPoint, - IdentityPub: pubKey, - }, - } - - // Add a close channel event. - channelEvents <- channelnotifier.ClosedChannelEvent{ - CloseSummary: &channeldb.ChannelCloseSummary{ - ChanPoint: chanPoint, - }, - } - - // Add a peer online event. - peerEvents <- peernotifier.PeerOfflineEvent{ - PubKey: vertex, - } - }, - }, - } - - for _, test := range tests { - test := test - - t.Run(test.name, func(t *testing.T) { - // Create a store with the channels and online peers - // specified by the test. - store := NewChannelEventStore(&Config{}) - - // Create channels which represent the subscriptions - // we have to peer and client events. - channelEvents := make(chan interface{}) - peerEvents := make(chan interface{}) - - store.wg.Add(1) - go store.consume(&subscriptions{ - channelUpdates: channelEvents, - peerUpdates: peerEvents, - cancel: func() {}, - }) - - // Add events to the store then kill the goroutine using - // store.Stop. - test.generateEvents(channelEvents, peerEvents) - store.Stop() - - // Retrieve the eventLog for the channel and check that - // its contents are as expected. - eventLog, ok := store.channels[chanPoint] - if !ok { - t.Fatalf("Expected to find event store") - } - - for i, e := range eventLog.events { - expectedType := test.expectedEvents[i] - if expectedType != e.eventType { - t.Fatalf("Expected type: %v, got: %v", - expectedType, e.eventType) - } - } + testEventStore(t, gen, 1, map[route.Vertex]bool{ + peer1: true, }) - } + }) + + t.Run("duplicate channel open events", func(t *testing.T) { + gen := func(ctx *chanEventStoreTestCtx) { + ctx.sendChannelOpenedUpdate(pubKey, chan1) + ctx.sendChannelOpenedUpdate(pubKey, chan1) + ctx.peerEvent(peer1, true) + } + + testEventStore(t, gen, 1, map[route.Vertex]bool{ + peer1: true, + }) + }) + + t.Run("peer online before channel created", func(t *testing.T) { + gen := func(ctx *chanEventStoreTestCtx) { + ctx.peerEvent(peer1, true) + ctx.sendChannelOpenedUpdate(pubKey, chan1) + } + + testEventStore(t, gen, 1, map[route.Vertex]bool{ + peer1: true, + }) + }) + + t.Run("multiple channels for peer", func(t *testing.T) { + gen := func(ctx *chanEventStoreTestCtx) { + ctx.peerEvent(peer1, true) + ctx.sendChannelOpenedUpdate(pubKey, chan1) + + ctx.peerEvent(peer1, false) + ctx.sendChannelOpenedUpdate(pubKey, chan2) + } + + testEventStore(t, gen, 2, map[route.Vertex]bool{ + peer1: false, + }) + }) + + t.Run("multiple channels for peer, one closed", func(t *testing.T) { + gen := func(ctx *chanEventStoreTestCtx) { + ctx.peerEvent(peer1, true) + ctx.sendChannelOpenedUpdate(pubKey, chan1) + + ctx.peerEvent(peer1, false) + ctx.sendChannelOpenedUpdate(pubKey, chan2) + + ctx.closeChannel(chan1, pubKey) + ctx.peerEvent(peer1, true) + } + + testEventStore(t, gen, 2, map[route.Vertex]bool{ + peer1: true, + }) + }) + +} + +// testEventStore creates a new test contexts, generates a set of events for it +// and tests that it has the number of channels and online state for peers that +// we expect. +func testEventStore(t *testing.T, generateEvents func(*chanEventStoreTestCtx), + expectedChannels int, expectedPeers map[route.Vertex]bool) { + + testCtx := newChanEventStoreTestCtx(t) + testCtx.start() + + generateEvents(testCtx) + + // Shutdown the store so that we can safely access the maps in our event + // store. + testCtx.stop() + require.Len(t, testCtx.store.channels, expectedChannels) + require.Equal(t, expectedPeers, testCtx.store.peers) } // TestGetLifetime tests the GetLifetime function for the cases where a channel @@ -319,45 +209,27 @@ func TestGetLifetime(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { - // Create and empty events store for testing. - store := NewChannelEventStore(&Config{}) - - // Start goroutine which consumes GetLifespan requests. - store.wg.Add(1) - go store.consume(&subscriptions{ - channelUpdates: make(chan interface{}), - peerUpdates: make(chan interface{}), - cancel: func() {}, - }) - - // Stop the store's go routine. - defer store.Stop() + ctx := newChanEventStoreTestCtx(t) // Add channel to eventStore if the test indicates that // it should be present. if test.channelFound { - store.channels[test.channelPoint] = + ctx.store.channels[test.channelPoint] = &chanEventLog{ openedAt: test.opened, closedAt: test.closed, } } - open, close, err := store.GetLifespan(test.channelPoint) - if test.expectedError != err { - t.Fatalf("Expected: %v, got: %v", - test.expectedError, err) - } + ctx.start() - if open != test.opened { - t.Errorf("Expected: %v, got %v", - test.opened, open) - } + open, close, err := ctx.store.GetLifespan(test.channelPoint) + require.Equal(t, test.expectedError, err) - if close != test.closed { - t.Errorf("Expected: %v, got %v", - test.closed, close) - } + require.Equal(t, test.opened, open) + require.Equal(t, test.closed, close) + + ctx.stop() }) } } @@ -460,44 +332,29 @@ func TestGetUptime(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { - // Set up event store with the events specified for the - // test and mocked time. - store := NewChannelEventStore(&Config{}) + ctx := newChanEventStoreTestCtx(t) - // Start goroutine which consumes GetUptime requests. - store.wg.Add(1) - go store.consume(&subscriptions{ - channelUpdates: make(chan interface{}), - peerUpdates: make(chan interface{}), - cancel: func() {}, - }) - - // Stop the store's goroutine. - defer store.Stop() - - // Add the channel to the store if it is intended to be - // found. + // If we're supposed to find the channel for this test, + // add events for it to the store. if test.channelFound { - store.channels[test.channelPoint] = &chanEventLog{ + eventLog := &chanEventLog{ events: test.events, now: func() time.Time { return now }, openedAt: test.openedAt, closedAt: test.closedAt, } + ctx.store.channels[test.channelPoint] = eventLog } - uptime, err := store.GetUptime( + ctx.start() + + uptime, err := ctx.store.GetUptime( test.channelPoint, test.startTime, test.endTime, ) - if test.expectedError != err { - t.Fatalf("Expected: %v, got: %v", - test.expectedError, err) - } + require.Equal(t, test.expectedError, err) + require.Equal(t, test.expectedUptime, uptime) - if uptime != test.expectedUptime { - t.Fatalf("Expected uptime percentage: %v, "+ - "got %v", test.expectedUptime, uptime) - } + ctx.stop() }) } } @@ -507,58 +364,25 @@ func TestGetUptime(t *testing.T) { // did not have an opened time set, and checks that an online event is set for // peers that are online at the time that a channel is opened. func TestAddChannel(t *testing.T) { - _, vertex, chanPoint := getTestChannel(t) + ctx := newChanEventStoreTestCtx(t) + ctx.start() - tests := []struct { - name string + // Create a channel for a peer that is not online yet. + _, _, channel1 := ctx.createChannel() - // peers maps peers to an online state. - peers map[route.Vertex]bool + // Get a set of values for another channel, but do not create it yet. + // + peer2, pubkey2, channel2 := ctx.newChannel() + ctx.peerEvent(peer2, true) + ctx.sendChannelOpenedUpdate(pubkey2, channel2) - expectedEvents []eventType - }{ - { - name: "peer offline", - peers: make(map[route.Vertex]bool), - expectedEvents: []eventType{}, - }, - { - name: "peer online", - peers: map[route.Vertex]bool{ - vertex: true, - }, - expectedEvents: []eventType{peerOnlineEvent}, - }, - } + ctx.stop() - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - store := NewChannelEventStore(&Config{}) - store.peers = test.peers + // Assert that our peer that was offline on connection has no events + // and our peer that was online on connection has one. + require.Len(t, ctx.store.channels[channel1].events, 0) - // Add channel to the store. - store.addChannel(chanPoint, vertex) - - // Check that the eventLog is successfully added. - eventLog, ok := store.channels[chanPoint] - if !ok { - t.Fatalf("channel should be in store") - } - - // Check that the eventLog contains the events we - // expect. - for i, e := range test.expectedEvents { - if e != eventLog.events[i].eventType { - t.Fatalf("expected: %v, got: %v", - e, eventLog.events[i].eventType) - } - } - - // Ensure that open time is always set. - if eventLog.openedAt.IsZero() { - t.Fatalf("channel should have opened at set") - } - }) - } + chan2Events := ctx.store.channels[channel2].events + require.Len(t, chan2Events, 1) + require.Equal(t, peerOnlineEvent, chan2Events[0].eventType) } diff --git a/chanfitness/chaneventstore_testctx_test.go b/chanfitness/chaneventstore_testctx_test.go new file mode 100644 index 00000000..86ff43a2 --- /dev/null +++ b/chanfitness/chaneventstore_testctx_test.go @@ -0,0 +1,215 @@ +package chanfitness + +import ( + "math/big" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/channelnotifier" + "github.com/lightningnetwork/lnd/peernotifier" + "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/subscribe" + "github.com/stretchr/testify/require" +) + +// timeout is the amount of time we allow our blocking test calls. +var timeout = time.Second + +// chanEventStoreTestCtx is a helper struct which can be used to test the +// channel event store. +type chanEventStoreTestCtx struct { + t *testing.T + + store *ChannelEventStore + + channelSubscription *mockSubscription + peerSubscription *mockSubscription + + // testVarIdx is an index which will be used to deterministically add + // channels and public keys to our test context. We use a single value + // for a single pubkey + channel combination because its actual value + // does not matter. + testVarIdx int +} + +// newChanEventStoreTestCtx creates a test context which can be used to test +// the event store. +func newChanEventStoreTestCtx(t *testing.T) *chanEventStoreTestCtx { + testCtx := &chanEventStoreTestCtx{ + t: t, + channelSubscription: newMockSubscription(t), + peerSubscription: newMockSubscription(t), + } + + cfg := &Config{ + SubscribeChannelEvents: func() (subscribe.Subscription, error) { + return testCtx.channelSubscription, nil + }, + SubscribePeerEvents: func() (subscribe.Subscription, error) { + return testCtx.peerSubscription, nil + }, + GetOpenChannels: func() ([]*channeldb.OpenChannel, error) { + return nil, nil + }, + } + + testCtx.store = NewChannelEventStore(cfg) + + return testCtx +} + +// start starts the test context's event store. +func (c *chanEventStoreTestCtx) start() { + require.NoError(c.t, c.store.Start()) +} + +// stop stops the channel event store's subscribe servers and the store itself. +func (c *chanEventStoreTestCtx) stop() { + c.store.Stop() + + // Make sure that the cancel function was called for both of our + // subscription mocks. + c.channelSubscription.assertCancelled() + c.peerSubscription.assertCancelled() +} + +// newChannel creates a new, unique test channel. Note that this function +// does not add it to the test event store, it just creates mocked values. +func (c *chanEventStoreTestCtx) newChannel() (route.Vertex, *btcec.PublicKey, + wire.OutPoint) { + + // Create a pubkey for our channel peer. + pubKey := &btcec.PublicKey{ + X: big.NewInt(int64(c.testVarIdx)), + Y: big.NewInt(int64(c.testVarIdx)), + Curve: btcec.S256(), + } + + // Create vertex from our pubkey. + vertex, err := route.NewVertexFromBytes(pubKey.SerializeCompressed()) + require.NoError(c.t, err) + + // Create a channel point using our channel index, then increment it. + chanPoint := wire.OutPoint{ + Hash: [chainhash.HashSize]byte{1, 2, 3}, + Index: uint32(c.testVarIdx), + } + + // Increment the index we use so that the next channel and pubkey we + // create will be unique. + c.testVarIdx++ + + return vertex, pubKey, chanPoint +} + +// createChannel creates a new channel, notifies the event store that it has +// been created and returns the peer vertex, pubkey and channel point. +func (c *chanEventStoreTestCtx) createChannel() (route.Vertex, *btcec.PublicKey, + wire.OutPoint) { + + vertex, pubKey, chanPoint := c.newChannel() + c.sendChannelOpenedUpdate(pubKey, chanPoint) + + return vertex, pubKey, chanPoint +} + +// closeChannel sends a close channel event to our subscribe server. +func (c *chanEventStoreTestCtx) closeChannel(channel wire.OutPoint, + peer *btcec.PublicKey) { + + update := channelnotifier.ClosedChannelEvent{ + CloseSummary: &channeldb.ChannelCloseSummary{ + ChanPoint: channel, + RemotePub: peer, + }, + } + + c.channelSubscription.sendUpdate(update) +} + +// peerEvent sends a peer online or offline event to the store for the peer +// provided. +func (c *chanEventStoreTestCtx) peerEvent(peer route.Vertex, online bool) { + var update interface{} + if online { + update = peernotifier.PeerOnlineEvent{PubKey: peer} + } else { + update = peernotifier.PeerOfflineEvent{PubKey: peer} + } + + c.peerSubscription.sendUpdate(update) +} + +// sendChannelOpenedUpdate notifies the test event store that a channel has +// been opened. +func (c *chanEventStoreTestCtx) sendChannelOpenedUpdate(pubkey *btcec.PublicKey, + channel wire.OutPoint) { + + update := channelnotifier.OpenChannelEvent{ + Channel: &channeldb.OpenChannel{ + FundingOutpoint: channel, + IdentityPub: pubkey, + }, + } + + c.channelSubscription.sendUpdate(update) +} + +// mockSubscription is a mock subscription client that blocks on sends into the +// updates channel. We use this mock rather than an actual subscribe client +// because they do not block, which makes tests race (because we have no way +// to guarantee that the test client consumes the update before shutdown). +type mockSubscription struct { + t *testing.T + updates chan interface{} + + // Embed the subscription interface in this mock so that we satisfy it. + subscribe.Subscription +} + +// newMockSubscription creates a mock subscription. +func newMockSubscription(t *testing.T) *mockSubscription { + return &mockSubscription{ + t: t, + updates: make(chan interface{}), + } +} + +// sendUpdate sends an update into our updates channel, mocking the dispatch of +// an update from a subscription server. This call will fail the test if the +// update is not consumed within our timeout. +func (m *mockSubscription) sendUpdate(update interface{}) { + select { + case m.updates <- update: + + case <-time.After(timeout): + m.t.Fatalf("update: %v timeout", update) + } +} + +// Updates returns the updates channel for the mock. +func (m *mockSubscription) Updates() <-chan interface{} { + return m.updates +} + +// Cancel should be called in case the client no longer wants to subscribe for +// updates from the server. +func (m *mockSubscription) Cancel() { + close(m.updates) +} + +// assertCancelled asserts that the cancel function has been called for this +// mock. +func (m *mockSubscription) assertCancelled() { + select { + case _, open := <-m.updates: + require.False(m.t, open, "subscription not cancelled") + + case <-time.After(timeout): + m.t.Fatalf("assert cancelled timeout") + } +} From 94accfb69d82b902a54641999443155a66471755 Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:15 +0200 Subject: [PATCH 04/13] chanfitness: pass clock in to chaneventstore for testing --- chanfitness/chanevent.go | 18 +++--- chanfitness/chanevent_test.go | 74 ++++++++++------------ chanfitness/chaneventstore.go | 7 +- chanfitness/chaneventstore_test.go | 30 +++++---- chanfitness/chaneventstore_testctx_test.go | 6 ++ server.go | 1 + 6 files changed, 71 insertions(+), 65 deletions(-) diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index c749cf1c..1125f373 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -5,6 +5,7 @@ import ( "time" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/routing/route" ) @@ -46,9 +47,8 @@ type chanEventLog struct { // events is a log of timestamped events observed for the channel. events []*channelEvent - // now is expected to return the current time. It is supplied as an - // external function to enable deterministic unit tests. - now func() time.Time + // clock allows creation of deterministic unit tests. + clock clock.Clock // openedAt tracks the first time this channel was seen. This is not // necessarily the time that it confirmed on chain because channel @@ -62,13 +62,13 @@ type chanEventLog struct { // newEventLog creates an event log for a channel with the openedAt time set. func newEventLog(channelPoint wire.OutPoint, peer route.Vertex, - now func() time.Time) *chanEventLog { + clock clock.Clock) *chanEventLog { eventlog := &chanEventLog{ channelPoint: channelPoint, peer: peer, - now: now, - openedAt: now(), + clock: clock, + openedAt: clock.Now(), } return eventlog @@ -76,7 +76,7 @@ func newEventLog(channelPoint wire.OutPoint, peer route.Vertex, // close sets the closing time for an event log. func (e *chanEventLog) close() { - e.closedAt = e.now() + e.closedAt = e.clock.Now() } // add appends an event with the given type and current time to the event log. @@ -91,7 +91,7 @@ func (e *chanEventLog) add(eventType eventType) { // Add the event to the eventLog with the current timestamp. event := &channelEvent{ - timestamp: e.now(), + timestamp: e.clock.Now(), eventType: eventType, } e.events = append(e.events, event) @@ -165,7 +165,7 @@ func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { // closure. It it is still open, we calculate it until the present. endTime := e.closedAt if endTime.IsZero() { - endTime = e.now() + endTime = e.clock.Now() } // Add the final online period to the set and return. diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 1fec1d95..457ae80b 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -3,6 +3,8 @@ package chanfitness import ( "testing" "time" + + "github.com/lightningnetwork/lnd/clock" ) // TestAdd tests adding events to an event log. It tests the case where the @@ -18,7 +20,7 @@ func TestAdd(t *testing.T) { { name: "Channel open", eventLog: &chanEventLog{ - now: time.Now, + clock: clock.NewTestClock(testNow), }, event: peerOnlineEvent, expected: []eventType{peerOnlineEvent}, @@ -26,7 +28,7 @@ func TestAdd(t *testing.T) { { name: "Channel closed, event not added", eventLog: &chanEventLog{ - now: time.Now, + clock: clock.NewTestClock(testNow), }, event: peerOnlineEvent, expected: []eventType{}, @@ -55,13 +57,10 @@ func TestAdd(t *testing.T) { // where no events present, and the case where an additional online period // must be added because the event log ends on an online event. func TestGetOnlinePeriod(t *testing.T) { - // Set time for consistent testing. - now := time.Now() - - fourHoursAgo := now.Add(time.Hour * -4) - threeHoursAgo := now.Add(time.Hour * -3) - twoHoursAgo := now.Add(time.Hour * -2) - oneHourAgo := now.Add(time.Hour * -1) + fourHoursAgo := testNow.Add(time.Hour * -4) + threeHoursAgo := testNow.Add(time.Hour * -3) + twoHoursAgo := testNow.Add(time.Hour * -2) + oneHourAgo := testNow.Add(time.Hour * -1) tests := []struct { name string @@ -112,7 +111,7 @@ func TestGetOnlinePeriod(t *testing.T) { expectedOnline: []*onlinePeriod{ { start: fourHoursAgo, - end: now, + end: testNow, }, }, }, @@ -139,10 +138,8 @@ func TestGetOnlinePeriod(t *testing.T) { t.Run(test.name, func(t *testing.T) { score := &chanEventLog{ - events: test.events, - now: func() time.Time { - return now - }, + events: test.events, + clock: clock.NewTestClock(testNow), openedAt: test.openedAt, closedAt: test.closedAt, } @@ -172,13 +169,10 @@ func TestGetOnlinePeriod(t *testing.T) { // TestUptime tests channel uptime calculation based on its event log. func TestUptime(t *testing.T) { - // Set time for consistent testing. - now := time.Now() - - fourHoursAgo := now.Add(time.Hour * -4) - threeHoursAgo := now.Add(time.Hour * -3) - twoHoursAgo := now.Add(time.Hour * -2) - oneHourAgo := now.Add(time.Hour * -1) + fourHoursAgo := testNow.Add(time.Hour * -4) + threeHoursAgo := testNow.Add(time.Hour * -3) + twoHoursAgo := testNow.Add(time.Hour * -2) + oneHourAgo := testNow.Add(time.Hour * -1) tests := []struct { name string @@ -216,7 +210,7 @@ func TestUptime(t *testing.T) { { name: "End before start", endTime: threeHoursAgo, - startTime: now, + startTime: testNow, expectErr: true, }, { @@ -234,7 +228,7 @@ func TestUptime(t *testing.T) { }, }, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour * 3, }, { @@ -247,7 +241,7 @@ func TestUptime(t *testing.T) { }, }, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour * 4, }, { @@ -261,7 +255,7 @@ func TestUptime(t *testing.T) { }, }, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, }, { name: "Online event before close", @@ -274,7 +268,7 @@ func TestUptime(t *testing.T) { }, }, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour, }, { @@ -292,7 +286,7 @@ func TestUptime(t *testing.T) { }, }, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour, }, { @@ -306,7 +300,7 @@ func TestUptime(t *testing.T) { }, }, startTime: twoHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour, }, { @@ -318,12 +312,12 @@ func TestUptime(t *testing.T) { eventType: peerOnlineEvent, }, { - timestamp: now.Add(time.Hour), + timestamp: testNow.Add(time.Hour), eventType: peerOfflineEvent, }, }, startTime: twoHoursAgo, - endTime: now, + endTime: testNow, expectedUptime: time.Hour * 2, }, { @@ -341,30 +335,30 @@ func TestUptime(t *testing.T) { }, { name: "Multiple online and offline", - openedAt: now.Add(time.Hour * -8), + openedAt: testNow.Add(time.Hour * -8), events: []*channelEvent{ { - timestamp: now.Add(time.Hour * -7), + timestamp: testNow.Add(time.Hour * -7), eventType: peerOnlineEvent, }, { - timestamp: now.Add(time.Hour * -6), + timestamp: testNow.Add(time.Hour * -6), eventType: peerOfflineEvent, }, { - timestamp: now.Add(time.Hour * -5), + timestamp: testNow.Add(time.Hour * -5), eventType: peerOnlineEvent, }, { - timestamp: now.Add(time.Hour * -4), + timestamp: testNow.Add(time.Hour * -4), eventType: peerOfflineEvent, }, { - timestamp: now.Add(time.Hour * -3), + timestamp: testNow.Add(time.Hour * -3), eventType: peerOnlineEvent, }, }, - startTime: now.Add(time.Hour * -8), + startTime: testNow.Add(time.Hour * -8), endTime: oneHourAgo, expectedUptime: time.Hour * 4, }, @@ -375,10 +369,8 @@ func TestUptime(t *testing.T) { t.Run(test.name, func(t *testing.T) { score := &chanEventLog{ - events: test.events, - now: func() time.Time { - return now - }, + events: test.events, + clock: clock.NewTestClock(testNow), openedAt: test.openedAt, closedAt: test.closedAt, } diff --git a/chanfitness/chaneventstore.go b/chanfitness/chaneventstore.go index 3bf7c324..6ff73ccf 100644 --- a/chanfitness/chaneventstore.go +++ b/chanfitness/chaneventstore.go @@ -18,6 +18,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" @@ -72,6 +73,10 @@ type Config struct { // used to populate the ChannelEventStore with a set of channels on // startup. GetOpenChannels func() ([]*channeldb.OpenChannel, error) + + // Clock is the time source that the subsystem uses, provided here + // for ease of testing. + Clock clock.Clock } // lifespanRequest contains the channel ID required to query the store for a @@ -212,7 +217,7 @@ func (c *ChannelEventStore) addChannel(channelPoint wire.OutPoint, } // Create an event log for the channel. - eventLog := newEventLog(channelPoint, peer, time.Now) + eventLog := newEventLog(channelPoint, peer, c.cfg.Clock) // If the peer is already online, add a peer online event to record // the starting state of the peer. diff --git a/chanfitness/chaneventstore_test.go b/chanfitness/chaneventstore_test.go index df7b34d3..206ec8ca 100644 --- a/chanfitness/chaneventstore_test.go +++ b/chanfitness/chaneventstore_test.go @@ -9,11 +9,15 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" "github.com/stretchr/testify/require" ) +// testNow is the current time tests will use. +var testNow = time.Unix(1592465134, 0) + // TestStartStoreError tests the starting of the store in cases where the setup // functions fail. It does not test the mechanics of consuming events because // these are covered in a separate set of tests. @@ -57,10 +61,13 @@ func TestStartStoreError(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { + clock := clock.NewTestClock(testNow) + store := NewChannelEventStore(&Config{ SubscribeChannelEvents: test.ChannelEvents, SubscribePeerEvents: test.PeerEvents, GetOpenChannels: test.GetChannels, + Clock: clock, }) err := store.Start() @@ -182,8 +189,6 @@ func testEventStore(t *testing.T, generateEvents func(*chanEventStoreTestCtx), // TestGetLifetime tests the GetLifetime function for the cases where a channel // is known and unknown to the store. func TestGetLifetime(t *testing.T) { - now := time.Now() - tests := []struct { name string channelFound bool @@ -195,8 +200,8 @@ func TestGetLifetime(t *testing.T) { { name: "Channel found", channelFound: true, - opened: now, - closed: now.Add(time.Hour * -1), + opened: testNow, + closed: testNow.Add(time.Hour * -1), expectedError: nil, }, { @@ -240,11 +245,8 @@ func TestGetLifetime(t *testing.T) { // tests the unexpected edge cases where a tracked channel does not have any // events recorded, and when a zero time is specified for the uptime range. func TestGetUptime(t *testing.T) { - // Set time for deterministic unit tests. - now := time.Now() - - twoHoursAgo := now.Add(time.Hour * -2) - fourHoursAgo := now.Add(time.Hour * -4) + twoHoursAgo := testNow.Add(time.Hour * -2) + fourHoursAgo := testNow.Add(time.Hour * -4) tests := []struct { name string @@ -282,7 +284,7 @@ func TestGetUptime(t *testing.T) { { name: "No events", startTime: twoHoursAgo, - endTime: now, + endTime: testNow, channelFound: true, expectedError: nil, }, @@ -301,7 +303,7 @@ func TestGetUptime(t *testing.T) { openedAt: fourHoursAgo, expectedUptime: time.Hour * 2, startTime: fourHoursAgo, - endTime: now, + endTime: testNow, channelFound: true, expectedError: nil, }, @@ -315,14 +317,14 @@ func TestGetUptime(t *testing.T) { }, openedAt: fourHoursAgo, expectedUptime: time.Hour * 4, - endTime: now, + endTime: testNow, channelFound: true, expectedError: nil, }, { name: "Channel not found", startTime: twoHoursAgo, - endTime: now, + endTime: testNow, channelFound: false, expectedError: ErrChannelNotFound, }, @@ -339,7 +341,7 @@ func TestGetUptime(t *testing.T) { if test.channelFound { eventLog := &chanEventLog{ events: test.events, - now: func() time.Time { return now }, + clock: clock.NewTestClock(testNow), openedAt: test.openedAt, closedAt: test.closedAt, } diff --git a/chanfitness/chaneventstore_testctx_test.go b/chanfitness/chaneventstore_testctx_test.go index 86ff43a2..6a6b8fd7 100644 --- a/chanfitness/chaneventstore_testctx_test.go +++ b/chanfitness/chaneventstore_testctx_test.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channelnotifier" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" @@ -34,6 +35,9 @@ type chanEventStoreTestCtx struct { // for a single pubkey + channel combination because its actual value // does not matter. testVarIdx int + + // clock is the clock that our test store will use. + clock *clock.TestClock } // newChanEventStoreTestCtx creates a test context which can be used to test @@ -43,9 +47,11 @@ func newChanEventStoreTestCtx(t *testing.T) *chanEventStoreTestCtx { t: t, channelSubscription: newMockSubscription(t), peerSubscription: newMockSubscription(t), + clock: clock.NewTestClock(testNow), } cfg := &Config{ + Clock: testCtx.clock, SubscribeChannelEvents: func() (subscribe.Subscription, error) { return testCtx.channelSubscription, nil }, diff --git a/server.go b/server.go index 91bbf211..d737a096 100644 --- a/server.go +++ b/server.go @@ -1211,6 +1211,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, return s.peerNotifier.SubscribePeerEvents() }, GetOpenChannels: s.remoteChanDB.FetchAllOpenChannels, + Clock: clock.NewDefaultClock(), }) if cfg.WtClient.Active { From c33d0aad465ef2454f44f0c3ca563a38869e7192 Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:15 +0200 Subject: [PATCH 05/13] chanfitness/test: switchover to testify --- chanfitness/chanevent_test.go | 66 ++++++++++++----------------------- 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 457ae80b..4086a40f 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/lightningnetwork/lnd/clock" + "github.com/stretchr/testify/require" ) // TestAdd tests adding events to an event log. It tests the case where the @@ -12,26 +13,32 @@ import ( // closed and the event should not be added. func TestAdd(t *testing.T) { tests := []struct { - name string - eventLog *chanEventLog - event eventType - expected []eventType + name string + eventLog *chanEventLog + event eventType + expectedEvents []*channelEvent }{ { name: "Channel open", eventLog: &chanEventLog{ clock: clock.NewTestClock(testNow), }, - event: peerOnlineEvent, - expected: []eventType{peerOnlineEvent}, + event: peerOnlineEvent, + expectedEvents: []*channelEvent{ + { + eventType: peerOnlineEvent, + timestamp: testNow, + }, + }, }, { name: "Channel closed, event not added", eventLog: &chanEventLog{ - clock: clock.NewTestClock(testNow), + clock: clock.NewTestClock(testNow), + closedAt: testNow, }, - event: peerOnlineEvent, - expected: []eventType{}, + event: peerOnlineEvent, + expectedEvents: nil, }, } @@ -41,14 +48,9 @@ func TestAdd(t *testing.T) { t.Run(test.name, func(t *testing.T) { test.eventLog.add(test.event) - for i, e := range test.expected { - eventType := test.eventLog.events[i].eventType - if eventType != e { - t.Fatalf("Expected event type: %v, "+ - "got: %v", e, eventType, - ) - } - } + require.Equal( + t, test.expectedEvents, test.eventLog.events, + ) }) } } @@ -146,22 +148,7 @@ func TestGetOnlinePeriod(t *testing.T) { online := score.getOnlinePeriods() - if len(online) != len(test.expectedOnline) { - t.Fatalf("Expectd: %v online periods, got: %v", - len(test.expectedOnline), len(online)) - } - - for i, o := range test.expectedOnline { - if online[i].start != o.start { - t.Errorf("Expected start: %v, got "+ - "%v", o.start, online[i].start) - } - - if online[i].end != o.end { - t.Errorf("Expected end: %v, got %v", - o.end, online[i].end) - } - } + require.Equal(t, test.expectedOnline, online) }) } @@ -378,17 +365,8 @@ func TestUptime(t *testing.T) { uptime, err := score.uptime( test.startTime, test.endTime, ) - if test.expectErr && err == nil { - t.Fatal("Expected an error, got nil") - } - if !test.expectErr && err != nil { - t.Fatalf("Expcted no error, got: %v", err) - } - - if uptime != test.expectedUptime { - t.Errorf("Expected uptime: %v, got: %v", - test.expectedUptime, uptime) - } + require.Equal(t, test.expectErr, err != nil) + require.Equal(t, test.expectedUptime, uptime) }) } } From 7930ef7cf46b2d861b7e47dbe38db9c15db5b754 Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:16 +0200 Subject: [PATCH 06/13] chanfitness: make online period calculation tolerant of duplicates To get our uptime, we first filter our event log to get online periods. This change updates this code to be tolerant of consecutive online or offline events in the log. This will be required for rate limiting, because we will not record every event for anti-dos reasons, so we could record an online event, ignore an offline event and then record another offline event. We could just ignore this duplicate event, but we will also need this tolerance for when we persist uptime and our peers can have their last event before restart as an online event and record another online event when we come back up. --- chanfitness/chanevent.go | 89 +++++++++++++++++---------- chanfitness/chanevent_test.go | 111 ++++++++++++++++++++++++++++++++-- 2 files changed, 164 insertions(+), 36 deletions(-) diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index 1125f373..6a3af2aa 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -107,8 +107,11 @@ type onlinePeriod struct { // getOnlinePeriods returns a list of all the periods that the event log has // recorded the remote peer as being online. In the unexpected case where there // are no events, the function returns early. Online periods are defined as a -// peer online event which is terminated by a peer offline event. This function -// expects the event log provided to be ordered by ascending timestamp. +// peer online event which is terminated by a peer offline event. If the event +// log ends on a peer online event, it appends a final period which is +// calculated until the present. This function expects the event log provided +// to be ordered by ascending timestamp, and can tolerate multiple consecutive +// online or offline events. func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { // Return early if there are no events, there are no online periods. if len(e.events) == 0 { @@ -116,8 +119,11 @@ func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { } var ( - lastOnline time.Time - offline bool + // lastEvent tracks the last event that we had that was of + // a different type to our own. It is used to determine the + // start time of our online periods when we experience an + // offline event, and to track our last recorded state. + lastEvent *channelEvent onlinePeriods []*onlinePeriod ) @@ -128,51 +134,72 @@ func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { // recent event is tracked using the offline bool so that we can add a // final online period if necessary. for _, event := range e.events { - switch event.eventType { case peerOnlineEvent: - lastOnline = event.timestamp - offline = false - - case peerOfflineEvent: - offline = true - - // Do not add to uptime if there is no previous online - // timestamp, the event log has started with an offline - // event - if lastOnline.IsZero() { - continue + // If our previous event is nil, we just set it and + // break out of the switch. + if lastEvent == nil { + lastEvent = event + break } - // The eventLog has recorded an offline event, having - // previously been online so we add an online period to - // set of online periods. - onlinePeriods = append(onlinePeriods, &onlinePeriod{ - start: lastOnline, - end: event.timestamp, - }) + // If our previous event was an offline event, we update + // it to this event. We do not do this if it was an + // online event because duplicate online events would + // progress our online timestamp forward (rather than + // keep it at our earliest online event timestamp). + if lastEvent.eventType == peerOfflineEvent { + lastEvent = event + } + + case peerOfflineEvent: + // If our previous event is nil, we just set it and + // break out of the switch since we cannot record an + // online period from this single event. + if lastEvent == nil { + lastEvent = event + break + } + + // If the last event we saw was an online event, we + // add an online period to our set and progress our + // previous event to this offline event. We do not + // do this if we have had duplicate offline events + // because we would be tracking the most recent offline + // event (rather than keep it at our earliest offline + // event timestamp). + if lastEvent.eventType == peerOnlineEvent { + onlinePeriods = append( + onlinePeriods, &onlinePeriod{ + start: lastEvent.timestamp, + end: event.timestamp, + }, + ) + + lastEvent = event + } } } // If the last event was an peer offline event, we do not need to // calculate a final online period and can return online periods as is. - if offline { + if lastEvent.eventType == peerOfflineEvent { return onlinePeriods } // The log ended on an online event, so we need to add a final online // event. If the channel is closed, this period is until channel // closure. It it is still open, we calculate it until the present. - endTime := e.closedAt - if endTime.IsZero() { - endTime = e.clock.Now() + finalEvent := &onlinePeriod{ + start: lastEvent.timestamp, + end: e.closedAt, + } + if finalEvent.end.IsZero() { + finalEvent.end = e.clock.Now() } // Add the final online period to the set and return. - return append(onlinePeriods, &onlinePeriod{ - start: lastOnline, - end: endTime, - }) + return append(onlinePeriods, finalEvent) } // uptime calculates the total uptime we have recorded for a channel over the diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 4086a40f..637e3627 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -72,10 +72,10 @@ func TestGetOnlinePeriod(t *testing.T) { closedAt time.Time }{ { - name: "No events", + name: "no events", }, { - name: "Start on online period", + name: "start on online period", events: []*channelEvent{ { timestamp: threeHoursAgo, @@ -94,7 +94,7 @@ func TestGetOnlinePeriod(t *testing.T) { }, }, { - name: "Start on offline period", + name: "start on offline period", events: []*channelEvent{ { timestamp: fourHoursAgo, @@ -103,7 +103,7 @@ func TestGetOnlinePeriod(t *testing.T) { }, }, { - name: "End on an online period, channel not closed", + name: "end on an online period, channel not closed", events: []*channelEvent{ { timestamp: fourHoursAgo, @@ -118,7 +118,7 @@ func TestGetOnlinePeriod(t *testing.T) { }, }, { - name: "End on an online period, channel closed", + name: "end on an online period, channel closed", events: []*channelEvent{ { timestamp: fourHoursAgo, @@ -133,12 +133,113 @@ func TestGetOnlinePeriod(t *testing.T) { }, closedAt: oneHourAgo, }, + { + name: "duplicate online events, channel not closed", + events: []*channelEvent{ + { + timestamp: fourHoursAgo, + eventType: peerOnlineEvent, + }, + { + timestamp: threeHoursAgo, + eventType: peerOnlineEvent, + }, + }, + expectedOnline: []*onlinePeriod{ + { + start: fourHoursAgo, + end: testNow, + }, + }, + }, + { + name: "duplicate online events, channel closed", + events: []*channelEvent{ + { + timestamp: fourHoursAgo, + eventType: peerOnlineEvent, + }, + { + timestamp: twoHoursAgo, + eventType: peerOnlineEvent, + }, + }, + expectedOnline: []*onlinePeriod{ + { + start: fourHoursAgo, + end: threeHoursAgo, + }, + }, + closedAt: threeHoursAgo, + }, + { + name: "duplicate offline events, channel not closed", + events: []*channelEvent{ + { + timestamp: fourHoursAgo, + eventType: peerOfflineEvent, + }, + { + timestamp: threeHoursAgo, + eventType: peerOfflineEvent, + }, + }, + expectedOnline: nil, + }, + { + name: "duplicate online then offline", + events: []*channelEvent{ + { + timestamp: fourHoursAgo, + eventType: peerOnlineEvent, + }, + { + timestamp: threeHoursAgo, + eventType: peerOnlineEvent, + }, + { + timestamp: twoHoursAgo, + eventType: peerOfflineEvent, + }, + }, + expectedOnline: []*onlinePeriod{ + { + start: fourHoursAgo, + end: twoHoursAgo, + }, + }, + }, + { + name: "duplicate offline then online", + events: []*channelEvent{ + { + timestamp: fourHoursAgo, + eventType: peerOfflineEvent, + }, + { + timestamp: threeHoursAgo, + eventType: peerOfflineEvent, + }, + { + timestamp: twoHoursAgo, + eventType: peerOnlineEvent, + }, + }, + expectedOnline: []*onlinePeriod{ + { + start: twoHoursAgo, + end: testNow, + }, + }, + }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { + t.Parallel() + score := &chanEventLog{ events: test.events, clock: clock.NewTestClock(testNow), From 10f9ba952eee09d0f8a8a42c6267b9deae3207b9 Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:17 +0200 Subject: [PATCH 07/13] chanfitness: unify requests to store in single chan info struct We currently query the store for uptime and lifespan individually. As we add more fields, we will need to add more queries with this design. This change combines requests into a single channel infor request so that we do not need to add unnecessary boilerplate going forward. --- chanfitness/chaneventstore.go | 154 +++++++----------- chanfitness/chaneventstore_test.go | 242 ++++++++--------------------- rpcserver.go | 36 ++--- 3 files changed, 133 insertions(+), 299 deletions(-) diff --git a/chanfitness/chaneventstore.go b/chanfitness/chaneventstore.go index 6ff73ccf..bd297fd2 100644 --- a/chanfitness/chaneventstore.go +++ b/chanfitness/chaneventstore.go @@ -46,11 +46,8 @@ type ChannelEventStore struct { // and offline events. peers map[route.Vertex]bool - // lifespanRequests serves requests for the lifespan of channels. - lifespanRequests chan lifespanRequest - - // uptimeRequests serves requests for the uptime of channels. - uptimeRequests chan uptimeRequest + // chanInfoRequests serves requests for information about our channel. + chanInfoRequests chan channelInfoRequest quit chan struct{} @@ -79,36 +76,14 @@ type Config struct { Clock clock.Clock } -// lifespanRequest contains the channel ID required to query the store for a -// channel's lifespan and a blocking response channel on which the result is -// sent. -type lifespanRequest struct { +type channelInfoRequest struct { channelPoint wire.OutPoint - responseChan chan lifespanResponse + responseChan chan channelInfoResponse } -// lifespanResponse contains the response to a lifespanRequest and an error if -// one occurred. -type lifespanResponse struct { - start time.Time - end time.Time - err error -} - -// uptimeRequest contains the parameters required to query the store for a -// channel's uptime and a blocking response channel on which the result is sent. -type uptimeRequest struct { - channelPoint wire.OutPoint - startTime time.Time - endTime time.Time - responseChan chan uptimeResponse -} - -// uptimeResponse contains the response to an uptimeRequest and an error if one -// occurred. -type uptimeResponse struct { - uptime time.Duration - err error +type channelInfoResponse struct { + info *ChannelInfo + err error } // NewChannelEventStore initializes an event store with the config provided. @@ -119,8 +94,7 @@ func NewChannelEventStore(config *Config) *ChannelEventStore { cfg: config, channels: make(map[wire.OutPoint]*chanEventLog), peers: make(map[route.Vertex]bool), - lifespanRequests: make(chan lifespanRequest), - uptimeRequests: make(chan uptimeRequest), + chanInfoRequests: make(chan channelInfoRequest), quit: make(chan struct{}), } @@ -314,35 +288,10 @@ func (c *ChannelEventStore) consume(subscriptions *subscriptions) { } // Serve all requests for channel lifetime. - case req := <-c.lifespanRequests: - var resp lifespanResponse - - channel, ok := c.channels[req.channelPoint] - if !ok { - resp.err = ErrChannelNotFound - } else { - resp.start = channel.openedAt - resp.end = channel.closedAt - } - - req.responseChan <- resp - - // Serve requests for channel uptime. - case req := <-c.uptimeRequests: - var resp uptimeResponse - - channel, ok := c.channels[req.channelPoint] - if !ok { - resp.err = ErrChannelNotFound - } else { - uptime, err := channel.uptime( - req.startTime, req.endTime, - ) - - resp.uptime = uptime - resp.err = err - } + case req := <-c.chanInfoRequests: + var resp channelInfoResponse + resp.info, resp.err = c.getChanInfo(req) req.responseChan <- resp // Exit if the store receives the signal to shutdown. @@ -352,65 +301,72 @@ func (c *ChannelEventStore) consume(subscriptions *subscriptions) { } } -// GetLifespan returns the opening and closing time observed for a channel and -// a boolean to indicate whether the channel is known the the event store. If -// the channel is still open, a zero close time is returned. -func (c *ChannelEventStore) GetLifespan( - channelPoint wire.OutPoint) (time.Time, time.Time, error) { +// ChannelInfo provides the set of information that the event store has recorded +// for a channel. +type ChannelInfo struct { + // Lifetime is the total amount of time we have monitored the channel + // for. + Lifetime time.Duration - request := lifespanRequest{ + // Uptime is the total amount of time that the channel peer has been + // observed as online during the monitored lifespan. + Uptime time.Duration +} + +// GetChanInfo gets all the information we have on a channel in the event store. +func (c *ChannelEventStore) GetChanInfo(channelPoint wire.OutPoint) ( + *ChannelInfo, error) { + + request := channelInfoRequest{ channelPoint: channelPoint, - responseChan: make(chan lifespanResponse), + responseChan: make(chan channelInfoResponse), } - // Send a request for the channel's lifespan to the main event loop, or - // return early with an error if the store has already received a + // Send a request for the channel's information to the main event loop, + // or return early with an error if the store has already received a // shutdown signal. select { - case c.lifespanRequests <- request: + case c.chanInfoRequests <- request: case <-c.quit: - return time.Time{}, time.Time{}, errShuttingDown + return nil, errShuttingDown } // Return the response we receive on the response channel or exit early // if the store is instructed to exit. select { case resp := <-request.responseChan: - return resp.start, resp.end, resp.err + return resp.info, resp.err case <-c.quit: - return time.Time{}, time.Time{}, errShuttingDown + return nil, errShuttingDown } } -// GetUptime returns the uptime of a channel over a period and an error if the -// channel cannot be found or the uptime calculation fails. -func (c *ChannelEventStore) GetUptime(channelPoint wire.OutPoint, startTime, - endTime time.Time) (time.Duration, error) { +// getChanInfo collects channel information for a channel. It gets uptime over +// the full lifetime of the channel. +func (c *ChannelEventStore) getChanInfo(req channelInfoRequest) (*ChannelInfo, + error) { - request := uptimeRequest{ - channelPoint: channelPoint, - startTime: startTime, - endTime: endTime, - responseChan: make(chan uptimeResponse), + // Look for the channel in our current set. + channel, ok := c.channels[req.channelPoint] + if !ok { + return nil, ErrChannelNotFound } - // Send a request for the channel's uptime to the main event loop, or - // return early with an error if the store has already received a - // shutdown signal. - select { - case c.uptimeRequests <- request: - case <-c.quit: - return 0, errShuttingDown + // If our channel is not closed, we want to calculate uptime until the + // present. + endTime := channel.closedAt + if endTime.IsZero() { + endTime = c.cfg.Clock.Now() } - // Return the response we receive on the response channel or exit early - // if the store is instructed to exit. - select { - case resp := <-request.responseChan: - return resp.uptime, resp.err - - case <-c.quit: - return 0, errShuttingDown + uptime, err := channel.uptime(channel.openedAt, endTime) + if err != nil { + return nil, err } + + return &ChannelInfo{ + Lifetime: endTime.Sub(channel.openedAt), + Uptime: uptime, + }, nil } diff --git a/chanfitness/chaneventstore_test.go b/chanfitness/chaneventstore_test.go index 206ec8ca..127756f7 100644 --- a/chanfitness/chaneventstore_test.go +++ b/chanfitness/chaneventstore_test.go @@ -186,181 +186,6 @@ func testEventStore(t *testing.T, generateEvents func(*chanEventStoreTestCtx), require.Equal(t, expectedPeers, testCtx.store.peers) } -// TestGetLifetime tests the GetLifetime function for the cases where a channel -// is known and unknown to the store. -func TestGetLifetime(t *testing.T) { - tests := []struct { - name string - channelFound bool - channelPoint wire.OutPoint - opened time.Time - closed time.Time - expectedError error - }{ - { - name: "Channel found", - channelFound: true, - opened: testNow, - closed: testNow.Add(time.Hour * -1), - expectedError: nil, - }, - { - name: "Channel not found", - expectedError: ErrChannelNotFound, - }, - } - - for _, test := range tests { - test := test - - t.Run(test.name, func(t *testing.T) { - ctx := newChanEventStoreTestCtx(t) - - // Add channel to eventStore if the test indicates that - // it should be present. - if test.channelFound { - ctx.store.channels[test.channelPoint] = - &chanEventLog{ - openedAt: test.opened, - closedAt: test.closed, - } - } - - ctx.start() - - open, close, err := ctx.store.GetLifespan(test.channelPoint) - require.Equal(t, test.expectedError, err) - - require.Equal(t, test.opened, open) - require.Equal(t, test.closed, close) - - ctx.stop() - }) - } -} - -// TestGetUptime tests the getUptime call for channels known to the event store. -// It does not test the trivial case where a channel is unknown to the store, -// because this is simply a zero return if an item is not found in a map. It -// tests the unexpected edge cases where a tracked channel does not have any -// events recorded, and when a zero time is specified for the uptime range. -func TestGetUptime(t *testing.T) { - twoHoursAgo := testNow.Add(time.Hour * -2) - fourHoursAgo := testNow.Add(time.Hour * -4) - - tests := []struct { - name string - - channelPoint wire.OutPoint - - // events is the set of events we expect to find in the channel - // store. - events []*channelEvent - - // openedAt is the time the channel is recorded as open by the - // store. - openedAt time.Time - - // closedAt is the time the channel is recorded as closed by the - // store. If the channel is still open, this value is zero. - closedAt time.Time - - // channelFound is true if we expect to find the channel in the - // store. - channelFound bool - - // startTime specifies the beginning of the uptime range we want - // to calculate. - startTime time.Time - - // endTime specified the end of the uptime range we want to - // calculate. - endTime time.Time - - expectedUptime time.Duration - - expectedError error - }{ - { - name: "No events", - startTime: twoHoursAgo, - endTime: testNow, - channelFound: true, - expectedError: nil, - }, - { - name: "50% Uptime", - events: []*channelEvent{ - { - timestamp: fourHoursAgo, - eventType: peerOnlineEvent, - }, - { - timestamp: twoHoursAgo, - eventType: peerOfflineEvent, - }, - }, - openedAt: fourHoursAgo, - expectedUptime: time.Hour * 2, - startTime: fourHoursAgo, - endTime: testNow, - channelFound: true, - expectedError: nil, - }, - { - name: "Zero start time", - events: []*channelEvent{ - { - timestamp: fourHoursAgo, - eventType: peerOnlineEvent, - }, - }, - openedAt: fourHoursAgo, - expectedUptime: time.Hour * 4, - endTime: testNow, - channelFound: true, - expectedError: nil, - }, - { - name: "Channel not found", - startTime: twoHoursAgo, - endTime: testNow, - channelFound: false, - expectedError: ErrChannelNotFound, - }, - } - - for _, test := range tests { - test := test - - t.Run(test.name, func(t *testing.T) { - ctx := newChanEventStoreTestCtx(t) - - // If we're supposed to find the channel for this test, - // add events for it to the store. - if test.channelFound { - eventLog := &chanEventLog{ - events: test.events, - clock: clock.NewTestClock(testNow), - openedAt: test.openedAt, - closedAt: test.closedAt, - } - ctx.store.channels[test.channelPoint] = eventLog - } - - ctx.start() - - uptime, err := ctx.store.GetUptime( - test.channelPoint, test.startTime, test.endTime, - ) - require.Equal(t, test.expectedError, err) - require.Equal(t, test.expectedUptime, uptime) - - ctx.stop() - }) - } -} - // TestAddChannel tests that channels are added to the event store with // appropriate timestamps. This test addresses a bug where offline channels // did not have an opened time set, and checks that an online event is set for @@ -388,3 +213,70 @@ func TestAddChannel(t *testing.T) { require.Len(t, chan2Events, 1) require.Equal(t, peerOnlineEvent, chan2Events[0].eventType) } + +// TestGetChanInfo tests the GetChanInfo function for the cases where a channel +// is known and unknown to the store. +func TestGetChanInfo(t *testing.T) { + ctx := newChanEventStoreTestCtx(t) + ctx.start() + + // Make a note of the time that our mocked clock starts on. + now := ctx.clock.Now() + + // Create mock vars for a channel but do not add them to our store yet. + peer, pk, channel := ctx.newChannel() + + // Send an online event for our peer, although we do not yet have an + // open channel. + ctx.peerEvent(peer, true) + + // Try to get info for a channel that has not been opened yet, we + // expect to get an error. + _, err := ctx.store.GetChanInfo(channel) + require.Equal(t, ErrChannelNotFound, err) + + // Now we send our store a notification that a channel has been opened. + ctx.sendChannelOpenedUpdate(pk, channel) + + // Wait for our channel to be recognized by our store. We need to wait + // for the channel to be created so that we do not update our time + // before the channel open is processed. + require.Eventually(t, func() bool { + _, err = ctx.store.GetChanInfo(channel) + return err == nil + }, timeout, time.Millisecond*20) + + // Increment our test clock by an hour. + now = now.Add(time.Hour) + ctx.clock.SetTime(now) + + // At this stage our channel has been open and online for an hour. + info, err := ctx.store.GetChanInfo(channel) + require.NoError(t, err) + require.Equal(t, time.Hour, info.Lifetime) + require.Equal(t, time.Hour, info.Uptime) + + // Now we send a peer offline event for our channel. + ctx.peerEvent(peer, false) + + // Since we have not bumped our mocked time, our uptime calculations + // should be the same, even though we've just processed an offline + // event. + info, err = ctx.store.GetChanInfo(channel) + require.NoError(t, err) + require.Equal(t, time.Hour, info.Lifetime) + require.Equal(t, time.Hour, info.Uptime) + + // Progress our time again. This time, our peer is currently tracked as + // being offline, so we expect our channel info to reflect that the peer + // has been offline for this period. + now = now.Add(time.Hour) + ctx.clock.SetTime(now) + + info, err = ctx.store.GetChanInfo(channel) + require.NoError(t, err) + require.Equal(t, time.Hour*2, info.Lifetime) + require.Equal(t, time.Hour, info.Uptime) + + ctx.stop() +} diff --git a/rpcserver.go b/rpcserver.go index be6e7a8e..69d3513f 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3536,42 +3536,28 @@ func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph, return channel, nil } - // Get the lifespan observed by the channel event store. If the channel is - // not known to the channel event store, return early because we cannot - // calculate any further uptime information. + // Query the event store for additional information about the channel. + // Do not fail if it is not available, because there is a potential + // race between a channel being added to our node and the event store + // being notified of it. outpoint := dbChannel.FundingOutpoint - startTime, endTime, err := r.server.chanEventStore.GetLifespan(outpoint) + info, err := r.server.chanEventStore.GetChanInfo(outpoint) switch err { + // If the store does not know about the channel, we just log it. case chanfitness.ErrChannelNotFound: rpcsLog.Infof("channel: %v not found by channel event store", outpoint) - return channel, nil + // If we got our channel info, we further populate the channel. case nil: - // If there is no error getting lifespan, continue to uptime - // calculation. + channel.Uptime = int64(info.Uptime.Seconds()) + channel.Lifetime = int64(info.Lifetime.Seconds()) + + // If we get an unexpected error, we return it. default: return nil, err } - // If endTime is zero, the channel is still open, progress endTime to - // the present so we can calculate lifetime. - if endTime.IsZero() { - endTime = time.Now() - } - channel.Lifetime = int64(endTime.Sub(startTime).Seconds()) - - // Once we have successfully obtained channel lifespan, we know that the - // channel is known to the event store, so we can return any non-nil error - // that occurs. - uptime, err := r.server.chanEventStore.GetUptime( - outpoint, startTime, endTime, - ) - if err != nil { - return nil, err - } - channel.Uptime = int64(uptime.Seconds()) - return channel, nil } From e05b4a8e2e17650d4a595aaa7e1f86278a0c777a Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:18 +0200 Subject: [PATCH 08/13] chanfitness: refactor to store channels by peer When dealing with online events, we actually need to track our events by peer, not by channel. All we need to track channels is to have a set of online events for a peer which at least contain those events. This change refactors chanfitness to track by peer. --- chanfitness/chanevent.go | 187 +++++++++++++------ chanfitness/chanevent_test.go | 285 ++++++++++++----------------- chanfitness/chaneventstore.go | 127 ++++++------- chanfitness/chaneventstore_test.go | 67 ++----- chanfitness/interface.go | 29 +++ rpcserver.go | 7 +- 6 files changed, 358 insertions(+), 344 deletions(-) create mode 100644 chanfitness/interface.go diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index 6a3af2aa..4fb6b142 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -6,7 +6,6 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/clock" - "github.com/lightningnetwork/lnd/routing/route" ) type eventType int @@ -29,74 +28,152 @@ func (e eventType) String() string { return "unknown" } -// channelEvent is a a timestamped event which is observed on a per channel -// basis. -type channelEvent struct { +type event struct { timestamp time.Time eventType eventType } -// chanEventLog stores all events that have occurred over a channel's lifetime. -type chanEventLog struct { - // channelPoint is the outpoint for the channel's funding transaction. - channelPoint wire.OutPoint +// peerLog tracks events for a peer and its channels. If we currently have no +// channels with the peer, it will simply track its current online state. If we +// do have channels open with the peer, it will track the peer's online and +// offline events so that we can calculate uptime for our channels. A single +// event log is used for these online and offline events, and uptime for a +// channel is calculated by examining a subsection of this log. +type peerLog struct { + // online stores whether the peer is currently online. + online bool - // peer is the compressed public key of the peer being monitored. - peer route.Vertex - - // events is a log of timestamped events observed for the channel. - events []*channelEvent + // onlineEvents is a log of timestamped events observed for the peer. + onlineEvents []*event // clock allows creation of deterministic unit tests. clock clock.Clock + // channels contains a set of currently open channels. Channels will be + // added and removed from this map as they are opened and closed. + channels map[wire.OutPoint]*channelInfo +} + +// newPeerLog creates a log for a peer. +func newPeerLog(clock clock.Clock) *peerLog { + return &peerLog{ + clock: clock, + channels: make(map[wire.OutPoint]*channelInfo), + } +} + +// channelInfo contains information about a channel. +type channelInfo struct { // openedAt tracks the first time this channel was seen. This is not // necessarily the time that it confirmed on chain because channel // events are not persisted at present. openedAt time.Time - - // closedAt is the time that the channel was closed. If the channel has - // not been closed yet, it is zero. - closedAt time.Time } -// newEventLog creates an event log for a channel with the openedAt time set. -func newEventLog(channelPoint wire.OutPoint, peer route.Vertex, - clock clock.Clock) *chanEventLog { - - eventlog := &chanEventLog{ - channelPoint: channelPoint, - peer: peer, - clock: clock, - openedAt: clock.Now(), +func newChannelInfo(openedAt time.Time) *channelInfo { + return &channelInfo{ + openedAt: openedAt, } - - return eventlog } -// close sets the closing time for an event log. -func (e *chanEventLog) close() { - e.closedAt = e.clock.Now() -} +// onlineEvent records a peer online or offline event in the log. +func (p *peerLog) onlineEvent(online bool) { + p.online = online -// add appends an event with the given type and current time to the event log. -// The open time for the eventLog will be set to the event's timestamp if it is -// not set yet. -func (e *chanEventLog) add(eventType eventType) { - // If the channel is already closed, return early without adding an - // event. - if !e.closedAt.IsZero() { + // If we have no channels currently open with the peer, we do not want + // to commit resources to tracking their online state beyond a simple + // online boolean, so we exit early. + if p.channelCount() == 0 { return } - // Add the event to the eventLog with the current timestamp. - event := &channelEvent{ - timestamp: e.clock.Now(), + p.addEvent(online, p.clock.Now()) +} + +// addEvent records an online or offline event in our event log. +func (p *peerLog) addEvent(online bool, time time.Time) { + eventType := peerOnlineEvent + if !online { + eventType = peerOfflineEvent + } + + event := &event{ + timestamp: time, eventType: eventType, } - e.events = append(e.events, event) - log.Debugf("Channel %v recording event: %v", e.channelPoint, eventType) + p.onlineEvents = append(p.onlineEvents, event) +} + +// addChannel adds a channel to our log. If we have not tracked any online +// events for our peer yet, we create one with our peer's current online state +// so that we know the state that the peer had at channel start, which is +// required to calculate uptime over the channel's lifetime. +func (p *peerLog) addChannel(channelPoint wire.OutPoint) error { + _, ok := p.channels[channelPoint] + if ok { + return fmt.Errorf("channel: %v already present", channelPoint) + } + + openTime := p.clock.Now() + p.channels[channelPoint] = newChannelInfo(openTime) + + // If we do not have any online events tracked for our peer (which is + // the case when we have no other channels open with the peer), we add + // an event with the peer's current online state so that we know that + // starting state for this peer when a channel was connected (which + // allows us to calculate uptime over the lifetime of the channel). + if len(p.onlineEvents) == 0 { + p.addEvent(p.online, openTime) + } + + return nil +} + +// removeChannel removes a channel from our log. If we have no more channels +// with the peer after removing this one, we clear our list of events. +func (p *peerLog) removeChannel(channelPoint wire.OutPoint) error { + _, ok := p.channels[channelPoint] + if !ok { + return fmt.Errorf("channel: %v not present", channelPoint) + } + + delete(p.channels, channelPoint) + + // If we have no more channels in our event log, we can discard all of + // our online events in memory, since we don't need them anymore. + // TODO(carla): this could be done on a per channel basis. + if p.channelCount() == 0 { + p.onlineEvents = nil + } + + return nil +} + +// channelCount returns the number of channels that we currently have +// with the peer. +func (p *peerLog) channelCount() int { + return len(p.channels) +} + +// channelUptime looks up a channel and returns the amount of time that the +// channel has been monitored for and its uptime over this period. +func (p *peerLog) channelUptime(channelPoint wire.OutPoint) (time.Duration, + time.Duration, error) { + + channel, ok := p.channels[channelPoint] + if !ok { + return 0, 0, ErrChannelNotFound + } + + now := p.clock.Now() + + uptime, err := p.uptime(channel.openedAt, now) + if err != nil { + return 0, 0, err + } + + return now.Sub(channel.openedAt), uptime, nil } // onlinePeriod represents a period of time over which a peer was online. @@ -112,9 +189,9 @@ type onlinePeriod struct { // calculated until the present. This function expects the event log provided // to be ordered by ascending timestamp, and can tolerate multiple consecutive // online or offline events. -func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { +func (p *peerLog) getOnlinePeriods() []*onlinePeriod { // Return early if there are no events, there are no online periods. - if len(e.events) == 0 { + if len(p.onlineEvents) == 0 { return nil } @@ -123,7 +200,7 @@ func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { // a different type to our own. It is used to determine the // start time of our online periods when we experience an // offline event, and to track our last recorded state. - lastEvent *channelEvent + lastEvent *event onlinePeriods []*onlinePeriod ) @@ -133,7 +210,7 @@ func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { // the online event and the present is not tracked. The type of the most // recent event is tracked using the offline bool so that we can add a // final online period if necessary. - for _, event := range e.events { + for _, event := range p.onlineEvents { switch event.eventType { case peerOnlineEvent: // If our previous event is nil, we just set it and @@ -188,24 +265,20 @@ func (e *chanEventLog) getOnlinePeriods() []*onlinePeriod { } // The log ended on an online event, so we need to add a final online - // event. If the channel is closed, this period is until channel - // closure. It it is still open, we calculate it until the present. + // period which terminates at the present. finalEvent := &onlinePeriod{ start: lastEvent.timestamp, - end: e.closedAt, - } - if finalEvent.end.IsZero() { - finalEvent.end = e.clock.Now() + end: p.clock.Now(), } // Add the final online period to the set and return. return append(onlinePeriods, finalEvent) } -// uptime calculates the total uptime we have recorded for a channel over the +// uptime calculates the total uptime we have recorded for a peer over the // inclusive range specified. An error is returned if the end of the range is // before the start or a zero end time is returned. -func (e *chanEventLog) uptime(start, end time.Time) (time.Duration, error) { +func (p *peerLog) uptime(start, end time.Time) (time.Duration, error) { // Error if we are provided with an invalid range to calculate uptime // for. if end.Before(start) { @@ -218,7 +291,7 @@ func (e *chanEventLog) uptime(start, end time.Time) (time.Duration, error) { var uptime time.Duration - for _, p := range e.getOnlinePeriods() { + for _, p := range p.getOnlinePeriods() { // The online period ends before the range we're looking at, so // we can skip over it. if p.end.Before(start) { diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 637e3627..c4dd97a8 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -4,55 +4,97 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/clock" "github.com/stretchr/testify/require" ) -// TestAdd tests adding events to an event log. It tests the case where the -// channel is open, and should have an event added, and the case where it is -// closed and the event should not be added. -func TestAdd(t *testing.T) { - tests := []struct { - name string - eventLog *chanEventLog - event eventType - expectedEvents []*channelEvent - }{ - { - name: "Channel open", - eventLog: &chanEventLog{ - clock: clock.NewTestClock(testNow), - }, - event: peerOnlineEvent, - expectedEvents: []*channelEvent{ - { - eventType: peerOnlineEvent, - timestamp: testNow, - }, - }, - }, - { - name: "Channel closed, event not added", - eventLog: &chanEventLog{ - clock: clock.NewTestClock(testNow), - closedAt: testNow, - }, - event: peerOnlineEvent, - expectedEvents: nil, - }, +// TestPeerLog tests the functionality of the peer log struct. +func TestPeerLog(t *testing.T) { + clock := clock.NewTestClock(testNow) + peerLog := newPeerLog(clock) + + require.Zero(t, peerLog.channelCount()) + require.False(t, peerLog.online) + + // Test that looking up an unknown channel fails. + _, _, err := peerLog.channelUptime(wire.OutPoint{Index: 1}) + require.Error(t, err) + + // Add an offline event, since we have no channels, we do not expect + // to have any online periods recorded for our peer. + peerLog.onlineEvent(false) + require.Len(t, peerLog.getOnlinePeriods(), 0) + + // Likewise, if we have an online event, nothing beyond the online state + // of our peer log should change. + peerLog.onlineEvent(true) + require.Len(t, peerLog.getOnlinePeriods(), 0) + + // Add a channel and assert that we have one channel listed. + chan1 := wire.OutPoint{ + Index: 1, } + require.NoError(t, peerLog.addChannel(chan1)) + require.Equal(t, 1, peerLog.channelCount()) - for _, test := range tests { - test := test + // Assert that we can now successfully get our added channel. + _, _, err = peerLog.channelUptime(chan1) + require.NoError(t, err) - t.Run(test.name, func(t *testing.T) { - test.eventLog.add(test.event) + // Bump our test clock's time so that our current time is different to + // channel open time. + now := testNow.Add(time.Hour) + clock.SetTime(now) - require.Equal( - t, test.expectedEvents, test.eventLog.events, - ) - }) + // Now that we have added a channel and an hour has passed, we expect + // our uptime and lifetime to both equal an hour. + lifetime, uptime, err := peerLog.channelUptime(chan1) + require.NoError(t, err) + require.Equal(t, time.Hour, lifetime) + require.Equal(t, time.Hour, uptime) + + // Add an offline event for our peer. + peerLog.onlineEvent(false) + + // Now we add another channel to our store and assert that we now report + // two channels for this peer. + chan2 := wire.OutPoint{ + Index: 2, } + require.NoError(t, peerLog.addChannel(chan2)) + require.Equal(t, 2, peerLog.channelCount()) + + // Progress our time again, so that our peer has now been offline for + // two hours. + now = now.Add(time.Hour * 2) + clock.SetTime(now) + + // Our first channel should report as having been monitored for three + // hours, but only online for one of those hours. + lifetime, uptime, err = peerLog.channelUptime(chan1) + require.NoError(t, err) + require.Equal(t, time.Hour*3, lifetime) + require.Equal(t, time.Hour, uptime) + + // Remove our first channel and check that we can still correctly query + // uptime for the second channel. + require.NoError(t, peerLog.removeChannel(chan1)) + require.Equal(t, 1, peerLog.channelCount()) + + // Our second channel, which was created when our peer was offline, + // should report as having been monitored for two hours, but have zero + // uptime. + lifetime, uptime, err = peerLog.channelUptime(chan2) + require.NoError(t, err) + require.Equal(t, time.Hour*2, lifetime) + require.Equal(t, time.Duration(0), uptime) + + // Finally, remove our second channel and assert that our peer cleans + // up its in memory set of events. + require.NoError(t, peerLog.removeChannel(chan2)) + require.Equal(t, 0, peerLog.channelCount()) + require.Len(t, peerLog.onlineEvents, 0) } // TestGetOnlinePeriod tests the getOnlinePeriod function. It tests the case @@ -62,21 +104,18 @@ func TestGetOnlinePeriod(t *testing.T) { fourHoursAgo := testNow.Add(time.Hour * -4) threeHoursAgo := testNow.Add(time.Hour * -3) twoHoursAgo := testNow.Add(time.Hour * -2) - oneHourAgo := testNow.Add(time.Hour * -1) tests := []struct { name string - events []*channelEvent + events []*event expectedOnline []*onlinePeriod - openedAt time.Time - closedAt time.Time }{ { name: "no events", }, { name: "start on online period", - events: []*channelEvent{ + events: []*event{ { timestamp: threeHoursAgo, eventType: peerOnlineEvent, @@ -95,7 +134,7 @@ func TestGetOnlinePeriod(t *testing.T) { }, { name: "start on offline period", - events: []*channelEvent{ + events: []*event{ { timestamp: fourHoursAgo, eventType: peerOfflineEvent, @@ -103,8 +142,8 @@ func TestGetOnlinePeriod(t *testing.T) { }, }, { - name: "end on an online period, channel not closed", - events: []*channelEvent{ + name: "end on an online period", + events: []*event{ { timestamp: fourHoursAgo, eventType: peerOnlineEvent, @@ -118,24 +157,8 @@ func TestGetOnlinePeriod(t *testing.T) { }, }, { - name: "end on an online period, channel closed", - events: []*channelEvent{ - { - timestamp: fourHoursAgo, - eventType: peerOnlineEvent, - }, - }, - expectedOnline: []*onlinePeriod{ - { - start: fourHoursAgo, - end: oneHourAgo, - }, - }, - closedAt: oneHourAgo, - }, - { - name: "duplicate online events, channel not closed", - events: []*channelEvent{ + name: "duplicate online events", + events: []*event{ { timestamp: fourHoursAgo, eventType: peerOnlineEvent, @@ -153,28 +176,8 @@ func TestGetOnlinePeriod(t *testing.T) { }, }, { - name: "duplicate online events, channel closed", - events: []*channelEvent{ - { - timestamp: fourHoursAgo, - eventType: peerOnlineEvent, - }, - { - timestamp: twoHoursAgo, - eventType: peerOnlineEvent, - }, - }, - expectedOnline: []*onlinePeriod{ - { - start: fourHoursAgo, - end: threeHoursAgo, - }, - }, - closedAt: threeHoursAgo, - }, - { - name: "duplicate offline events, channel not closed", - events: []*channelEvent{ + name: "duplicate offline events", + events: []*event{ { timestamp: fourHoursAgo, eventType: peerOfflineEvent, @@ -188,7 +191,7 @@ func TestGetOnlinePeriod(t *testing.T) { }, { name: "duplicate online then offline", - events: []*channelEvent{ + events: []*event{ { timestamp: fourHoursAgo, eventType: peerOnlineEvent, @@ -211,7 +214,7 @@ func TestGetOnlinePeriod(t *testing.T) { }, { name: "duplicate offline then online", - events: []*channelEvent{ + events: []*event{ { timestamp: fourHoursAgo, eventType: peerOfflineEvent, @@ -240,11 +243,9 @@ func TestGetOnlinePeriod(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - score := &chanEventLog{ - events: test.events, - clock: clock.NewTestClock(testNow), - openedAt: test.openedAt, - closedAt: test.closedAt, + score := &peerLog{ + onlineEvents: test.events, + clock: clock.NewTestClock(testNow), } online := score.getOnlinePeriods() @@ -265,18 +266,9 @@ func TestUptime(t *testing.T) { tests := []struct { name string - // opened at is the time the channel was recorded as being open, - // and is never expected to be zero. - openedAt time.Time - - // closed at is the time the channel was recorded as being - // closed, and can have a zero value if the channel is not - // closed. - closedAt time.Time - // events is the set of event log that we are calculating uptime // for. - events []*channelEvent + events []*event // startTime is the beginning of the period that we are // calculating uptime for, it cannot have a zero value. @@ -306,23 +298,8 @@ func TestUptime(t *testing.T) { expectErr: true, }, { - name: "Online event and closed", - openedAt: fourHoursAgo, - closedAt: oneHourAgo, - events: []*channelEvent{ - { - timestamp: fourHoursAgo, - eventType: peerOnlineEvent, - }, - }, - startTime: fourHoursAgo, - endTime: testNow, - expectedUptime: time.Hour * 3, - }, - { - name: "Online event and not closed", - openedAt: fourHoursAgo, - events: []*channelEvent{ + name: "online event and no offline", + events: []*event{ { timestamp: fourHoursAgo, eventType: peerOnlineEvent, @@ -333,37 +310,8 @@ func TestUptime(t *testing.T) { expectedUptime: time.Hour * 4, }, { - name: "Offline event and closed", - openedAt: fourHoursAgo, - closedAt: threeHoursAgo, - events: []*channelEvent{ - { - timestamp: fourHoursAgo, - eventType: peerOfflineEvent, - }, - }, - startTime: fourHoursAgo, - endTime: testNow, - }, - { - name: "Online event before close", - openedAt: fourHoursAgo, - closedAt: oneHourAgo, - events: []*channelEvent{ - { - timestamp: twoHoursAgo, - eventType: peerOnlineEvent, - }, - }, - startTime: fourHoursAgo, - endTime: testNow, - expectedUptime: time.Hour, - }, - { - name: "Online then offline event", - openedAt: fourHoursAgo, - closedAt: oneHourAgo, - events: []*channelEvent{ + name: "online then offline event", + events: []*event{ { timestamp: threeHoursAgo, eventType: peerOnlineEvent, @@ -378,10 +326,8 @@ func TestUptime(t *testing.T) { expectedUptime: time.Hour, }, { - name: "Online event before uptime period", - openedAt: fourHoursAgo, - closedAt: oneHourAgo, - events: []*channelEvent{ + name: "online event before uptime period", + events: []*event{ { timestamp: threeHoursAgo, eventType: peerOnlineEvent, @@ -389,12 +335,11 @@ func TestUptime(t *testing.T) { }, startTime: twoHoursAgo, endTime: testNow, - expectedUptime: time.Hour, + expectedUptime: time.Hour * 2, }, { - name: "Offline event after uptime period", - openedAt: fourHoursAgo, - events: []*channelEvent{ + name: "offline event after uptime period", + events: []*event{ { timestamp: fourHoursAgo, eventType: peerOnlineEvent, @@ -409,9 +354,8 @@ func TestUptime(t *testing.T) { expectedUptime: time.Hour * 2, }, { - name: "All events within period", - openedAt: fourHoursAgo, - events: []*channelEvent{ + name: "all events within period", + events: []*event{ { timestamp: twoHoursAgo, eventType: peerOnlineEvent, @@ -422,9 +366,8 @@ func TestUptime(t *testing.T) { expectedUptime: time.Hour, }, { - name: "Multiple online and offline", - openedAt: testNow.Add(time.Hour * -8), - events: []*channelEvent{ + name: "multiple online and offline", + events: []*event{ { timestamp: testNow.Add(time.Hour * -7), eventType: peerOnlineEvent, @@ -456,11 +399,9 @@ func TestUptime(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { - score := &chanEventLog{ - events: test.events, - clock: clock.NewTestClock(testNow), - openedAt: test.openedAt, - closedAt: test.closedAt, + score := &peerLog{ + onlineEvents: test.events, + clock: clock.NewTestClock(testNow), } uptime, err := score.uptime( diff --git a/chanfitness/chaneventstore.go b/chanfitness/chaneventstore.go index bd297fd2..fc05bd21 100644 --- a/chanfitness/chaneventstore.go +++ b/chanfitness/chaneventstore.go @@ -32,6 +32,10 @@ var ( // ErrChannelNotFound is returned when a query is made for a channel // that the event store does not have knowledge of. ErrChannelNotFound = errors.New("channel not found in event store") + + // ErrPeerNotFound is returned when a query is made for a channel + // that has a peer that the event store is not currently tracking. + ErrPeerNotFound = errors.New("peer not found in event store") ) // ChannelEventStore maintains a set of event logs for the node's channels to @@ -39,12 +43,8 @@ var ( type ChannelEventStore struct { cfg *Config - // channels maps channel points to event logs. - channels map[wire.OutPoint]*chanEventLog - - // peers tracks the current online status of peers based on online - // and offline events. - peers map[route.Vertex]bool + // peers tracks all of our currently monitored peers and their channels. + peers map[route.Vertex]peerMonitor // chanInfoRequests serves requests for information about our channel. chanInfoRequests chan channelInfoRequest @@ -77,6 +77,7 @@ type Config struct { } type channelInfoRequest struct { + peer route.Vertex channelPoint wire.OutPoint responseChan chan channelInfoResponse } @@ -92,8 +93,7 @@ type channelInfoResponse struct { func NewChannelEventStore(config *Config) *ChannelEventStore { store := &ChannelEventStore{ cfg: config, - channels: make(map[wire.OutPoint]*chanEventLog), - peers: make(map[route.Vertex]bool), + peers: make(map[route.Vertex]peerMonitor), chanInfoRequests: make(chan channelInfoRequest), quit: make(chan struct{}), } @@ -173,60 +173,56 @@ func (c *ChannelEventStore) Stop() { c.wg.Wait() } -// addChannel adds a new channel to the ChannelEventStore's map of channels with -// an initial peer online state (if the peer is online). If the channel is -// already present in the map, the function returns early. This function should -// be called to add existing channels on startup and when open channel events -// are observed. +// addChannel checks whether we are already tracking a channel's peer, creates a +// new peer log to track it if we are not yet monitoring it, and adds the +// channel. func (c *ChannelEventStore) addChannel(channelPoint wire.OutPoint, peer route.Vertex) { - // Check for the unexpected case where the channel is already in the - // store. - _, ok := c.channels[channelPoint] - if ok { - log.Errorf("Channel %v duplicated in channel store", - channelPoint) - return + peerMonitor, ok := c.peers[peer] + if !ok { + peerMonitor = newPeerLog(c.cfg.Clock) + c.peers[peer] = peerMonitor } - // Create an event log for the channel. - eventLog := newEventLog(channelPoint, peer, c.cfg.Clock) - - // If the peer is already online, add a peer online event to record - // the starting state of the peer. - if c.peers[peer] { - eventLog.add(peerOnlineEvent) + if err := peerMonitor.addChannel(channelPoint); err != nil { + log.Errorf("could not add channel: %v", err) } - - c.channels[channelPoint] = eventLog } // closeChannel records a closed time for a channel, and returns early is the -// channel is not known to the event store. -func (c *ChannelEventStore) closeChannel(channelPoint wire.OutPoint) { - // Check for the unexpected case where the channel is unknown to the - // store. - eventLog, ok := c.channels[channelPoint] +// channel is not known to the event store. We log warnings (rather than errors) +// when we cannot find a peer/channel because channels that we restore from a +// static channel backup do not have their open notified, so the event store +// never learns about them, but they are closed using the regular flow so we +// will try to remove them on close. At present, we cannot easily distinguish +// between these closes and others. +func (c *ChannelEventStore) closeChannel(channelPoint wire.OutPoint, + peer route.Vertex) { + + peerMonitor, ok := c.peers[peer] if !ok { - log.Errorf("Close channel %v unknown to store", channelPoint) + log.Warnf("peer not known to store: %v", peer) return } - eventLog.close() + if err := peerMonitor.removeChannel(channelPoint); err != nil { + log.Warnf("could not remove channel: %v", err) + } } -// peerEvent adds a peer online or offline event to all channels we currently -// have open with a peer. -func (c *ChannelEventStore) peerEvent(peer route.Vertex, event eventType) { - // Track current online status of peers in the channelEventStore. - c.peers[peer] = event == peerOnlineEvent - - for _, eventLog := range c.channels { - if eventLog.peer == peer { - eventLog.add(event) - } +// peerEvent creates a peer monitor for a peer if we do not currently have +// one, and adds an online event to it. +func (c *ChannelEventStore) peerEvent(peer route.Vertex, online bool) { + // If we are not currently tracking events for this peer, add a peer + // log for it. + peerMonitor, ok := c.peers[peer] + if !ok { + peerMonitor = newPeerLog(c.cfg.Clock) + c.peers[peer] = peerMonitor } + + peerMonitor.onlineEvent(online) } // subscriptions abstracts away from subscription clients to allow for mocking. @@ -268,7 +264,19 @@ func (c *ChannelEventStore) consume(subscriptions *subscriptions) { // A channel has been closed, we must remove the channel // from the store and record a channel closed event. case channelnotifier.ClosedChannelEvent: - c.closeChannel(event.CloseSummary.ChanPoint) + compressed := event.CloseSummary.RemotePub.SerializeCompressed() + peerKey, err := route.NewVertexFromBytes( + compressed, + ) + if err != nil { + log.Errorf("Could not get vertex "+ + "from: %v", compressed) + continue + } + + c.closeChannel( + event.CloseSummary.ChanPoint, peerKey, + ) } // Process peer online and offline events. @@ -278,13 +286,13 @@ func (c *ChannelEventStore) consume(subscriptions *subscriptions) { // and should record an online event for any channels // with that peer. case peernotifier.PeerOnlineEvent: - c.peerEvent(event.PubKey, peerOnlineEvent) + c.peerEvent(event.PubKey, true) // We have lost a connection with our peer, and should // record an offline event for any channels with that // peer. case peernotifier.PeerOfflineEvent: - c.peerEvent(event.PubKey, peerOfflineEvent) + c.peerEvent(event.PubKey, false) } // Serve all requests for channel lifetime. @@ -314,10 +322,11 @@ type ChannelInfo struct { } // GetChanInfo gets all the information we have on a channel in the event store. -func (c *ChannelEventStore) GetChanInfo(channelPoint wire.OutPoint) ( - *ChannelInfo, error) { +func (c *ChannelEventStore) GetChanInfo(channelPoint wire.OutPoint, + peer route.Vertex) (*ChannelInfo, error) { request := channelInfoRequest{ + peer: peer, channelPoint: channelPoint, responseChan: make(chan channelInfoResponse), } @@ -347,26 +356,18 @@ func (c *ChannelEventStore) GetChanInfo(channelPoint wire.OutPoint) ( func (c *ChannelEventStore) getChanInfo(req channelInfoRequest) (*ChannelInfo, error) { - // Look for the channel in our current set. - channel, ok := c.channels[req.channelPoint] + peerMonitor, ok := c.peers[req.peer] if !ok { - return nil, ErrChannelNotFound + return nil, ErrPeerNotFound } - // If our channel is not closed, we want to calculate uptime until the - // present. - endTime := channel.closedAt - if endTime.IsZero() { - endTime = c.cfg.Clock.Now() - } - - uptime, err := channel.uptime(channel.openedAt, endTime) + lifetime, uptime, err := peerMonitor.channelUptime(req.channelPoint) if err != nil { return nil, err } return &ChannelInfo{ - Lifetime: endTime.Sub(channel.openedAt), + Lifetime: lifetime, Uptime: uptime, }, nil } diff --git a/chanfitness/chaneventstore_test.go b/chanfitness/chaneventstore_test.go index 127756f7..771f3bb0 100644 --- a/chanfitness/chaneventstore_test.go +++ b/chanfitness/chaneventstore_test.go @@ -107,9 +107,7 @@ func TestMonitorChannelEvents(t *testing.T) { ctx.peerEvent(peer1, true) } - testEventStore(t, gen, 1, map[route.Vertex]bool{ - peer1: true, - }) + testEventStore(t, gen, peer1, 1) }) t.Run("duplicate channel open events", func(t *testing.T) { @@ -119,9 +117,7 @@ func TestMonitorChannelEvents(t *testing.T) { ctx.peerEvent(peer1, true) } - testEventStore(t, gen, 1, map[route.Vertex]bool{ - peer1: true, - }) + testEventStore(t, gen, peer1, 1) }) t.Run("peer online before channel created", func(t *testing.T) { @@ -130,9 +126,7 @@ func TestMonitorChannelEvents(t *testing.T) { ctx.sendChannelOpenedUpdate(pubKey, chan1) } - testEventStore(t, gen, 1, map[route.Vertex]bool{ - peer1: true, - }) + testEventStore(t, gen, peer1, 1) }) t.Run("multiple channels for peer", func(t *testing.T) { @@ -144,9 +138,7 @@ func TestMonitorChannelEvents(t *testing.T) { ctx.sendChannelOpenedUpdate(pubKey, chan2) } - testEventStore(t, gen, 2, map[route.Vertex]bool{ - peer1: false, - }) + testEventStore(t, gen, peer1, 2) }) t.Run("multiple channels for peer, one closed", func(t *testing.T) { @@ -161,18 +153,15 @@ func TestMonitorChannelEvents(t *testing.T) { ctx.peerEvent(peer1, true) } - testEventStore(t, gen, 2, map[route.Vertex]bool{ - peer1: true, - }) + testEventStore(t, gen, peer1, 1) }) } // testEventStore creates a new test contexts, generates a set of events for it -// and tests that it has the number of channels and online state for peers that -// we expect. +// and tests that it has the number of channels we expect. func testEventStore(t *testing.T, generateEvents func(*chanEventStoreTestCtx), - expectedChannels int, expectedPeers map[route.Vertex]bool) { + peer route.Vertex, expectedChannels int) { testCtx := newChanEventStoreTestCtx(t) testCtx.start() @@ -182,36 +171,12 @@ func testEventStore(t *testing.T, generateEvents func(*chanEventStoreTestCtx), // Shutdown the store so that we can safely access the maps in our event // store. testCtx.stop() - require.Len(t, testCtx.store.channels, expectedChannels) - require.Equal(t, expectedPeers, testCtx.store.peers) -} -// TestAddChannel tests that channels are added to the event store with -// appropriate timestamps. This test addresses a bug where offline channels -// did not have an opened time set, and checks that an online event is set for -// peers that are online at the time that a channel is opened. -func TestAddChannel(t *testing.T) { - ctx := newChanEventStoreTestCtx(t) - ctx.start() + // Get our peer and check that it has the channels we expect. + monitor, ok := testCtx.store.peers[peer] + require.True(t, ok) - // Create a channel for a peer that is not online yet. - _, _, channel1 := ctx.createChannel() - - // Get a set of values for another channel, but do not create it yet. - // - peer2, pubkey2, channel2 := ctx.newChannel() - ctx.peerEvent(peer2, true) - ctx.sendChannelOpenedUpdate(pubkey2, channel2) - - ctx.stop() - - // Assert that our peer that was offline on connection has no events - // and our peer that was online on connection has one. - require.Len(t, ctx.store.channels[channel1].events, 0) - - chan2Events := ctx.store.channels[channel2].events - require.Len(t, chan2Events, 1) - require.Equal(t, peerOnlineEvent, chan2Events[0].eventType) + require.Equal(t, expectedChannels, monitor.channelCount()) } // TestGetChanInfo tests the GetChanInfo function for the cases where a channel @@ -232,7 +197,7 @@ func TestGetChanInfo(t *testing.T) { // Try to get info for a channel that has not been opened yet, we // expect to get an error. - _, err := ctx.store.GetChanInfo(channel) + _, err := ctx.store.GetChanInfo(channel, peer) require.Equal(t, ErrChannelNotFound, err) // Now we send our store a notification that a channel has been opened. @@ -242,7 +207,7 @@ func TestGetChanInfo(t *testing.T) { // for the channel to be created so that we do not update our time // before the channel open is processed. require.Eventually(t, func() bool { - _, err = ctx.store.GetChanInfo(channel) + _, err = ctx.store.GetChanInfo(channel, peer) return err == nil }, timeout, time.Millisecond*20) @@ -251,7 +216,7 @@ func TestGetChanInfo(t *testing.T) { ctx.clock.SetTime(now) // At this stage our channel has been open and online for an hour. - info, err := ctx.store.GetChanInfo(channel) + info, err := ctx.store.GetChanInfo(channel, peer) require.NoError(t, err) require.Equal(t, time.Hour, info.Lifetime) require.Equal(t, time.Hour, info.Uptime) @@ -262,7 +227,7 @@ func TestGetChanInfo(t *testing.T) { // Since we have not bumped our mocked time, our uptime calculations // should be the same, even though we've just processed an offline // event. - info, err = ctx.store.GetChanInfo(channel) + info, err = ctx.store.GetChanInfo(channel, peer) require.NoError(t, err) require.Equal(t, time.Hour, info.Lifetime) require.Equal(t, time.Hour, info.Uptime) @@ -273,7 +238,7 @@ func TestGetChanInfo(t *testing.T) { now = now.Add(time.Hour) ctx.clock.SetTime(now) - info, err = ctx.store.GetChanInfo(channel) + info, err = ctx.store.GetChanInfo(channel, peer) require.NoError(t, err) require.Equal(t, time.Hour*2, info.Lifetime) require.Equal(t, time.Hour, info.Uptime) diff --git a/chanfitness/interface.go b/chanfitness/interface.go new file mode 100644 index 00000000..2da9302c --- /dev/null +++ b/chanfitness/interface.go @@ -0,0 +1,29 @@ +package chanfitness + +import ( + "time" + + "github.com/btcsuite/btcd/wire" +) + +// peerMonitor is an interface implemented by entities that monitor our peers +// online events and the channels we currently have open with them. +type peerMonitor interface { + // event adds an online or offline event. + onlineEvent(online bool) + + // addChannel adds a new channel. + addChannel(channelPoint wire.OutPoint) error + + // removeChannel removes a channel. + removeChannel(channelPoint wire.OutPoint) error + + // channelCount returns the number of channels that we currently have + // with the peer. + channelCount() int + + // channelUptime looks up a channel and returns the amount of time that + // the channel has been monitored for and its uptime over this period. + channelUptime(channelPoint wire.OutPoint) (time.Duration, + time.Duration, error) +} diff --git a/rpcserver.go b/rpcserver.go index 69d3513f..e1e7cd0d 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3536,12 +3536,17 @@ func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph, return channel, nil } + peer, err := route.NewVertexFromBytes(nodePub.SerializeCompressed()) + if err != nil { + return nil, err + } + // Query the event store for additional information about the channel. // Do not fail if it is not available, because there is a potential // race between a channel being added to our node and the event store // being notified of it. outpoint := dbChannel.FundingOutpoint - info, err := r.server.chanEventStore.GetChanInfo(outpoint) + info, err := r.server.chanEventStore.GetChanInfo(outpoint, peer) switch err { // If the store does not know about the channel, we just log it. case chanfitness.ErrChannelNotFound: From 8b09b2d7165f3b59340629b50ced300bf0c74385 Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:19 +0200 Subject: [PATCH 09/13] chanfitness: record timestamped flap count for peers In preparation for storing our flap count on disk, we start tracking flap count per-peer. --- chanfitness/chanevent.go | 25 ++++++++++++++++++-- chanfitness/chanevent_test.go | 43 ++++++++++++++++++++++++++++------- chanfitness/interface.go | 5 ++++ 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index 4fb6b142..30e856ee 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -46,6 +46,14 @@ type peerLog struct { // onlineEvents is a log of timestamped events observed for the peer. onlineEvents []*event + // flapCount is the number of times this peer has been observed as + // going offline. + flapCount int + + // lastFlap is the timestamp of the last flap we recorded for the peer. + // This value will be nil if we have never recorded a flap for the peer. + lastFlap *time.Time + // clock allows creation of deterministic unit tests. clock clock.Clock @@ -76,8 +84,15 @@ func newChannelInfo(openedAt time.Time) *channelInfo { } } -// onlineEvent records a peer online or offline event in the log. +// onlineEvent records a peer online or offline event in the log and increments +// the peer's flap count. func (p *peerLog) onlineEvent(online bool) { + eventTime := p.clock.Now() + + // Record flap count information and online state regardless of whether + // we have any channels open with this peer. + p.flapCount++ + p.lastFlap = &eventTime p.online = online // If we have no channels currently open with the peer, we do not want @@ -87,7 +102,7 @@ func (p *peerLog) onlineEvent(online bool) { return } - p.addEvent(online, p.clock.Now()) + p.addEvent(online, eventTime) } // addEvent records an online or offline event in our event log. @@ -176,6 +191,12 @@ func (p *peerLog) channelUptime(channelPoint wire.OutPoint) (time.Duration, return now.Sub(channel.openedAt), uptime, nil } +// getFlapCount returns the peer's flap count and the timestamp that we last +// recorded a flap. +func (p *peerLog) getFlapCount() (int, *time.Time) { + return p.flapCount, p.lastFlap +} + // onlinePeriod represents a period of time over which a peer was online. type onlinePeriod struct { start, end time.Time diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index c4dd97a8..7d75841d 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -14,29 +14,53 @@ func TestPeerLog(t *testing.T) { clock := clock.NewTestClock(testNow) peerLog := newPeerLog(clock) + // assertFlapCount is a helper that asserts that our peer's flap count + // and timestamp is set to expected values. + assertFlapCount := func(expectedCount int, expectedTs *time.Time) { + flapCount, flapTs := peerLog.getFlapCount() + require.Equal(t, expectedCount, flapCount) + require.Equal(t, expectedTs, flapTs) + } + require.Zero(t, peerLog.channelCount()) require.False(t, peerLog.online) + assertFlapCount(0, nil) // Test that looking up an unknown channel fails. _, _, err := peerLog.channelUptime(wire.OutPoint{Index: 1}) require.Error(t, err) + lastFlap := clock.Now() + // Add an offline event, since we have no channels, we do not expect - // to have any online periods recorded for our peer. + // to have any online periods recorded for our peer. However, we should + // increment our flap count for the peer. peerLog.onlineEvent(false) require.Len(t, peerLog.getOnlinePeriods(), 0) + assertFlapCount(1, &lastFlap) + + // Bump our test clock's time by an hour so that we can create an online + // event with a distinct time. + lastFlap = testNow.Add(time.Hour) + clock.SetTime(lastFlap) // Likewise, if we have an online event, nothing beyond the online state - // of our peer log should change. + // of our peer log should change, but our flap count should change. peerLog.onlineEvent(true) require.Len(t, peerLog.getOnlinePeriods(), 0) + assertFlapCount(2, &lastFlap) - // Add a channel and assert that we have one channel listed. + // Add a channel and assert that we have one channel listed. Since this + // is the first channel we track for the peer, we expect an online + // event to be added, however, our flap count should not change because + // this is not a new online event, we are just copying one into our log + // for our purposes. chan1 := wire.OutPoint{ Index: 1, } require.NoError(t, peerLog.addChannel(chan1)) require.Equal(t, 1, peerLog.channelCount()) + assertFlapCount(2, &lastFlap) // Assert that we can now successfully get our added channel. _, _, err = peerLog.channelUptime(chan1) @@ -44,8 +68,8 @@ func TestPeerLog(t *testing.T) { // Bump our test clock's time so that our current time is different to // channel open time. - now := testNow.Add(time.Hour) - clock.SetTime(now) + lastFlap = clock.Now().Add(time.Hour) + clock.SetTime(lastFlap) // Now that we have added a channel and an hour has passed, we expect // our uptime and lifetime to both equal an hour. @@ -54,8 +78,10 @@ func TestPeerLog(t *testing.T) { require.Equal(t, time.Hour, lifetime) require.Equal(t, time.Hour, uptime) - // Add an offline event for our peer. + // Add an offline event for our peer and assert that our flap count is + // incremented. peerLog.onlineEvent(false) + assertFlapCount(3, &lastFlap) // Now we add another channel to our store and assert that we now report // two channels for this peer. @@ -67,7 +93,7 @@ func TestPeerLog(t *testing.T) { // Progress our time again, so that our peer has now been offline for // two hours. - now = now.Add(time.Hour * 2) + now := lastFlap.Add(time.Hour * 2) clock.SetTime(now) // Our first channel should report as having been monitored for three @@ -91,10 +117,11 @@ func TestPeerLog(t *testing.T) { require.Equal(t, time.Duration(0), uptime) // Finally, remove our second channel and assert that our peer cleans - // up its in memory set of events. + // up its in memory set of events but keeps its flap count record. require.NoError(t, peerLog.removeChannel(chan2)) require.Equal(t, 0, peerLog.channelCount()) require.Len(t, peerLog.onlineEvents, 0) + assertFlapCount(3, &lastFlap) } // TestGetOnlinePeriod tests the getOnlinePeriod function. It tests the case diff --git a/chanfitness/interface.go b/chanfitness/interface.go index 2da9302c..22678d65 100644 --- a/chanfitness/interface.go +++ b/chanfitness/interface.go @@ -26,4 +26,9 @@ type peerMonitor interface { // the channel has been monitored for and its uptime over this period. channelUptime(channelPoint wire.OutPoint) (time.Duration, time.Duration, error) + + // getFlapCount returns the peer's flap count and the timestamp that we + // last recorded a flap, which may be nil if we have never recorded a + // flap for this peer. + getFlapCount() (int, *time.Time) } From 70bca1f350c4bac858053c2fe40e106a4bfecd8c Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:20 +0200 Subject: [PATCH 10/13] chanfitness: add flap count based rate limiting To prevent flapping peers from endlessly dos-ing us with online and offline events, we rate limit the number of events we will store per period using their flap rate to determine how often we will add their events to our in memory list of online events. Since we are tracking online events, we need to track the aggregate change over the rate limited period, otherwise we will lose track of a peer's current state. For example, if we store an online event, then do not store the subsequent offline event, we will believe that the peer is online when they actually aren't. To address this, we "stage" a single event which keeps track of all the events that occurred while we were rate limiting the peer. At the end of the rate limting period, we will store the last state for that peer, thereby ensureing that we maintain our record of their most recent state. --- chanfitness/chanevent.go | 67 ++++++++++++++++++++++++--- chanfitness/chanevent_test.go | 82 ++++++++++++++++++++++++++++++++++ chanfitness/rate_limit.go | 37 +++++++++++++++ chanfitness/rate_limit_test.go | 51 +++++++++++++++++++++ 4 files changed, 232 insertions(+), 5 deletions(-) create mode 100644 chanfitness/rate_limit.go create mode 100644 chanfitness/rate_limit_test.go diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index 30e856ee..6338887f 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -43,9 +43,31 @@ type peerLog struct { // online stores whether the peer is currently online. online bool - // onlineEvents is a log of timestamped events observed for the peer. + // onlineEvents is a log of timestamped events observed for the peer + // that we have committed to allocating memory to. onlineEvents []*event + // stagedEvent represents an event that is pending addition to the + // events list. It has not yet been added because we rate limit the + // frequency that we store events at. We need to store this value + // in the log (rather than just ignore events) so that we can flush the + // aggregate outcome to our event log once the rate limiting period has + // ended. + // + // Take the following example: + // - Peer online event recorded + // - Peer offline event, not recorded due to rate limit + // - No more events, we incorrectly believe our peer to be online + // Instead of skipping events, we stage the most recent event during the + // rate limited period so that we know what happened (on aggregate) + // while we were rate limiting events. + // + // Note that we currently only store offline/online events so we can + // use this field to track our online state. With the addition of other + // event types, we need to only stage online/offline events, or split + // them out. + stagedEvent *event + // flapCount is the number of times this peer has been observed as // going offline. flapCount int @@ -105,7 +127,8 @@ func (p *peerLog) onlineEvent(online bool) { p.addEvent(online, eventTime) } -// addEvent records an online or offline event in our event log. +// addEvent records an online or offline event in our event log. and increments +// the peer's flap count. func (p *peerLog) addEvent(online bool, time time.Time) { eventType := peerOnlineEvent if !online { @@ -117,7 +140,26 @@ func (p *peerLog) addEvent(online bool, time time.Time) { eventType: eventType, } - p.onlineEvents = append(p.onlineEvents, event) + // If we have no staged events, we can just stage this event and return. + if p.stagedEvent == nil { + p.stagedEvent = event + return + } + + // We get the amount of time we require between events according to + // peer flap count. + aggregation := getRateLimit(p.flapCount) + nextRecordTime := p.stagedEvent.timestamp.Add(aggregation) + flushEvent := nextRecordTime.Before(event.timestamp) + + // If enough time has passed since our last staged event, we add our + // event to our in-memory list. + if flushEvent { + p.onlineEvents = append(p.onlineEvents, p.stagedEvent) + } + + // Finally, we replace our staged event with the new event we received. + p.stagedEvent = event } // addChannel adds a channel to our log. If we have not tracked any online @@ -160,6 +202,7 @@ func (p *peerLog) removeChannel(channelPoint wire.OutPoint) error { // TODO(carla): this could be done on a per channel basis. if p.channelCount() == 0 { p.onlineEvents = nil + p.stagedEvent = nil } return nil @@ -197,6 +240,18 @@ func (p *peerLog) getFlapCount() (int, *time.Time) { return p.flapCount, p.lastFlap } +// listEvents returns all of the events that our event log has tracked, +// including events that are staged for addition to our set of events but have +// not yet been committed to (because we rate limit and store only the aggregate +// outcome over a period). +func (p *peerLog) listEvents() []*event { + if p.stagedEvent == nil { + return p.onlineEvents + } + + return append(p.onlineEvents, p.stagedEvent) +} + // onlinePeriod represents a period of time over which a peer was online. type onlinePeriod struct { start, end time.Time @@ -211,8 +266,10 @@ type onlinePeriod struct { // to be ordered by ascending timestamp, and can tolerate multiple consecutive // online or offline events. func (p *peerLog) getOnlinePeriods() []*onlinePeriod { + events := p.listEvents() + // Return early if there are no events, there are no online periods. - if len(p.onlineEvents) == 0 { + if len(events) == 0 { return nil } @@ -231,7 +288,7 @@ func (p *peerLog) getOnlinePeriods() []*onlinePeriod { // the online event and the present is not tracked. The type of the most // recent event is tracked using the offline bool so that we can add a // final online period if necessary. - for _, event := range p.onlineEvents { + for _, event := range events { switch event.eventType { case peerOnlineEvent: // If our previous event is nil, we just set it and diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 7d75841d..03eec515 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -122,6 +122,88 @@ func TestPeerLog(t *testing.T) { require.Equal(t, 0, peerLog.channelCount()) require.Len(t, peerLog.onlineEvents, 0) assertFlapCount(3, &lastFlap) + + require.Len(t, peerLog.listEvents(), 0) + require.Nil(t, peerLog.stagedEvent) +} + +// TestRateLimitAdd tests the addition of events to the event log with rate +// limiting in place. +func TestRateLimitAdd(t *testing.T) { + // Create a mock clock specifically for this test so that we can + // progress time without affecting the other tests. + mockedClock := clock.NewTestClock(testNow) + + // Create a new peer log. + peerLog := newPeerLog(mockedClock) + require.Nil(t, peerLog.stagedEvent) + + // Create a channel for our peer log, otherwise it will not track online + // events. + require.NoError(t, peerLog.addChannel(wire.OutPoint{})) + + // First, we add an event to the event log. Since we have no previous + // events, we expect this event to staged immediately. + peerEvent := &event{ + timestamp: testNow, + eventType: peerOfflineEvent, + } + + peerLog.onlineEvent(false) + require.Equal(t, peerEvent, peerLog.stagedEvent) + + // We immediately add another event to our event log. We expect our + // staged event to be replaced with this new event, because insufficient + // time has passed since our last event. + peerEvent = &event{ + timestamp: testNow, + eventType: peerOnlineEvent, + } + + peerLog.onlineEvent(true) + require.Equal(t, peerEvent, peerLog.stagedEvent) + + // We get the amount of time that we need to pass before we record an + // event from our rate limiting tiers. We then progress our test clock + // to just after this point. + delta := getRateLimit(peerLog.flapCount) + newNow := testNow.Add(delta + 1) + mockedClock.SetTime(newNow) + + // Now, when we add an event, we expect our staged event to be added + // to our events list and for our new event to be staged. + newEvent := &event{ + timestamp: newNow, + eventType: peerOfflineEvent, + } + peerLog.onlineEvent(false) + + require.Equal(t, []*event{peerEvent}, peerLog.onlineEvents) + require.Equal(t, newEvent, peerLog.stagedEvent) + + // Now, we test the case where we add many events to our log. We expect + // our set of events to be untouched, but for our staged event to be + // updated. + nextEvent := &event{ + timestamp: newNow, + eventType: peerOnlineEvent, + } + + for i := 0; i < 5; i++ { + // We flip the kind of event for each type so that we can check + // that our staged event is definitely changing each time. + if i%2 == 0 { + nextEvent.eventType = peerOfflineEvent + } else { + nextEvent.eventType = peerOnlineEvent + } + + online := nextEvent.eventType == peerOnlineEvent + + peerLog.onlineEvent(online) + require.Equal(t, []*event{peerEvent}, peerLog.onlineEvents) + require.Equal(t, nextEvent, peerLog.stagedEvent) + } } // TestGetOnlinePeriod tests the getOnlinePeriod function. It tests the case diff --git a/chanfitness/rate_limit.go b/chanfitness/rate_limit.go new file mode 100644 index 00000000..3fa5a325 --- /dev/null +++ b/chanfitness/rate_limit.go @@ -0,0 +1,37 @@ +package chanfitness + +import "time" + +// rateLimitScale is the number of events we allow per rate limited tier. +// Increasing this value makes our rate limiting more lenient, decreasing it +// makes us less lenient. +const rateLimitScale = 200 + +// rateLimits is the set of rate limit tiers we apply to our peers based on +// their flap count. A peer can be placed in their tier by dividing their flap +// count by the rateLimitScale and returning the value at that index. +var rateLimits = []time.Duration{ + time.Second, + time.Second * 5, + time.Second * 30, + time.Minute, + time.Minute * 30, + time.Hour, +} + +// getRateLimit returns the value of the rate limited tier that we are on based +// on current flap count. If a peer's flap count exceeds the top tier, we just +// return our highest tier. +func getRateLimit(flapCount int) time.Duration { + // Figure out the tier we fall into based on our current flap count. + tier := flapCount / rateLimitScale + + // If we have more events than our number of tiers, we just use the + // last tier + tierLen := len(rateLimits) + if tier >= tierLen { + tier = tierLen - 1 + } + + return rateLimits[tier] +} diff --git a/chanfitness/rate_limit_test.go b/chanfitness/rate_limit_test.go new file mode 100644 index 00000000..0a9aa0e3 --- /dev/null +++ b/chanfitness/rate_limit_test.go @@ -0,0 +1,51 @@ +package chanfitness + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// TestGetRateLimit tests getting of our rate limit using the current constants. +// It creates test cases that are relative to our constants so that they +// can be adjusted without breaking the unit test. +func TestGetRateLimit(t *testing.T) { + tests := []struct { + name string + flapCount int + rateLimit time.Duration + }{ + { + name: "zero flaps", + flapCount: 0, + rateLimit: rateLimits[0], + }, + { + name: "middle tier", + flapCount: rateLimitScale * (len(rateLimits) / 2), + rateLimit: rateLimits[len(rateLimits)/2], + }, + { + name: "last tier", + flapCount: rateLimitScale * (len(rateLimits) - 1), + rateLimit: rateLimits[len(rateLimits)-1], + }, + { + name: "beyond last tier", + flapCount: rateLimitScale * (len(rateLimits) * 2), + rateLimit: rateLimits[len(rateLimits)-1], + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + limit := getRateLimit(test.flapCount) + require.Equal(t, test.rateLimit, limit) + }) + } +} From a550ca3d64b8af577b5b0c2e6c78618b0d822e93 Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:21 +0200 Subject: [PATCH 11/13] multi: store peer flap rate on disk on best effort basis Since we will use peer flap rate to determine how we rate limit, we store this value on disk per peer per channel. This allows us to restart with memory of our peers past behaviour, so we don't give badly behaving peers have a fresh start on restart. Last flap timestamp is stored with our flap count so that we can degrade this all time flap count over time for peers that have not recently flapped. --- chanfitness/chanevent.go | 14 ++- chanfitness/chanevent_test.go | 4 +- chanfitness/chaneventstore.go | 123 +++++++++++++++++++-- chanfitness/chaneventstore_test.go | 45 ++++++++ chanfitness/chaneventstore_testctx_test.go | 87 ++++++++++++++- channeldb/db.go | 7 ++ channeldb/peers.go | 121 ++++++++++++++++++++ channeldb/peers_test.go | 50 +++++++++ server.go | 3 + 9 files changed, 435 insertions(+), 19 deletions(-) create mode 100644 channeldb/peers.go create mode 100644 channeldb/peers_test.go diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index 6338887f..4cfff3d9 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -84,11 +84,17 @@ type peerLog struct { channels map[wire.OutPoint]*channelInfo } -// newPeerLog creates a log for a peer. -func newPeerLog(clock clock.Clock) *peerLog { +// newPeerLog creates a log for a peer, taking its historical flap count and +// last flap time as parameters. These values may be zero/nil if we have no +// record of historical flap count for the peer. +func newPeerLog(clock clock.Clock, flapCount int, + lastFlap *time.Time) *peerLog { + return &peerLog{ - clock: clock, - channels: make(map[wire.OutPoint]*channelInfo), + clock: clock, + flapCount: flapCount, + lastFlap: lastFlap, + channels: make(map[wire.OutPoint]*channelInfo), } } diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 03eec515..2ccbb726 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -12,7 +12,7 @@ import ( // TestPeerLog tests the functionality of the peer log struct. func TestPeerLog(t *testing.T) { clock := clock.NewTestClock(testNow) - peerLog := newPeerLog(clock) + peerLog := newPeerLog(clock, 0, nil) // assertFlapCount is a helper that asserts that our peer's flap count // and timestamp is set to expected values. @@ -135,7 +135,7 @@ func TestRateLimitAdd(t *testing.T) { mockedClock := clock.NewTestClock(testNow) // Create a new peer log. - peerLog := newPeerLog(mockedClock) + peerLog := newPeerLog(mockedClock, 0, nil) require.Nil(t, peerLog.stagedEvent) // Create a channel for our peer log, otherwise it will not track online diff --git a/chanfitness/chaneventstore.go b/chanfitness/chaneventstore.go index fc05bd21..1f84db2f 100644 --- a/chanfitness/chaneventstore.go +++ b/chanfitness/chaneventstore.go @@ -22,6 +22,13 @@ import ( "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" + "github.com/lightningnetwork/lnd/ticker" +) + +const ( + // FlapCountFlushRate determines how often we write peer total flap + // count to disk. + FlapCountFlushRate = time.Hour ) var ( @@ -74,8 +81,22 @@ type Config struct { // Clock is the time source that the subsystem uses, provided here // for ease of testing. Clock clock.Clock + + // WriteFlapCounts records the flap count for a set of peers on disk. + WriteFlapCount func(map[route.Vertex]*channeldb.FlapCount) error + + // ReadFlapCount gets the flap count for a peer on disk. + ReadFlapCount func(route.Vertex) (*channeldb.FlapCount, error) + + // FlapCountTicker is a ticker which controls how often we flush our + // peer's flap count to disk. + FlapCountTicker ticker.Ticker } +// peerFlapCountMap is the map used to map peers to flap counts, declared here +// to allow shorter function signatures. +type peerFlapCountMap map[route.Vertex]*channeldb.FlapCount + type channelInfoRequest struct { peer route.Vertex channelPoint wire.OutPoint @@ -167,6 +188,8 @@ func (c *ChannelEventStore) Start() error { func (c *ChannelEventStore) Stop() { log.Info("Stopping event store") + c.cfg.FlapCountTicker.Stop() + // Stop the consume goroutine. close(c.quit) @@ -179,10 +202,10 @@ func (c *ChannelEventStore) Stop() { func (c *ChannelEventStore) addChannel(channelPoint wire.OutPoint, peer route.Vertex) { - peerMonitor, ok := c.peers[peer] - if !ok { - peerMonitor = newPeerLog(c.cfg.Clock) - c.peers[peer] = peerMonitor + peerMonitor, err := c.getPeerMonitor(peer) + if err != nil { + log.Error("could not create monitor: %v", err) + return } if err := peerMonitor.addChannel(channelPoint); err != nil { @@ -190,6 +213,42 @@ func (c *ChannelEventStore) addChannel(channelPoint wire.OutPoint, } } +// getPeerMonitor tries to get an existing peer monitor from our in memory list, +// and falls back to creating a new monitor if it is not currently known. +func (c *ChannelEventStore) getPeerMonitor(peer route.Vertex) (peerMonitor, + error) { + + peerMonitor, ok := c.peers[peer] + if ok { + return peerMonitor, nil + } + + var ( + flapCount int + lastFlap *time.Time + ) + + historicalFlap, err := c.cfg.ReadFlapCount(peer) + switch err { + // If we do not have any records for this peer we set a 0 flap count + // and timestamp. + case channeldb.ErrNoPeerBucket: + + case nil: + flapCount = int(historicalFlap.Count) + lastFlap = &historicalFlap.LastFlap + + // Return if we get an unexpected error. + default: + return nil, err + } + + peerMonitor = newPeerLog(c.cfg.Clock, flapCount, lastFlap) + c.peers[peer] = peerMonitor + + return peerMonitor, nil +} + // closeChannel records a closed time for a channel, and returns early is the // channel is not known to the event store. We log warnings (rather than errors) // when we cannot find a peer/channel because channels that we restore from a @@ -214,12 +273,10 @@ func (c *ChannelEventStore) closeChannel(channelPoint wire.OutPoint, // peerEvent creates a peer monitor for a peer if we do not currently have // one, and adds an online event to it. func (c *ChannelEventStore) peerEvent(peer route.Vertex, online bool) { - // If we are not currently tracking events for this peer, add a peer - // log for it. - peerMonitor, ok := c.peers[peer] - if !ok { - peerMonitor = newPeerLog(c.cfg.Clock) - c.peers[peer] = peerMonitor + peerMonitor, err := c.getPeerMonitor(peer) + if err != nil { + log.Error("could not create monitor: %v", err) + return } peerMonitor.onlineEvent(online) @@ -236,8 +293,22 @@ type subscriptions struct { // the event store with channel and peer events, and serves requests for channel // uptime and lifespan. func (c *ChannelEventStore) consume(subscriptions *subscriptions) { - defer c.wg.Done() - defer subscriptions.cancel() + // Start our flap count ticker. + c.cfg.FlapCountTicker.Resume() + + // On exit, we will cancel our subscriptions and write our most recent + // flap counts to disk. This ensures that we have consistent data in + // the case of a graceful shutdown. If we do not shutdown gracefully, + // our worst case is data from our last flap count tick (1H). + defer func() { + subscriptions.cancel() + + if err := c.recordFlapCount(); err != nil { + log.Errorf("error recording flap on shutdown: %v", err) + } + + c.wg.Done() + }() // Consume events until the channel is closed. for { @@ -302,6 +373,12 @@ func (c *ChannelEventStore) consume(subscriptions *subscriptions) { resp.info, resp.err = c.getChanInfo(req) req.responseChan <- resp + case <-c.cfg.FlapCountTicker.Ticks(): + if err := c.recordFlapCount(); err != nil { + log.Errorf("could not record flap "+ + "count: %v", err) + } + // Exit if the store receives the signal to shutdown. case <-c.quit: return @@ -371,3 +448,25 @@ func (c *ChannelEventStore) getChanInfo(req channelInfoRequest) (*ChannelInfo, Uptime: uptime, }, nil } + +// recordFlapCount will record our flap count for each peer that we are +// currently tracking, skipping peers that have a 0 flap count. +func (c *ChannelEventStore) recordFlapCount() error { + updates := make(peerFlapCountMap) + + for peer, monitor := range c.peers { + flapCount, lastFlap := monitor.getFlapCount() + if lastFlap == nil { + continue + } + + updates[peer] = &channeldb.FlapCount{ + Count: uint32(flapCount), + LastFlap: *lastFlap, + } + } + + log.Debugf("recording flap count for: %v peers", len(updates)) + + return c.cfg.WriteFlapCount(updates) +} diff --git a/chanfitness/chaneventstore_test.go b/chanfitness/chaneventstore_test.go index 771f3bb0..67e8c35b 100644 --- a/chanfitness/chaneventstore_test.go +++ b/chanfitness/chaneventstore_test.go @@ -179,6 +179,51 @@ func testEventStore(t *testing.T, generateEvents func(*chanEventStoreTestCtx), require.Equal(t, expectedChannels, monitor.channelCount()) } +// TestStoreFlapCount tests flushing of flap counts to disk on timer ticks and +// on store shutdown. +func TestStoreFlapCount(t *testing.T) { + testCtx := newChanEventStoreTestCtx(t) + testCtx.start() + + pubkey, _, _ := testCtx.createChannel() + testCtx.peerEvent(pubkey, false) + + // Now, we tick our flap count ticker. We expect our main goroutine to + // flush our tick count to disk. + testCtx.tickFlapCount() + + // Since we just tracked a offline event, we expect a single flap for + // our peer. + expectedUpdate := peerFlapCountMap{ + pubkey: { + Count: 1, + LastFlap: testCtx.clock.Now(), + }, + } + + testCtx.assertFlapCountUpdated() + testCtx.assertFlapCountUpdates(expectedUpdate) + + // Create three events for out peer, online/offline/online. + testCtx.peerEvent(pubkey, true) + testCtx.peerEvent(pubkey, false) + testCtx.peerEvent(pubkey, true) + + // Trigger another write. + testCtx.tickFlapCount() + + // Since we have processed 3 more events for our peer, we update our + // expected online map to have a flap count of 4 for this peer. + expectedUpdate[pubkey] = &channeldb.FlapCount{ + Count: 4, + LastFlap: testCtx.clock.Now(), + } + testCtx.assertFlapCountUpdated() + testCtx.assertFlapCountUpdates(expectedUpdate) + + testCtx.stop() +} + // TestGetChanInfo tests the GetChanInfo function for the cases where a channel // is known and unknown to the store. func TestGetChanInfo(t *testing.T) { diff --git a/chanfitness/chaneventstore_testctx_test.go b/chanfitness/chaneventstore_testctx_test.go index 6a6b8fd7..8b498310 100644 --- a/chanfitness/chaneventstore_testctx_test.go +++ b/chanfitness/chaneventstore_testctx_test.go @@ -14,6 +14,7 @@ import ( "github.com/lightningnetwork/lnd/peernotifier" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/subscribe" + "github.com/lightningnetwork/lnd/ticker" "github.com/stretchr/testify/require" ) @@ -38,6 +39,17 @@ type chanEventStoreTestCtx struct { // clock is the clock that our test store will use. clock *clock.TestClock + + // flapUpdates stores our most recent set of updates flap counts. + flapUpdates peerFlapCountMap + + // flapCountUpdates is a channel which receives new flap counts. + flapCountUpdates chan peerFlapCountMap + + // stopped is closed when our test context is fully shutdown. It is + // used to prevent calling of functions which can only be called after + // shutdown. + stopped chan struct{} } // newChanEventStoreTestCtx creates a test context which can be used to test @@ -48,6 +60,9 @@ func newChanEventStoreTestCtx(t *testing.T) *chanEventStoreTestCtx { channelSubscription: newMockSubscription(t), peerSubscription: newMockSubscription(t), clock: clock.NewTestClock(testNow), + flapUpdates: make(peerFlapCountMap), + flapCountUpdates: make(chan peerFlapCountMap), + stopped: make(chan struct{}), } cfg := &Config{ @@ -61,6 +76,28 @@ func newChanEventStoreTestCtx(t *testing.T) *chanEventStoreTestCtx { GetOpenChannels: func() ([]*channeldb.OpenChannel, error) { return nil, nil }, + WriteFlapCount: func(updates map[route.Vertex]*channeldb.FlapCount) error { + // Send our whole update map into the test context's + // updates channel. The test will need to assert flap + // count updated or this send will timeout. + select { + case testCtx.flapCountUpdates <- updates: + + case <-time.After(timeout): + t.Fatalf("WriteFlapCount timeout") + } + + return nil + }, + ReadFlapCount: func(peer route.Vertex) (*channeldb.FlapCount, error) { + count, ok := testCtx.flapUpdates[peer] + if !ok { + return nil, channeldb.ErrNoPeerBucket + } + + return count, nil + }, + FlapCountTicker: ticker.NewForce(FlapCountFlushRate), } testCtx.store = NewChannelEventStore(cfg) @@ -75,7 +112,25 @@ func (c *chanEventStoreTestCtx) start() { // stop stops the channel event store's subscribe servers and the store itself. func (c *chanEventStoreTestCtx) stop() { - c.store.Stop() + // On shutdown of our event store, we write flap counts to disk. In our + // test context, this write function is blocked on asserting that the + // update has occurred. We stop our store in a goroutine so that we + // can shut it down and assert that it performs these on-shutdown + // updates. The stopped channel is used to ensure that we do not finish + // our test before this shutdown has completed. + go func() { + c.store.Stop() + close(c.stopped) + }() + + // We write our flap count to disk on shutdown, assert that the most + // recent record that the server has is written on shutdown. Calling + // this assert unblocks the stop function above. We don't check values + // here, so that our tests don't all require providing an expected swap + // count, but at least assert that the write occurred. + c.assertFlapCountUpdated() + + <-c.stopped // Make sure that the cancel function was called for both of our // subscription mocks. @@ -137,6 +192,18 @@ func (c *chanEventStoreTestCtx) closeChannel(channel wire.OutPoint, c.channelSubscription.sendUpdate(update) } +// tickFlapCount forces a tick for our flap count ticker with the current time. +func (c *chanEventStoreTestCtx) tickFlapCount() { + testTicker := c.store.cfg.FlapCountTicker.(*ticker.Force) + + select { + case testTicker.Force <- c.store.cfg.Clock.Now(): + + case <-time.After(timeout): + c.t.Fatalf("could not tick flap count ticker") + } +} + // peerEvent sends a peer online or offline event to the store for the peer // provided. func (c *chanEventStoreTestCtx) peerEvent(peer route.Vertex, online bool) { @@ -165,6 +232,24 @@ func (c *chanEventStoreTestCtx) sendChannelOpenedUpdate(pubkey *btcec.PublicKey, c.channelSubscription.sendUpdate(update) } +// assertFlapCountUpdated asserts that our store has made an attempt to write +// our current set of flap counts to disk and sets this value in our test ctx. +// Note that it does not check the values of the update. +func (c *chanEventStoreTestCtx) assertFlapCountUpdated() { + select { + case c.flapUpdates = <-c.flapCountUpdates: + + case <-time.After(timeout): + c.t.Fatalf("assertFlapCountUpdated timeout") + } +} + +// assertFlapCountUpdates asserts that out current record of flap counts is +// as expected. +func (c *chanEventStoreTestCtx) assertFlapCountUpdates(expected peerFlapCountMap) { + require.Equal(c.t, expected, c.flapUpdates) +} + // mockSubscription is a mock subscription client that blocks on sends into the // updates channel. We use this mock rather than an actual subscribe client // because they do not block, which makes tests race (because we have no way diff --git a/channeldb/db.go b/channeldb/db.go index a4c5c516..983b4fbb 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -164,6 +164,12 @@ var ( number: 17, migration: mig.CreateTLB(closeSummaryBucket), }, + { + // Create a top level bucket which holds information + // about our peers. + number: 18, + migration: mig.CreateTLB(peersBucket), + }, } // Big endian is the preferred byte order, due to cursor scans over @@ -278,6 +284,7 @@ var topLevelBuckets = [][]byte{ invoiceBucket, payAddrIndexBucket, paymentsIndexBucket, + peersBucket, nodeInfoBucket, nodeBucket, edgeBucket, diff --git a/channeldb/peers.go b/channeldb/peers.go new file mode 100644 index 00000000..fabb5361 --- /dev/null +++ b/channeldb/peers.go @@ -0,0 +1,121 @@ +package channeldb + +import ( + "bytes" + "errors" + "fmt" + "time" + + "github.com/btcsuite/btcwallet/walletdb" + "github.com/lightningnetwork/lnd/routing/route" +) + +var ( + // peersBucket is the name of a top level bucket in which we store + // information about our peers. Information for different peers is + // stored in buckets keyed by their public key. + // + // + // peers-bucket + // | + // |-- + // | |--flap-count-key: + // | + // |-- + // | |--flap-count-key: + peersBucket = []byte("peers-bucket") + + // flapCountKey is a key used in the peer pubkey sub-bucket that stores + // the timestamp of a peer's last flap count and its all time flap + // count. + flapCountKey = []byte("flap-count") +) + +var ( + // ErrNoPeerBucket is returned when we try to read entries for a peer + // that is not tracked. + ErrNoPeerBucket = errors.New("peer bucket not found") +) + +// FlapCount contains information about a peer's flap count. +type FlapCount struct { + // Count provides the total flap count for a peer. + Count uint32 + + // LastFlap is the timestamp of the last flap recorded for a peer. + LastFlap time.Time +} + +// WriteFlapCounts writes the flap count for a set of peers to disk, creating a +// bucket for the peer's pubkey if necessary. Note that this function overwrites +// the current value. +func (d *DB) WriteFlapCounts(flapCounts map[route.Vertex]*FlapCount) error { + return d.Update(func(tx walletdb.ReadWriteTx) error { + // Run through our set of flap counts and record them for + // each peer, creating a bucket for the peer pubkey if required. + for peer, flapCount := range flapCounts { + peers := tx.ReadWriteBucket(peersBucket) + + peerBucket, err := peers.CreateBucketIfNotExists( + peer[:], + ) + if err != nil { + return err + } + + var b bytes.Buffer + err = serializeTime(&b, flapCount.LastFlap) + if err != nil { + return err + } + + if err = WriteElement(&b, flapCount.Count); err != nil { + return err + } + + err = peerBucket.Put(flapCountKey, b.Bytes()) + if err != nil { + return err + } + } + + return nil + }) +} + +// ReadFlapCount attempts to read the flap count for a peer, failing if the +// peer is not found or we do not have flap count stored. +func (d *DB) ReadFlapCount(pubkey route.Vertex) (*FlapCount, error) { + var flapCount FlapCount + + if err := d.View(func(tx walletdb.ReadTx) error { + peers := tx.ReadBucket(peersBucket) + + peerBucket := peers.NestedReadBucket(pubkey[:]) + if peerBucket == nil { + return ErrNoPeerBucket + } + + flapBytes := peerBucket.Get(flapCountKey) + if flapBytes == nil { + return fmt.Errorf("flap count not recorded for: %v", + pubkey) + } + + var ( + err error + r = bytes.NewReader(flapBytes) + ) + + flapCount.LastFlap, err = deserializeTime(r) + if err != nil { + return err + } + + return ReadElements(r, &flapCount.Count) + }); err != nil { + return nil, err + } + + return &flapCount, nil +} diff --git a/channeldb/peers_test.go b/channeldb/peers_test.go new file mode 100644 index 00000000..b702c18d --- /dev/null +++ b/channeldb/peers_test.go @@ -0,0 +1,50 @@ +package channeldb + +import ( + "testing" + "time" + + "github.com/lightningnetwork/lnd/routing/route" + "github.com/stretchr/testify/require" +) + +// TestFlapCount tests lookup and writing of flap count to disk. +func TestFlapCount(t *testing.T) { + db, cleanup, err := MakeTestDB() + require.NoError(t, err) + defer cleanup() + + // Try to read flap count for a peer that we have no records for. + _, err = db.ReadFlapCount(testPub) + require.Equal(t, ErrNoPeerBucket, err) + + var ( + testPub2 = route.Vertex{2, 2, 2} + peer1FlapCount = &FlapCount{ + Count: 20, + LastFlap: time.Unix(100, 23), + } + peer2FlapCount = &FlapCount{ + Count: 39, + LastFlap: time.Unix(200, 23), + } + ) + + peers := map[route.Vertex]*FlapCount{ + testPub: peer1FlapCount, + testPub2: peer2FlapCount, + } + + err = db.WriteFlapCounts(peers) + require.NoError(t, err) + + // Lookup flap count for our first pubkey. + count, err := db.ReadFlapCount(testPub) + require.NoError(t, err) + require.Equal(t, peer1FlapCount, count) + + // Lookup our flap count for the second peer. + count, err = db.ReadFlapCount(testPub2) + require.NoError(t, err) + require.Equal(t, peer2FlapCount, count) +} diff --git a/server.go b/server.go index d737a096..648dea10 100644 --- a/server.go +++ b/server.go @@ -1212,6 +1212,9 @@ func newServer(cfg *Config, listenAddrs []net.Addr, }, GetOpenChannels: s.remoteChanDB.FetchAllOpenChannels, Clock: clock.NewDefaultClock(), + ReadFlapCount: s.remoteChanDB.ReadFlapCount, + WriteFlapCount: s.remoteChanDB.WriteFlapCounts, + FlapCountTicker: ticker.New(chanfitness.FlapCountFlushRate), }) if cfg.WtClient.Active { From 6cf66aea47ec01b9216859fecd82d5aa037dc96d Mon Sep 17 00:00:00 2001 From: carla Date: Tue, 8 Sep 2020 13:47:22 +0200 Subject: [PATCH 12/13] chanfitness: cool down flap count for rate limiting Since we store all-time flap count for a peer, we add a cooldown factor which will discount poor flap counts in the past. This is only applied to peers that have not flapped for at least a cooldown period, so that we do not downgrade our rate limiting for badly behaved peers. --- chanfitness/chanevent.go | 10 +++++++ chanfitness/chanevent_test.go | 41 +++++++++++++++++++++++++ chanfitness/rate_limit.go | 55 ++++++++++++++++++++++++++++++---- chanfitness/rate_limit_test.go | 54 +++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 5 deletions(-) diff --git a/chanfitness/chanevent.go b/chanfitness/chanevent.go index 4cfff3d9..0dc4b29d 100644 --- a/chanfitness/chanevent.go +++ b/chanfitness/chanevent.go @@ -117,6 +117,16 @@ func newChannelInfo(openedAt time.Time) *channelInfo { func (p *peerLog) onlineEvent(online bool) { eventTime := p.clock.Now() + // If we have a non-nil last flap time, potentially apply a cooldown + // factor to the peer's flap count before we rate limit it. This allows + // us to decrease the penalty for historical flaps over time, provided + // the peer has not flapped for a while. + if p.lastFlap != nil { + p.flapCount = cooldownFlapCount( + p.clock.Now(), p.flapCount, *p.lastFlap, + ) + } + // Record flap count information and online state regardless of whether // we have any channels open with this peer. p.flapCount++ diff --git a/chanfitness/chanevent_test.go b/chanfitness/chanevent_test.go index 2ccbb726..111c8e4e 100644 --- a/chanfitness/chanevent_test.go +++ b/chanfitness/chanevent_test.go @@ -204,6 +204,47 @@ func TestRateLimitAdd(t *testing.T) { require.Equal(t, []*event{peerEvent}, peerLog.onlineEvents) require.Equal(t, nextEvent, peerLog.stagedEvent) } + + // Now, we test the case where a peer's flap count is cooled down + // because it has not flapped for a while. Set our peer's flap count so + // that we fall within our second rate limiting tier and assert that we + // are at this level. + peerLog.flapCount = rateLimitScale + 1 + rateLimit := getRateLimit(peerLog.flapCount) + require.Equal(t, rateLimits[1], rateLimit) + + // Progress our clock to the point where we will have our flap count + // cooled. + newNow = mockedClock.Now().Add(flapCountCooldownPeriod) + mockedClock.SetTime(newNow) + + // Add an online event, and expect it to be staged. + onlineEvent := &event{ + timestamp: newNow, + eventType: peerOnlineEvent, + } + peerLog.onlineEvent(true) + require.Equal(t, onlineEvent, peerLog.stagedEvent) + + // Progress our clock by the rate limit level that we will be on if + // our flap rate is cooled down to a lower level. + newNow = mockedClock.Now().Add(rateLimits[0] + 1) + mockedClock.SetTime(newNow) + + // Add another event. We expect this event to be staged and our previous + // event to be flushed to the event log (because our cooldown has been + // applied). + offlineEvent := &event{ + timestamp: newNow, + eventType: peerOfflineEvent, + } + peerLog.onlineEvent(false) + require.Equal(t, offlineEvent, peerLog.stagedEvent) + + flushedEventIdx := len(peerLog.onlineEvents) - 1 + require.Equal( + t, onlineEvent, peerLog.onlineEvents[flushedEventIdx], + ) } // TestGetOnlinePeriod tests the getOnlinePeriod function. It tests the case diff --git a/chanfitness/rate_limit.go b/chanfitness/rate_limit.go index 3fa5a325..b070a445 100644 --- a/chanfitness/rate_limit.go +++ b/chanfitness/rate_limit.go @@ -1,11 +1,25 @@ package chanfitness -import "time" +import ( + "math" + "time" +) -// rateLimitScale is the number of events we allow per rate limited tier. -// Increasing this value makes our rate limiting more lenient, decreasing it -// makes us less lenient. -const rateLimitScale = 200 +const ( + // rateLimitScale is the number of events we allow per rate limited + // tier. Increasing this value makes our rate limiting more lenient, + // decreasing it makes us less lenient. + rateLimitScale = 200 + + // flapCountCooldownFactor is the factor by which we decrease a peer's + // flap count if they have not flapped for the cooldown period. + flapCountCooldownFactor = 0.95 + + // flapCountCooldownPeriod is the amount of time that we require a peer + // has not flapped for before we reduce their all time flap count using + // our cooldown factor. + flapCountCooldownPeriod = time.Hour * 8 +) // rateLimits is the set of rate limit tiers we apply to our peers based on // their flap count. A peer can be placed in their tier by dividing their flap @@ -35,3 +49,34 @@ func getRateLimit(flapCount int) time.Duration { return rateLimits[tier] } + +// cooldownFlapCount takes a timestamped flap count, and returns its value +// scaled down by our cooldown factor if at least our cooldown period has +// elapsed since the peer last flapped. We do this because we store all-time +// flap count for peers, and want to allow downgrading of peers that have not +// flapped for a long time. +func cooldownFlapCount(now time.Time, flapCount int, + lastFlap time.Time) int { + + // Calculate time since our last flap, and the number of times we need + // to apply our cooldown factor. + timeSinceFlap := now.Sub(lastFlap) + + // If our cooldown period has not elapsed yet, we just return our flap + // count. We allow fractional cooldown periods once this period has + // elapsed, so we do not want to apply a fractional cooldown before the + // full cooldown period has elapsed. + if timeSinceFlap < flapCountCooldownPeriod { + return flapCount + } + + // Get the factor by which we need to cooldown our flap count. If + // insufficient time has passed to cooldown our flap count. Use use a + // float so that we allow fractional cooldown periods. + cooldownPeriods := float64(timeSinceFlap) / + float64(flapCountCooldownPeriod) + + effectiveFactor := math.Pow(flapCountCooldownFactor, cooldownPeriods) + + return int(float64(flapCount) * effectiveFactor) +} diff --git a/chanfitness/rate_limit_test.go b/chanfitness/rate_limit_test.go index 0a9aa0e3..b9bca808 100644 --- a/chanfitness/rate_limit_test.go +++ b/chanfitness/rate_limit_test.go @@ -49,3 +49,57 @@ func TestGetRateLimit(t *testing.T) { }) } } + +// TestCooldownFlapCount tests cooldown of all time flap counts. +func TestCooldownFlapCount(t *testing.T) { + tests := []struct { + name string + flapCount int + lastFlap time.Time + expected int + }{ + { + name: "just flapped, do not cooldown", + flapCount: 1, + lastFlap: testNow, + expected: 1, + }, + { + name: "period not elapsed, do not cooldown", + flapCount: 1, + lastFlap: testNow.Add(flapCountCooldownPeriod / 2 * -1), + expected: 1, + }, + { + name: "rounded to 0", + flapCount: 1, + lastFlap: testNow.Add(flapCountCooldownPeriod * -1), + expected: 0, + }, + { + name: "decreased to integer value", + flapCount: 10, + lastFlap: testNow.Add(flapCountCooldownPeriod * -1), + expected: 9, + }, + { + name: "multiple cooldown periods", + flapCount: 10, + lastFlap: testNow.Add(flapCountCooldownPeriod * -3), + expected: 8, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + flapCount := cooldownFlapCount( + testNow, test.flapCount, test.lastFlap, + ) + require.Equal(t, test.expected, flapCount) + }) + } +} From e2c0604657c9e79c1d414b1ecb340e55e2d11e2f Mon Sep 17 00:00:00 2001 From: carla Date: Thu, 27 Aug 2020 09:20:46 +0200 Subject: [PATCH 13/13] multi: add flap count and last flap time to listpeers --- chanfitness/chaneventstore.go | 88 ++ chanfitness/chaneventstore_test.go | 51 + lnrpc/rpc.pb.go | 1527 ++++++++++++++-------------- lnrpc/rpc.proto | 14 + lnrpc/rpc.swagger.json | 10 + rpcserver.go | 24 + 6 files changed, 964 insertions(+), 750 deletions(-) diff --git a/chanfitness/chaneventstore.go b/chanfitness/chaneventstore.go index 1f84db2f..03b23f1d 100644 --- a/chanfitness/chaneventstore.go +++ b/chanfitness/chaneventstore.go @@ -56,6 +56,9 @@ type ChannelEventStore struct { // chanInfoRequests serves requests for information about our channel. chanInfoRequests chan channelInfoRequest + // peerRequests serves requests for information about a peer. + peerRequests chan peerRequest + quit chan struct{} wg sync.WaitGroup @@ -108,6 +111,17 @@ type channelInfoResponse struct { err error } +type peerRequest struct { + peer route.Vertex + responseChan chan peerResponse +} + +type peerResponse struct { + flapCount int + ts *time.Time + err error +} + // NewChannelEventStore initializes an event store with the config provided. // Note that this function does not start the main event loop, Start() must be // called. @@ -116,6 +130,7 @@ func NewChannelEventStore(config *Config) *ChannelEventStore { cfg: config, peers: make(map[route.Vertex]peerMonitor), chanInfoRequests: make(chan channelInfoRequest), + peerRequests: make(chan peerRequest), quit: make(chan struct{}), } @@ -373,6 +388,15 @@ func (c *ChannelEventStore) consume(subscriptions *subscriptions) { resp.info, resp.err = c.getChanInfo(req) req.responseChan <- resp + // Serve all requests for information about our peer. + case req := <-c.peerRequests: + var resp peerResponse + + resp.flapCount, resp.ts, resp.err = c.flapCount( + req.peer, + ) + req.responseChan <- resp + case <-c.cfg.FlapCountTicker.Ticks(): if err := c.recordFlapCount(); err != nil { log.Errorf("could not record flap "+ @@ -449,6 +473,70 @@ func (c *ChannelEventStore) getChanInfo(req channelInfoRequest) (*ChannelInfo, }, nil } +// FlapCount returns the flap count we have for a peer and the timestamp of its +// last flap. If we do not have any flaps recorded for the peer, the last flap +// timestamp will be nil. +func (c *ChannelEventStore) FlapCount(peer route.Vertex) (int, *time.Time, + error) { + + request := peerRequest{ + peer: peer, + responseChan: make(chan peerResponse), + } + + // Send a request for the peer's information to the main event loop, + // or return early with an error if the store has already received a + // shutdown signal. + select { + case c.peerRequests <- request: + case <-c.quit: + return 0, nil, errShuttingDown + } + + // Return the response we receive on the response channel or exit early + // if the store is instructed to exit. + select { + case resp := <-request.responseChan: + return resp.flapCount, resp.ts, resp.err + + case <-c.quit: + return 0, nil, errShuttingDown + } +} + +// flapCount gets our peer flap count and last flap timestamp from our in memory +// record of a peer, falling back to on disk if we are not currently tracking +// the peer. If we have no flap count recorded for the peer, a nil last flap +// time will be returned. +func (c *ChannelEventStore) flapCount(peer route.Vertex) (int, *time.Time, + error) { + + // First check whether we are tracking this peer in memory, because this + // record will have the most accurate flap count. We do not fail if we + // can't find the peer in memory, because we may have previously + // recorded its flap count on disk. + peerMonitor, ok := c.peers[peer] + if ok { + count, ts := peerMonitor.getFlapCount() + return count, ts, nil + } + + // Try to get our flap count from the database. If this value is not + // recorded, we return a nil last flap time to indicate that we have no + // record of the peer's flap count. + flapCount, err := c.cfg.ReadFlapCount(peer) + switch err { + case channeldb.ErrNoPeerBucket: + return 0, nil, nil + + case nil: + return int(flapCount.Count), &flapCount.LastFlap, nil + + default: + return 0, nil, err + } +} + // recordFlapCount will record our flap count for each peer that we are // currently tracking, skipping peers that have a 0 flap count. func (c *ChannelEventStore) recordFlapCount() error { diff --git a/chanfitness/chaneventstore_test.go b/chanfitness/chaneventstore_test.go index 67e8c35b..0c992111 100644 --- a/chanfitness/chaneventstore_test.go +++ b/chanfitness/chaneventstore_test.go @@ -290,3 +290,54 @@ func TestGetChanInfo(t *testing.T) { ctx.stop() } + +// TestFlapCount tests querying the store for peer flap counts, covering the +// case where the peer is tracked in memory, and the case where we need to +// lookup the peer on disk. +func TestFlapCount(t *testing.T) { + clock := clock.NewTestClock(testNow) + + var ( + peer = route.Vertex{9, 9, 9} + peerFlapCount = 3 + lastFlap = clock.Now() + ) + + // Create a test context with one peer's flap count already recorded, + // which mocks it already having its flap count stored on disk. + ctx := newChanEventStoreTestCtx(t) + ctx.flapUpdates[peer] = &channeldb.FlapCount{ + Count: uint32(peerFlapCount), + LastFlap: lastFlap, + } + + ctx.start() + + // Create test variables for a peer and channel, but do not add it to + // our store yet. + peer1 := route.Vertex{1, 2, 3} + + // First, query for a peer that we have no record of in memory or on + // disk and confirm that we indicate that the peer was not found. + _, ts, err := ctx.store.FlapCount(peer1) + require.NoError(t, err) + require.Nil(t, ts) + + // Send an online event for our peer. + ctx.peerEvent(peer1, true) + + // Assert that we now find a record of the peer with flap count = 1. + count, ts, err := ctx.store.FlapCount(peer1) + require.NoError(t, err) + require.Equal(t, lastFlap, *ts) + require.Equal(t, 1, count) + + // Make a request for our peer that not tracked in memory, but does + // have its flap count stored on disk. + count, ts, err = ctx.store.FlapCount(peer) + require.NoError(t, err) + require.Equal(t, lastFlap, *ts) + require.Equal(t, peerFlapCount, count) + + ctx.stop() +} diff --git a/lnrpc/rpc.pb.go b/lnrpc/rpc.pb.go index b2a37ea0..c69de0ea 100644 --- a/lnrpc/rpc.pb.go +++ b/lnrpc/rpc.pb.go @@ -3728,10 +3728,20 @@ type Peer struct { //are not persisted across lnd restarts. Note that these errors are only //stored for peers that we have channels open with, to prevent peers from //spamming us with errors at no cost. - Errors []*TimestampedError `protobuf:"bytes,12,rep,name=errors,proto3" json:"errors,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Errors []*TimestampedError `protobuf:"bytes,12,rep,name=errors,proto3" json:"errors,omitempty"` + // + //The number of times we have recorded this peer going offline or coming + //online, recorded across restarts. Note that this value is decreased over + //time if the peer has not recently flapped, so that we can forgive peers + //with historically high flap counts. + FlapCount int32 `protobuf:"varint,13,opt,name=flap_count,json=flapCount,proto3" json:"flap_count,omitempty"` + // + //The timestamp of the last flap we observed for this peer. If this value is + //zero, we have not observed any flaps for this peer. + LastFlapNs int64 `protobuf:"varint,14,opt,name=last_flap_ns,json=lastFlapNs,proto3" json:"last_flap_ns,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *Peer) Reset() { *m = Peer{} } @@ -3836,6 +3846,20 @@ func (m *Peer) GetErrors() []*TimestampedError { return nil } +func (m *Peer) GetFlapCount() int32 { + if m != nil { + return m.FlapCount + } + return 0 +} + +func (m *Peer) GetLastFlapNs() int64 { + if m != nil { + return m.LastFlapNs + } + return 0 +} + type TimestampedError struct { // The unix timestamp in seconds when the error occurred. Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` @@ -12454,753 +12478,756 @@ func init() { func init() { proto.RegisterFile("rpc.proto", fileDescriptor_77a6da22d6a3feb1) } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 11936 bytes of a gzipped FileDescriptorProto + // 11972 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x7d, 0x59, 0x6c, 0x23, 0x49, - 0x96, 0x58, 0xf1, 0x12, 0xc9, 0x47, 0x52, 0xa2, 0x42, 0x17, 0x4b, 0xd5, 0xd5, 0x55, 0x9d, 0xdd, - 0xd3, 0x5d, 0x53, 0xdd, 0xa3, 0xae, 0xae, 0xee, 0xea, 0x63, 0xca, 0xdb, 0x33, 0x94, 0x44, 0x95, - 0x38, 0x25, 0x91, 0x9a, 0x24, 0xd5, 0xbd, 0xbd, 0xd8, 0xdd, 0xdc, 0x14, 0x19, 0x92, 0xd2, 0x45, - 0x66, 0xb2, 0x33, 0x93, 0x2a, 0x69, 0x0c, 0xff, 0xad, 0x0f, 0x2c, 0xd6, 0x06, 0x0c, 0x78, 0x0d, - 0xf8, 0x58, 0xf8, 0x82, 0xed, 0xbf, 0x85, 0xe1, 0x5d, 0xfb, 0xcb, 0xdf, 0x5e, 0x18, 0xb0, 0x61, - 0x18, 0x5e, 0xc3, 0x07, 0x16, 0x0b, 0x18, 0xb0, 0xd7, 0x1f, 0x06, 0x8c, 0x05, 0xec, 0x1f, 0x7f, - 0x18, 0x30, 0x22, 0x5e, 0x44, 0x64, 0xe4, 0xa1, 0xaa, 0xea, 0x99, 0xf6, 0xfc, 0x48, 0x8c, 0xf7, - 0x5e, 0xdc, 0x11, 0x2f, 0xde, 0x15, 0x91, 0x50, 0xf5, 0x67, 0xa3, 0xad, 0x99, 0xef, 0x85, 0x1e, - 0x29, 0x4d, 0x5c, 0x7f, 0x36, 0x32, 0xfe, 0x38, 0x07, 0xc5, 0xe3, 0xf0, 0xd2, 0x23, 0x8f, 0xa0, - 0x6e, 0x8f, 0xc7, 0x3e, 0x0d, 0x02, 0x2b, 0xbc, 0x9a, 0xd1, 0x56, 0xee, 0x6e, 0xee, 0xde, 0xe2, - 0x43, 0xb2, 0xc5, 0xc9, 0xb6, 0xda, 0x88, 0x1a, 0x5e, 0xcd, 0xa8, 0x59, 0xb3, 0xa3, 0x04, 0x69, - 0x41, 0x59, 0x24, 0x5b, 0xf9, 0xbb, 0xb9, 0x7b, 0x55, 0x53, 0x26, 0xc9, 0x6d, 0x00, 0x7b, 0xea, - 0xcd, 0xdd, 0xd0, 0x0a, 0xec, 0xb0, 0x55, 0xb8, 0x9b, 0xbb, 0x57, 0x30, 0xab, 0x08, 0x19, 0xd8, - 0x21, 0xb9, 0x05, 0xd5, 0xd9, 0x33, 0x2b, 0x18, 0xf9, 0xce, 0x2c, 0x6c, 0x15, 0x79, 0xd6, 0xca, - 0xec, 0xd9, 0x80, 0xa7, 0xc9, 0xbb, 0x50, 0xf1, 0xe6, 0xe1, 0xcc, 0x73, 0xdc, 0xb0, 0x55, 0xba, - 0x9b, 0xbb, 0x57, 0x7b, 0xb8, 0x24, 0x1a, 0xd2, 0x9f, 0x87, 0x47, 0x0c, 0x6c, 0x2a, 0x02, 0xf2, - 0x16, 0x34, 0x46, 0x9e, 0x7b, 0xea, 0xf8, 0x53, 0x3b, 0x74, 0x3c, 0x37, 0x68, 0x2d, 0xf0, 0xba, - 0xe2, 0x40, 0xe3, 0x5f, 0xe4, 0xa1, 0x36, 0xf4, 0x6d, 0x37, 0xb0, 0x47, 0x0c, 0x40, 0x36, 0xa0, - 0x1c, 0x5e, 0x5a, 0xe7, 0x76, 0x70, 0xce, 0xbb, 0x5a, 0x35, 0x17, 0xc2, 0xcb, 0x7d, 0x3b, 0x38, - 0x27, 0xeb, 0xb0, 0x80, 0xad, 0xe4, 0x1d, 0x2a, 0x98, 0x22, 0x45, 0xde, 0x85, 0x65, 0x77, 0x3e, - 0xb5, 0xe2, 0x55, 0xb1, 0x6e, 0x95, 0xcc, 0xa6, 0x3b, 0x9f, 0xee, 0xe8, 0x70, 0xd6, 0xf9, 0x93, - 0x89, 0x37, 0x7a, 0x86, 0x15, 0x60, 0xf7, 0xaa, 0x1c, 0xc2, 0xeb, 0x78, 0x03, 0xea, 0x02, 0x4d, - 0x9d, 0xb3, 0x73, 0xec, 0x63, 0xc9, 0xac, 0x21, 0x01, 0x07, 0xb1, 0x12, 0x42, 0x67, 0x4a, 0xad, - 0x20, 0xb4, 0xa7, 0x33, 0xd1, 0xa5, 0x2a, 0x83, 0x0c, 0x18, 0x80, 0xa3, 0xbd, 0xd0, 0x9e, 0x58, - 0xa7, 0x94, 0x06, 0xad, 0xb2, 0x40, 0x33, 0xc8, 0x1e, 0xa5, 0x01, 0xf9, 0x0e, 0x2c, 0x8e, 0x69, - 0x10, 0x5a, 0x62, 0x32, 0x68, 0xd0, 0xaa, 0xdc, 0x2d, 0xdc, 0xab, 0x9a, 0x0d, 0x06, 0x6d, 0x4b, - 0x20, 0x79, 0x0d, 0xc0, 0xb7, 0x9f, 0x5b, 0x6c, 0x20, 0xe8, 0x65, 0xab, 0x8a, 0xb3, 0xe0, 0xdb, - 0xcf, 0x87, 0x97, 0xfb, 0xf4, 0x92, 0xac, 0x42, 0x69, 0x62, 0x9f, 0xd0, 0x49, 0x0b, 0x38, 0x02, - 0x13, 0xc6, 0x2f, 0xc1, 0xfa, 0x13, 0x1a, 0x6a, 0x43, 0x19, 0x98, 0xf4, 0xeb, 0x39, 0x0d, 0x42, - 0xd6, 0xab, 0x20, 0xb4, 0xfd, 0x50, 0xf6, 0x2a, 0x87, 0xbd, 0xe2, 0xb0, 0xa8, 0x57, 0xd4, 0x1d, - 0x4b, 0x82, 0x3c, 0x27, 0xa8, 0x52, 0x77, 0x8c, 0x68, 0xe3, 0x00, 0x88, 0x56, 0xf0, 0x2e, 0x0d, - 0x6d, 0x67, 0x12, 0x90, 0x8f, 0xa1, 0x1e, 0x6a, 0xd5, 0xb5, 0x72, 0x77, 0x0b, 0xf7, 0x6a, 0x6a, - 0x69, 0x6a, 0x19, 0xcc, 0x18, 0x9d, 0x71, 0x0e, 0x95, 0x3d, 0x4a, 0x0f, 0x9c, 0xa9, 0x13, 0x92, - 0x75, 0x28, 0x9d, 0x3a, 0x97, 0x74, 0xcc, 0x1b, 0x55, 0xd8, 0xbf, 0x61, 0x62, 0x92, 0xdc, 0x01, - 0xe0, 0x3f, 0xac, 0xa9, 0x5a, 0xa5, 0xfb, 0x37, 0xcc, 0x2a, 0x87, 0x1d, 0x06, 0x76, 0x48, 0x36, - 0xa1, 0x3c, 0xa3, 0xfe, 0x88, 0xca, 0xf5, 0xb0, 0x7f, 0xc3, 0x94, 0x80, 0xed, 0x32, 0x94, 0x26, - 0xac, 0x74, 0xe3, 0xf7, 0x4b, 0x50, 0x1b, 0x50, 0x77, 0x2c, 0x47, 0x82, 0x40, 0x91, 0x0d, 0x34, - 0xaf, 0xac, 0x6e, 0xf2, 0xdf, 0xe4, 0x4d, 0xa8, 0xf1, 0x29, 0x09, 0x42, 0xdf, 0x71, 0xcf, 0x70, - 0xb7, 0x6c, 0xe7, 0x5b, 0x39, 0x13, 0x18, 0x78, 0xc0, 0xa1, 0xa4, 0x09, 0x05, 0x7b, 0x2a, 0x77, - 0x0b, 0xfb, 0x49, 0x6e, 0x42, 0xc5, 0x9e, 0x86, 0xd8, 0xbc, 0x3a, 0x07, 0x97, 0xed, 0x69, 0xc8, - 0x9b, 0xf6, 0x06, 0xd4, 0x67, 0xf6, 0xd5, 0x94, 0xba, 0x61, 0xb4, 0xcc, 0xea, 0x66, 0x4d, 0xc0, - 0xf8, 0x42, 0x7b, 0x08, 0x2b, 0x3a, 0x89, 0xac, 0xbc, 0xa4, 0x2a, 0x5f, 0xd6, 0xa8, 0x45, 0x1b, - 0xde, 0x81, 0x25, 0x99, 0xc7, 0xc7, 0xfe, 0xf0, 0xe5, 0x57, 0x35, 0x17, 0x05, 0x58, 0xf6, 0xf2, - 0x1e, 0x34, 0x4f, 0x1d, 0xd7, 0x9e, 0x58, 0xa3, 0x49, 0x78, 0x61, 0x8d, 0xe9, 0x24, 0xb4, 0xf9, - 0x4a, 0x2c, 0x99, 0x8b, 0x1c, 0xbe, 0x33, 0x09, 0x2f, 0x76, 0x19, 0x94, 0xbc, 0x07, 0xd5, 0x53, - 0x4a, 0x2d, 0x3e, 0x58, 0xad, 0x4a, 0x6c, 0x43, 0xcb, 0x19, 0x32, 0x2b, 0xa7, 0x72, 0xae, 0xde, - 0x83, 0xa6, 0x37, 0x0f, 0xcf, 0x3c, 0xc7, 0x3d, 0xb3, 0x46, 0xe7, 0xb6, 0x6b, 0x39, 0x63, 0xbe, - 0x36, 0x8b, 0xdb, 0xf9, 0x07, 0x39, 0x73, 0x51, 0xe2, 0x76, 0xce, 0x6d, 0xb7, 0x3b, 0x26, 0x6f, - 0xc3, 0xd2, 0xc4, 0x0e, 0x42, 0xeb, 0xdc, 0x9b, 0x59, 0xb3, 0xf9, 0xc9, 0x33, 0x7a, 0xd5, 0x6a, - 0xf0, 0x81, 0x68, 0x30, 0xf0, 0xbe, 0x37, 0x3b, 0xe2, 0x40, 0xb6, 0xf4, 0x78, 0x3b, 0xb1, 0x11, - 0x6c, 0x49, 0x37, 0xcc, 0x2a, 0x83, 0x60, 0xa5, 0x5f, 0xc1, 0x0a, 0x9f, 0x9e, 0xd1, 0x3c, 0x08, - 0xbd, 0xa9, 0xe5, 0xd3, 0x91, 0xe7, 0x8f, 0x83, 0x56, 0x8d, 0xaf, 0xb5, 0xef, 0x8a, 0xc6, 0x6a, - 0x73, 0xbc, 0xb5, 0x4b, 0x83, 0x70, 0x87, 0x13, 0x9b, 0x48, 0xdb, 0x71, 0x43, 0xff, 0xca, 0x5c, - 0x1e, 0x27, 0xe1, 0xe4, 0x3d, 0x20, 0xf6, 0x64, 0xe2, 0x3d, 0xb7, 0x02, 0x3a, 0x39, 0xb5, 0xc4, - 0x20, 0xb6, 0x16, 0xef, 0xe6, 0xee, 0x55, 0xcc, 0x26, 0xc7, 0x0c, 0xe8, 0xe4, 0xf4, 0x08, 0xe1, - 0xe4, 0x63, 0xe0, 0x9b, 0xd4, 0x3a, 0xa5, 0x76, 0x38, 0xf7, 0x69, 0xd0, 0x5a, 0xba, 0x5b, 0xb8, - 0xb7, 0xf8, 0x70, 0x59, 0x8d, 0x17, 0x07, 0x6f, 0x3b, 0xa1, 0x59, 0x67, 0x74, 0x22, 0x1d, 0x6c, - 0xee, 0xc2, 0x7a, 0x76, 0x93, 0xd8, 0xa2, 0x62, 0xa3, 0xc2, 0x16, 0x63, 0xd1, 0x64, 0x3f, 0xd9, - 0xce, 0xbe, 0xb0, 0x27, 0x73, 0xca, 0x57, 0x61, 0xdd, 0xc4, 0xc4, 0xf7, 0xf3, 0x9f, 0xe6, 0x8c, - 0xdf, 0xcb, 0x41, 0x1d, 0x7b, 0x19, 0xcc, 0x3c, 0x37, 0xa0, 0xe4, 0x4d, 0x68, 0xc8, 0xd5, 0x40, - 0x7d, 0xdf, 0xf3, 0x05, 0xb7, 0x94, 0x2b, 0xaf, 0xc3, 0x60, 0xe4, 0xbb, 0xd0, 0x94, 0x44, 0x33, - 0x9f, 0x3a, 0x53, 0xfb, 0x4c, 0x16, 0x2d, 0x97, 0xd2, 0x91, 0x00, 0x93, 0x0f, 0xa2, 0xf2, 0x7c, - 0x6f, 0x1e, 0x52, 0xbe, 0xd6, 0x6b, 0x0f, 0xeb, 0xa2, 0x7b, 0x26, 0x83, 0xa9, 0xd2, 0x79, 0xea, - 0x15, 0xd6, 0xb9, 0xf1, 0x5b, 0x39, 0x20, 0xac, 0xd9, 0x43, 0x0f, 0x0b, 0x88, 0x38, 0x52, 0x2c, - 0x67, 0xee, 0x95, 0x77, 0x48, 0xfe, 0x45, 0x3b, 0xc4, 0x80, 0x12, 0xb6, 0xbd, 0x98, 0xd1, 0x76, - 0x44, 0xfd, 0xa8, 0x58, 0x29, 0x34, 0x8b, 0xc6, 0x7f, 0x2e, 0xc0, 0x2a, 0x5b, 0xa7, 0x2e, 0x9d, - 0xb4, 0x47, 0x23, 0x3a, 0x53, 0x7b, 0xe7, 0x0e, 0xd4, 0x5c, 0x6f, 0x4c, 0xe5, 0x8a, 0xc5, 0x86, - 0x01, 0x03, 0x69, 0xcb, 0xf5, 0xdc, 0x76, 0x5c, 0x6c, 0x38, 0x0e, 0x66, 0x95, 0x43, 0x78, 0xb3, - 0xdf, 0x86, 0xa5, 0x19, 0x75, 0xc7, 0xfa, 0x16, 0x29, 0xe0, 0xaa, 0x17, 0x60, 0xb1, 0x3b, 0xee, - 0x40, 0xed, 0x74, 0x8e, 0x74, 0x8c, 0xb1, 0x14, 0xf9, 0x1a, 0x00, 0x01, 0x6a, 0x23, 0x7f, 0x99, - 0xcd, 0x83, 0x73, 0x8e, 0x2d, 0x71, 0x6c, 0x99, 0xa5, 0x19, 0xea, 0x36, 0xc0, 0x78, 0x1e, 0x84, - 0x62, 0xc7, 0x2c, 0x70, 0x64, 0x95, 0x41, 0x70, 0xc7, 0x7c, 0x0f, 0x56, 0xa6, 0xf6, 0xa5, 0xc5, - 0xd7, 0x8e, 0xe5, 0xb8, 0xd6, 0xe9, 0x84, 0x33, 0xf5, 0x32, 0xa7, 0x6b, 0x4e, 0xed, 0xcb, 0x2f, - 0x18, 0xa6, 0xeb, 0xee, 0x71, 0x38, 0x63, 0x2b, 0x23, 0x1c, 0x09, 0xcb, 0xa7, 0x01, 0xf5, 0x2f, - 0x28, 0xe7, 0x04, 0x45, 0x73, 0x51, 0x80, 0x4d, 0x84, 0xb2, 0x16, 0x4d, 0x59, 0xbf, 0xc3, 0xc9, - 0x08, 0xb7, 0xbd, 0x59, 0x9e, 0x3a, 0xee, 0x7e, 0x38, 0x19, 0xb1, 0xf3, 0x8a, 0xf1, 0x91, 0x19, - 0xf5, 0xad, 0x67, 0xcf, 0xf9, 0x1e, 0x2e, 0x72, 0xbe, 0x71, 0x44, 0xfd, 0xa7, 0xcf, 0x99, 0x48, - 0x31, 0x0a, 0x38, 0x23, 0xb2, 0xaf, 0x5a, 0x35, 0xbe, 0xc1, 0x2b, 0xa3, 0x80, 0xb1, 0x20, 0xfb, - 0x8a, 0x6d, 0x42, 0xd6, 0x5a, 0x9b, 0xcf, 0x02, 0x1d, 0xf3, 0xe2, 0x03, 0xce, 0x51, 0x1b, 0xbc, - 0xb1, 0x6d, 0x81, 0x60, 0xf5, 0x04, 0x6c, 0xd5, 0xcb, 0xc6, 0x9e, 0x4e, 0xec, 0xb3, 0x80, 0xb3, - 0x94, 0x86, 0x59, 0x17, 0xc0, 0x3d, 0x06, 0x33, 0xbe, 0x84, 0xb5, 0xc4, 0xdc, 0x8a, 0x3d, 0xc3, - 0x44, 0x08, 0x0e, 0xe1, 0xf3, 0x5a, 0x31, 0x45, 0x2a, 0x6b, 0xd2, 0xf2, 0x19, 0x93, 0x66, 0xfc, - 0x76, 0x0e, 0xea, 0xa2, 0x64, 0x2e, 0xec, 0x90, 0x2d, 0x20, 0x72, 0x16, 0xc3, 0x4b, 0x67, 0x6c, - 0x9d, 0x5c, 0x85, 0x34, 0xc0, 0x45, 0xb3, 0x7f, 0xc3, 0x6c, 0x0a, 0xdc, 0xf0, 0xd2, 0x19, 0x6f, - 0x33, 0x0c, 0xb9, 0x0f, 0xcd, 0x18, 0x7d, 0x10, 0xfa, 0xb8, 0xa2, 0xf7, 0x6f, 0x98, 0x8b, 0x1a, - 0xf5, 0x20, 0xf4, 0xd9, 0x1e, 0x61, 0xa2, 0xd4, 0x3c, 0xb4, 0x1c, 0x77, 0x4c, 0x2f, 0xf9, 0x32, - 0x6a, 0x98, 0x35, 0x84, 0x75, 0x19, 0x68, 0x7b, 0x11, 0xea, 0x7a, 0x71, 0xc6, 0x19, 0x54, 0xa4, - 0x1c, 0xc6, 0x05, 0x91, 0x44, 0x93, 0xcc, 0x6a, 0xa8, 0x5a, 0x72, 0x13, 0x2a, 0xf1, 0x16, 0x98, - 0xe5, 0xf0, 0x95, 0x2b, 0x36, 0x3e, 0x87, 0xe6, 0x01, 0x5b, 0x3c, 0x2e, 0x5b, 0xac, 0x42, 0xae, - 0x5c, 0x87, 0x05, 0x6d, 0xd3, 0x54, 0x4d, 0x91, 0x62, 0x67, 0xee, 0xb9, 0x17, 0x84, 0xa2, 0x16, - 0xfe, 0xdb, 0xf8, 0xfd, 0x1c, 0x90, 0x4e, 0x10, 0x3a, 0x53, 0x3b, 0xa4, 0x7b, 0x54, 0xb1, 0x85, - 0x3e, 0xd4, 0x59, 0x69, 0x43, 0xaf, 0x8d, 0x82, 0x1e, 0x0a, 0x14, 0xef, 0x8a, 0x6d, 0x9c, 0xce, - 0xb0, 0xa5, 0x53, 0x23, 0x9b, 0x8f, 0x15, 0xc0, 0x76, 0x59, 0x68, 0xfb, 0x67, 0x34, 0xe4, 0xe2, - 0xa1, 0x90, 0x6b, 0x00, 0x41, 0x4c, 0x30, 0xdc, 0xfc, 0x01, 0x2c, 0xa7, 0xca, 0xd0, 0xf9, 0x72, - 0x35, 0x83, 0x2f, 0x17, 0x74, 0xbe, 0x6c, 0xc1, 0x4a, 0xac, 0x5d, 0x62, 0xa5, 0x6d, 0x40, 0x99, - 0x6d, 0x08, 0x26, 0x1c, 0xe4, 0x50, 0x5a, 0x3d, 0xa5, 0x94, 0x89, 0xd7, 0xef, 0xc3, 0xea, 0x29, - 0xa5, 0xbe, 0x1d, 0x72, 0x24, 0xdf, 0x31, 0x6c, 0x86, 0x44, 0xc1, 0xcb, 0x02, 0x37, 0xb0, 0xc3, - 0x23, 0xea, 0xb3, 0x99, 0x32, 0xfe, 0x4f, 0x0e, 0x96, 0x18, 0x07, 0x3d, 0xb4, 0xdd, 0x2b, 0x39, - 0x4e, 0x07, 0x99, 0xe3, 0x74, 0x4f, 0x3b, 0x0c, 0x35, 0xea, 0x6f, 0x3a, 0x48, 0x85, 0xe4, 0x20, - 0x91, 0xbb, 0x50, 0x8f, 0xb5, 0xb5, 0xc4, 0xdb, 0x0a, 0x81, 0x6a, 0x64, 0x24, 0x91, 0x2e, 0x68, - 0x12, 0xe9, 0xcf, 0x3e, 0xb8, 0x6f, 0x43, 0x33, 0xea, 0x8c, 0x18, 0x59, 0x02, 0x45, 0xb6, 0x50, - 0x45, 0x01, 0xfc, 0xb7, 0xf1, 0x4f, 0x72, 0x48, 0xb8, 0xe3, 0x39, 0x91, 0xd4, 0x4b, 0xa0, 0xc8, - 0xa4, 0x6c, 0x49, 0xc8, 0x7e, 0x5f, 0xab, 0x43, 0x7c, 0x0b, 0x43, 0x70, 0x13, 0x2a, 0x01, 0x13, - 0xa1, 0xed, 0x09, 0x8e, 0x42, 0xc5, 0x2c, 0xb3, 0x74, 0x7b, 0x32, 0x89, 0x46, 0xa7, 0xac, 0xcb, - 0xeb, 0xef, 0xc0, 0xb2, 0xd6, 0xe6, 0x17, 0xf4, 0xae, 0x07, 0xe4, 0xc0, 0x09, 0xc2, 0x63, 0x37, - 0x98, 0x69, 0x42, 0xde, 0x2d, 0xa8, 0x32, 0x6e, 0xcc, 0xda, 0x1b, 0x08, 0x89, 0x9e, 0xb1, 0x67, - 0xd6, 0xda, 0x80, 0x23, 0xed, 0x4b, 0x81, 0xcc, 0x0b, 0xa4, 0x7d, 0xc9, 0x91, 0xc6, 0xa7, 0xb0, - 0x12, 0x2b, 0x4f, 0x54, 0xfd, 0x06, 0x94, 0xe6, 0xe1, 0xa5, 0x27, 0xc5, 0xf8, 0x9a, 0x58, 0x4d, - 0x4c, 0x09, 0x35, 0x11, 0x63, 0x3c, 0x86, 0xe5, 0x1e, 0x7d, 0x2e, 0x36, 0xbc, 0x6c, 0xc8, 0xdb, - 0x50, 0x7c, 0x89, 0x62, 0xca, 0xf1, 0xc6, 0x16, 0x10, 0x3d, 0xb3, 0xa8, 0x55, 0xd3, 0x53, 0x73, - 0x31, 0x3d, 0xd5, 0x78, 0x1b, 0xc8, 0xc0, 0x39, 0x73, 0x0f, 0x69, 0x10, 0xd8, 0x67, 0x8a, 0x45, - 0x34, 0xa1, 0x30, 0x0d, 0xce, 0x04, 0x3f, 0x63, 0x3f, 0x8d, 0x0f, 0x61, 0x25, 0x46, 0x27, 0x0a, - 0x7e, 0x0d, 0xaa, 0x81, 0x73, 0xe6, 0x72, 0x21, 0x4c, 0x14, 0x1d, 0x01, 0x8c, 0x3d, 0x58, 0xfd, - 0x82, 0xfa, 0xce, 0xe9, 0xd5, 0xcb, 0x8a, 0x8f, 0x97, 0x93, 0x4f, 0x96, 0xd3, 0x81, 0xb5, 0x44, - 0x39, 0xa2, 0x7a, 0x5c, 0xd4, 0x62, 0x26, 0x2b, 0x26, 0x26, 0x34, 0x1e, 0x99, 0xd7, 0x79, 0xa4, - 0x71, 0x0c, 0x64, 0xc7, 0x73, 0x5d, 0x3a, 0x0a, 0x8f, 0x28, 0xf5, 0x65, 0x63, 0xde, 0xd5, 0x56, - 0x70, 0xed, 0xe1, 0x86, 0x18, 0xd9, 0x24, 0xe3, 0x15, 0x4b, 0x9b, 0x40, 0x71, 0x46, 0xfd, 0x29, - 0x2f, 0xb8, 0x62, 0xf2, 0xdf, 0xc6, 0x1a, 0xac, 0xc4, 0x8a, 0xc5, 0xb6, 0x19, 0x0f, 0x60, 0x6d, - 0xd7, 0x09, 0x46, 0xe9, 0x0a, 0x37, 0xa0, 0x3c, 0x9b, 0x9f, 0x58, 0x71, 0x1e, 0xfe, 0x94, 0x5e, - 0x19, 0x2d, 0x58, 0x4f, 0xe6, 0x10, 0x65, 0xfd, 0x7a, 0x0e, 0x8a, 0xfb, 0xc3, 0x83, 0x1d, 0xb2, - 0x09, 0x15, 0xc7, 0x1d, 0x79, 0x53, 0x26, 0xa4, 0x61, 0x9f, 0x55, 0xfa, 0xda, 0x6d, 0x77, 0x0b, - 0xaa, 0x5c, 0xb6, 0x63, 0xea, 0xb5, 0x10, 0x93, 0x2a, 0x0c, 0x70, 0xe0, 0x8d, 0x9e, 0x31, 0xbd, - 0x9e, 0x5e, 0xce, 0x1c, 0x9f, 0x6b, 0xee, 0x52, 0x33, 0x2d, 0xa2, 0x5c, 0x10, 0x21, 0x84, 0x82, - 0xfa, 0xeb, 0x79, 0x20, 0xe2, 0x64, 0xde, 0xf1, 0xdc, 0x20, 0xf4, 0x6d, 0xc7, 0x0d, 0x83, 0xb8, - 0xe4, 0x91, 0x4b, 0x48, 0x1e, 0xf7, 0xa0, 0xc9, 0x4f, 0x7b, 0x21, 0xf5, 0x70, 0x66, 0x9d, 0x8f, - 0x24, 0x1f, 0x21, 0xf6, 0x30, 0xa6, 0xfd, 0x16, 0x2c, 0x46, 0x02, 0x97, 0x32, 0x9b, 0x14, 0xcd, - 0xba, 0x12, 0xba, 0x04, 0x6b, 0x67, 0x9b, 0x4e, 0x4a, 0x12, 0x4a, 0x3b, 0x44, 0xd9, 0x6e, 0x79, - 0x6a, 0x5f, 0x1e, 0x51, 0x29, 0xde, 0x71, 0x3d, 0xd1, 0x80, 0x86, 0x14, 0xa8, 0x90, 0x12, 0xe5, - 0xbc, 0x9a, 0x90, 0xaa, 0x38, 0x4d, 0xb6, 0x78, 0xb4, 0x90, 0x2d, 0x1e, 0x19, 0xff, 0xa1, 0x0a, - 0x65, 0x31, 0x0c, 0x28, 0xec, 0x84, 0xce, 0x05, 0x8d, 0x84, 0x1d, 0x96, 0x62, 0x22, 0x94, 0x4f, - 0xa7, 0x5e, 0xa8, 0x64, 0x5c, 0x5c, 0x8a, 0x75, 0x04, 0x0a, 0x29, 0x57, 0x93, 0xb3, 0xd0, 0xda, - 0x53, 0x40, 0xa2, 0x91, 0x2e, 0xfd, 0xdc, 0x82, 0xb2, 0x14, 0x97, 0x8a, 0x4a, 0x0d, 0x5c, 0x18, - 0xa1, 0x80, 0xbb, 0x09, 0x95, 0x91, 0x3d, 0xb3, 0x47, 0x4e, 0x78, 0x25, 0xb8, 0xa5, 0x4a, 0xb3, - 0xd2, 0x27, 0xde, 0xc8, 0x9e, 0x58, 0x27, 0xf6, 0xc4, 0x76, 0x47, 0x54, 0x98, 0x51, 0xea, 0x1c, - 0xb8, 0x8d, 0x30, 0xf2, 0x1d, 0x58, 0x14, 0xed, 0x94, 0x54, 0x68, 0x4d, 0x11, 0xad, 0x97, 0x64, - 0x4c, 0x1e, 0xf7, 0xa6, 0x6c, 0x5e, 0x4e, 0x29, 0x4a, 0xae, 0x05, 0xb3, 0x8a, 0x90, 0x3d, 0xca, - 0x7b, 0x2b, 0xd0, 0xcf, 0x71, 0x05, 0x55, 0xb1, 0x2a, 0x04, 0x7e, 0x89, 0xd6, 0x8f, 0xb4, 0xf8, - 0x5a, 0xd0, 0xc4, 0xd7, 0x77, 0x61, 0x79, 0xee, 0x06, 0x34, 0x0c, 0x27, 0x74, 0xac, 0xda, 0x52, - 0xe3, 0x44, 0x4d, 0x85, 0x90, 0xcd, 0xd9, 0x82, 0x15, 0xb4, 0xff, 0x04, 0x76, 0xe8, 0x05, 0xe7, - 0x4e, 0x60, 0x05, 0x4c, 0xa9, 0x44, 0x0b, 0xc1, 0x32, 0x47, 0x0d, 0x04, 0x66, 0x80, 0x5a, 0xe5, - 0x46, 0x82, 0xde, 0xa7, 0x23, 0xea, 0x5c, 0xd0, 0x31, 0x17, 0x6d, 0x0b, 0xe6, 0x5a, 0x2c, 0x8f, - 0x29, 0x90, 0x5c, 0x4f, 0x99, 0x4f, 0xad, 0xf9, 0x6c, 0x6c, 0x33, 0xf9, 0x6e, 0x11, 0xf5, 0x07, - 0x77, 0x3e, 0x3d, 0x46, 0x08, 0x79, 0x00, 0x52, 0x78, 0x15, 0x6b, 0x66, 0x29, 0xc6, 0xd6, 0xd9, - 0x9e, 0x35, 0xeb, 0x82, 0x02, 0x65, 0xeb, 0x3b, 0xfa, 0x66, 0x69, 0xb2, 0x15, 0xc6, 0xf5, 0xac, - 0x68, 0xc3, 0xb4, 0xa0, 0x3c, 0xf3, 0x9d, 0x0b, 0x3b, 0xa4, 0xad, 0x65, 0x3c, 0xe1, 0x44, 0x92, - 0x31, 0x49, 0xc7, 0x75, 0x42, 0xc7, 0x0e, 0x3d, 0xbf, 0x45, 0x38, 0x2e, 0x02, 0x90, 0xfb, 0xb0, - 0xcc, 0xd7, 0x49, 0x10, 0xda, 0xe1, 0x3c, 0x10, 0x82, 0xfb, 0x0a, 0x5f, 0x50, 0x5c, 0xf5, 0x18, - 0x70, 0x38, 0x97, 0xdd, 0xc9, 0x27, 0xb0, 0x8e, 0x4b, 0x23, 0xb5, 0x35, 0x57, 0xd9, 0x70, 0xf0, - 0x16, 0xad, 0x70, 0x8a, 0x9d, 0xf8, 0x1e, 0xfd, 0x0c, 0x36, 0xc4, 0x72, 0x49, 0xe5, 0x5c, 0x53, - 0x39, 0x57, 0x91, 0x24, 0x91, 0x75, 0x0b, 0x96, 0x59, 0xd3, 0x9c, 0x91, 0x25, 0x4a, 0x60, 0xbb, - 0x62, 0x9d, 0xf5, 0x82, 0x67, 0x5a, 0x42, 0xa4, 0xc9, 0x71, 0x4f, 0xe9, 0x15, 0xf9, 0x1c, 0x96, - 0x70, 0xf9, 0x70, 0xed, 0x94, 0x1f, 0x7e, 0x9b, 0xfc, 0xf0, 0x5b, 0x13, 0x83, 0xbb, 0xa3, 0xb0, - 0xfc, 0xfc, 0x5b, 0x1c, 0xc5, 0xd2, 0x6c, 0x6b, 0x4c, 0x9c, 0x53, 0x1a, 0x3a, 0x53, 0xda, 0xda, - 0xc0, 0xc5, 0x26, 0xd3, 0x6c, 0xd7, 0xce, 0x67, 0x1c, 0xd3, 0x42, 0x56, 0x89, 0x29, 0xbe, 0x8e, - 0x27, 0x5e, 0x40, 0xa5, 0xe5, 0xb0, 0x75, 0x53, 0x6c, 0x48, 0x06, 0x94, 0x22, 0x38, 0xd3, 0x63, - 0x50, 0x67, 0x54, 0xf6, 0xdd, 0x5b, 0x7c, 0x61, 0x34, 0x50, 0x75, 0x94, 0x36, 0x5e, 0x26, 0xee, - 0x9c, 0xdb, 0xcf, 0x25, 0x53, 0x7d, 0x8d, 0x73, 0x13, 0x60, 0x20, 0x61, 0x0e, 0xdc, 0x83, 0x65, - 0x31, 0x0b, 0x11, 0x33, 0x6d, 0xdd, 0xe6, 0xc7, 0xd0, 0x4d, 0xd9, 0xc7, 0x14, 0xb7, 0x35, 0x9b, - 0x38, 0x2f, 0x1a, 0xff, 0xdd, 0x07, 0x22, 0x27, 0x45, 0x2b, 0xe8, 0xf5, 0x97, 0x15, 0xb4, 0x2c, - 0xa6, 0x29, 0x02, 0x19, 0xbf, 0x9b, 0x43, 0xa9, 0x45, 0x50, 0x07, 0x9a, 0xbe, 0x8e, 0x7c, 0xcd, - 0xf2, 0xdc, 0xc9, 0x95, 0x60, 0x75, 0x80, 0xa0, 0xbe, 0x3b, 0xe1, 0xbc, 0xc6, 0x71, 0x75, 0x12, - 0x3c, 0x20, 0xeb, 0x12, 0xc8, 0x89, 0xee, 0x40, 0x6d, 0x36, 0x3f, 0x99, 0x38, 0x23, 0x24, 0x29, - 0x60, 0x29, 0x08, 0xe2, 0x04, 0x6f, 0x40, 0x5d, 0xac, 0x75, 0xa4, 0x28, 0x72, 0x8a, 0x9a, 0x80, - 0x71, 0x12, 0x7e, 0x00, 0x53, 0x9f, 0x33, 0xbb, 0xba, 0xc9, 0x7f, 0x1b, 0xdb, 0xb0, 0x1a, 0x6f, - 0xb4, 0x90, 0x0e, 0xee, 0x43, 0x45, 0x70, 0x52, 0x69, 0xc9, 0x5a, 0x8c, 0x8f, 0x86, 0xa9, 0xf0, - 0xc6, 0x7f, 0x2c, 0xc1, 0x8a, 0x1c, 0x23, 0x36, 0xd9, 0x83, 0xf9, 0x74, 0x6a, 0xfb, 0x19, 0x2c, - 0x3a, 0xf7, 0x62, 0x16, 0x9d, 0x4f, 0xb1, 0xe8, 0xb8, 0x29, 0x03, 0x39, 0x7c, 0xdc, 0x94, 0xc1, - 0x56, 0x17, 0x6a, 0x97, 0xba, 0xc1, 0xbc, 0x21, 0xc0, 0x43, 0x34, 0xcc, 0xa7, 0x0e, 0x94, 0x52, - 0xc6, 0x81, 0xa2, 0x1f, 0x07, 0x0b, 0x89, 0xe3, 0xe0, 0x0d, 0xc0, 0x65, 0x2c, 0xd7, 0x63, 0x19, - 0x15, 0x4e, 0x0e, 0x13, 0x0b, 0xf2, 0x1d, 0x58, 0x4a, 0x72, 0x60, 0x64, 0xf5, 0x8b, 0x19, 0xfc, - 0xd7, 0x99, 0x52, 0x2e, 0x52, 0x68, 0xc4, 0x55, 0xc1, 0x7f, 0x9d, 0x29, 0x3d, 0xe0, 0x18, 0x49, - 0xdf, 0x01, 0xc0, 0xba, 0xf9, 0x36, 0x06, 0xbe, 0x8d, 0xdf, 0x4e, 0xac, 0x4c, 0x6d, 0xd4, 0xb7, - 0x58, 0x62, 0xee, 0x53, 0xbe, 0xaf, 0xab, 0x3c, 0x27, 0xdf, 0xd2, 0x9f, 0xc0, 0xa2, 0x37, 0xa3, - 0xae, 0x15, 0x71, 0xc1, 0x1a, 0x2f, 0xaa, 0x29, 0x8a, 0xea, 0x4a, 0xb8, 0xd9, 0x60, 0x74, 0x2a, - 0x49, 0x3e, 0xc3, 0x41, 0xa6, 0x5a, 0xce, 0xfa, 0x35, 0x39, 0x17, 0x39, 0x61, 0x94, 0xf5, 0x43, - 0xa8, 0xf9, 0x34, 0xf0, 0x26, 0x73, 0xb4, 0xbe, 0x37, 0xf8, 0x3a, 0x92, 0xe6, 0x48, 0x53, 0x61, - 0x4c, 0x9d, 0xca, 0xf8, 0x8d, 0x1c, 0xd4, 0xb4, 0x3e, 0x90, 0x35, 0x58, 0xde, 0xe9, 0xf7, 0x8f, - 0x3a, 0x66, 0x7b, 0xd8, 0xfd, 0xa2, 0x63, 0xed, 0x1c, 0xf4, 0x07, 0x9d, 0xe6, 0x0d, 0x06, 0x3e, - 0xe8, 0xef, 0xb4, 0x0f, 0xac, 0xbd, 0xbe, 0xb9, 0x23, 0xc1, 0x39, 0xb2, 0x0e, 0xc4, 0xec, 0x1c, - 0xf6, 0x87, 0x9d, 0x18, 0x3c, 0x4f, 0x9a, 0x50, 0xdf, 0x36, 0x3b, 0xed, 0x9d, 0x7d, 0x01, 0x29, - 0x90, 0x55, 0x68, 0xee, 0x1d, 0xf7, 0x76, 0xbb, 0xbd, 0x27, 0xd6, 0x4e, 0xbb, 0xb7, 0xd3, 0x39, - 0xe8, 0xec, 0x36, 0x8b, 0xa4, 0x01, 0xd5, 0xf6, 0x76, 0xbb, 0xb7, 0xdb, 0xef, 0x75, 0x76, 0x9b, - 0x25, 0xe3, 0x7f, 0xe4, 0x00, 0xa2, 0x86, 0x32, 0xbe, 0x1a, 0x35, 0x55, 0xf7, 0x76, 0xad, 0xa5, - 0x3a, 0x85, 0x7c, 0xd5, 0x8f, 0xa5, 0xc9, 0x43, 0x28, 0x7b, 0xf3, 0x70, 0xe4, 0x4d, 0x51, 0x50, - 0x5f, 0x7c, 0xd8, 0x4a, 0xe5, 0xeb, 0x23, 0xde, 0x94, 0x84, 0x31, 0x8f, 0x56, 0xe1, 0x65, 0x1e, - 0xad, 0xb8, 0xeb, 0x0c, 0xe5, 0x3a, 0xcd, 0x75, 0x76, 0x1b, 0x20, 0x78, 0x4e, 0xe9, 0x8c, 0x1b, - 0x63, 0xc4, 0x2e, 0xa8, 0x72, 0xc8, 0x90, 0xe9, 0x71, 0x7f, 0x94, 0x83, 0x35, 0xbe, 0x96, 0xc6, - 0x49, 0x26, 0x76, 0x17, 0x6a, 0x23, 0xcf, 0x9b, 0x31, 0xd5, 0x3f, 0x92, 0xd7, 0x74, 0x10, 0x63, - 0x50, 0xc8, 0x90, 0x4f, 0x3d, 0x7f, 0x44, 0x05, 0x0f, 0x03, 0x0e, 0xda, 0x63, 0x10, 0xb6, 0x87, - 0xc4, 0x26, 0x44, 0x0a, 0x64, 0x61, 0x35, 0x84, 0x21, 0xc9, 0x3a, 0x2c, 0x9c, 0xf8, 0xd4, 0x1e, - 0x9d, 0x0b, 0xee, 0x25, 0x52, 0xe4, 0xbb, 0x91, 0x51, 0x6a, 0xc4, 0xf6, 0xc4, 0x84, 0x62, 0xe3, - 0x2b, 0xe6, 0x92, 0x80, 0xef, 0x08, 0x30, 0x3b, 0xe7, 0xed, 0x13, 0xdb, 0x1d, 0x7b, 0x2e, 0x1d, - 0x0b, 0x2d, 0x37, 0x02, 0x18, 0x47, 0xb0, 0x9e, 0xec, 0x9f, 0xe0, 0x77, 0x1f, 0x6b, 0xfc, 0x0e, - 0xd5, 0xcb, 0xcd, 0xeb, 0xf7, 0x98, 0xc6, 0xfb, 0xfe, 0x52, 0x11, 0x8a, 0x4c, 0xdd, 0xb8, 0x56, - 0x33, 0xd1, 0xf5, 0xc7, 0x42, 0xca, 0xcf, 0xc9, 0x6d, 0x5f, 0x28, 0x80, 0x89, 0xc9, 0xe2, 0x10, - 0x2e, 0x78, 0x29, 0xb4, 0x4f, 0x47, 0x17, 0x42, 0xf2, 0x46, 0xb4, 0x49, 0x47, 0x17, 0x5c, 0x9d, - 0xb7, 0x43, 0xcc, 0x8b, 0xfc, 0xaa, 0x1c, 0xd8, 0x21, 0xcf, 0x29, 0x50, 0x3c, 0x5f, 0x59, 0xa1, - 0x78, 0xae, 0x16, 0x94, 0x1d, 0xf7, 0xc4, 0x9b, 0xbb, 0x63, 0xce, 0x9e, 0x2a, 0xa6, 0x4c, 0x72, - 0xb7, 0x2a, 0xe7, 0xa4, 0xec, 0x68, 0x47, 0x6e, 0x54, 0x61, 0x80, 0x21, 0x3b, 0xdc, 0x3f, 0x80, - 0x6a, 0x70, 0xe5, 0x8e, 0x74, 0x1e, 0xb4, 0x2a, 0xc6, 0x87, 0xf5, 0x7e, 0x6b, 0x70, 0xe5, 0x8e, - 0xf8, 0x8a, 0xaf, 0x04, 0xe2, 0x17, 0x79, 0x04, 0x15, 0xe5, 0x88, 0xc0, 0x13, 0xe4, 0xa6, 0x9e, - 0x43, 0x7a, 0x1f, 0xd0, 0xde, 0xa3, 0x48, 0xc9, 0xfb, 0xb0, 0xc0, 0xbd, 0x05, 0x41, 0xab, 0xce, - 0x33, 0x49, 0xa5, 0x92, 0x35, 0x83, 0x7b, 0x34, 0xe9, 0x98, 0x7b, 0x0e, 0x4c, 0x41, 0xb6, 0xf9, - 0x14, 0x1a, 0xb1, 0xb2, 0x74, 0xfb, 0x4d, 0x03, 0xed, 0x37, 0x6f, 0xe9, 0xf6, 0x9b, 0xe8, 0x24, - 0x13, 0xd9, 0x74, 0x7b, 0xce, 0x0f, 0xa0, 0x22, 0xbb, 0xc2, 0x58, 0xc6, 0x71, 0xef, 0x69, 0xaf, - 0xff, 0x65, 0xcf, 0x1a, 0x7c, 0xd5, 0xdb, 0x69, 0xde, 0x20, 0x4b, 0x50, 0x6b, 0xef, 0x70, 0x2e, - 0xc4, 0x01, 0x39, 0x46, 0x72, 0xd4, 0x1e, 0x0c, 0x14, 0x24, 0x6f, 0xec, 0x41, 0x33, 0xd9, 0x52, - 0xb6, 0x26, 0x43, 0x09, 0x13, 0xbe, 0x94, 0x08, 0xc0, 0xf4, 0x70, 0x74, 0x8f, 0xa0, 0x96, 0x83, - 0x09, 0xe3, 0x11, 0x34, 0xd9, 0xb9, 0xcc, 0x86, 0x4a, 0xf7, 0x92, 0x4e, 0x98, 0xe4, 0xac, 0xfb, - 0x53, 0x2a, 0x66, 0x0d, 0x61, 0xbc, 0x2a, 0xe3, 0x63, 0x58, 0xd6, 0xb2, 0x45, 0x76, 0x13, 0x76, - 0xd6, 0x27, 0xed, 0x26, 0x5c, 0x4b, 0x46, 0x8c, 0xb1, 0x01, 0x6b, 0x2c, 0xd9, 0xb9, 0xa0, 0x6e, - 0x38, 0x98, 0x9f, 0xa0, 0x73, 0xdd, 0xf1, 0x5c, 0xa6, 0x3d, 0x57, 0x15, 0xe6, 0xfa, 0x45, 0xbe, - 0x25, 0x4c, 0x2c, 0xc8, 0xd5, 0x36, 0xb5, 0x1a, 0x78, 0xc6, 0x2d, 0xfe, 0x37, 0x66, 0x6a, 0xa9, - 0x2a, 0x10, 0x1b, 0xd6, 0xa3, 0x4e, 0xc7, 0xb4, 0xfa, 0xbd, 0x83, 0x6e, 0x8f, 0xf1, 0x76, 0x36, - 0xac, 0x1c, 0xb0, 0xb7, 0xc7, 0x21, 0x39, 0xa3, 0x09, 0x8b, 0x4f, 0x68, 0xd8, 0x75, 0x4f, 0x3d, - 0x31, 0x18, 0xc6, 0x5f, 0x58, 0x80, 0x25, 0x05, 0x8a, 0x4c, 0x35, 0x17, 0xd4, 0x0f, 0x1c, 0xcf, - 0xe5, 0xea, 0x46, 0xd5, 0x94, 0x49, 0xc6, 0x9d, 0x84, 0x92, 0xc5, 0xa5, 0x84, 0x55, 0x8e, 0x15, - 0x6a, 0x19, 0x17, 0x11, 0xde, 0x81, 0x25, 0x67, 0x4c, 0xdd, 0xd0, 0x09, 0xaf, 0xac, 0x98, 0x91, - 0x78, 0x51, 0x82, 0x85, 0x98, 0xb0, 0x0a, 0x25, 0x7b, 0xe2, 0xd8, 0x32, 0x68, 0x01, 0x13, 0x0c, - 0x3a, 0xf2, 0x26, 0x9e, 0xcf, 0xd5, 0x8e, 0xaa, 0x89, 0x09, 0xf2, 0x00, 0x56, 0x99, 0x0a, 0xa4, - 0x5b, 0xee, 0x39, 0x83, 0x41, 0x7b, 0x35, 0x71, 0xe7, 0xd3, 0xa3, 0xc8, 0x7a, 0xcf, 0x30, 0x4c, - 0x38, 0x60, 0x39, 0x84, 0x34, 0xa8, 0x32, 0xa0, 0x51, 0x61, 0xd9, 0x9d, 0x4f, 0xdb, 0x1c, 0xa3, - 0xe8, 0x1f, 0xc2, 0x1a, 0xa3, 0x57, 0xf2, 0xa3, 0xca, 0xb1, 0xc4, 0x73, 0xb0, 0xc2, 0xba, 0x02, - 0xa7, 0xf2, 0xdc, 0x82, 0x2a, 0xb6, 0x8a, 0x2d, 0x89, 0x12, 0x9a, 0x1c, 0x78, 0x53, 0xa8, 0x1f, - 0xa4, 0xe2, 0x0b, 0x50, 0x8f, 0x4f, 0xc6, 0x17, 0x68, 0x11, 0x0a, 0x95, 0x64, 0x84, 0xc2, 0x43, - 0x58, 0x3b, 0x61, 0x6b, 0xf4, 0x9c, 0xda, 0x63, 0xea, 0x5b, 0xd1, 0xca, 0x47, 0x6d, 0x71, 0x85, - 0x21, 0xf7, 0x39, 0x4e, 0x6d, 0x14, 0x26, 0xc8, 0x31, 0xbe, 0x41, 0xc7, 0x56, 0xe8, 0x59, 0x5c, - 0xbe, 0xe3, 0x1c, 0xa8, 0x62, 0x36, 0x10, 0x3c, 0xf4, 0x76, 0x18, 0x30, 0x4e, 0x77, 0xe6, 0xdb, - 0xb3, 0x73, 0xa1, 0xcb, 0x29, 0xba, 0x27, 0x0c, 0x48, 0x5e, 0x83, 0x32, 0xdb, 0x13, 0x2e, 0x45, - 0x77, 0x2d, 0x6a, 0x49, 0x12, 0x44, 0xde, 0x82, 0x05, 0x5e, 0x47, 0xd0, 0x6a, 0xf2, 0x0d, 0x51, - 0x8f, 0x38, 0xbd, 0xe3, 0x9a, 0x02, 0xc7, 0xa4, 0xe5, 0xb9, 0xef, 0x20, 0x1b, 0xaa, 0x9a, 0xfc, - 0x37, 0xf9, 0xa1, 0xc6, 0xd3, 0x56, 0x78, 0xde, 0xb7, 0x44, 0xde, 0xc4, 0x52, 0xbc, 0x8e, 0xbd, - 0x7d, 0xab, 0xdc, 0xea, 0x47, 0xc5, 0x4a, 0xad, 0x59, 0x37, 0x5a, 0x3c, 0xac, 0xc2, 0xa4, 0x23, - 0xef, 0x82, 0xfa, 0x57, 0xb1, 0x3d, 0x92, 0x83, 0x8d, 0x14, 0x2a, 0xf2, 0xce, 0xfa, 0x02, 0x6e, - 0x4d, 0xbd, 0xb1, 0x3c, 0xd3, 0xeb, 0x12, 0x78, 0xe8, 0x8d, 0x99, 0xec, 0xb1, 0xac, 0x88, 0x4e, - 0x1d, 0xd7, 0x09, 0xce, 0xe9, 0x58, 0x1c, 0xed, 0x4d, 0x89, 0xd8, 0x13, 0x70, 0x26, 0x40, 0xcf, - 0x7c, 0xef, 0x4c, 0x9d, 0x74, 0x39, 0x53, 0xa5, 0x8d, 0x4f, 0xa0, 0x84, 0x33, 0xc8, 0x36, 0x0a, - 0x9f, 0xdf, 0x9c, 0xd8, 0x28, 0x1c, 0xda, 0x82, 0xb2, 0x4b, 0xc3, 0xe7, 0x9e, 0xff, 0x4c, 0xba, - 0x7a, 0x44, 0xd2, 0xf8, 0x09, 0xb7, 0x3b, 0xaa, 0xf8, 0x18, 0xb4, 0x1d, 0xb0, 0x25, 0x8c, 0x4b, - 0x30, 0x38, 0xb7, 0x85, 0x29, 0xb4, 0xc2, 0x01, 0x83, 0x73, 0x3b, 0xb5, 0x84, 0xf3, 0xe9, 0x10, - 0x99, 0xb7, 0x60, 0x51, 0x46, 0xe4, 0x04, 0xd6, 0x84, 0x9e, 0x86, 0x62, 0x4b, 0xd6, 0x45, 0x38, - 0x4e, 0x70, 0x40, 0x4f, 0x43, 0xe3, 0x10, 0x96, 0xc5, 0xa6, 0xe9, 0xcf, 0xa8, 0xac, 0xfa, 0xd3, - 0x2c, 0xa5, 0xa6, 0xf6, 0x70, 0x25, 0x2e, 0x2d, 0xa0, 0x5c, 0x16, 0xd3, 0x74, 0x8c, 0x1f, 0x47, - 0x06, 0x40, 0x26, 0x4b, 0x88, 0xf2, 0x84, 0x6a, 0x21, 0x3d, 0x64, 0xd2, 0xd1, 0xac, 0x14, 0x18, - 0x67, 0xcc, 0x46, 0x27, 0x98, 0x8f, 0x46, 0x32, 0x52, 0xaa, 0x62, 0xca, 0xa4, 0xf1, 0xef, 0x72, - 0xb0, 0xc2, 0x0b, 0x93, 0x4a, 0x99, 0x38, 0x29, 0x7e, 0xea, 0x46, 0xb2, 0xf9, 0xd1, 0x05, 0x38, - 0x4c, 0x7c, 0x73, 0xef, 0x43, 0x31, 0xe5, 0x7d, 0xf8, 0x2e, 0x34, 0xc7, 0x74, 0xe2, 0xf0, 0xa5, - 0x24, 0xe5, 0x21, 0x14, 0x40, 0x97, 0x24, 0x5c, 0x18, 0x09, 0x8c, 0xbf, 0x96, 0x83, 0x65, 0x14, - 0xb7, 0xb8, 0xd9, 0x45, 0x0c, 0xd4, 0x63, 0x69, 0x5f, 0x10, 0xec, 0x54, 0xf4, 0x29, 0x12, 0x43, - 0x38, 0x14, 0x89, 0xf7, 0x6f, 0x08, 0xbb, 0x83, 0x80, 0x92, 0xef, 0x73, 0x45, 0xd2, 0xb5, 0x38, - 0x50, 0x88, 0xd1, 0x37, 0x33, 0x04, 0x3c, 0x95, 0x9d, 0x69, 0x99, 0x2e, 0x07, 0x6d, 0x57, 0x60, - 0x01, 0x8d, 0x58, 0xc6, 0x1e, 0x34, 0x62, 0xd5, 0xc4, 0x9c, 0x21, 0x75, 0x74, 0x86, 0xa4, 0x9c, - 0x93, 0xf9, 0xb4, 0x73, 0xf2, 0x0a, 0x56, 0x4c, 0x6a, 0x8f, 0xaf, 0xf6, 0x3c, 0xff, 0x28, 0x38, - 0x09, 0xf7, 0x50, 0x86, 0x65, 0x67, 0x90, 0xf2, 0xb8, 0xc7, 0x3c, 0x0e, 0xd2, 0xf1, 0x2a, 0xad, - 0x28, 0xdf, 0x81, 0xc5, 0xc8, 0x35, 0xaf, 0x59, 0xad, 0x1b, 0xca, 0x3b, 0xcf, 0x8d, 0xd7, 0x4c, - 0xdf, 0x0f, 0x4e, 0x42, 0x61, 0xb7, 0xe6, 0xbf, 0x8d, 0xff, 0x5d, 0x04, 0xc2, 0x56, 0x73, 0x62, - 0xc1, 0x24, 0x82, 0x0a, 0xf2, 0xa9, 0xa0, 0x82, 0x07, 0x40, 0x34, 0x02, 0x19, 0xeb, 0x50, 0x50, - 0xb1, 0x0e, 0xcd, 0x88, 0x56, 0x84, 0x3a, 0x3c, 0x80, 0x55, 0xa1, 0x10, 0xc4, 0x9b, 0x8a, 0x4b, - 0x83, 0xa0, 0x66, 0x10, 0x6b, 0xaf, 0x0c, 0x28, 0x90, 0x86, 0xe6, 0x02, 0x06, 0x14, 0x48, 0x7b, - 0x90, 0xb6, 0x00, 0x17, 0x5e, 0xba, 0x00, 0xcb, 0xa9, 0x05, 0xa8, 0xd9, 0x06, 0x2b, 0x71, 0xdb, - 0x60, 0xca, 0xca, 0x8d, 0xd2, 0x6f, 0xcc, 0xca, 0x7d, 0x0f, 0x9a, 0xd2, 0x4e, 0xa4, 0x2c, 0x90, - 0x18, 0x09, 0x24, 0x6c, 0xc0, 0x3b, 0xd2, 0x06, 0x19, 0x73, 0x7b, 0xd5, 0x12, 0x6e, 0xaf, 0x77, - 0x61, 0x39, 0x60, 0xeb, 0xd7, 0x9a, 0xbb, 0x22, 0x1c, 0x90, 0x8e, 0xb9, 0x3a, 0x5d, 0x31, 0x9b, - 0x1c, 0x71, 0x1c, 0xc1, 0xd3, 0x16, 0xb5, 0x46, 0x86, 0x45, 0xed, 0x51, 0xe4, 0x61, 0x0f, 0xce, - 0x9d, 0x29, 0x17, 0x7c, 0xa2, 0x10, 0x37, 0x31, 0xc0, 0x83, 0x73, 0x67, 0x6a, 0xca, 0x70, 0x0e, - 0x96, 0x20, 0x3b, 0x70, 0x47, 0xf4, 0x27, 0x23, 0x12, 0x03, 0x47, 0x61, 0x89, 0x4b, 0xaa, 0x9b, - 0x48, 0x76, 0x98, 0x08, 0xca, 0x48, 0x0c, 0x0a, 0x2b, 0x04, 0x8d, 0xb8, 0x4d, 0x7d, 0x50, 0x0e, - 0xed, 0x4b, 0x34, 0xfb, 0xff, 0xaf, 0x1c, 0x34, 0xd9, 0xb2, 0x8b, 0xed, 0xe8, 0xcf, 0x80, 0xf3, - 0x9e, 0x57, 0xdc, 0xd0, 0x35, 0x46, 0x2b, 0xf7, 0xf3, 0x27, 0xc0, 0x37, 0xa8, 0xe5, 0xcd, 0xa8, - 0x2b, 0xb6, 0x73, 0x2b, 0xbe, 0x9d, 0x23, 0x96, 0xbd, 0x7f, 0x03, 0xf5, 0x35, 0x06, 0x21, 0x9f, - 0x41, 0x95, 0xed, 0x03, 0xbe, 0x28, 0x45, 0x80, 0xe8, 0xa6, 0xd2, 0xc1, 0x53, 0x5b, 0x92, 0x65, - 0x9d, 0x89, 0x64, 0x56, 0x0c, 0x46, 0x31, 0x23, 0x06, 0x43, 0xe3, 0x17, 0xfb, 0x00, 0x4f, 0xe9, - 0xd5, 0x81, 0x37, 0xe2, 0xd6, 0x90, 0xdb, 0x00, 0x6c, 0xeb, 0x9c, 0xda, 0x53, 0x47, 0xd8, 0x01, - 0x4b, 0x66, 0xf5, 0x19, 0xbd, 0xda, 0xe3, 0x00, 0xb6, 0x6e, 0x18, 0x3a, 0x62, 0x1a, 0x25, 0xb3, - 0xf2, 0x8c, 0x5e, 0x21, 0xc7, 0xb0, 0xa0, 0xf1, 0x94, 0x5e, 0xed, 0x52, 0x14, 0xcc, 0x3d, 0x9f, - 0xad, 0x59, 0xdf, 0x7e, 0xce, 0x24, 0xf1, 0x58, 0xfc, 0x44, 0xcd, 0xb7, 0x9f, 0x3f, 0xa5, 0x57, - 0x32, 0x96, 0xa3, 0xcc, 0xf0, 0x13, 0x6f, 0x24, 0x44, 0x09, 0x69, 0x7a, 0x89, 0x1a, 0x65, 0x2e, - 0x3c, 0xe3, 0xbf, 0x8d, 0x3f, 0xc9, 0x41, 0x83, 0xb5, 0x9f, 0x9f, 0x02, 0x7c, 0x85, 0x88, 0x80, - 0xc2, 0x5c, 0x14, 0x50, 0xf8, 0x50, 0x30, 0x51, 0x3c, 0x52, 0xf2, 0xd7, 0x1f, 0x29, 0x7c, 0x6e, - 0xf0, 0x3c, 0xf9, 0x00, 0xaa, 0xc8, 0x05, 0x18, 0x5b, 0x29, 0xc4, 0x26, 0x38, 0xd6, 0x21, 0xb3, - 0xc2, 0xc9, 0x9e, 0x62, 0xfc, 0x92, 0x66, 0xe5, 0xc6, 0x21, 0xae, 0xfa, 0xca, 0xb6, 0x9d, 0x31, - 0x0d, 0xa5, 0x6b, 0xe2, 0x97, 0x74, 0x13, 0xf2, 0x42, 0xd2, 0x84, 0x6c, 0xb8, 0x50, 0x61, 0x53, - 0xcd, 0x3b, 0x9b, 0x51, 0x68, 0x2e, 0xab, 0x50, 0x26, 0x78, 0xd8, 0xec, 0x0c, 0x62, 0x7c, 0x35, - 0x2f, 0x04, 0x0f, 0x3b, 0xa0, 0xac, 0x20, 0xd6, 0x70, 0xd7, 0xb3, 0xb8, 0x4d, 0x56, 0x58, 0x2b, - 0x2b, 0x66, 0xd5, 0xf5, 0x8e, 0x10, 0x60, 0xfc, 0xb9, 0x1c, 0xd4, 0xb4, 0xfd, 0xc8, 0x8d, 0xf4, - 0x6a, 0x38, 0x71, 0xf3, 0xc6, 0x77, 0x40, 0x6c, 0x3e, 0xf6, 0x6f, 0x98, 0x8d, 0x51, 0x6c, 0x82, - 0xb6, 0xc4, 0x52, 0xe6, 0x39, 0xf3, 0x31, 0xcb, 0x90, 0xec, 0x97, 0x5c, 0xbf, 0xec, 0xf7, 0xf6, - 0x02, 0x14, 0x19, 0xa9, 0xf1, 0x18, 0x96, 0xb5, 0x66, 0xa0, 0xe5, 0xe4, 0x55, 0x07, 0xc0, 0xf8, - 0x65, 0x95, 0x99, 0xd5, 0x81, 0x9e, 0x65, 0x19, 0x2a, 0x46, 0xc7, 0x38, 0x2e, 0x22, 0x24, 0x0d, - 0x41, 0x7c, 0x64, 0x5e, 0x35, 0x7c, 0xe9, 0x57, 0x61, 0x45, 0x2b, 0x7d, 0xcf, 0x71, 0xed, 0x89, - 0xf3, 0x13, 0x2e, 0x7e, 0x04, 0xce, 0x99, 0x9b, 0x28, 0x1f, 0x41, 0xdf, 0xa8, 0xfc, 0xbf, 0x9e, - 0x87, 0x55, 0x51, 0x01, 0x0f, 0xfe, 0x75, 0x98, 0x4c, 0x79, 0x18, 0x9c, 0x91, 0xcf, 0xa0, 0xc1, - 0xc6, 0xc6, 0xf2, 0xe9, 0x99, 0x13, 0x84, 0x54, 0x7a, 0xb4, 0x33, 0xd8, 0x28, 0x13, 0x2d, 0x18, - 0xa9, 0x29, 0x28, 0xc9, 0x63, 0xa8, 0xf1, 0xac, 0x68, 0x99, 0x12, 0x13, 0xd1, 0x4a, 0x67, 0xc4, - 0x81, 0xde, 0xbf, 0x61, 0x42, 0x10, 0x0d, 0xfb, 0x63, 0xa8, 0xf1, 0x39, 0xbc, 0xe0, 0x03, 0x99, - 0xe0, 0x64, 0xa9, 0x81, 0x66, 0x99, 0x67, 0xd1, 0xb0, 0xb7, 0xa1, 0x81, 0xbc, 0x4c, 0x8c, 0x93, - 0x08, 0x2a, 0xdc, 0x4c, 0x67, 0x97, 0x23, 0xc9, 0x1a, 0x3f, 0xd3, 0xd2, 0xdb, 0x55, 0x28, 0x87, - 0xbe, 0x73, 0x76, 0x46, 0x7d, 0x63, 0x5d, 0x0d, 0x0d, 0x63, 0xd2, 0x74, 0x10, 0xd2, 0x19, 0x53, - 0x16, 0x8c, 0x7f, 0x95, 0x83, 0x9a, 0x60, 0xbb, 0x3f, 0xb5, 0x1b, 0x7d, 0x33, 0x61, 0xc3, 0xac, - 0x6a, 0x26, 0xcb, 0x77, 0x60, 0x69, 0xca, 0x34, 0x1b, 0xa6, 0x79, 0xc7, 0x7c, 0xe8, 0x8b, 0x12, - 0x2c, 0x84, 0xf6, 0x2d, 0x58, 0xe1, 0x32, 0x7c, 0x60, 0x85, 0xce, 0xc4, 0x92, 0x48, 0x11, 0x01, - 0xbf, 0x8c, 0xa8, 0xa1, 0x33, 0x39, 0x14, 0x08, 0x26, 0xca, 0x06, 0xa1, 0x7d, 0x46, 0xc5, 0xd6, - 0xc7, 0x04, 0xd3, 0x96, 0x12, 0x4a, 0xb7, 0xd4, 0x96, 0xfe, 0xef, 0x32, 0x6c, 0xa4, 0x50, 0x42, - 0x5b, 0x52, 0x4e, 0xd3, 0x89, 0x33, 0x3d, 0xf1, 0x94, 0xd1, 0x3e, 0xa7, 0x39, 0x4d, 0x0f, 0x18, - 0x46, 0x1a, 0xed, 0x29, 0xac, 0xc9, 0x05, 0xc9, 0xad, 0xee, 0x4a, 0x2f, 0xcf, 0x73, 0xad, 0xf1, - 0x83, 0xf8, 0x19, 0x97, 0xac, 0x4e, 0xc2, 0x75, 0x41, 0x6d, 0x65, 0x96, 0x82, 0x05, 0xe4, 0x4f, - 0x43, 0x4b, 0xad, 0x7b, 0xa1, 0x44, 0x68, 0x46, 0x06, 0x56, 0xd3, 0x7b, 0x2f, 0xa9, 0x29, 0x66, - 0x0e, 0xe5, 0x92, 0xdc, 0xba, 0xdc, 0x32, 0x58, 0xa0, 0xaa, 0xeb, 0x02, 0x5e, 0x97, 0x75, 0x71, - 0xa5, 0x20, 0x5d, 0x63, 0xf1, 0x95, 0xfa, 0xc6, 0x4d, 0xbd, 0xb1, 0x6a, 0xcd, 0x5b, 0xa2, 0x60, - 0x85, 0xd2, 0xeb, 0x3d, 0x87, 0xf5, 0xe7, 0xb6, 0x13, 0xca, 0x3e, 0x6a, 0x36, 0x8e, 0x12, 0xaf, - 0xef, 0xe1, 0x4b, 0xea, 0xfb, 0x12, 0x33, 0xc7, 0xd4, 0xa4, 0xd5, 0xe7, 0x69, 0x60, 0xb0, 0xf9, - 0x77, 0x0b, 0xb0, 0x18, 0x2f, 0x85, 0x31, 0x16, 0x71, 0x16, 0x49, 0xe9, 0x57, 0x88, 0xe4, 0xc2, - 0xa1, 0xd4, 0x43, 0xa9, 0x37, 0xed, 0xea, 0xca, 0x67, 0xb8, 0xba, 0x74, 0x0f, 0x53, 0xe1, 0x65, - 0x01, 0x07, 0xc5, 0x57, 0x0a, 0x38, 0x28, 0x65, 0x05, 0x1c, 0x7c, 0x78, 0xad, 0x87, 0x1a, 0xed, - 0xc4, 0x99, 0xde, 0xe9, 0x47, 0xd7, 0x7b, 0xa7, 0x51, 0x96, 0xbe, 0xce, 0x33, 0xad, 0xf9, 0xd5, - 0x2b, 0xd7, 0xf8, 0x85, 0x34, 0x4f, 0x7b, 0x86, 0x67, 0xba, 0xfa, 0x0d, 0x3c, 0xd3, 0x9b, 0x7f, - 0x92, 0x03, 0x92, 0xde, 0x1d, 0xe4, 0x09, 0x7a, 0x11, 0x5d, 0x3a, 0x11, 0x9c, 0xfb, 0x7b, 0xaf, - 0xb6, 0xc3, 0xe4, 0x82, 0x90, 0xb9, 0xc9, 0xfb, 0xb0, 0xa2, 0xdf, 0xd3, 0xd1, 0x6d, 0x08, 0x0d, - 0x93, 0xe8, 0xa8, 0xc8, 0x1a, 0xa6, 0x45, 0x77, 0x14, 0x5f, 0x1a, 0xdd, 0x51, 0x7a, 0x69, 0x74, - 0xc7, 0x42, 0x3c, 0xba, 0x63, 0xf3, 0xdf, 0xe6, 0x60, 0x25, 0x63, 0x11, 0x7f, 0x7b, 0x7d, 0x66, - 0x6b, 0x2f, 0xc6, 0xd6, 0xf2, 0x62, 0xed, 0xe9, 0x1c, 0xed, 0x40, 0x5a, 0x50, 0xd9, 0x54, 0x04, - 0xe2, 0xa4, 0xba, 0xff, 0x32, 0xee, 0x12, 0xe5, 0x30, 0xf5, 0xec, 0x9b, 0x7f, 0x3f, 0x0f, 0x35, - 0x0d, 0xc9, 0x46, 0x11, 0x97, 0xac, 0x16, 0x5b, 0x88, 0x82, 0x23, 0xb7, 0x80, 0xdc, 0x01, 0xe1, - 0x27, 0x42, 0x3c, 0x6e, 0x2e, 0x21, 0x25, 0x72, 0x82, 0x2d, 0x58, 0x91, 0x1e, 0x5e, 0x1a, 0x85, - 0x1b, 0x8b, 0xb3, 0x46, 0x38, 0xeb, 0x45, 0x23, 0x39, 0xfd, 0xfb, 0x52, 0x39, 0x8d, 0xe6, 0x4e, - 0xf3, 0x98, 0x2d, 0x8b, 0x30, 0x01, 0x31, 0x89, 0x6c, 0x9d, 0x7f, 0x00, 0x6b, 0x2a, 0x4e, 0x20, - 0x96, 0x03, 0xfd, 0x32, 0x44, 0xc6, 0x03, 0x68, 0x59, 0x7e, 0x08, 0xb7, 0x13, 0x6d, 0x4a, 0x64, - 0xc5, 0xb8, 0xf8, 0x9b, 0xb1, 0xd6, 0xe9, 0x25, 0x6c, 0xfe, 0x19, 0x68, 0xc4, 0x18, 0xe5, 0xb7, - 0x37, 0xe5, 0x49, 0xab, 0x13, 0x8e, 0xa8, 0x6e, 0x75, 0xda, 0xfc, 0x9f, 0x05, 0x20, 0x69, 0x5e, - 0xfd, 0xf3, 0x6c, 0x42, 0x7a, 0x61, 0x16, 0x32, 0x16, 0xe6, 0xff, 0x37, 0xf9, 0x21, 0x32, 0x7e, - 0x6a, 0x6e, 0x7a, 0xdc, 0x9c, 0x4d, 0x85, 0x90, 0xad, 0xf8, 0x24, 0x19, 0xcc, 0x54, 0x89, 0x5d, - 0x35, 0xd3, 0x04, 0xa8, 0x44, 0x4c, 0xd3, 0x31, 0x2c, 0xd8, 0xee, 0xe8, 0xdc, 0xf3, 0x05, 0x1f, - 0xfc, 0x85, 0x6f, 0x7c, 0x7c, 0x6e, 0xb5, 0x79, 0x7e, 0x2e, 0xb5, 0x99, 0xa2, 0x30, 0xe3, 0x03, - 0xa8, 0x69, 0x60, 0x52, 0x85, 0xd2, 0x41, 0xf7, 0x70, 0xbb, 0xdf, 0xbc, 0x41, 0x1a, 0x50, 0x35, - 0x3b, 0x3b, 0xfd, 0x2f, 0x3a, 0x66, 0x67, 0xb7, 0x99, 0x23, 0x15, 0x28, 0x1e, 0xf4, 0x07, 0xc3, - 0x66, 0xde, 0xd8, 0x84, 0x96, 0x28, 0x31, 0xed, 0x06, 0xfa, 0xad, 0xa2, 0x32, 0x5e, 0x72, 0xa4, - 0xd0, 0xe0, 0x3f, 0x84, 0xba, 0x2e, 0xde, 0x88, 0x15, 0x91, 0x88, 0x14, 0x61, 0xba, 0xbb, 0xa7, - 0xf1, 0xea, 0x1d, 0xc0, 0x38, 0x81, 0xb1, 0xca, 0x96, 0x8f, 0xc9, 0xad, 0x19, 0x0e, 0x57, 0xae, - 0xfc, 0xc4, 0x96, 0xe1, 0x9f, 0x82, 0xc5, 0xb8, 0xcb, 0x43, 0x70, 0xa4, 0x2c, 0x7d, 0x94, 0xe5, - 0x8e, 0xf9, 0x40, 0xc8, 0x0f, 0xa1, 0x99, 0x74, 0x99, 0x08, 0xe1, 0xf9, 0x9a, 0xfc, 0x4b, 0x4e, - 0xdc, 0x8b, 0x42, 0xf6, 0x61, 0x35, 0x4b, 0xc0, 0xe3, 0xeb, 0xe3, 0x7a, 0x1b, 0x06, 0x49, 0x0b, - 0x71, 0xe4, 0x53, 0xe1, 0x3a, 0x2b, 0xf1, 0xe9, 0x7f, 0x2b, 0x5e, 0xbf, 0x36, 0xd8, 0x5b, 0xf8, - 0x4f, 0x73, 0xa2, 0x5d, 0x00, 0x44, 0x30, 0xd2, 0x84, 0x7a, 0xff, 0xa8, 0xd3, 0xb3, 0x76, 0xf6, - 0xdb, 0xbd, 0x5e, 0xe7, 0xa0, 0x79, 0x83, 0x10, 0x58, 0xe4, 0xc1, 0x0e, 0xbb, 0x0a, 0x96, 0x63, - 0x30, 0xe1, 0xc2, 0x94, 0xb0, 0x3c, 0x59, 0x85, 0x66, 0xb7, 0x97, 0x80, 0x16, 0x48, 0x0b, 0x56, - 0x8f, 0x3a, 0x18, 0x1f, 0x11, 0x2b, 0xb7, 0xc8, 0x94, 0x06, 0xd1, 0x5d, 0xa6, 0x34, 0x7c, 0x69, - 0x4f, 0x26, 0x34, 0x14, 0xfb, 0x40, 0xca, 0xd2, 0x7f, 0x23, 0x07, 0x6b, 0x09, 0x44, 0xe4, 0x77, - 0x40, 0x49, 0x3a, 0x2e, 0x43, 0xd7, 0x39, 0x50, 0xee, 0xa6, 0x77, 0x61, 0x59, 0x99, 0xc1, 0x12, - 0xa7, 0x52, 0x53, 0x21, 0x24, 0xf1, 0xfb, 0xb0, 0xa2, 0x59, 0xd3, 0x12, 0xbc, 0x82, 0x68, 0x28, - 0x91, 0xc1, 0xd8, 0x50, 0xb7, 0x6f, 0x12, 0xad, 0x1e, 0xc3, 0x7a, 0x12, 0x11, 0x79, 0x16, 0xe3, - 0xed, 0x95, 0x49, 0xf2, 0x20, 0xb1, 0x10, 0xe2, 0xad, 0xd5, 0x27, 0x5c, 0x56, 0xff, 0x3b, 0x0b, - 0x40, 0x7e, 0x3c, 0xa7, 0xfe, 0x15, 0xbf, 0xf5, 0x15, 0xbc, 0x2c, 0xb4, 0x59, 0x1a, 0x62, 0xf2, - 0xaf, 0x74, 0xb3, 0x33, 0xeb, 0x66, 0x65, 0xf1, 0xe5, 0x37, 0x2b, 0x4b, 0x2f, 0xbb, 0x59, 0xf9, - 0x26, 0x34, 0x9c, 0x33, 0xd7, 0x63, 0xac, 0x90, 0x49, 0xc2, 0x41, 0x6b, 0xe1, 0x6e, 0xe1, 0x5e, - 0xdd, 0xac, 0x0b, 0x20, 0x93, 0x83, 0x03, 0xf2, 0x38, 0x22, 0xa2, 0xe3, 0x33, 0x7e, 0xbb, 0x58, - 0x67, 0x82, 0x9d, 0xf1, 0x19, 0x15, 0x76, 0x27, 0xae, 0x69, 0xc8, 0xcc, 0x0c, 0x1e, 0x90, 0xb7, - 0x60, 0x31, 0xf0, 0xe6, 0x4c, 0xb1, 0x90, 0xc3, 0x80, 0xae, 0xc5, 0x3a, 0x42, 0x8f, 0xa4, 0xa3, - 0x79, 0x65, 0x1e, 0x50, 0x6b, 0xea, 0x04, 0x01, 0x13, 0xcf, 0x46, 0x9e, 0x1b, 0xfa, 0xde, 0x44, - 0x78, 0x0b, 0x97, 0xe7, 0x01, 0x3d, 0x44, 0xcc, 0x0e, 0x22, 0xc8, 0x47, 0x51, 0x93, 0x66, 0xb6, - 0xe3, 0x07, 0x2d, 0xe0, 0x4d, 0x92, 0x3d, 0xe5, 0xf2, 0xbb, 0xed, 0xf8, 0xaa, 0x2d, 0x2c, 0x11, - 0x24, 0x6e, 0x7c, 0xd6, 0x92, 0x37, 0x3e, 0x7f, 0x2d, 0xfb, 0xc6, 0x27, 0xc6, 0x37, 0x3d, 0x10, - 0x45, 0xa7, 0xa7, 0xf8, 0x1b, 0x5d, 0xfc, 0x4c, 0x5f, 0x64, 0x5d, 0xfc, 0x26, 0x17, 0x59, 0x97, - 0xb2, 0x2e, 0xb2, 0x7e, 0x00, 0x35, 0x7e, 0xc5, 0xd0, 0x3a, 0xe7, 0x51, 0x8e, 0xe8, 0xfd, 0x6c, - 0xea, 0x77, 0x10, 0xf7, 0x1d, 0x37, 0x34, 0xc1, 0x97, 0x3f, 0x83, 0xf4, 0x9d, 0xd2, 0xe5, 0x9f, - 0xe3, 0x9d, 0x52, 0x71, 0x15, 0x72, 0x0b, 0x2a, 0x72, 0x9e, 0x08, 0x81, 0xe2, 0xa9, 0xef, 0x4d, - 0xa5, 0xc7, 0x85, 0xfd, 0x26, 0x8b, 0x90, 0x0f, 0x3d, 0x91, 0x39, 0x1f, 0x7a, 0xc6, 0xaf, 0x40, - 0x4d, 0x5b, 0x6a, 0xe4, 0x0d, 0x34, 0x5b, 0x32, 0xdd, 0x4c, 0xc8, 0x96, 0x38, 0x8a, 0x55, 0x01, - 0xed, 0x8e, 0x19, 0xbf, 0x19, 0x3b, 0x3e, 0xe5, 0xb7, 0xbf, 0x2d, 0x9f, 0x5e, 0x50, 0x3f, 0x90, - 0x1e, 0xb0, 0xa6, 0x42, 0x98, 0x08, 0x37, 0x7e, 0x15, 0x56, 0x62, 0x73, 0x2b, 0x58, 0xc4, 0x5b, - 0xb0, 0xc0, 0xc7, 0x4d, 0x86, 0x59, 0xc4, 0xef, 0x76, 0x0a, 0x1c, 0xbf, 0xe9, 0x8e, 0xce, 0x3b, - 0x6b, 0xe6, 0x7b, 0x27, 0xbc, 0x92, 0x9c, 0x59, 0x13, 0xb0, 0x23, 0xdf, 0x3b, 0x31, 0xfe, 0xb0, - 0x00, 0x85, 0x7d, 0x6f, 0xa6, 0x47, 0x46, 0xe6, 0x52, 0x91, 0x91, 0x42, 0xe1, 0xb4, 0x94, 0x42, - 0x29, 0x64, 0x76, 0xee, 0xb6, 0x92, 0x4a, 0xe5, 0x3d, 0x58, 0x64, 0x7c, 0x22, 0xf4, 0x98, 0xc6, - 0xfe, 0xdc, 0xf6, 0x51, 0x20, 0xc6, 0x40, 0xe3, 0xba, 0x3d, 0x0d, 0x87, 0xde, 0x1e, 0xc2, 0xc9, - 0x2a, 0x14, 0x94, 0xfa, 0xc2, 0xd1, 0x2c, 0x49, 0xd6, 0x61, 0x81, 0xdf, 0x63, 0xb8, 0x12, 0x61, - 0x02, 0x22, 0x45, 0xbe, 0x07, 0x2b, 0xf1, 0x72, 0x91, 0x15, 0x09, 0xd9, 0x48, 0x2f, 0x98, 0xf3, - 0xa4, 0x9b, 0xc0, 0xf8, 0x08, 0xd2, 0x88, 0x70, 0xa4, 0x53, 0x4a, 0x39, 0x4a, 0x63, 0x7a, 0x95, - 0x18, 0xd3, 0xbb, 0x03, 0xb5, 0x70, 0x72, 0x61, 0xcd, 0xec, 0xab, 0x89, 0x67, 0x8f, 0xc5, 0xfe, - 0x86, 0x70, 0x72, 0x71, 0x84, 0x10, 0xf2, 0x3e, 0xc0, 0x74, 0x36, 0x13, 0x7b, 0x8f, 0xbb, 0x62, - 0xa2, 0xa5, 0x7c, 0x78, 0x74, 0x84, 0x4b, 0xce, 0xac, 0x4e, 0x67, 0x33, 0xfc, 0x49, 0x76, 0x61, - 0x31, 0xf3, 0x86, 0xf6, 0x6d, 0x19, 0x6f, 0xee, 0xcd, 0xb6, 0x32, 0x36, 0x67, 0x63, 0xa4, 0xc3, - 0x36, 0x7f, 0x08, 0xe4, 0x67, 0xbc, 0x27, 0x3d, 0x84, 0xaa, 0x6a, 0x9f, 0x7e, 0xcd, 0x98, 0x5f, - 0xa4, 0xa9, 0xc5, 0xae, 0x19, 0xb7, 0xc7, 0x63, 0x9f, 0xf1, 0x45, 0x3c, 0x30, 0x15, 0xcb, 0x07, - 0xed, 0xc4, 0x14, 0x37, 0x35, 0x8c, 0xff, 0x92, 0x83, 0x12, 0xde, 0x79, 0x7e, 0x1b, 0x96, 0x90, - 0x5e, 0x45, 0x99, 0x8a, 0xe0, 0x02, 0x3c, 0x77, 0x87, 0x22, 0xc0, 0x94, 0x6d, 0x0b, 0xed, 0x1d, - 0x88, 0xbc, 0x9a, 0x79, 0xed, 0x2d, 0x88, 0x3b, 0x50, 0x55, 0x55, 0x6b, 0x4b, 0xa7, 0x22, 0x6b, - 0x26, 0xaf, 0x43, 0xf1, 0xdc, 0x9b, 0x49, 0xcb, 0x0f, 0x44, 0x23, 0x69, 0x72, 0x78, 0xd4, 0x16, - 0x56, 0x47, 0x74, 0x83, 0xa4, 0x20, 0xda, 0xc2, 0x2a, 0xe1, 0xcb, 0x20, 0xdd, 0xc7, 0x85, 0x8c, - 0x3e, 0x1e, 0xc3, 0x12, 0xe3, 0x03, 0x5a, 0x84, 0xc3, 0xf5, 0x87, 0xe6, 0x77, 0x99, 0x84, 0x37, - 0x9a, 0xcc, 0xc7, 0x54, 0xb7, 0xbd, 0xf1, 0x90, 0x41, 0x01, 0x97, 0x92, 0xb5, 0xf1, 0x3b, 0x39, - 0xe4, 0x2f, 0xac, 0x5c, 0x72, 0x0f, 0x8a, 0xae, 0x8c, 0x86, 0x88, 0xe4, 0x38, 0x75, 0xa3, 0x89, - 0xd1, 0x99, 0x9c, 0x82, 0x4d, 0x1d, 0x8f, 0x21, 0xd0, 0x4b, 0x6f, 0x98, 0x35, 0x77, 0x3e, 0x55, - 0xa6, 0xab, 0xef, 0xc8, 0x6e, 0x25, 0xcc, 0x3e, 0xd8, 0x7b, 0xb5, 0x4d, 0xb7, 0xb4, 0xd8, 0xc3, - 0x62, 0xec, 0xc4, 0x94, 0x52, 0xe0, 0xf8, 0x8c, 0x6a, 0x31, 0x87, 0xbf, 0x97, 0x87, 0x46, 0xac, - 0x45, 0x3c, 0xf8, 0x92, 0x1d, 0x00, 0xe8, 0x77, 0x12, 0xf3, 0x0d, 0x0c, 0x24, 0x04, 0x75, 0x6d, - 0x9c, 0xf2, 0xb1, 0x71, 0x52, 0xe1, 0x4c, 0x05, 0x3d, 0x9c, 0xe9, 0x01, 0x54, 0xa3, 0xf7, 0x3f, - 0xe2, 0x4d, 0x62, 0xf5, 0xc9, 0x7b, 0x5d, 0x11, 0x51, 0x14, 0x00, 0x55, 0xd2, 0x03, 0xa0, 0x3e, - 0xd7, 0xe2, 0x65, 0x16, 0x78, 0x31, 0x46, 0xd6, 0x88, 0xfe, 0x5c, 0xa2, 0x65, 0x8c, 0xc7, 0x50, - 0xd3, 0x1a, 0xaf, 0xc7, 0x9c, 0xe4, 0x62, 0x31, 0x27, 0xea, 0x5e, 0x66, 0x3e, 0xba, 0x97, 0x69, - 0xfc, 0xf9, 0x3c, 0x34, 0xd8, 0xfe, 0x72, 0xdc, 0xb3, 0x23, 0x6f, 0xe2, 0x8c, 0xb8, 0x1f, 0x4a, - 0xed, 0x30, 0x21, 0x68, 0xc9, 0x7d, 0x26, 0xb6, 0x18, 0xca, 0x59, 0xfa, 0xa5, 0x74, 0x64, 0xd2, - 0xea, 0x52, 0xba, 0x01, 0x0d, 0xc6, 0x18, 0xb9, 0x47, 0x29, 0x7a, 0x45, 0xc4, 0xac, 0x9d, 0x52, - 0xba, 0x6d, 0x07, 0xc8, 0x21, 0xbf, 0x07, 0x2b, 0x8c, 0x86, 0xdf, 0xc7, 0x9d, 0x3a, 0x93, 0x89, - 0x13, 0x5d, 0xd9, 0x2a, 0x98, 0xcd, 0x53, 0x4a, 0x4d, 0x3b, 0xa4, 0x87, 0x0c, 0x21, 0x1e, 0x1d, - 0xa9, 0x8c, 0x9d, 0xc0, 0x3e, 0x89, 0x42, 0x64, 0x55, 0x9a, 0xfb, 0xb9, 0x85, 0x9f, 0x36, 0xda, - 0x64, 0x45, 0xb3, 0x36, 0x45, 0x2f, 0x2d, 0xcf, 0x9f, 0x58, 0x49, 0xe5, 0xe4, 0x4a, 0x32, 0xfe, - 0x79, 0x1e, 0x6a, 0xda, 0xb2, 0x7c, 0x95, 0xd3, 0xf5, 0x76, 0xca, 0x6f, 0x58, 0xd5, 0x5d, 0x84, - 0x6f, 0xc6, 0xab, 0x2c, 0xa8, 0x7b, 0x3d, 0xfa, 0x02, 0xbe, 0x05, 0x55, 0xb6, 0xeb, 0x3e, 0xe0, - 0x26, 0x58, 0xf1, 0xe8, 0x0f, 0x07, 0x1c, 0xcd, 0x4f, 0x24, 0xf2, 0x21, 0x47, 0x96, 0x22, 0xe4, - 0x43, 0x86, 0x7c, 0x51, 0x5c, 0xff, 0x27, 0x50, 0x17, 0xa5, 0xf2, 0x39, 0xe5, 0xdd, 0x8d, 0x76, - 0x7d, 0x6c, 0xbe, 0xcd, 0x1a, 0x56, 0x87, 0x93, 0x2f, 0x32, 0x3e, 0x94, 0x19, 0x2b, 0x2f, 0xcb, - 0xf8, 0x10, 0x13, 0xc6, 0x9e, 0xba, 0x2a, 0xc1, 0x23, 0xd5, 0x24, 0x1f, 0x7b, 0x1f, 0x56, 0x24, - 0xbb, 0x9a, 0xbb, 0xb6, 0xeb, 0x7a, 0x73, 0x77, 0x44, 0xe5, 0xd5, 0x4c, 0x22, 0x50, 0xc7, 0x11, - 0xc6, 0x18, 0xab, 0x7b, 0xfe, 0x18, 0xf1, 0x76, 0x1f, 0x4a, 0x28, 0x97, 0xa3, 0xf0, 0x91, 0xcd, - 0xb8, 0x90, 0x84, 0xdc, 0x83, 0x12, 0x8a, 0xe7, 0xf9, 0x6b, 0x99, 0x0d, 0x12, 0x18, 0x6d, 0x20, - 0x2c, 0xe3, 0x21, 0x0d, 0x7d, 0x67, 0x14, 0x44, 0xb7, 0x3e, 0x4b, 0x4c, 0xff, 0xc4, 0xba, 0x22, - 0xcb, 0x6d, 0x44, 0xc9, 0x75, 0x54, 0xa4, 0x61, 0x07, 0xd3, 0x4a, 0xac, 0x0c, 0x21, 0x2e, 0x4d, - 0x60, 0xfd, 0x84, 0x86, 0xcf, 0x29, 0x75, 0x5d, 0x26, 0x0c, 0x8d, 0xa8, 0x1b, 0xfa, 0xf6, 0x84, - 0x4d, 0x12, 0xf6, 0xe0, 0x51, 0xaa, 0xd4, 0xc8, 0x06, 0xb2, 0x1d, 0x65, 0xdc, 0x51, 0xf9, 0x90, - 0x77, 0xac, 0x9d, 0x64, 0xe1, 0x36, 0x7f, 0x19, 0x36, 0xaf, 0xcf, 0x94, 0x71, 0xe3, 0xfb, 0x5e, - 0x9c, 0xab, 0x28, 0x3f, 0xe0, 0xc4, 0xb3, 0x43, 0x6c, 0x8d, 0xce, 0x59, 0x7a, 0x50, 0xd3, 0x30, - 0xd1, 0xd9, 0x9f, 0xe3, 0xc2, 0x1d, 0x26, 0xd8, 0x89, 0xe4, 0x7a, 0xfe, 0x94, 0xfb, 0xdd, 0xc6, - 0x56, 0x54, 0x7a, 0xce, 0x5c, 0x8a, 0xe0, 0x3c, 0xc6, 0xc2, 0xd8, 0x82, 0x25, 0x2e, 0xd9, 0x6b, - 0x07, 0xdd, 0x8b, 0x84, 0x41, 0x63, 0x15, 0x48, 0x0f, 0x79, 0x97, 0x1e, 0xfd, 0xf7, 0xef, 0x0b, - 0x50, 0xd3, 0xc0, 0xec, 0x34, 0xe2, 0x21, 0x93, 0xd6, 0xd8, 0xb1, 0xa7, 0x54, 0x3a, 0x39, 0x1b, - 0x66, 0x83, 0x43, 0x77, 0x05, 0x90, 0x9d, 0xc5, 0xf6, 0xc5, 0x99, 0xe5, 0xcd, 0x43, 0x6b, 0x4c, - 0xcf, 0x7c, 0x2a, 0x5b, 0x59, 0xb7, 0x2f, 0xce, 0xfa, 0xf3, 0x70, 0x97, 0xc3, 0x18, 0x15, 0xe3, - 0x25, 0x1a, 0x95, 0x88, 0xa0, 0x9b, 0xda, 0x97, 0x11, 0x95, 0x08, 0x35, 0xc5, 0x95, 0x59, 0x54, - 0xa1, 0xa6, 0xa8, 0x2d, 0x26, 0x0f, 0xd0, 0x52, 0xfa, 0x00, 0xfd, 0x08, 0xd6, 0xf1, 0x00, 0x15, - 0xac, 0xd9, 0x4a, 0xec, 0xe4, 0x55, 0x8e, 0x15, 0x9d, 0xd4, 0xc4, 0xde, 0x26, 0xeb, 0x81, 0x64, - 0x4b, 0x81, 0xf3, 0x13, 0x64, 0x64, 0x39, 0x93, 0xf5, 0x4c, 0x14, 0x3e, 0x70, 0x7e, 0x42, 0x19, - 0x25, 0x8f, 0xd5, 0xd1, 0x29, 0xc5, 0xad, 0x9d, 0xa9, 0xe3, 0x26, 0x29, 0xed, 0xcb, 0x38, 0x65, - 0x55, 0x50, 0xda, 0x97, 0x3a, 0xe5, 0x23, 0xd8, 0x98, 0xd2, 0xb1, 0x63, 0xc7, 0x8b, 0xb5, 0x22, - 0xc1, 0x6d, 0x15, 0xd1, 0x5a, 0x9e, 0x01, 0x2a, 0xee, 0x6c, 0x34, 0x7e, 0xe2, 0x4d, 0x4f, 0x1c, - 0x94, 0x59, 0x30, 0x7a, 0xa8, 0x68, 0x2e, 0xba, 0xf3, 0xe9, 0x2f, 0x71, 0x30, 0xcb, 0x12, 0x18, - 0x0d, 0xa8, 0x0d, 0x42, 0x6f, 0x26, 0xa7, 0x79, 0x11, 0xea, 0x98, 0x14, 0xf7, 0x9d, 0x6f, 0xc1, - 0x4d, 0xce, 0x12, 0x86, 0xde, 0xcc, 0x9b, 0x78, 0x67, 0x57, 0x31, 0x3b, 0xde, 0xbf, 0xce, 0xc1, - 0x4a, 0x0c, 0x2b, 0xd8, 0xeb, 0x47, 0xc8, 0xcf, 0xd4, 0x6d, 0xcd, 0x5c, 0xec, 0xaa, 0x0e, 0x9b, - 0x2f, 0x24, 0x44, 0x66, 0x26, 0x6f, 0x70, 0xb6, 0xa3, 0x87, 0x59, 0x64, 0x46, 0x64, 0x29, 0xad, - 0x34, 0x4b, 0x11, 0xf9, 0xe5, 0x93, 0x2d, 0xb2, 0x88, 0x5f, 0x10, 0x37, 0xab, 0xc6, 0xa2, 0xcb, - 0x85, 0xf8, 0xdd, 0x0b, 0xdd, 0xe6, 0x27, 0x5b, 0x10, 0x19, 0x02, 0x03, 0xe3, 0xef, 0xe5, 0x00, - 0xa2, 0xd6, 0xf1, 0xdb, 0x1f, 0x4a, 0x6e, 0xc9, 0xf1, 0xc0, 0x5d, 0x4d, 0x46, 0x79, 0x03, 0xea, - 0x2a, 0xc6, 0x3b, 0x92, 0x84, 0x6a, 0x12, 0xc6, 0xc4, 0xa1, 0x77, 0x60, 0xe9, 0x6c, 0xe2, 0x9d, - 0x70, 0x89, 0x55, 0xc8, 0x2d, 0x18, 0x3d, 0xb7, 0x88, 0x60, 0x29, 0x8d, 0x44, 0x72, 0x53, 0x31, - 0x33, 0x0c, 0x5c, 0x97, 0x82, 0x8c, 0xbf, 0x92, 0x57, 0x81, 0xa4, 0xd1, 0x48, 0xbc, 0x58, 0xbd, - 0xfb, 0x69, 0x42, 0x6d, 0x5e, 0xe4, 0x5e, 0x7c, 0x0c, 0x8b, 0x3e, 0x1e, 0x4a, 0xf2, 0xc4, 0x2a, - 0xbe, 0xe0, 0xc4, 0x6a, 0xf8, 0x31, 0x49, 0xe7, 0xbb, 0xd0, 0xb4, 0xc7, 0x17, 0xd4, 0x0f, 0x1d, - 0x6e, 0xad, 0xe7, 0xf2, 0xb1, 0x08, 0xdd, 0xd4, 0xe0, 0x5c, 0x10, 0x7d, 0x07, 0x96, 0xc4, 0x1d, - 0x7c, 0x45, 0x29, 0x5e, 0x00, 0x8b, 0xc0, 0x8c, 0xd0, 0xf8, 0x47, 0x32, 0x72, 0x35, 0x3e, 0xbb, - 0x2f, 0x1e, 0x15, 0xbd, 0x87, 0xf9, 0xb4, 0x03, 0x55, 0x2c, 0x24, 0xe1, 0x04, 0x10, 0xfc, 0x08, - 0x81, 0xc2, 0x05, 0x10, 0x1f, 0xd6, 0xe2, 0xab, 0x0c, 0xab, 0xf1, 0x6f, 0x72, 0x50, 0xde, 0xf7, - 0x66, 0xfb, 0x0e, 0xde, 0x7f, 0xe0, 0xdb, 0x44, 0xf9, 0xa8, 0x16, 0x58, 0x92, 0xc7, 0x05, 0xbd, - 0xe0, 0x16, 0x63, 0xa6, 0x98, 0xd7, 0x88, 0x8b, 0x79, 0x9f, 0xc3, 0x2d, 0xee, 0x02, 0xf4, 0xbd, - 0x99, 0xe7, 0xb3, 0xad, 0x6a, 0x4f, 0x50, 0xdc, 0xf3, 0xdc, 0xf0, 0x5c, 0xf2, 0xce, 0x9b, 0xa7, - 0x94, 0x1e, 0x69, 0x14, 0x87, 0x8a, 0x80, 0xdf, 0x60, 0x9e, 0x84, 0x17, 0x16, 0x6a, 0xe8, 0x42, - 0x1e, 0x45, 0x8e, 0xba, 0xc4, 0x10, 0x1d, 0x0e, 0xe7, 0x12, 0xa9, 0xf1, 0x29, 0x54, 0x95, 0xb1, - 0x87, 0xbc, 0x0b, 0xd5, 0x73, 0x6f, 0x26, 0x2c, 0x42, 0xb9, 0xd8, 0x4d, 0x4f, 0xd1, 0x6b, 0xb3, - 0x72, 0x8e, 0x3f, 0x02, 0xe3, 0x0f, 0xcb, 0x50, 0xee, 0xba, 0x17, 0x9e, 0x33, 0xe2, 0xb1, 0xaf, - 0x53, 0x3a, 0xf5, 0xe4, 0x43, 0x20, 0xec, 0x37, 0x0f, 0xdd, 0x8a, 0xde, 0xf1, 0x2a, 0x88, 0xd0, - 0x2d, 0xf5, 0x82, 0xd7, 0x1a, 0x2c, 0xf8, 0xfa, 0x43, 0x5c, 0x25, 0x9f, 0xdf, 0x18, 0x50, 0xe7, - 0x65, 0x49, 0x7b, 0x5e, 0x85, 0x95, 0x85, 0x61, 0x89, 0x7c, 0xc8, 0xf0, 0x16, 0x72, 0x95, 0x43, - 0xf8, 0x80, 0xbd, 0x06, 0x65, 0x71, 0xb1, 0x12, 0xaf, 0x79, 0x61, 0x88, 0xbf, 0x00, 0xf1, 0xd5, - 0xe0, 0x53, 0x74, 0xe1, 0x2a, 0x41, 0xb6, 0x60, 0xd6, 0x25, 0x70, 0x97, 0xad, 0xb5, 0x3b, 0x50, - 0x43, 0x7a, 0x24, 0xa9, 0x88, 0x90, 0x51, 0x0e, 0xe2, 0x04, 0x19, 0xef, 0xd9, 0x55, 0x33, 0xdf, - 0xb3, 0xe3, 0xc1, 0xcd, 0x8a, 0xcb, 0x62, 0x17, 0x01, 0x5f, 0x31, 0xd3, 0xe0, 0xf2, 0x91, 0x48, - 0x61, 0x53, 0xc1, 0x0b, 0xfa, 0xd2, 0xa6, 0xf2, 0x26, 0x34, 0x4e, 0xed, 0xc9, 0xe4, 0xc4, 0x1e, - 0x3d, 0x43, 0x53, 0x40, 0x1d, 0xad, 0x9f, 0x12, 0xc8, 0x6d, 0x01, 0x77, 0xa0, 0xa6, 0xcd, 0x32, - 0x8f, 0x07, 0x2d, 0x9a, 0x10, 0xcd, 0x6f, 0xd2, 0xc2, 0xb7, 0xf8, 0x0a, 0x16, 0x3e, 0x2d, 0x2e, - 0x76, 0x29, 0x1e, 0x17, 0x7b, 0x8b, 0x73, 0x53, 0x11, 0x91, 0xd8, 0xc4, 0x27, 0xb3, 0xec, 0xf1, - 0x98, 0x47, 0x24, 0x72, 0x43, 0x16, 0x0e, 0x1e, 0xe2, 0x97, 0x51, 0x97, 0x40, 0x18, 0x92, 0xdc, - 0x46, 0x33, 0xf5, 0xcc, 0x76, 0xc6, 0xfc, 0x9a, 0x06, 0x5a, 0x0f, 0xca, 0xf6, 0x34, 0x3c, 0xb2, - 0x9d, 0x31, 0xb9, 0x0b, 0x75, 0x89, 0xe6, 0xa7, 0xe3, 0x0a, 0x8e, 0xbf, 0x40, 0x0f, 0xf0, 0xf9, - 0x09, 0x45, 0x31, 0x55, 0x37, 0xec, 0xcd, 0x9a, 0x20, 0xe1, 0xeb, 0xe0, 0x03, 0x1e, 0xe5, 0x13, - 0x52, 0x7e, 0x87, 0x7e, 0xf1, 0xe1, 0x2d, 0x15, 0x7c, 0xc0, 0x57, 0xa9, 0xfc, 0x8f, 0xce, 0x31, - 0xa4, 0x64, 0xc2, 0x1d, 0xfa, 0xe8, 0xd6, 0x63, 0xf2, 0xaf, 0x20, 0xe5, 0x3e, 0x3a, 0x24, 0x20, - 0x9f, 0x6a, 0xfa, 0x6b, 0x8b, 0x13, 0xbf, 0x96, 0x28, 0xff, 0xba, 0x6b, 0x6c, 0xb7, 0x01, 0x9c, - 0x80, 0x9d, 0x32, 0x01, 0x75, 0xc7, 0xfc, 0x2a, 0x7c, 0xc5, 0xac, 0x3a, 0xc1, 0x53, 0x04, 0x7c, - 0xbb, 0x8a, 0x6d, 0x1b, 0xea, 0x7a, 0x37, 0x49, 0x05, 0x8a, 0xfd, 0xa3, 0x4e, 0xaf, 0x79, 0x83, - 0xd4, 0xa0, 0x3c, 0xe8, 0x0c, 0x87, 0x07, 0xdc, 0xd3, 0x57, 0x87, 0x8a, 0xba, 0xe8, 0x9a, 0x67, - 0xa9, 0xf6, 0xce, 0x4e, 0xe7, 0x68, 0xd8, 0xd9, 0x6d, 0x16, 0x7e, 0x54, 0xac, 0xe4, 0x9b, 0x05, - 0xe3, 0x8f, 0x0a, 0x50, 0xd3, 0x46, 0xe1, 0xc5, 0xcc, 0xf8, 0x36, 0x00, 0xd7, 0x24, 0xa3, 0x80, - 0xd5, 0xa2, 0x59, 0x65, 0x10, 0x9c, 0x7c, 0xdd, 0x47, 0x81, 0x6f, 0x91, 0x28, 0x1f, 0xc5, 0x9b, - 0xd0, 0xc0, 0xd7, 0x42, 0x74, 0x7f, 0x6d, 0xc9, 0xac, 0x23, 0x50, 0xb0, 0x6a, 0x7e, 0x6d, 0x9e, - 0x13, 0xf1, 0x0b, 0x89, 0xe2, 0x39, 0x23, 0x04, 0xf1, 0x2b, 0x89, 0xfc, 0x3e, 0x69, 0xe0, 0x4d, - 0x2e, 0x28, 0x52, 0xa0, 0x44, 0x58, 0x13, 0xb0, 0xa1, 0x78, 0x92, 0x40, 0xf0, 0x43, 0xed, 0xde, - 0x76, 0xc9, 0xac, 0x23, 0x50, 0x54, 0xf4, 0x3d, 0xb9, 0x80, 0x30, 0x7a, 0x65, 0x23, 0xbd, 0x1a, - 0x62, 0x8b, 0xe7, 0x20, 0x65, 0x46, 0xac, 0xf2, 0x85, 0xf1, 0x9d, 0x74, 0xbe, 0x97, 0x9b, 0x13, - 0xc9, 0xbb, 0x40, 0xa6, 0xb3, 0x99, 0x95, 0x61, 0xe0, 0x2b, 0x9a, 0x4b, 0xd3, 0xd9, 0x6c, 0xa8, - 0xd9, 0xbf, 0xbe, 0x05, 0xdb, 0xe3, 0xd7, 0x40, 0xda, 0x6c, 0x03, 0xf3, 0x26, 0x2a, 0x55, 0x2c, - 0x62, 0xcb, 0x39, 0x9d, 0x2d, 0x67, 0x70, 0xbf, 0x7c, 0x26, 0xf7, 0x7b, 0x11, 0x9f, 0x30, 0xf6, - 0xa0, 0x76, 0xa4, 0x3d, 0x9a, 0x78, 0x97, 0x9d, 0x10, 0xf2, 0xb9, 0x44, 0x3c, 0x3b, 0xd0, 0xa6, - 0xe8, 0x8b, 0x57, 0x12, 0xb5, 0xd6, 0xe4, 0xb5, 0xd6, 0x18, 0x7f, 0x27, 0x87, 0x8f, 0x4c, 0xa9, - 0xc6, 0x47, 0xef, 0x34, 0x4a, 0xf7, 0x5b, 0xf4, 0xbc, 0x42, 0x4d, 0xba, 0xdd, 0xc4, 0xcb, 0x08, - 0xbc, 0x69, 0x96, 0x77, 0x7a, 0x1a, 0x50, 0x19, 0xe3, 0x51, 0xe3, 0xb0, 0x3e, 0x07, 0x49, 0xe1, - 0x9b, 0x49, 0xf8, 0x0e, 0x96, 0x1f, 0x88, 0xc0, 0x0e, 0x26, 0x7c, 0x1f, 0xda, 0x97, 0xa2, 0xd6, - 0x80, 0x89, 0x20, 0xc2, 0x3f, 0x20, 0xaf, 0x17, 0xab, 0xb4, 0xf1, 0x37, 0xc5, 0x0b, 0x10, 0xc9, - 0xf1, 0xbd, 0x0f, 0x15, 0x55, 0x6a, 0xfc, 0x84, 0x95, 0x94, 0x0a, 0xcf, 0xce, 0x71, 0x6e, 0x0c, - 0x89, 0xb5, 0x18, 0x37, 0x17, 0xf7, 0xf1, 0x74, 0xb5, 0x56, 0xbf, 0x07, 0xe4, 0xd4, 0xf1, 0x93, - 0xc4, 0xb8, 0xd9, 0x9a, 0x1c, 0xa3, 0x51, 0x1b, 0xc7, 0xb0, 0x22, 0xb9, 0x84, 0xa6, 0x11, 0xc4, - 0x27, 0x2f, 0xf7, 0x12, 0x26, 0x9f, 0x4f, 0x31, 0x79, 0xe3, 0x37, 0x4a, 0x50, 0x96, 0x0f, 0x90, - 0x66, 0x3d, 0x9a, 0x59, 0x8d, 0x3f, 0x9a, 0xd9, 0x8a, 0x3d, 0xa5, 0xc6, 0xa7, 0x5e, 0x9c, 0xf7, - 0xef, 0x24, 0x8f, 0x6c, 0xcd, 0x57, 0x11, 0x3b, 0xb6, 0x85, 0xaf, 0xa2, 0x14, 0xf7, 0x55, 0x64, - 0x3d, 0x24, 0x8a, 0xa2, 0x67, 0xea, 0x21, 0xd1, 0x5b, 0x80, 0x72, 0x84, 0x16, 0xdc, 0x56, 0xe1, - 0x00, 0x71, 0x45, 0x5e, 0x13, 0x3b, 0x2a, 0x49, 0xb1, 0xe3, 0x95, 0x45, 0x82, 0x8f, 0x60, 0x01, - 0x5f, 0x93, 0x11, 0xd7, 0xa5, 0xe5, 0xc1, 0x21, 0xc6, 0x4a, 0xfe, 0xc7, 0x0b, 0x11, 0xa6, 0xa0, - 0xd5, 0x5f, 0xe5, 0xab, 0xc5, 0x5e, 0xe5, 0xd3, 0x7d, 0x28, 0xf5, 0xb8, 0x0f, 0xe5, 0x1e, 0x34, - 0xd5, 0xc0, 0x71, 0x8b, 0xa4, 0x1b, 0x88, 0xbb, 0x96, 0x8b, 0x12, 0xce, 0xb8, 0x61, 0x2f, 0x88, - 0x0e, 0xbe, 0xc5, 0xd8, 0xc1, 0xc7, 0x78, 0x55, 0x3b, 0x0c, 0xe9, 0x74, 0x16, 0xca, 0x83, 0x4f, - 0x7b, 0xbb, 0x15, 0x67, 0x1e, 0x2f, 0x83, 0xc8, 0xe9, 0xc5, 0xd5, 0xb1, 0x0d, 0x8b, 0xa7, 0xb6, - 0x33, 0x99, 0xfb, 0xd4, 0xf2, 0xa9, 0x1d, 0x78, 0x2e, 0xdf, 0xfc, 0xd1, 0x19, 0x2c, 0xba, 0xb8, - 0x87, 0x34, 0x26, 0x27, 0x31, 0x1b, 0xa7, 0x7a, 0x92, 0x5f, 0xa9, 0xd2, 0x47, 0x82, 0x1d, 0x59, - 0xe2, 0xd6, 0x35, 0xc6, 0xaa, 0x74, 0x7b, 0xd6, 0xde, 0x41, 0xf7, 0xc9, 0xfe, 0xb0, 0x99, 0x63, - 0xc9, 0xc1, 0xf1, 0xce, 0x4e, 0xa7, 0xb3, 0xcb, 0x8f, 0x30, 0x80, 0x85, 0xbd, 0x76, 0xf7, 0x40, - 0x1c, 0x60, 0xc5, 0x66, 0xc9, 0xf8, 0x67, 0x79, 0xa8, 0x69, 0xbd, 0x21, 0x8f, 0xd4, 0x24, 0xe0, - 0x33, 0x0d, 0xb7, 0xd3, 0x3d, 0xde, 0x92, 0x1c, 0x5e, 0x9b, 0x05, 0xf5, 0x4a, 0x6b, 0xfe, 0xda, - 0x57, 0x5a, 0xc9, 0xdb, 0xb0, 0x64, 0x63, 0x09, 0x6a, 0xd0, 0x85, 0x71, 0x5f, 0x80, 0xc5, 0x98, - 0xbf, 0x2d, 0x9e, 0x8c, 0x10, 0xc7, 0x14, 0xa3, 0x2b, 0xca, 0xa0, 0x4d, 0x75, 0x52, 0xf1, 0xb9, - 0x29, 0x8b, 0x91, 0x11, 0xce, 0x78, 0x75, 0xe0, 0x8b, 0xf1, 0x92, 0x68, 0xbc, 0x67, 0xa9, 0xad, - 0xf0, 0xba, 0xa9, 0xd2, 0xc6, 0xc7, 0x00, 0x51, 0x7f, 0xe2, 0xc3, 0x77, 0x23, 0x3e, 0x7c, 0x39, - 0x6d, 0xf8, 0xf2, 0xc6, 0x3f, 0x14, 0xac, 0x4b, 0xcc, 0x85, 0x32, 0xf5, 0x7d, 0x0f, 0xa4, 0xf1, - 0xd1, 0xe2, 0x41, 0xde, 0xb3, 0x09, 0x0d, 0xe5, 0x55, 0xd1, 0x65, 0x81, 0xe9, 0x2a, 0x44, 0x8a, - 0xd5, 0xe6, 0xd3, 0xac, 0xf6, 0x0d, 0xa8, 0xf3, 0x37, 0xc8, 0x44, 0x45, 0x82, 0x5d, 0xd5, 0xa6, - 0xf6, 0xa5, 0xac, 0x3b, 0xc6, 0x63, 0x8b, 0x09, 0x1e, 0xfb, 0xb7, 0x72, 0xf8, 0x60, 0x4d, 0xd4, - 0xd0, 0x88, 0xc9, 0xaa, 0x32, 0xe3, 0x4c, 0x56, 0x90, 0x9a, 0x0a, 0x7f, 0x0d, 0xe3, 0xcc, 0x67, - 0x33, 0xce, 0x6c, 0x96, 0x5c, 0xc8, 0x64, 0xc9, 0xc6, 0x26, 0xb4, 0x76, 0x29, 0x1b, 0x8a, 0xf6, - 0x64, 0x92, 0x18, 0x4b, 0xe3, 0x16, 0xdc, 0xcc, 0xc0, 0x09, 0xab, 0xcd, 0x6f, 0xe6, 0x60, 0xad, - 0x8d, 0xef, 0x54, 0x7c, 0x6b, 0x77, 0x39, 0x3f, 0x83, 0x9b, 0x2a, 0x62, 0x5b, 0xbb, 0x22, 0xa6, - 0x3f, 0x32, 0x24, 0x83, 0xbd, 0xb5, 0x7b, 0x0a, 0xec, 0xcc, 0x34, 0x5a, 0xb0, 0x9e, 0x6c, 0x8d, - 0x68, 0xe8, 0x1e, 0x2c, 0xef, 0xd2, 0x93, 0xf9, 0xd9, 0x01, 0xbd, 0x88, 0xda, 0x48, 0xa0, 0x18, - 0x9c, 0x7b, 0xcf, 0xc5, 0xc2, 0xe0, 0xbf, 0x79, 0x48, 0x27, 0xa3, 0xb1, 0x82, 0x19, 0x1d, 0x49, - 0xab, 0x3f, 0x87, 0x0c, 0x66, 0x74, 0x64, 0x3c, 0x02, 0xa2, 0x97, 0x23, 0x66, 0x91, 0xa9, 0x64, - 0xf3, 0x13, 0x2b, 0xb8, 0x0a, 0x42, 0x3a, 0x95, 0xd7, 0x1f, 0x21, 0x98, 0x9f, 0x0c, 0x10, 0x62, - 0xbc, 0x03, 0xf5, 0x23, 0xfb, 0xca, 0xa4, 0x5f, 0x8b, 0x5b, 0x86, 0x1b, 0x50, 0x9e, 0xd9, 0x57, - 0x8c, 0x17, 0x2b, 0x07, 0x20, 0x47, 0x1b, 0xff, 0xb8, 0x08, 0x0b, 0x48, 0x49, 0xee, 0xe2, 0xfb, - 0xe9, 0x8e, 0xcb, 0x79, 0xa1, 0x3c, 0x95, 0x34, 0x50, 0xea, 0xe0, 0xca, 0xa7, 0x0f, 0x2e, 0x61, - 0xad, 0x94, 0x8f, 0xa0, 0x49, 0x57, 0x8d, 0x3b, 0x9f, 0xca, 0x97, 0xcf, 0xe2, 0xef, 0x3c, 0x14, - 0xa3, 0x77, 0xf7, 0xf1, 0x8e, 0x7b, 0xdc, 0x99, 0x1e, 0x29, 0x7e, 0xd8, 0x3a, 0x79, 0x1e, 0x8b, - 0x33, 0x4b, 0x07, 0x65, 0x6a, 0x97, 0x65, 0x79, 0x75, 0x36, 0xae, 0x5d, 0xa6, 0xb4, 0xc8, 0xca, - 0xcb, 0xb5, 0x48, 0x34, 0x63, 0xbe, 0x40, 0x8b, 0x84, 0x57, 0xd0, 0x22, 0x5f, 0xc1, 0x91, 0x7d, - 0x13, 0x2a, 0x5c, 0xc8, 0xd2, 0x8e, 0x30, 0x26, 0x5c, 0xb1, 0x23, 0xec, 0x13, 0x4d, 0xcf, 0xc2, - 0x28, 0x1a, 0xed, 0x0c, 0x31, 0xe9, 0xd7, 0x3f, 0x1f, 0x07, 0xe1, 0x57, 0x50, 0x16, 0x50, 0xb6, - 0xa0, 0x5d, 0x7b, 0x2a, 0x9f, 0xd3, 0xe4, 0xbf, 0xd9, 0xb0, 0xf1, 0xc7, 0xef, 0xbe, 0x9e, 0x3b, - 0x3e, 0x1d, 0xcb, 0x27, 0xb8, 0x1c, 0xbe, 0xbf, 0x19, 0x84, 0x75, 0x90, 0xe9, 0x7c, 0xae, 0xf7, - 0xdc, 0x15, 0x7c, 0xab, 0xec, 0x04, 0x4f, 0x59, 0xd2, 0x20, 0xd0, 0xe4, 0x8f, 0xef, 0xce, 0x3c, - 0x5f, 0x4a, 0x08, 0xc6, 0xef, 0xe6, 0xa0, 0x29, 0x76, 0x97, 0xc2, 0xe9, 0x2a, 0x57, 0xe9, 0xba, - 0xa0, 0x8f, 0x17, 0x3f, 0xa8, 0x65, 0x40, 0x83, 0x5b, 0x9a, 0x94, 0xb8, 0x80, 0x96, 0xb2, 0x1a, - 0x03, 0xee, 0x09, 0x91, 0xe1, 0x75, 0xa8, 0xc9, 0x80, 0xf3, 0xa9, 0x33, 0x91, 0x9f, 0xd8, 0xc0, - 0x88, 0xf3, 0x43, 0x67, 0x22, 0xa5, 0x0d, 0xdf, 0x16, 0x57, 0xb9, 0x73, 0x5c, 0xda, 0x30, 0xed, - 0x90, 0x1a, 0xff, 0x34, 0x07, 0xcb, 0x5a, 0x57, 0xc4, 0xbe, 0xfd, 0x3e, 0xd4, 0xd5, 0xab, 0xd7, - 0x54, 0x89, 0xb9, 0x1b, 0x71, 0x1e, 0x15, 0x65, 0xab, 0x8d, 0x14, 0x24, 0x60, 0x8d, 0x19, 0xdb, - 0x57, 0x18, 0x15, 0x3d, 0x9f, 0x4a, 0x4d, 0x72, 0x6c, 0x5f, 0xed, 0x51, 0x3a, 0x98, 0x4f, 0xc9, - 0x5d, 0xa8, 0x3f, 0xa7, 0xf4, 0x99, 0x22, 0x40, 0xd6, 0x0b, 0x0c, 0x26, 0x28, 0x0c, 0x68, 0x4c, - 0x3d, 0x37, 0x3c, 0x57, 0x24, 0x42, 0xc4, 0xe7, 0x40, 0xa4, 0x31, 0xfe, 0x20, 0x0f, 0x2b, 0x68, - 0xcf, 0x14, 0x76, 0x64, 0xc1, 0xba, 0x5a, 0xb0, 0x80, 0xa6, 0x5d, 0x64, 0x5e, 0xfb, 0x37, 0x4c, - 0x91, 0x26, 0x1f, 0xbd, 0xa2, 0x0d, 0x56, 0xde, 0x16, 0xbf, 0x66, 0xf8, 0x0b, 0xe9, 0xe1, 0xbf, - 0x7e, 0x78, 0xb3, 0xbc, 0xca, 0xa5, 0x2c, 0xaf, 0xf2, 0xab, 0xf8, 0x72, 0x53, 0xf7, 0x9a, 0xcb, - 0xe9, 0xd7, 0x3b, 0x1f, 0xc1, 0x46, 0x8c, 0x86, 0x73, 0x6b, 0xe7, 0xd4, 0xa1, 0xf2, 0x7d, 0xa0, - 0x55, 0x8d, 0x7a, 0x20, 0x71, 0xdb, 0x65, 0x28, 0x05, 0x23, 0x6f, 0x46, 0x8d, 0x75, 0x58, 0x8d, - 0x8f, 0xaa, 0x38, 0x26, 0x7e, 0x3b, 0x07, 0x2d, 0x11, 0x03, 0xe4, 0xb8, 0x67, 0xfb, 0x4e, 0x10, - 0x7a, 0xbe, 0x7a, 0x1d, 0xfa, 0x36, 0x00, 0x7e, 0xee, 0x83, 0x2b, 0xee, 0xe2, 0x45, 0x1c, 0x0e, - 0xe1, 0x6a, 0xfb, 0x4d, 0xa8, 0x50, 0x77, 0x8c, 0x48, 0x5c, 0x0d, 0x65, 0xea, 0x8e, 0xa5, 0xd2, - 0x9f, 0x3a, 0x86, 0x1b, 0x71, 0x01, 0x43, 0xbc, 0xed, 0xc0, 0x46, 0x87, 0x5e, 0x70, 0x71, 0xa0, - 0xa8, 0xde, 0x76, 0x38, 0xb4, 0x2f, 0x79, 0x44, 0x6d, 0x60, 0xfc, 0xd5, 0x3c, 0x2c, 0x45, 0xed, - 0xc3, 0xd7, 0x6d, 0x5e, 0xfc, 0x4e, 0xcf, 0x5d, 0xb1, 0x1c, 0x1c, 0xa6, 0x2c, 0x69, 0x56, 0xde, - 0x0a, 0x6e, 0xce, 0xae, 0x4b, 0x0c, 0xa8, 0x49, 0x0a, 0x6f, 0x1e, 0x6a, 0x2f, 0x8e, 0x56, 0x91, - 0xa4, 0x3f, 0x0f, 0x99, 0x76, 0xcb, 0xd4, 0x7c, 0xc7, 0x15, 0xfa, 0x65, 0xc9, 0x9e, 0x86, 0x5d, - 0xfe, 0x4d, 0x19, 0x06, 0x66, 0xd9, 0x70, 0x22, 0x19, 0x15, 0xa3, 0x6f, 0xa2, 0xb2, 0x83, 0x33, - 0xc7, 0x15, 0x1d, 0x5d, 0x13, 0xc0, 0x67, 0xf0, 0x95, 0x26, 0xf0, 0x3a, 0xd4, 0xb0, 0xf0, 0xe8, - 0x1a, 0x3b, 0x7f, 0xfe, 0x2b, 0xec, 0xba, 0x1c, 0x2f, 0x2c, 0x6e, 0xde, 0x3c, 0x66, 0x67, 0x00, - 0xac, 0x8a, 0x87, 0xd8, 0xfc, 0x66, 0x0e, 0x6e, 0x66, 0x4c, 0x9b, 0xd8, 0xe5, 0x3b, 0xb0, 0x7c, - 0xaa, 0x90, 0x72, 0x74, 0x71, 0xab, 0xaf, 0x4b, 0xb6, 0x1a, 0x1f, 0x53, 0xb3, 0x79, 0x1a, 0x07, - 0x44, 0x1a, 0x2e, 0xce, 0x60, 0xec, 0x91, 0x04, 0x2e, 0x4e, 0xe1, 0x34, 0xa2, 0x72, 0x79, 0x04, - 0x9b, 0x9d, 0x4b, 0xc6, 0x31, 0x54, 0x58, 0xee, 0xe8, 0xd9, 0x5c, 0x7a, 0xbe, 0x12, 0xd6, 0xfc, - 0xdc, 0x2b, 0x59, 0xf3, 0xc7, 0x78, 0xcd, 0x59, 0x95, 0xf5, 0xd3, 0x14, 0xc2, 0x0f, 0x50, 0x96, - 0xe7, 0x84, 0x17, 0x21, 0x5f, 0x4b, 0x60, 0x20, 0x2c, 0xd4, 0x08, 0x60, 0xe9, 0x70, 0x3e, 0x09, - 0x9d, 0x1d, 0x05, 0x22, 0x1f, 0x89, 0x3c, 0xbc, 0x1e, 0x39, 0x6a, 0x99, 0x15, 0x81, 0xaa, 0x88, - 0x0f, 0xd6, 0x94, 0x15, 0x64, 0xa5, 0xeb, 0x5b, 0x9a, 0xc6, 0x6b, 0x30, 0x6e, 0xc2, 0x46, 0x94, - 0xc2, 0x61, 0x93, 0x47, 0xcd, 0xdf, 0xce, 0x61, 0xf8, 0x3e, 0xe2, 0x06, 0xae, 0x3d, 0x0b, 0xce, - 0xbd, 0x90, 0x74, 0x60, 0x25, 0x70, 0xdc, 0xb3, 0x09, 0xd5, 0x8b, 0x0f, 0xc4, 0x20, 0xac, 0xc5, - 0xdb, 0x86, 0x59, 0x03, 0x73, 0x19, 0x73, 0x44, 0xa5, 0x05, 0x64, 0xfb, 0xba, 0x46, 0x46, 0xcb, - 0x22, 0x31, 0x1a, 0xe9, 0xc6, 0x77, 0x61, 0x31, 0x5e, 0x11, 0xf9, 0x44, 0xbc, 0x0e, 0x10, 0xb5, - 0xaa, 0x90, 0xb8, 0x1b, 0x1d, 0x2d, 0x88, 0x5a, 0x34, 0xf6, 0x81, 0xf1, 0x97, 0x73, 0xd0, 0x32, - 0x29, 0x5b, 0xb9, 0x5a, 0x2b, 0xe5, 0x9a, 0xf9, 0x7e, 0xaa, 0xd4, 0xeb, 0xfb, 0x2a, 0x1f, 0x1d, - 0x90, 0x2d, 0x7a, 0xef, 0xda, 0xc9, 0xd8, 0xbf, 0x91, 0xea, 0xd1, 0x76, 0x05, 0x16, 0x90, 0xc4, - 0xd8, 0x80, 0x35, 0xd1, 0x1e, 0xd9, 0x96, 0xc8, 0x55, 0x1b, 0xab, 0x31, 0xe6, 0xaa, 0xdd, 0x84, - 0x16, 0xde, 0xf3, 0xd5, 0x3b, 0x21, 0x32, 0xee, 0x02, 0x39, 0xb4, 0x47, 0xb6, 0xef, 0x79, 0xee, - 0x11, 0xf5, 0x45, 0x30, 0x34, 0x97, 0x30, 0xb9, 0x27, 0x53, 0x8a, 0xc2, 0x98, 0x92, 0xef, 0x2c, - 0x7b, 0xae, 0x8c, 0xfd, 0xc2, 0x94, 0xe1, 0xc3, 0xca, 0xb6, 0xfd, 0x8c, 0xca, 0x92, 0xe4, 0x10, - 0x3d, 0x86, 0xda, 0x4c, 0x15, 0x2a, 0xc7, 0x5d, 0x3e, 0x96, 0x92, 0xae, 0xd6, 0xd4, 0xa9, 0x19, - 0x0b, 0xf2, 0x3d, 0x2f, 0xe4, 0x0f, 0x13, 0x48, 0x67, 0x98, 0x59, 0x65, 0xa0, 0xa7, 0xf4, 0xaa, - 0x3b, 0x36, 0x1e, 0xc2, 0x6a, 0xbc, 0x4e, 0xc1, 0x5a, 0x36, 0xa1, 0x32, 0x15, 0x30, 0xd1, 0x7a, - 0x95, 0x66, 0xca, 0x08, 0x53, 0xf9, 0x64, 0x9e, 0xee, 0xae, 0x52, 0xa9, 0x1e, 0xc3, 0x46, 0x0a, - 0x23, 0x0a, 0xbc, 0x0b, 0x75, 0xad, 0x21, 0xd8, 0x8d, 0x22, 0x13, 0x59, 0x45, 0x4b, 0x02, 0xe3, - 0x33, 0xd8, 0x40, 0x7d, 0x2c, 0xca, 0x2e, 0x87, 0x20, 0xd1, 0x8b, 0x5c, 0xb2, 0x17, 0x1f, 0x49, - 0x35, 0x4f, 0xcf, 0x1a, 0x5d, 0x15, 0x18, 0x73, 0x9c, 0x0c, 0xdf, 0x91, 0x49, 0xe3, 0x18, 0xd6, - 0xd3, 0xc3, 0xc7, 0xda, 0xff, 0x33, 0x0d, 0xb9, 0x1c, 0x9e, 0x08, 0xad, 0x86, 0xe7, 0xbf, 0xe6, - 0x70, 0x7c, 0x62, 0x28, 0xd1, 0xcc, 0x31, 0x90, 0x29, 0x0d, 0xcf, 0xbd, 0xb1, 0x95, 0xae, 0xf9, - 0x91, 0x8a, 0x1e, 0xca, 0xcc, 0xbb, 0x75, 0xc8, 0x33, 0x6a, 0x18, 0x11, 0xc7, 0x3e, 0x4d, 0xc2, - 0x37, 0x47, 0xb0, 0x9e, 0x4d, 0x9c, 0x11, 0x73, 0xf3, 0x61, 0x5c, 0x50, 0xbf, 0x7d, 0x6d, 0xf7, - 0x59, 0xb3, 0x74, 0xb9, 0xfd, 0xb7, 0x2a, 0x50, 0x16, 0x56, 0x12, 0xb2, 0x05, 0xc5, 0x91, 0x8c, - 0xdf, 0x8c, 0x1e, 0xa2, 0x13, 0x58, 0xf9, 0x7f, 0x87, 0x47, 0x71, 0x32, 0x3a, 0xf2, 0x18, 0x16, - 0xe3, 0x21, 0x0c, 0x89, 0x47, 0x2a, 0xe2, 0xb1, 0x07, 0x8d, 0x51, 0xc2, 0x59, 0x5d, 0x8d, 0x84, - 0x2b, 0x94, 0x39, 0x2b, 0xe7, 0x9a, 0xf4, 0xe5, 0xb9, 0x4c, 0x5f, 0x0b, 0xce, 0x6d, 0xeb, 0xe1, - 0xa3, 0x8f, 0xc5, 0x2b, 0x15, 0x35, 0x0e, 0x1c, 0x9c, 0xdb, 0x0f, 0x1f, 0x7d, 0x9c, 0xd4, 0xc4, - 0xc4, 0x1b, 0x15, 0x9a, 0x26, 0xb6, 0x0a, 0x25, 0x7c, 0x8c, 0x1a, 0x03, 0xf1, 0x30, 0x41, 0x1e, - 0xc0, 0xaa, 0x34, 0xbc, 0x89, 0x2b, 0x13, 0x78, 0x8a, 0x56, 0xf0, 0x96, 0xaa, 0xc0, 0x0d, 0x38, - 0x0a, 0x4d, 0x75, 0xeb, 0xb0, 0x70, 0x1e, 0xbd, 0x2e, 0xde, 0x30, 0x45, 0xca, 0xf8, 0x83, 0x12, - 0xd4, 0xb4, 0x41, 0x21, 0x75, 0xa8, 0x98, 0x9d, 0x41, 0xc7, 0xfc, 0xa2, 0xb3, 0xdb, 0xbc, 0x41, - 0xee, 0xc1, 0x5b, 0xdd, 0xde, 0x4e, 0xdf, 0x34, 0x3b, 0x3b, 0x43, 0xab, 0x6f, 0x5a, 0xf2, 0x39, - 0xc4, 0xa3, 0xf6, 0x57, 0x87, 0x9d, 0xde, 0xd0, 0xda, 0xed, 0x0c, 0xdb, 0xdd, 0x83, 0x41, 0x33, - 0x47, 0x5e, 0x83, 0x56, 0x44, 0x29, 0xd1, 0xed, 0xc3, 0xfe, 0x71, 0x6f, 0xd8, 0xcc, 0x93, 0x3b, - 0x70, 0x6b, 0xaf, 0xdb, 0x6b, 0x1f, 0x58, 0x11, 0xcd, 0xce, 0xc1, 0xf0, 0x0b, 0xab, 0xf3, 0x8b, - 0x47, 0x5d, 0xf3, 0xab, 0x66, 0x21, 0x8b, 0x60, 0x7f, 0x78, 0xb0, 0x23, 0x4b, 0x28, 0x92, 0x9b, - 0xb0, 0x86, 0x04, 0x98, 0xc5, 0x1a, 0xf6, 0xfb, 0xd6, 0xa0, 0xdf, 0xef, 0x35, 0x4b, 0x64, 0x19, - 0x1a, 0xdd, 0xde, 0x17, 0xed, 0x83, 0xee, 0xae, 0x65, 0x76, 0xda, 0x07, 0x87, 0xcd, 0x05, 0xb2, - 0x02, 0x4b, 0x49, 0xba, 0x32, 0x2b, 0x42, 0xd2, 0xf5, 0x7b, 0xdd, 0x7e, 0xcf, 0xfa, 0xa2, 0x63, - 0x0e, 0xba, 0xfd, 0x5e, 0xb3, 0x42, 0xd6, 0x81, 0xc4, 0x51, 0xfb, 0x87, 0xed, 0x9d, 0x66, 0x95, - 0xac, 0xc1, 0x72, 0x1c, 0xfe, 0xb4, 0xf3, 0x55, 0x13, 0x48, 0x0b, 0x56, 0xb1, 0x61, 0xd6, 0x76, - 0xe7, 0xa0, 0xff, 0xa5, 0x75, 0xd8, 0xed, 0x75, 0x0f, 0x8f, 0x0f, 0x9b, 0x35, 0xfe, 0xa6, 0x6c, - 0xa7, 0x63, 0x75, 0x7b, 0x83, 0xe3, 0xbd, 0xbd, 0xee, 0x4e, 0xb7, 0xd3, 0x1b, 0x36, 0xeb, 0x58, - 0x73, 0x56, 0xc7, 0x1b, 0x2c, 0x83, 0xb8, 0x57, 0x65, 0xed, 0x76, 0x07, 0xed, 0xed, 0x83, 0xce, - 0x6e, 0x73, 0x91, 0xdc, 0x86, 0x9b, 0xc3, 0xce, 0xe1, 0x51, 0xdf, 0x6c, 0x9b, 0x5f, 0xc9, 0x7b, - 0x57, 0xd6, 0x5e, 0xbb, 0x7b, 0x70, 0x6c, 0x76, 0x9a, 0x4b, 0xe4, 0x0d, 0xb8, 0x6d, 0x76, 0x7e, - 0x7c, 0xdc, 0x35, 0x3b, 0xbb, 0x56, 0xaf, 0xbf, 0xdb, 0xb1, 0xf6, 0x3a, 0xed, 0xe1, 0xb1, 0xd9, - 0xb1, 0x0e, 0xbb, 0x83, 0x41, 0xb7, 0xf7, 0xa4, 0xd9, 0x24, 0x6f, 0xc1, 0x5d, 0x45, 0xa2, 0x0a, - 0x48, 0x50, 0x2d, 0xb3, 0xfe, 0xc9, 0x29, 0xed, 0x75, 0x7e, 0x71, 0x68, 0x1d, 0x75, 0x3a, 0x66, - 0x93, 0x90, 0x4d, 0x58, 0x8f, 0xaa, 0xc7, 0x0a, 0x44, 0xdd, 0x2b, 0x0c, 0x77, 0xd4, 0x31, 0x0f, - 0xdb, 0x3d, 0x36, 0xc1, 0x31, 0xdc, 0x2a, 0x6b, 0x76, 0x84, 0x4b, 0x36, 0x7b, 0x8d, 0x10, 0x58, - 0xd4, 0x66, 0x65, 0xaf, 0x6d, 0x36, 0xd7, 0xc9, 0x12, 0xd4, 0x0e, 0x8f, 0x8e, 0xac, 0x61, 0xf7, - 0xb0, 0xd3, 0x3f, 0x1e, 0x36, 0x37, 0xc8, 0x1a, 0x34, 0xbb, 0xbd, 0x61, 0xc7, 0x64, 0x73, 0x2d, - 0xb3, 0xfe, 0xb7, 0x32, 0x59, 0x85, 0x25, 0xd9, 0x52, 0x09, 0xfd, 0xe3, 0x32, 0xd9, 0x00, 0x72, - 0xdc, 0x33, 0x3b, 0xed, 0x5d, 0x36, 0x70, 0x0a, 0xf1, 0xdf, 0xcb, 0xc2, 0x9d, 0xf9, 0xbb, 0x05, - 0x25, 0xec, 0x45, 0xf1, 0x41, 0xf1, 0x4f, 0x6e, 0xd4, 0xb5, 0x4f, 0x65, 0xbc, 0xec, 0xc3, 0x59, - 0x9a, 0x6a, 0x5e, 0x48, 0xa9, 0xe6, 0x29, 0xdb, 0x4f, 0x43, 0xd7, 0x1d, 0xde, 0x84, 0xc6, 0x14, - 0x3f, 0xbf, 0x21, 0xde, 0x96, 0x07, 0x11, 0x2c, 0x87, 0x40, 0x7c, 0x58, 0x3e, 0xf5, 0xe5, 0xa8, - 0x52, 0xfa, 0xcb, 0x51, 0x59, 0xfa, 0xe1, 0x42, 0x96, 0x7e, 0x78, 0x1f, 0x96, 0x91, 0x35, 0x39, - 0xae, 0x33, 0x95, 0x56, 0x17, 0xd4, 0x22, 0x96, 0x38, 0x8b, 0x42, 0xb8, 0x54, 0x47, 0xa5, 0xca, - 0x2a, 0x58, 0x48, 0x59, 0x68, 0xab, 0x31, 0x4d, 0x15, 0x39, 0x87, 0xd2, 0x54, 0x55, 0x0d, 0xf6, - 0x65, 0x54, 0x43, 0x4d, 0xab, 0x01, 0xe1, 0xbc, 0x86, 0xfb, 0xb0, 0x4c, 0x2f, 0x43, 0xdf, 0xb6, - 0xbc, 0x99, 0xfd, 0xf5, 0x9c, 0xc7, 0x5b, 0xd8, 0xdc, 0x06, 0x54, 0x37, 0x97, 0x38, 0xa2, 0xcf, - 0xe1, 0xbb, 0x76, 0x68, 0x1b, 0xbf, 0x02, 0xa0, 0x4e, 0xd5, 0x31, 0x63, 0x80, 0xae, 0x27, 0xaf, - 0xdd, 0xd5, 0x4d, 0x4c, 0xf0, 0x79, 0x0c, 0x3d, 0xdf, 0x3e, 0xa3, 0x5d, 0xf9, 0xd2, 0x4b, 0x04, - 0x20, 0xb7, 0xa0, 0xe0, 0xcd, 0x64, 0x28, 0x59, 0x55, 0x3e, 0x96, 0x3c, 0x33, 0x19, 0xd4, 0xf8, - 0x18, 0xf2, 0xfd, 0xd9, 0xb5, 0xa2, 0x52, 0x0b, 0xca, 0xf2, 0x5b, 0x91, 0x79, 0x1e, 0x3e, 0x26, - 0x93, 0xf7, 0xff, 0x2c, 0xd4, 0xb4, 0x2f, 0xc6, 0x90, 0x0d, 0x58, 0xf9, 0xb2, 0x3b, 0xec, 0x75, - 0x06, 0x03, 0xeb, 0xe8, 0x78, 0xfb, 0x69, 0xe7, 0x2b, 0x6b, 0xbf, 0x3d, 0xd8, 0x6f, 0xde, 0x60, - 0xbc, 0xa4, 0xd7, 0x19, 0x0c, 0x3b, 0xbb, 0x31, 0x78, 0x8e, 0xbc, 0x0e, 0x9b, 0xc7, 0xbd, 0xe3, - 0x41, 0x67, 0xd7, 0xca, 0xca, 0x97, 0x67, 0x9b, 0x47, 0xe0, 0x33, 0xb2, 0x17, 0xee, 0xff, 0x2a, - 0x2c, 0xc6, 0x5f, 0x46, 0x20, 0x00, 0x0b, 0x07, 0x9d, 0x27, 0xed, 0x9d, 0xaf, 0xf0, 0x31, 0xec, - 0xc1, 0xb0, 0x3d, 0xec, 0xee, 0x58, 0xe2, 0xf1, 0x6b, 0xc6, 0xa8, 0x72, 0xa4, 0x06, 0xe5, 0x76, - 0x6f, 0x67, 0xbf, 0x6f, 0x0e, 0x9a, 0x79, 0xf2, 0x1a, 0x6c, 0xc8, 0x2d, 0xb4, 0xd3, 0x3f, 0x3c, - 0xec, 0x0e, 0x39, 0x8f, 0x1e, 0x7e, 0x75, 0xc4, 0x76, 0xcc, 0x7d, 0x1b, 0xaa, 0xd1, 0xbb, 0xdd, - 0x9c, 0xef, 0x75, 0x87, 0xdd, 0xf6, 0x30, 0x62, 0xfa, 0xcd, 0x1b, 0x8c, 0xad, 0x46, 0x60, 0xfe, - 0xf8, 0x76, 0x33, 0x87, 0x97, 0x47, 0x25, 0x10, 0x6b, 0x6f, 0xe6, 0xd9, 0x5e, 0x8f, 0xa0, 0xdb, - 0xfd, 0x21, 0xeb, 0xc2, 0xaf, 0xc1, 0x62, 0xfc, 0x79, 0x6c, 0xd2, 0x84, 0x3a, 0xab, 0x5f, 0xab, - 0x02, 0x60, 0x01, 0x5b, 0xdc, 0xcc, 0x21, 0x63, 0xdf, 0xe9, 0x1f, 0x76, 0x7b, 0x4f, 0xf8, 0x69, - 0xd0, 0xcc, 0x33, 0x50, 0xff, 0x78, 0xf8, 0xa4, 0xaf, 0x40, 0x05, 0x96, 0x03, 0xbb, 0xd3, 0x2c, - 0xde, 0xff, 0x1a, 0x96, 0x53, 0x0f, 0x69, 0xb3, 0x56, 0xf7, 0x8f, 0x87, 0x3b, 0xfd, 0x43, 0xbd, - 0x9e, 0x1a, 0x94, 0x77, 0x0e, 0xda, 0xdd, 0x43, 0xee, 0x08, 0x69, 0x40, 0xf5, 0xb8, 0x27, 0x93, - 0xf9, 0xf8, 0x13, 0xe0, 0x05, 0xc6, 0xa2, 0xf6, 0xba, 0xe6, 0x60, 0x68, 0x0d, 0x86, 0xed, 0x27, - 0x9d, 0x66, 0x91, 0xe5, 0x95, 0xfc, 0xaa, 0x74, 0xff, 0x33, 0x58, 0x8c, 0xc7, 0x3d, 0xc7, 0x1d, - 0x58, 0x9b, 0xb0, 0xbe, 0xdd, 0x19, 0x7e, 0xd9, 0xe9, 0xf4, 0xf8, 0x94, 0xef, 0x74, 0x7a, 0x43, - 0xb3, 0x7d, 0xd0, 0x1d, 0x7e, 0xd5, 0xcc, 0xdd, 0x7f, 0x0c, 0xcd, 0x64, 0x90, 0x41, 0x2c, 0x2a, - 0xe3, 0x45, 0xe1, 0x1b, 0xf7, 0xff, 0x53, 0x0e, 0x56, 0xb3, 0xfc, 0x6b, 0x6c, 0x61, 0x0a, 0x46, - 0xc8, 0x8e, 0xc3, 0x41, 0xbf, 0x67, 0xf5, 0xfa, 0xfc, 0x51, 0xdd, 0x4d, 0x58, 0x4f, 0x20, 0x64, - 0x2f, 0x72, 0xe4, 0x16, 0x6c, 0xa4, 0x32, 0x59, 0x66, 0xff, 0x98, 0xcf, 0x65, 0x0b, 0x56, 0x13, - 0xc8, 0x8e, 0x69, 0xf6, 0xcd, 0x66, 0x81, 0xbc, 0x07, 0xf7, 0x12, 0x98, 0xb4, 0x10, 0x20, 0x65, - 0x84, 0x22, 0x79, 0x07, 0xde, 0x4c, 0x51, 0x47, 0xe7, 0xa4, 0xb5, 0xdd, 0x3e, 0x60, 0xdd, 0x6b, - 0x96, 0xee, 0xff, 0x83, 0x02, 0x40, 0x74, 0xb1, 0x90, 0xd5, 0xbf, 0xdb, 0x1e, 0xb6, 0x0f, 0xfa, - 0x6c, 0xcf, 0x98, 0xfd, 0x21, 0x2b, 0xdd, 0xec, 0xfc, 0xb8, 0x79, 0x23, 0x13, 0xd3, 0x3f, 0x62, - 0x1d, 0xda, 0x80, 0x15, 0x5c, 0x7f, 0x07, 0xac, 0x1b, 0x6c, 0xb9, 0xf0, 0xf7, 0x99, 0xb9, 0xa4, - 0x71, 0x7c, 0xb4, 0x67, 0xf6, 0x7b, 0x43, 0x6b, 0xb0, 0x7f, 0x3c, 0xdc, 0xe5, 0xaf, 0x3b, 0xef, - 0x98, 0xdd, 0x23, 0x2c, 0xb3, 0xf8, 0x22, 0x02, 0x56, 0x74, 0x89, 0x6d, 0xf0, 0x27, 0xfd, 0xc1, - 0xa0, 0x7b, 0x64, 0xfd, 0xf8, 0xb8, 0x63, 0x76, 0x3b, 0x03, 0x9e, 0x71, 0x21, 0x03, 0xce, 0xe8, - 0xcb, 0x6c, 0xcd, 0x0e, 0x0f, 0xbe, 0x10, 0x02, 0x04, 0x23, 0xad, 0xc4, 0x41, 0x8c, 0xaa, 0xca, - 0x66, 0x87, 0x9d, 0xc0, 0x19, 0x25, 0xc3, 0x35, 0x38, 0x96, 0xaf, 0xc6, 0x64, 0x8b, 0xd4, 0xce, - 0xe7, 0xd9, 0xea, 0xd9, 0x28, 0x96, 0x8b, 0x8b, 0x1d, 0x4a, 0x48, 0xdb, 0xdd, 0x35, 0x79, 0x86, - 0xc5, 0x14, 0x94, 0xd1, 0x2e, 0xb1, 0x45, 0xc8, 0x8e, 0x68, 0x46, 0xd2, 0x94, 0x09, 0x86, 0x59, - 0x7e, 0xf8, 0x2f, 0xdf, 0x80, 0xaa, 0xba, 0x60, 0x40, 0x7e, 0x04, 0x8d, 0xd8, 0x8d, 0x6f, 0x22, - 0x4d, 0xf8, 0x59, 0x17, 0xc4, 0x37, 0x5f, 0xcb, 0x46, 0x0a, 0xe5, 0xe4, 0x50, 0xb3, 0x06, 0x60, - 0x61, 0xaf, 0x25, 0x35, 0xf4, 0x58, 0x69, 0xb7, 0xaf, 0xc1, 0x8a, 0xe2, 0x9e, 0xf2, 0xa7, 0xa2, - 0xf5, 0x0f, 0x0f, 0x93, 0xdb, 0xd1, 0xbb, 0xbd, 0x19, 0x1f, 0x24, 0xde, 0xbc, 0x99, 0xfe, 0x44, - 0xb0, 0xfc, 0xa6, 0xf0, 0x2e, 0xd4, 0xb4, 0xef, 0xe9, 0x91, 0x9b, 0xd7, 0x7e, 0xfb, 0x6f, 0x73, - 0x33, 0x0b, 0x25, 0x9a, 0xf4, 0x39, 0x54, 0xd5, 0xb7, 0xd5, 0xc8, 0x86, 0xf6, 0x5d, 0x3c, 0xfd, - 0x0b, 0x71, 0x9b, 0xad, 0x34, 0x42, 0xe4, 0xdf, 0x85, 0x9a, 0xf6, 0x89, 0x34, 0xd5, 0x8a, 0xf4, - 0x67, 0xd8, 0x54, 0x2b, 0xb2, 0xbe, 0xa8, 0x76, 0x00, 0x6b, 0xc2, 0xe6, 0x70, 0x42, 0xbf, 0xc9, - 0xf0, 0x64, 0x7c, 0x41, 0xf9, 0x41, 0x8e, 0x3c, 0x86, 0x8a, 0xfc, 0x18, 0x1e, 0x59, 0xcf, 0xfe, - 0xd4, 0xdf, 0xe6, 0x46, 0x0a, 0x2e, 0x9a, 0xd2, 0x06, 0x88, 0x3e, 0xbe, 0x46, 0x64, 0xc7, 0x53, - 0x1f, 0x73, 0x53, 0x33, 0x93, 0xf1, 0xa5, 0xb6, 0x5d, 0xa8, 0x69, 0xdf, 0x59, 0x53, 0x63, 0x92, - 0xfe, 0x46, 0x9b, 0x1a, 0x93, 0xac, 0xcf, 0xb2, 0xfd, 0x08, 0x1a, 0xb1, 0x0f, 0xa6, 0xa9, 0x75, - 0x9c, 0xf5, 0x39, 0x36, 0xb5, 0x8e, 0xb3, 0xbf, 0xb1, 0xb6, 0x0b, 0x35, 0xed, 0xf3, 0x66, 0xaa, - 0x45, 0xe9, 0x2f, 0xa9, 0xa9, 0x16, 0x65, 0x7c, 0x0d, 0x8d, 0xed, 0x86, 0xf8, 0xb7, 0xcd, 0xd4, - 0x6e, 0xc8, 0xfc, 0x48, 0x9a, 0xda, 0x0d, 0xd9, 0x1f, 0x44, 0x63, 0x4b, 0x4f, 0xbd, 0x11, 0x4f, - 0x36, 0x62, 0xaa, 0x7e, 0xf4, 0xd8, 0xbc, 0x5a, 0x7a, 0xe9, 0xe7, 0xe4, 0x9f, 0xc0, 0x8a, 0x5a, - 0x34, 0xea, 0x85, 0xf7, 0x40, 0xb5, 0x29, 0xf3, 0x1d, 0xf9, 0xcd, 0x66, 0x12, 0xfb, 0x20, 0x47, - 0x3e, 0x85, 0xb2, 0x78, 0x36, 0x9b, 0xac, 0x25, 0x9f, 0xd1, 0xc6, 0x46, 0xac, 0x67, 0xbf, 0xae, - 0x4d, 0x8e, 0xf8, 0x86, 0xd6, 0xdf, 0xb5, 0xd6, 0x57, 0x6c, 0xc6, 0x53, 0xd8, 0x9b, 0xaf, 0x5f, - 0x87, 0x8e, 0x4a, 0x4c, 0xbe, 0xc5, 0x7e, 0xfb, 0xba, 0x97, 0x58, 0xe2, 0x25, 0x5e, 0xf7, 0x64, - 0xdc, 0x13, 0xa8, 0xeb, 0x5f, 0xd6, 0x21, 0xfa, 0x3e, 0x4c, 0x96, 0x75, 0x2b, 0x13, 0x27, 0x0a, - 0xfa, 0x02, 0xd6, 0xd5, 0x78, 0xeb, 0xcf, 0x82, 0x04, 0xe4, 0x4e, 0xc6, 0x63, 0x21, 0xb1, 0x51, - 0xbf, 0x79, 0xed, 0x6b, 0x22, 0x0f, 0x72, 0x9c, 0xc9, 0xc6, 0x3e, 0x86, 0x11, 0x31, 0xd9, 0xac, - 0x6f, 0x80, 0x44, 0x4c, 0x36, 0xfb, 0x0b, 0x1a, 0x6d, 0x58, 0xd2, 0x9e, 0x35, 0x19, 0x5c, 0xb9, - 0x23, 0xb5, 0xde, 0xd3, 0x0f, 0x0e, 0x6f, 0x66, 0x59, 0xbe, 0xc9, 0x0e, 0xd4, 0xf4, 0x97, 0x51, - 0x5e, 0x90, 0x7d, 0x43, 0x43, 0xe9, 0x6f, 0xca, 0x3e, 0xc8, 0x91, 0x03, 0x68, 0x26, 0xdf, 0x31, - 0x54, 0x5b, 0x38, 0xeb, 0xed, 0xc7, 0xcd, 0x04, 0x32, 0xf6, 0xfa, 0x21, 0x5b, 0x17, 0xb1, 0x2f, - 0xf5, 0x7a, 0x7e, 0xf2, 0x28, 0x8a, 0x7f, 0xc1, 0x57, 0x95, 0x96, 0xf5, 0xed, 0xe6, 0x7b, 0xb9, - 0x07, 0x39, 0xb2, 0x07, 0xf5, 0xd8, 0x33, 0x5e, 0xb1, 0xbb, 0x2e, 0x89, 0x6e, 0xb6, 0x74, 0x5c, - 0xa2, 0x9f, 0x87, 0xb0, 0x18, 0x0f, 0xd1, 0x50, 0x0d, 0xcb, 0x8c, 0x23, 0x51, 0xd3, 0x97, 0x1d, - 0xd7, 0x41, 0x7e, 0x80, 0xdf, 0xa1, 0x97, 0xa1, 0x7c, 0x24, 0xfd, 0xdd, 0x72, 0x35, 0x67, 0xfa, - 0x57, 0xbe, 0x8d, 0xc2, 0x5f, 0xcc, 0xe7, 0x78, 0xbf, 0xbe, 0x8f, 0x5f, 0x81, 0x95, 0xd1, 0x5c, - 0x6c, 0xfe, 0x5f, 0xb5, 0x10, 0xb2, 0x87, 0x95, 0x8b, 0x6f, 0x70, 0x47, 0x9c, 0x3b, 0xf5, 0x5d, - 0xee, 0x97, 0xb4, 0xa1, 0x8d, 0x6d, 0x10, 0x79, 0x62, 0x6b, 0xf0, 0x15, 0xcb, 0x22, 0x9f, 0x00, - 0x44, 0x21, 0xb2, 0x24, 0x11, 0xa8, 0xa9, 0x36, 0x54, 0x46, 0x14, 0x6d, 0x07, 0xf7, 0xbb, 0x8a, - 0x14, 0xd5, 0x8f, 0xe4, 0x78, 0xd0, 0x6a, 0xec, 0x48, 0x4e, 0x16, 0xf3, 0x21, 0x34, 0x0e, 0x3c, - 0xef, 0xd9, 0x7c, 0xa6, 0xee, 0x59, 0xc4, 0xc3, 0x98, 0xf6, 0xed, 0xe0, 0x7c, 0x33, 0xd1, 0x2c, - 0xd2, 0x86, 0x65, 0xc5, 0x22, 0xa2, 0x50, 0xd5, 0x38, 0x51, 0x8c, 0x31, 0x24, 0x0a, 0x78, 0x90, - 0x23, 0x0f, 0xa1, 0xbe, 0x4b, 0x47, 0xfc, 0x99, 0x0d, 0x1e, 0x34, 0xb3, 0x12, 0x0b, 0xc0, 0xc0, - 0x68, 0x9b, 0xcd, 0x46, 0x0c, 0x28, 0x59, 0x5c, 0x14, 0xb8, 0xa5, 0x9f, 0x19, 0xf1, 0xe8, 0xa7, - 0x18, 0x8b, 0x4b, 0x05, 0x6f, 0x7d, 0x01, 0xcb, 0xa9, 0xd0, 0x28, 0xc5, 0xdd, 0xae, 0x0b, 0xa8, - 0xda, 0xbc, 0x7b, 0x3d, 0x81, 0x28, 0xf7, 0x87, 0xd0, 0xc0, 0x27, 0x86, 0x4f, 0x28, 0x5e, 0x93, - 0x4d, 0xbc, 0x31, 0xa5, 0xdf, 0xc1, 0x4d, 0xb2, 0x24, 0xcc, 0xf0, 0x84, 0x7f, 0x78, 0x44, 0xbb, - 0x84, 0xaa, 0xe6, 0x35, 0x7d, 0x31, 0x56, 0xcd, 0x6b, 0xd6, 0x7d, 0xd7, 0xcf, 0xa0, 0xf6, 0x84, - 0x86, 0xf2, 0x5a, 0xa7, 0x92, 0x8f, 0x12, 0xf7, 0x3c, 0x37, 0x33, 0x2e, 0xe3, 0x92, 0x8f, 0x79, - 0x56, 0xf5, 0x44, 0xc1, 0xba, 0x56, 0x8b, 0x9e, 0x75, 0x29, 0x01, 0x67, 0xd2, 0x87, 0xf6, 0x50, - 0x89, 0x6a, 0x78, 0xfa, 0x61, 0x1a, 0xd5, 0xf0, 0xac, 0x77, 0x4d, 0x7e, 0x80, 0x23, 0xa0, 0x5d, - 0x24, 0x8d, 0x44, 0xb0, 0xe4, 0x9d, 0x53, 0xd5, 0x7c, 0x9d, 0xfc, 0x11, 0xc0, 0x20, 0xf4, 0x66, - 0xbb, 0x36, 0x9d, 0x7a, 0x6e, 0xc4, 0x13, 0xa2, 0x2b, 0x8c, 0xd1, 0x46, 0xd4, 0xee, 0x31, 0x92, - 0x2f, 0x35, 0xd9, 0x34, 0x36, 0x25, 0x72, 0xda, 0xaf, 0xbd, 0xe5, 0xa8, 0xba, 0x93, 0x71, 0xd3, - 0x91, 0x33, 0x09, 0x88, 0x22, 0xcf, 0x94, 0xa4, 0x99, 0x0a, 0x6a, 0x53, 0x7b, 0x3d, 0x23, 0x4c, - 0xed, 0x73, 0xa8, 0x46, 0x21, 0x3b, 0x1b, 0xd1, 0xab, 0x49, 0xb1, 0x00, 0x1f, 0xc5, 0xbd, 0xd3, - 0xe1, 0x32, 0x3d, 0x58, 0xc1, 0xe6, 0xa8, 0xe3, 0x8f, 0x5f, 0xb4, 0x53, 0xdf, 0xcd, 0x49, 0xc7, - 0xa9, 0xa8, 0xfd, 0x93, 0x15, 0x6d, 0xc1, 0xf6, 0x4f, 0xca, 0x6b, 0xaf, 0xf6, 0xcf, 0x75, 0x61, - 0x18, 0x6a, 0xff, 0x5c, 0xef, 0xf0, 0xef, 0xc1, 0x4a, 0x86, 0xff, 0x9d, 0xbc, 0x21, 0x15, 0x9b, - 0x6b, 0x7d, 0xf3, 0x9b, 0x99, 0x7e, 0x5a, 0x32, 0x84, 0x0d, 0xcc, 0xd3, 0x9e, 0x4c, 0x12, 0xee, - 0xde, 0xd7, 0xb5, 0x0c, 0x19, 0x2e, 0xec, 0x98, 0x28, 0x93, 0x70, 0x63, 0xf7, 0xa0, 0x99, 0xf4, - 0x94, 0x92, 0xeb, 0xc9, 0x37, 0xef, 0xc4, 0x44, 0xf6, 0xb4, 0x77, 0x95, 0x7c, 0xa1, 0xfc, 0xb5, - 0x89, 0x36, 0xde, 0x89, 0xbe, 0xd6, 0x96, 0xe9, 0x5d, 0x56, 0xda, 0x40, 0xa6, 0xbb, 0x97, 0xfc, - 0x22, 0x6c, 0x24, 0x57, 0xb4, 0x2c, 0xf9, 0x6e, 0xd6, 0x70, 0x5d, 0x2b, 0xca, 0xc5, 0x3b, 0xf4, - 0x20, 0xc7, 0x18, 0xb1, 0xee, 0x55, 0x55, 0x0b, 0x29, 0xc3, 0xbd, 0xab, 0x16, 0x52, 0xa6, 0x1b, - 0xf6, 0x08, 0x96, 0x12, 0x0e, 0x55, 0x25, 0x06, 0x67, 0xbb, 0x60, 0x95, 0x18, 0x7c, 0x9d, 0x1f, - 0x76, 0x00, 0xcd, 0xa4, 0xab, 0x54, 0xcd, 0xf5, 0x35, 0xee, 0xd7, 0xcd, 0x3b, 0xd7, 0xe2, 0xe3, - 0xcd, 0xd4, 0x9c, 0x8a, 0xb1, 0x66, 0xa6, 0x5d, 0xa1, 0xb1, 0x66, 0x66, 0xb8, 0x34, 0xb7, 0xdf, - 0xf9, 0xa5, 0xef, 0x9c, 0x39, 0xe1, 0xf9, 0xfc, 0x64, 0x6b, 0xe4, 0x4d, 0xdf, 0x9f, 0x48, 0xab, - 0x86, 0xb8, 0x77, 0xfe, 0xfe, 0xc4, 0x1d, 0xbf, 0xcf, 0x0b, 0x38, 0x59, 0x98, 0xf9, 0x5e, 0xe8, - 0x7d, 0xf8, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x2f, 0xed, 0xbd, 0x5e, 0x11, 0x8b, 0x00, 0x00, + 0x96, 0x58, 0xf1, 0x12, 0xc9, 0xc7, 0x43, 0x54, 0xe8, 0x62, 0xa9, 0xba, 0xba, 0xaa, 0xb3, 0x7b, + 0xba, 0x6b, 0xaa, 0x7b, 0xd4, 0xd5, 0xd5, 0x5d, 0x7d, 0x4c, 0x79, 0x7b, 0x86, 0xa2, 0xa8, 0x12, + 0xa7, 0x24, 0x52, 0x93, 0xa4, 0xba, 0xb7, 0x17, 0xbb, 0x9b, 0x9b, 0x22, 0x43, 0x52, 0xba, 0xc8, + 0x4c, 0x76, 0x66, 0x52, 0x25, 0x8d, 0xe1, 0xbf, 0xf5, 0x81, 0xc5, 0xc2, 0x80, 0x01, 0xaf, 0x01, + 0x1f, 0x0b, 0x5f, 0xb0, 0xfd, 0xb7, 0x30, 0xbc, 0x6b, 0x7f, 0xf9, 0xdb, 0x0b, 0x03, 0x3e, 0x60, + 0x78, 0x0d, 0x1f, 0x58, 0x2c, 0x60, 0xc0, 0x5e, 0x7f, 0x18, 0x30, 0x16, 0xb0, 0x7f, 0xfc, 0x61, + 0xc0, 0x88, 0x17, 0x47, 0x46, 0x26, 0x53, 0x55, 0xd5, 0x33, 0xed, 0xf9, 0x91, 0x18, 0xef, 0xbd, + 0xb8, 0x23, 0x5e, 0xbc, 0x2b, 0x22, 0xa1, 0xec, 0xcf, 0x46, 0xdb, 0x33, 0xdf, 0x0b, 0x3d, 0x52, + 0x98, 0xb8, 0xfe, 0x6c, 0x64, 0xfc, 0x71, 0x06, 0xf2, 0xc7, 0xe1, 0xa5, 0x47, 0x1e, 0x41, 0xd5, + 0x1e, 0x8f, 0x7d, 0x1a, 0x04, 0x56, 0x78, 0x35, 0xa3, 0xcd, 0xcc, 0xdd, 0xcc, 0xbd, 0xfa, 0x43, + 0xb2, 0x8d, 0x64, 0xdb, 0x2d, 0x8e, 0x1a, 0x5e, 0xcd, 0xa8, 0x59, 0xb1, 0xa3, 0x04, 0x69, 0x42, + 0x51, 0x24, 0x9b, 0xd9, 0xbb, 0x99, 0x7b, 0x65, 0x53, 0x26, 0xc9, 0x6d, 0x00, 0x7b, 0xea, 0xcd, + 0xdd, 0xd0, 0x0a, 0xec, 0xb0, 0x99, 0xbb, 0x9b, 0xb9, 0x97, 0x33, 0xcb, 0x1c, 0x32, 0xb0, 0x43, + 0x72, 0x0b, 0xca, 0xb3, 0x67, 0x56, 0x30, 0xf2, 0x9d, 0x59, 0xd8, 0xcc, 0x63, 0xd6, 0xd2, 0xec, + 0xd9, 0x00, 0xd3, 0xe4, 0x5d, 0x28, 0x79, 0xf3, 0x70, 0xe6, 0x39, 0x6e, 0xd8, 0x2c, 0xdc, 0xcd, + 0xdc, 0xab, 0x3c, 0x5c, 0x16, 0x0d, 0xe9, 0xcf, 0xc3, 0x23, 0x06, 0x36, 0x15, 0x01, 0x79, 0x0b, + 0x6a, 0x23, 0xcf, 0x3d, 0x75, 0xfc, 0xa9, 0x1d, 0x3a, 0x9e, 0x1b, 0x34, 0x97, 0xb0, 0xae, 0x38, + 0xd0, 0xf8, 0xe7, 0x59, 0xa8, 0x0c, 0x7d, 0xdb, 0x0d, 0xec, 0x11, 0x03, 0x90, 0x4d, 0x28, 0x86, + 0x97, 0xd6, 0xb9, 0x1d, 0x9c, 0x63, 0x57, 0xcb, 0xe6, 0x52, 0x78, 0xb9, 0x6f, 0x07, 0xe7, 0x64, + 0x03, 0x96, 0x78, 0x2b, 0xb1, 0x43, 0x39, 0x53, 0xa4, 0xc8, 0xbb, 0xb0, 0xe2, 0xce, 0xa7, 0x56, + 0xbc, 0x2a, 0xd6, 0xad, 0x82, 0xd9, 0x70, 0xe7, 0xd3, 0xb6, 0x0e, 0x67, 0x9d, 0x3f, 0x99, 0x78, + 0xa3, 0x67, 0xbc, 0x02, 0xde, 0xbd, 0x32, 0x42, 0xb0, 0x8e, 0x37, 0xa0, 0x2a, 0xd0, 0xd4, 0x39, + 0x3b, 0xe7, 0x7d, 0x2c, 0x98, 0x15, 0x4e, 0x80, 0x20, 0x56, 0x42, 0xe8, 0x4c, 0xa9, 0x15, 0x84, + 0xf6, 0x74, 0x26, 0xba, 0x54, 0x66, 0x90, 0x01, 0x03, 0x20, 0xda, 0x0b, 0xed, 0x89, 0x75, 0x4a, + 0x69, 0xd0, 0x2c, 0x0a, 0x34, 0x83, 0xec, 0x51, 0x1a, 0x90, 0xef, 0x40, 0x7d, 0x4c, 0x83, 0xd0, + 0x12, 0x93, 0x41, 0x83, 0x66, 0xe9, 0x6e, 0xee, 0x5e, 0xd9, 0xac, 0x31, 0x68, 0x4b, 0x02, 0xc9, + 0x6b, 0x00, 0xbe, 0xfd, 0xdc, 0x62, 0x03, 0x41, 0x2f, 0x9b, 0x65, 0x3e, 0x0b, 0xbe, 0xfd, 0x7c, + 0x78, 0xb9, 0x4f, 0x2f, 0xc9, 0x1a, 0x14, 0x26, 0xf6, 0x09, 0x9d, 0x34, 0x01, 0x11, 0x3c, 0x61, + 0xfc, 0x12, 0x6c, 0x3c, 0xa1, 0xa1, 0x36, 0x94, 0x81, 0x49, 0xbf, 0x9e, 0xd3, 0x20, 0x64, 0xbd, + 0x0a, 0x42, 0xdb, 0x0f, 0x65, 0xaf, 0x32, 0xbc, 0x57, 0x08, 0x8b, 0x7a, 0x45, 0xdd, 0xb1, 0x24, + 0xc8, 0x22, 0x41, 0x99, 0xba, 0x63, 0x8e, 0x36, 0x0e, 0x80, 0x68, 0x05, 0xef, 0xd2, 0xd0, 0x76, + 0x26, 0x01, 0xf9, 0x18, 0xaa, 0xa1, 0x56, 0x5d, 0x33, 0x73, 0x37, 0x77, 0xaf, 0xa2, 0x96, 0xa6, + 0x96, 0xc1, 0x8c, 0xd1, 0x19, 0xe7, 0x50, 0xda, 0xa3, 0xf4, 0xc0, 0x99, 0x3a, 0x21, 0xd9, 0x80, + 0xc2, 0xa9, 0x73, 0x49, 0xc7, 0xd8, 0xa8, 0xdc, 0xfe, 0x0d, 0x93, 0x27, 0xc9, 0x1d, 0x00, 0xfc, + 0x61, 0x4d, 0xd5, 0x2a, 0xdd, 0xbf, 0x61, 0x96, 0x11, 0x76, 0x18, 0xd8, 0x21, 0xd9, 0x82, 0xe2, + 0x8c, 0xfa, 0x23, 0x2a, 0xd7, 0xc3, 0xfe, 0x0d, 0x53, 0x02, 0x76, 0x8a, 0x50, 0x98, 0xb0, 0xd2, + 0x8d, 0xdf, 0x2f, 0x40, 0x65, 0x40, 0xdd, 0xb1, 0x1c, 0x09, 0x02, 0x79, 0x36, 0xd0, 0x58, 0x59, + 0xd5, 0xc4, 0xdf, 0xe4, 0x4d, 0xa8, 0xe0, 0x94, 0x04, 0xa1, 0xef, 0xb8, 0x67, 0x7c, 0xb7, 0xec, + 0x64, 0x9b, 0x19, 0x13, 0x18, 0x78, 0x80, 0x50, 0xd2, 0x80, 0x9c, 0x3d, 0x95, 0xbb, 0x85, 0xfd, + 0x24, 0x37, 0xa1, 0x64, 0x4f, 0x43, 0xde, 0xbc, 0x2a, 0x82, 0x8b, 0xf6, 0x34, 0xc4, 0xa6, 0xbd, + 0x01, 0xd5, 0x99, 0x7d, 0x35, 0xa5, 0x6e, 0x18, 0x2d, 0xb3, 0xaa, 0x59, 0x11, 0x30, 0x5c, 0x68, + 0x0f, 0x61, 0x55, 0x27, 0x91, 0x95, 0x17, 0x54, 0xe5, 0x2b, 0x1a, 0xb5, 0x68, 0xc3, 0x3b, 0xb0, + 0x2c, 0xf3, 0xf8, 0xbc, 0x3f, 0xb8, 0xfc, 0xca, 0x66, 0x5d, 0x80, 0x65, 0x2f, 0xef, 0x41, 0xe3, + 0xd4, 0x71, 0xed, 0x89, 0x35, 0x9a, 0x84, 0x17, 0xd6, 0x98, 0x4e, 0x42, 0x1b, 0x57, 0x62, 0xc1, + 0xac, 0x23, 0xbc, 0x3d, 0x09, 0x2f, 0x76, 0x19, 0x94, 0xbc, 0x07, 0xe5, 0x53, 0x4a, 0x2d, 0x1c, + 0xac, 0x66, 0x29, 0xb6, 0xa1, 0xe5, 0x0c, 0x99, 0xa5, 0x53, 0x39, 0x57, 0xef, 0x41, 0xc3, 0x9b, + 0x87, 0x67, 0x9e, 0xe3, 0x9e, 0x59, 0xa3, 0x73, 0xdb, 0xb5, 0x9c, 0x31, 0xae, 0xcd, 0xfc, 0x4e, + 0xf6, 0x41, 0xc6, 0xac, 0x4b, 0x5c, 0xfb, 0xdc, 0x76, 0xbb, 0x63, 0xf2, 0x36, 0x2c, 0x4f, 0xec, + 0x20, 0xb4, 0xce, 0xbd, 0x99, 0x35, 0x9b, 0x9f, 0x3c, 0xa3, 0x57, 0xcd, 0x1a, 0x0e, 0x44, 0x8d, + 0x81, 0xf7, 0xbd, 0xd9, 0x11, 0x02, 0xd9, 0xd2, 0xc3, 0x76, 0xf2, 0x46, 0xb0, 0x25, 0x5d, 0x33, + 0xcb, 0x0c, 0xc2, 0x2b, 0xfd, 0x0a, 0x56, 0x71, 0x7a, 0x46, 0xf3, 0x20, 0xf4, 0xa6, 0x96, 0x4f, + 0x47, 0x9e, 0x3f, 0x0e, 0x9a, 0x15, 0x5c, 0x6b, 0xdf, 0x15, 0x8d, 0xd5, 0xe6, 0x78, 0x7b, 0x97, + 0x06, 0x61, 0x1b, 0x89, 0x4d, 0x4e, 0xdb, 0x71, 0x43, 0xff, 0xca, 0x5c, 0x19, 0x27, 0xe1, 0xe4, + 0x3d, 0x20, 0xf6, 0x64, 0xe2, 0x3d, 0xb7, 0x02, 0x3a, 0x39, 0xb5, 0xc4, 0x20, 0x36, 0xeb, 0x77, + 0x33, 0xf7, 0x4a, 0x66, 0x03, 0x31, 0x03, 0x3a, 0x39, 0x3d, 0xe2, 0x70, 0xf2, 0x31, 0xe0, 0x26, + 0xb5, 0x4e, 0xa9, 0x1d, 0xce, 0x7d, 0x1a, 0x34, 0x97, 0xef, 0xe6, 0xee, 0xd5, 0x1f, 0xae, 0xa8, + 0xf1, 0x42, 0xf0, 0x8e, 0x13, 0x9a, 0x55, 0x46, 0x27, 0xd2, 0xc1, 0xd6, 0x2e, 0x6c, 0xa4, 0x37, + 0x89, 0x2d, 0x2a, 0x36, 0x2a, 0x6c, 0x31, 0xe6, 0x4d, 0xf6, 0x93, 0xed, 0xec, 0x0b, 0x7b, 0x32, + 0xa7, 0xb8, 0x0a, 0xab, 0x26, 0x4f, 0x7c, 0x3f, 0xfb, 0x69, 0xc6, 0xf8, 0xbd, 0x0c, 0x54, 0x79, + 0x2f, 0x83, 0x99, 0xe7, 0x06, 0x94, 0xbc, 0x09, 0x35, 0xb9, 0x1a, 0xa8, 0xef, 0x7b, 0xbe, 0xe0, + 0x96, 0x72, 0xe5, 0x75, 0x18, 0x8c, 0x7c, 0x17, 0x1a, 0x92, 0x68, 0xe6, 0x53, 0x67, 0x6a, 0x9f, + 0xc9, 0xa2, 0xe5, 0x52, 0x3a, 0x12, 0x60, 0xf2, 0x41, 0x54, 0x9e, 0xef, 0xcd, 0x43, 0x8a, 0x6b, + 0xbd, 0xf2, 0xb0, 0x2a, 0xba, 0x67, 0x32, 0x98, 0x2a, 0x1d, 0x53, 0xaf, 0xb0, 0xce, 0x8d, 0xdf, + 0xca, 0x00, 0x61, 0xcd, 0x1e, 0x7a, 0xbc, 0x80, 0x88, 0x23, 0xc5, 0x72, 0x66, 0x5e, 0x79, 0x87, + 0x64, 0x5f, 0xb4, 0x43, 0x0c, 0x28, 0xf0, 0xb6, 0xe7, 0x53, 0xda, 0xce, 0x51, 0x3f, 0xca, 0x97, + 0x72, 0x8d, 0xbc, 0xf1, 0x9f, 0x73, 0xb0, 0xc6, 0xd6, 0xa9, 0x4b, 0x27, 0xad, 0xd1, 0x88, 0xce, + 0xd4, 0xde, 0xb9, 0x03, 0x15, 0xd7, 0x1b, 0x53, 0xb9, 0x62, 0x79, 0xc3, 0x80, 0x81, 0xb4, 0xe5, + 0x7a, 0x6e, 0x3b, 0x2e, 0x6f, 0x38, 0x1f, 0xcc, 0x32, 0x42, 0xb0, 0xd9, 0x6f, 0xc3, 0xf2, 0x8c, + 0xba, 0x63, 0x7d, 0x8b, 0xe4, 0xf8, 0xaa, 0x17, 0x60, 0xb1, 0x3b, 0xee, 0x40, 0xe5, 0x74, 0xce, + 0xe9, 0x18, 0x63, 0xc9, 0xe3, 0x1a, 0x00, 0x01, 0x6a, 0x71, 0xfe, 0x32, 0x9b, 0x07, 0xe7, 0x88, + 0x2d, 0x20, 0xb6, 0xc8, 0xd2, 0x0c, 0x75, 0x1b, 0x60, 0x3c, 0x0f, 0x42, 0xb1, 0x63, 0x96, 0x10, + 0x59, 0x66, 0x10, 0xbe, 0x63, 0xbe, 0x07, 0xab, 0x53, 0xfb, 0xd2, 0xc2, 0xb5, 0x63, 0x39, 0xae, + 0x75, 0x3a, 0x41, 0xa6, 0x5e, 0x44, 0xba, 0xc6, 0xd4, 0xbe, 0xfc, 0x82, 0x61, 0xba, 0xee, 0x1e, + 0xc2, 0x19, 0x5b, 0x19, 0xf1, 0x91, 0xb0, 0x7c, 0x1a, 0x50, 0xff, 0x82, 0x22, 0x27, 0xc8, 0x9b, + 0x75, 0x01, 0x36, 0x39, 0x94, 0xb5, 0x68, 0xca, 0xfa, 0x1d, 0x4e, 0x46, 0x7c, 0xdb, 0x9b, 0xc5, + 0xa9, 0xe3, 0xee, 0x87, 0x93, 0x11, 0x3b, 0xaf, 0x18, 0x1f, 0x99, 0x51, 0xdf, 0x7a, 0xf6, 0x1c, + 0xf7, 0x70, 0x1e, 0xf9, 0xc6, 0x11, 0xf5, 0x9f, 0x3e, 0x67, 0x22, 0xc5, 0x28, 0x40, 0x46, 0x64, + 0x5f, 0x35, 0x2b, 0xb8, 0xc1, 0x4b, 0xa3, 0x80, 0xb1, 0x20, 0xfb, 0x8a, 0x6d, 0x42, 0xd6, 0x5a, + 0x1b, 0x67, 0x81, 0x8e, 0xb1, 0xf8, 0x00, 0x39, 0x6a, 0x0d, 0x1b, 0xdb, 0x12, 0x08, 0x56, 0x4f, + 0xc0, 0x56, 0xbd, 0x6c, 0xec, 0xe9, 0xc4, 0x3e, 0x0b, 0x90, 0xa5, 0xd4, 0xcc, 0xaa, 0x00, 0xee, + 0x31, 0x98, 0xf1, 0x25, 0xac, 0x27, 0xe6, 0x56, 0xec, 0x19, 0x26, 0x42, 0x20, 0x04, 0xe7, 0xb5, + 0x64, 0x8a, 0x54, 0xda, 0xa4, 0x65, 0x53, 0x26, 0xcd, 0xf8, 0xed, 0x0c, 0x54, 0x45, 0xc9, 0x28, + 0xec, 0x90, 0x6d, 0x20, 0x72, 0x16, 0xc3, 0x4b, 0x67, 0x6c, 0x9d, 0x5c, 0x85, 0x34, 0xe0, 0x8b, + 0x66, 0xff, 0x86, 0xd9, 0x10, 0xb8, 0xe1, 0xa5, 0x33, 0xde, 0x61, 0x18, 0x72, 0x1f, 0x1a, 0x31, + 0xfa, 0x20, 0xf4, 0xf9, 0x8a, 0xde, 0xbf, 0x61, 0xd6, 0x35, 0xea, 0x41, 0xe8, 0xb3, 0x3d, 0xc2, + 0x44, 0xa9, 0x79, 0x68, 0x39, 0xee, 0x98, 0x5e, 0xe2, 0x32, 0xaa, 0x99, 0x15, 0x0e, 0xeb, 0x32, + 0xd0, 0x4e, 0x1d, 0xaa, 0x7a, 0x71, 0xc6, 0x19, 0x94, 0xa4, 0x1c, 0x86, 0x82, 0x48, 0xa2, 0x49, + 0x66, 0x39, 0x54, 0x2d, 0xb9, 0x09, 0xa5, 0x78, 0x0b, 0xcc, 0x62, 0xf8, 0xca, 0x15, 0x1b, 0x9f, + 0x43, 0xe3, 0x80, 0x2d, 0x1e, 0x97, 0x2d, 0x56, 0x21, 0x57, 0x6e, 0xc0, 0x92, 0xb6, 0x69, 0xca, + 0xa6, 0x48, 0xb1, 0x33, 0xf7, 0xdc, 0x0b, 0x42, 0x51, 0x0b, 0xfe, 0x36, 0x7e, 0x3f, 0x03, 0xa4, + 0x13, 0x84, 0xce, 0xd4, 0x0e, 0xe9, 0x1e, 0x55, 0x6c, 0xa1, 0x0f, 0x55, 0x56, 0xda, 0xd0, 0x6b, + 0x71, 0x41, 0x8f, 0x0b, 0x14, 0xef, 0x8a, 0x6d, 0xbc, 0x98, 0x61, 0x5b, 0xa7, 0xe6, 0x6c, 0x3e, + 0x56, 0x00, 0xdb, 0x65, 0xa1, 0xed, 0x9f, 0xd1, 0x10, 0xc5, 0x43, 0x21, 0xd7, 0x00, 0x07, 0x31, + 0xc1, 0x70, 0xeb, 0x07, 0xb0, 0xb2, 0x50, 0x86, 0xce, 0x97, 0xcb, 0x29, 0x7c, 0x39, 0xa7, 0xf3, + 0x65, 0x0b, 0x56, 0x63, 0xed, 0x12, 0x2b, 0x6d, 0x13, 0x8a, 0x6c, 0x43, 0x30, 0xe1, 0x20, 0xc3, + 0xa5, 0xd5, 0x53, 0x4a, 0x99, 0x78, 0xfd, 0x3e, 0xac, 0x9d, 0x52, 0xea, 0xdb, 0x21, 0x22, 0x71, + 0xc7, 0xb0, 0x19, 0x12, 0x05, 0xaf, 0x08, 0xdc, 0xc0, 0x0e, 0x8f, 0xa8, 0xcf, 0x66, 0xca, 0xf8, + 0x3f, 0x19, 0x58, 0x66, 0x1c, 0xf4, 0xd0, 0x76, 0xaf, 0xe4, 0x38, 0x1d, 0xa4, 0x8e, 0xd3, 0x3d, + 0xed, 0x30, 0xd4, 0xa8, 0xbf, 0xe9, 0x20, 0xe5, 0x92, 0x83, 0x44, 0xee, 0x42, 0x35, 0xd6, 0xd6, + 0x02, 0xb6, 0x15, 0x02, 0xd5, 0xc8, 0x48, 0x22, 0x5d, 0xd2, 0x24, 0xd2, 0x9f, 0x7d, 0x70, 0xdf, + 0x86, 0x46, 0xd4, 0x19, 0x31, 0xb2, 0x04, 0xf2, 0x6c, 0xa1, 0x8a, 0x02, 0xf0, 0xb7, 0xf1, 0x8f, + 0x33, 0x9c, 0xb0, 0xed, 0x39, 0x91, 0xd4, 0x4b, 0x20, 0xcf, 0xa4, 0x6c, 0x49, 0xc8, 0x7e, 0x5f, + 0xab, 0x43, 0x7c, 0x0b, 0x43, 0x70, 0x13, 0x4a, 0x01, 0x13, 0xa1, 0xed, 0x09, 0x1f, 0x85, 0x92, + 0x59, 0x64, 0xe9, 0xd6, 0x64, 0x12, 0x8d, 0x4e, 0x51, 0x97, 0xd7, 0xdf, 0x81, 0x15, 0xad, 0xcd, + 0x2f, 0xe8, 0x5d, 0x0f, 0xc8, 0x81, 0x13, 0x84, 0xc7, 0x6e, 0x30, 0xd3, 0x84, 0xbc, 0x5b, 0x50, + 0x66, 0xdc, 0x98, 0xb5, 0x37, 0x10, 0x12, 0x3d, 0x63, 0xcf, 0xac, 0xb5, 0x01, 0x22, 0xed, 0x4b, + 0x81, 0xcc, 0x0a, 0xa4, 0x7d, 0x89, 0x48, 0xe3, 0x53, 0x58, 0x8d, 0x95, 0x27, 0xaa, 0x7e, 0x03, + 0x0a, 0xf3, 0xf0, 0xd2, 0x93, 0x62, 0x7c, 0x45, 0xac, 0x26, 0xa6, 0x84, 0x9a, 0x1c, 0x63, 0x3c, + 0x86, 0x95, 0x1e, 0x7d, 0x2e, 0x36, 0xbc, 0x6c, 0xc8, 0xdb, 0x90, 0x7f, 0x89, 0x62, 0x8a, 0x78, + 0x63, 0x1b, 0x88, 0x9e, 0x59, 0xd4, 0xaa, 0xe9, 0xa9, 0x99, 0x98, 0x9e, 0x6a, 0xbc, 0x0d, 0x64, + 0xe0, 0x9c, 0xb9, 0x87, 0x34, 0x08, 0xec, 0x33, 0xc5, 0x22, 0x1a, 0x90, 0x9b, 0x06, 0x67, 0x82, + 0x9f, 0xb1, 0x9f, 0xc6, 0x87, 0xb0, 0x1a, 0xa3, 0x13, 0x05, 0xbf, 0x06, 0xe5, 0xc0, 0x39, 0x73, + 0x51, 0x08, 0x13, 0x45, 0x47, 0x00, 0x63, 0x0f, 0xd6, 0xbe, 0xa0, 0xbe, 0x73, 0x7a, 0xf5, 0xb2, + 0xe2, 0xe3, 0xe5, 0x64, 0x93, 0xe5, 0x74, 0x60, 0x3d, 0x51, 0x8e, 0xa8, 0x9e, 0x2f, 0x6a, 0x31, + 0x93, 0x25, 0x93, 0x27, 0x34, 0x1e, 0x99, 0xd5, 0x79, 0xa4, 0x71, 0x0c, 0xa4, 0xed, 0xb9, 0x2e, + 0x1d, 0x85, 0x47, 0x94, 0xfa, 0xb2, 0x31, 0xef, 0x6a, 0x2b, 0xb8, 0xf2, 0x70, 0x53, 0x8c, 0x6c, + 0x92, 0xf1, 0x8a, 0xa5, 0x4d, 0x20, 0x3f, 0xa3, 0xfe, 0x14, 0x0b, 0x2e, 0x99, 0xf8, 0xdb, 0x58, + 0x87, 0xd5, 0x58, 0xb1, 0xbc, 0x6d, 0xc6, 0x03, 0x58, 0xdf, 0x75, 0x82, 0xd1, 0x62, 0x85, 0x9b, + 0x50, 0x9c, 0xcd, 0x4f, 0xac, 0x38, 0x0f, 0x7f, 0x4a, 0xaf, 0x8c, 0x26, 0x6c, 0x24, 0x73, 0x88, + 0xb2, 0x7e, 0x3d, 0x03, 0xf9, 0xfd, 0xe1, 0x41, 0x9b, 0x6c, 0x41, 0xc9, 0x71, 0x47, 0xde, 0x94, + 0x09, 0x69, 0xbc, 0xcf, 0x2a, 0x7d, 0xed, 0xb6, 0xbb, 0x05, 0x65, 0x94, 0xed, 0x98, 0x7a, 0x2d, + 0xc4, 0xa4, 0x12, 0x03, 0x1c, 0x78, 0xa3, 0x67, 0x4c, 0xaf, 0xa7, 0x97, 0x33, 0xc7, 0x47, 0xcd, + 0x5d, 0x6a, 0xa6, 0x79, 0x2e, 0x17, 0x44, 0x08, 0xa1, 0xa0, 0xfe, 0x7a, 0x16, 0x88, 0x38, 0x99, + 0xdb, 0x9e, 0x1b, 0x84, 0xbe, 0xed, 0xb8, 0x61, 0x10, 0x97, 0x3c, 0x32, 0x09, 0xc9, 0xe3, 0x1e, + 0x34, 0xf0, 0xb4, 0x17, 0x52, 0x0f, 0x32, 0xeb, 0x6c, 0x24, 0xf9, 0x08, 0xb1, 0x87, 0x31, 0xed, + 0xb7, 0xa0, 0x1e, 0x09, 0x5c, 0xca, 0x6c, 0x92, 0x37, 0xab, 0x4a, 0xe8, 0x12, 0xac, 0x9d, 0x6d, + 0x3a, 0x29, 0x49, 0x28, 0xed, 0x90, 0xcb, 0x76, 0x2b, 0x53, 0xfb, 0xf2, 0x88, 0x4a, 0xf1, 0x0e, + 0xf5, 0x44, 0x03, 0x6a, 0x52, 0xa0, 0xe2, 0x94, 0x5c, 0xce, 0xab, 0x08, 0xa9, 0x0a, 0x69, 0xd2, + 0xc5, 0xa3, 0xa5, 0x74, 0xf1, 0xc8, 0xf8, 0x0f, 0x65, 0x28, 0x8a, 0x61, 0xe0, 0xc2, 0x4e, 0xe8, + 0x5c, 0xd0, 0x48, 0xd8, 0x61, 0x29, 0x26, 0x42, 0xf9, 0x74, 0xea, 0x85, 0x4a, 0xc6, 0xe5, 0x4b, + 0xb1, 0xca, 0x81, 0x42, 0xca, 0xd5, 0xe4, 0x2c, 0x6e, 0xed, 0xc9, 0x71, 0xa2, 0x91, 0x2e, 0xfd, + 0xdc, 0x82, 0xa2, 0x14, 0x97, 0xf2, 0x4a, 0x0d, 0x5c, 0x1a, 0x71, 0x01, 0x77, 0x0b, 0x4a, 0x23, + 0x7b, 0x66, 0x8f, 0x9c, 0xf0, 0x4a, 0x70, 0x4b, 0x95, 0x66, 0xa5, 0x4f, 0xbc, 0x91, 0x3d, 0xb1, + 0x4e, 0xec, 0x89, 0xed, 0x8e, 0xa8, 0x30, 0xa3, 0x54, 0x11, 0xb8, 0xc3, 0x61, 0xe4, 0x3b, 0x50, + 0x17, 0xed, 0x94, 0x54, 0xdc, 0x9a, 0x22, 0x5a, 0x2f, 0xc9, 0x98, 0x3c, 0xee, 0x4d, 0xd9, 0xbc, + 0x9c, 0x52, 0x2e, 0xb9, 0xe6, 0xcc, 0x32, 0x87, 0xec, 0x51, 0xec, 0xad, 0x40, 0x3f, 0xe7, 0x2b, + 0xa8, 0xcc, 0xab, 0xe2, 0xc0, 0x2f, 0xb9, 0xf5, 0x63, 0x51, 0x7c, 0xcd, 0x69, 0xe2, 0xeb, 0xbb, + 0xb0, 0x32, 0x77, 0x03, 0x1a, 0x86, 0x13, 0x3a, 0x56, 0x6d, 0xa9, 0x20, 0x51, 0x43, 0x21, 0x64, + 0x73, 0xb6, 0x61, 0x95, 0xdb, 0x7f, 0x02, 0x3b, 0xf4, 0x82, 0x73, 0x27, 0xb0, 0x02, 0xa6, 0x54, + 0x72, 0x0b, 0xc1, 0x0a, 0xa2, 0x06, 0x02, 0x33, 0xe0, 0x5a, 0xe5, 0x66, 0x82, 0xde, 0xa7, 0x23, + 0xea, 0x5c, 0xd0, 0x31, 0x8a, 0xb6, 0x39, 0x73, 0x3d, 0x96, 0xc7, 0x14, 0x48, 0xd4, 0x53, 0xe6, + 0x53, 0x6b, 0x3e, 0x1b, 0xdb, 0x4c, 0xbe, 0xab, 0x73, 0xfd, 0xc1, 0x9d, 0x4f, 0x8f, 0x39, 0x84, + 0x3c, 0x00, 0x29, 0xbc, 0x8a, 0x35, 0xb3, 0x1c, 0x63, 0xeb, 0x6c, 0xcf, 0x9a, 0x55, 0x41, 0xc1, + 0x65, 0xeb, 0x3b, 0xfa, 0x66, 0x69, 0xb0, 0x15, 0x86, 0x7a, 0x56, 0xb4, 0x61, 0x9a, 0x50, 0x9c, + 0xf9, 0xce, 0x85, 0x1d, 0xd2, 0xe6, 0x0a, 0x3f, 0xe1, 0x44, 0x92, 0x31, 0x49, 0xc7, 0x75, 0x42, + 0xc7, 0x0e, 0x3d, 0xbf, 0x49, 0x10, 0x17, 0x01, 0xc8, 0x7d, 0x58, 0xc1, 0x75, 0x12, 0x84, 0x76, + 0x38, 0x0f, 0x84, 0xe0, 0xbe, 0x8a, 0x0b, 0x0a, 0x55, 0x8f, 0x01, 0xc2, 0x51, 0x76, 0x27, 0x9f, + 0xc0, 0x06, 0x5f, 0x1a, 0x0b, 0x5b, 0x73, 0x8d, 0x0d, 0x07, 0xb6, 0x68, 0x15, 0x29, 0xda, 0xf1, + 0x3d, 0xfa, 0x19, 0x6c, 0x8a, 0xe5, 0xb2, 0x90, 0x73, 0x5d, 0xe5, 0x5c, 0xe3, 0x24, 0x89, 0xac, + 0xdb, 0xb0, 0xc2, 0x9a, 0xe6, 0x8c, 0x2c, 0x51, 0x02, 0xdb, 0x15, 0x1b, 0xac, 0x17, 0x98, 0x69, + 0x99, 0x23, 0x4d, 0xc4, 0x3d, 0xa5, 0x57, 0xe4, 0x73, 0x58, 0xe6, 0xcb, 0x07, 0xb5, 0x53, 0x3c, + 0xfc, 0xb6, 0xf0, 0xf0, 0x5b, 0x17, 0x83, 0xdb, 0x56, 0x58, 0x3c, 0xff, 0xea, 0xa3, 0x58, 0x9a, + 0x6d, 0x8d, 0x89, 0x73, 0x4a, 0x43, 0x67, 0x4a, 0x9b, 0x9b, 0x7c, 0xb1, 0xc9, 0x34, 0xdb, 0xb5, + 0xf3, 0x19, 0x62, 0x9a, 0x9c, 0x55, 0xf2, 0x14, 0xae, 0xe3, 0x89, 0x17, 0x50, 0x69, 0x39, 0x6c, + 0xde, 0x14, 0x1b, 0x92, 0x01, 0xa5, 0x08, 0xce, 0xf4, 0x18, 0xae, 0x33, 0x2a, 0xfb, 0xee, 0x2d, + 0x5c, 0x18, 0x35, 0xae, 0x3a, 0x4a, 0x1b, 0x2f, 0x13, 0x77, 0xce, 0xed, 0xe7, 0x92, 0xa9, 0xbe, + 0x86, 0xdc, 0x04, 0x18, 0x48, 0x98, 0x03, 0xf7, 0x60, 0x45, 0xcc, 0x42, 0xc4, 0x4c, 0x9b, 0xb7, + 0xf1, 0x18, 0xba, 0x29, 0xfb, 0xb8, 0xc0, 0x6d, 0xcd, 0x06, 0x9f, 0x17, 0x8d, 0xff, 0xee, 0x03, + 0x91, 0x93, 0xa2, 0x15, 0xf4, 0xfa, 0xcb, 0x0a, 0x5a, 0x11, 0xd3, 0x14, 0x81, 0x8c, 0xdf, 0xcd, + 0x70, 0xa9, 0x45, 0x50, 0x07, 0x9a, 0xbe, 0xce, 0xf9, 0x9a, 0xe5, 0xb9, 0x93, 0x2b, 0xc1, 0xea, + 0x80, 0x83, 0xfa, 0xee, 0x04, 0x79, 0x8d, 0xe3, 0xea, 0x24, 0xfc, 0x80, 0xac, 0x4a, 0x20, 0x12, + 0xdd, 0x81, 0xca, 0x6c, 0x7e, 0x32, 0x71, 0x46, 0x9c, 0x24, 0xc7, 0x4b, 0xe1, 0x20, 0x24, 0x78, + 0x03, 0xaa, 0x62, 0xad, 0x73, 0x8a, 0x3c, 0x52, 0x54, 0x04, 0x0c, 0x49, 0xf0, 0x00, 0xa6, 0x3e, + 0x32, 0xbb, 0xaa, 0x89, 0xbf, 0x8d, 0x1d, 0x58, 0x8b, 0x37, 0x5a, 0x48, 0x07, 0xf7, 0xa1, 0x24, + 0x38, 0xa9, 0xb4, 0x64, 0xd5, 0xe3, 0xa3, 0x61, 0x2a, 0xbc, 0xf1, 0x1f, 0x0b, 0xb0, 0x2a, 0xc7, + 0x88, 0x4d, 0xf6, 0x60, 0x3e, 0x9d, 0xda, 0x7e, 0x0a, 0x8b, 0xce, 0xbc, 0x98, 0x45, 0x67, 0x17, + 0x58, 0x74, 0xdc, 0x94, 0xc1, 0x39, 0x7c, 0xdc, 0x94, 0xc1, 0x56, 0x17, 0xd7, 0x2e, 0x75, 0x83, + 0x79, 0x4d, 0x80, 0x87, 0xdc, 0x30, 0xbf, 0x70, 0xa0, 0x14, 0x52, 0x0e, 0x14, 0xfd, 0x38, 0x58, + 0x4a, 0x1c, 0x07, 0x6f, 0x00, 0x5f, 0xc6, 0x72, 0x3d, 0x16, 0xb9, 0xc2, 0x89, 0x30, 0xb1, 0x20, + 0xdf, 0x81, 0xe5, 0x24, 0x07, 0xe6, 0xac, 0xbe, 0x9e, 0xc2, 0x7f, 0x9d, 0x29, 0x45, 0x91, 0x42, + 0x23, 0x2e, 0x0b, 0xfe, 0xeb, 0x4c, 0xe9, 0x01, 0x62, 0x24, 0x7d, 0x07, 0x80, 0xd7, 0x8d, 0xdb, + 0x18, 0x70, 0x1b, 0xbf, 0x9d, 0x58, 0x99, 0xda, 0xa8, 0x6f, 0xb3, 0xc4, 0xdc, 0xa7, 0xb8, 0xaf, + 0xcb, 0x98, 0x13, 0xb7, 0xf4, 0x27, 0x50, 0xf7, 0x66, 0xd4, 0xb5, 0x22, 0x2e, 0x58, 0xc1, 0xa2, + 0x1a, 0xa2, 0xa8, 0xae, 0x84, 0x9b, 0x35, 0x46, 0xa7, 0x92, 0xe4, 0x33, 0x3e, 0xc8, 0x54, 0xcb, + 0x59, 0xbd, 0x26, 0x67, 0x1d, 0x09, 0xa3, 0xac, 0x1f, 0x42, 0xc5, 0xa7, 0x81, 0x37, 0x99, 0x73, + 0xeb, 0x7b, 0x0d, 0xd7, 0x91, 0x34, 0x47, 0x9a, 0x0a, 0x63, 0xea, 0x54, 0xc6, 0x6f, 0x64, 0xa0, + 0xa2, 0xf5, 0x81, 0xac, 0xc3, 0x4a, 0xbb, 0xdf, 0x3f, 0xea, 0x98, 0xad, 0x61, 0xf7, 0x8b, 0x8e, + 0xd5, 0x3e, 0xe8, 0x0f, 0x3a, 0x8d, 0x1b, 0x0c, 0x7c, 0xd0, 0x6f, 0xb7, 0x0e, 0xac, 0xbd, 0xbe, + 0xd9, 0x96, 0xe0, 0x0c, 0xd9, 0x00, 0x62, 0x76, 0x0e, 0xfb, 0xc3, 0x4e, 0x0c, 0x9e, 0x25, 0x0d, + 0xa8, 0xee, 0x98, 0x9d, 0x56, 0x7b, 0x5f, 0x40, 0x72, 0x64, 0x0d, 0x1a, 0x7b, 0xc7, 0xbd, 0xdd, + 0x6e, 0xef, 0x89, 0xd5, 0x6e, 0xf5, 0xda, 0x9d, 0x83, 0xce, 0x6e, 0x23, 0x4f, 0x6a, 0x50, 0x6e, + 0xed, 0xb4, 0x7a, 0xbb, 0xfd, 0x5e, 0x67, 0xb7, 0x51, 0x30, 0xfe, 0x47, 0x06, 0x20, 0x6a, 0x28, + 0xe3, 0xab, 0x51, 0x53, 0x75, 0x6f, 0xd7, 0xfa, 0x42, 0xa7, 0x38, 0x5f, 0xf5, 0x63, 0x69, 0xf2, + 0x10, 0x8a, 0xde, 0x3c, 0x1c, 0x79, 0x53, 0x2e, 0xa8, 0xd7, 0x1f, 0x36, 0x17, 0xf2, 0xf5, 0x39, + 0xde, 0x94, 0x84, 0x31, 0x8f, 0x56, 0xee, 0x65, 0x1e, 0xad, 0xb8, 0xeb, 0x8c, 0xcb, 0x75, 0x9a, + 0xeb, 0xec, 0x36, 0x40, 0xf0, 0x9c, 0xd2, 0x19, 0x1a, 0x63, 0xc4, 0x2e, 0x28, 0x23, 0x64, 0xc8, + 0xf4, 0xb8, 0x3f, 0xca, 0xc0, 0x3a, 0xae, 0xa5, 0x71, 0x92, 0x89, 0xdd, 0x85, 0xca, 0xc8, 0xf3, + 0x66, 0x4c, 0xf5, 0x8f, 0xe4, 0x35, 0x1d, 0xc4, 0x18, 0x14, 0x67, 0xc8, 0xa7, 0x9e, 0x3f, 0xa2, + 0x82, 0x87, 0x01, 0x82, 0xf6, 0x18, 0x84, 0xed, 0x21, 0xb1, 0x09, 0x39, 0x05, 0x67, 0x61, 0x15, + 0x0e, 0xe3, 0x24, 0x1b, 0xb0, 0x74, 0xe2, 0x53, 0x7b, 0x74, 0x2e, 0xb8, 0x97, 0x48, 0x91, 0xef, + 0x46, 0x46, 0xa9, 0x11, 0xdb, 0x13, 0x13, 0xca, 0x1b, 0x5f, 0x32, 0x97, 0x05, 0xbc, 0x2d, 0xc0, + 0xec, 0x9c, 0xb7, 0x4f, 0x6c, 0x77, 0xec, 0xb9, 0x74, 0x2c, 0xb4, 0xdc, 0x08, 0x60, 0x1c, 0xc1, + 0x46, 0xb2, 0x7f, 0x82, 0xdf, 0x7d, 0xac, 0xf1, 0x3b, 0xae, 0x5e, 0x6e, 0x5d, 0xbf, 0xc7, 0x34, + 0xde, 0xf7, 0xaf, 0xf3, 0x90, 0x67, 0xea, 0xc6, 0xb5, 0x9a, 0x89, 0xae, 0x3f, 0xe6, 0x16, 0xfc, + 0x9c, 0x68, 0xfb, 0xe2, 0x02, 0x98, 0x98, 0x2c, 0x84, 0xa0, 0xe0, 0xa5, 0xd0, 0x3e, 0x1d, 0x5d, + 0x08, 0xc9, 0x9b, 0xa3, 0x4d, 0x3a, 0xba, 0x40, 0x75, 0xde, 0x0e, 0x79, 0x5e, 0xce, 0xaf, 0x8a, + 0x81, 0x1d, 0x62, 0x4e, 0x81, 0xc2, 0x7c, 0x45, 0x85, 0xc2, 0x5c, 0x4d, 0x28, 0x3a, 0xee, 0x89, + 0x37, 0x77, 0xc7, 0xc8, 0x9e, 0x4a, 0xa6, 0x4c, 0xa2, 0x5b, 0x15, 0x39, 0x29, 0x3b, 0xda, 0x39, + 0x37, 0x2a, 0x31, 0xc0, 0x90, 0x1d, 0xee, 0x1f, 0x40, 0x39, 0xb8, 0x72, 0x47, 0x3a, 0x0f, 0x5a, + 0x13, 0xe3, 0xc3, 0x7a, 0xbf, 0x3d, 0xb8, 0x72, 0x47, 0xb8, 0xe2, 0x4b, 0x81, 0xf8, 0x45, 0x1e, + 0x41, 0x49, 0x39, 0x22, 0xf8, 0x09, 0x72, 0x53, 0xcf, 0x21, 0xbd, 0x0f, 0xdc, 0xde, 0xa3, 0x48, + 0xc9, 0xfb, 0xb0, 0x84, 0xde, 0x82, 0xa0, 0x59, 0xc5, 0x4c, 0x52, 0xa9, 0x64, 0xcd, 0x40, 0x8f, + 0x26, 0x1d, 0xa3, 0xe7, 0xc0, 0x14, 0x64, 0x6c, 0x98, 0x4e, 0x27, 0xf6, 0xcc, 0x1a, 0xa1, 0xfa, + 0x56, 0xe3, 0x8e, 0x41, 0x06, 0x69, 0xa3, 0x06, 0x77, 0x17, 0xaa, 0xe8, 0xe4, 0x41, 0x1a, 0x97, + 0xcb, 0xa1, 0x39, 0x13, 0x18, 0x6c, 0x6f, 0x62, 0xcf, 0x7a, 0xc1, 0xd6, 0x53, 0xa8, 0xc5, 0x1a, + 0xa3, 0x1b, 0x80, 0x6a, 0xdc, 0x00, 0xf4, 0x96, 0x6e, 0x00, 0x8a, 0x8e, 0x42, 0x91, 0x4d, 0x37, + 0x08, 0xfd, 0x00, 0x4a, 0x72, 0x2c, 0x18, 0xcf, 0x39, 0xee, 0x3d, 0xed, 0xf5, 0xbf, 0xec, 0x59, + 0x83, 0xaf, 0x7a, 0xed, 0xc6, 0x0d, 0xb2, 0x0c, 0x95, 0x56, 0x1b, 0xd9, 0x18, 0x02, 0x32, 0x8c, + 0xe4, 0xa8, 0x35, 0x18, 0x28, 0x48, 0xd6, 0xd8, 0x83, 0x46, 0xb2, 0xab, 0x6c, 0x51, 0x87, 0x12, + 0x26, 0x9c, 0x31, 0x11, 0x80, 0x29, 0xf2, 0xdc, 0xbf, 0xc2, 0xd5, 0x24, 0x9e, 0x30, 0x1e, 0x41, + 0x83, 0x1d, 0xec, 0x6c, 0xac, 0x75, 0x37, 0xeb, 0x84, 0x89, 0xde, 0xba, 0x43, 0xa6, 0x64, 0x56, + 0x38, 0x0c, 0xab, 0x32, 0x3e, 0x86, 0x15, 0x2d, 0x5b, 0x64, 0x78, 0x61, 0xc2, 0x42, 0xd2, 0xf0, + 0x82, 0x6a, 0x36, 0xc7, 0x18, 0x9b, 0xb0, 0xce, 0x92, 0x9d, 0x0b, 0xea, 0x86, 0x83, 0xf9, 0x09, + 0xf7, 0xce, 0x3b, 0x9e, 0xcb, 0xd4, 0xef, 0xb2, 0xc2, 0x5c, 0xbf, 0x4b, 0xb6, 0x85, 0x8d, 0x86, + 0xb3, 0xc5, 0x2d, 0xad, 0x06, 0xcc, 0xb8, 0x8d, 0x7f, 0x63, 0xb6, 0x9a, 0xb2, 0x02, 0xb1, 0x61, + 0x3d, 0xea, 0x74, 0x4c, 0xab, 0xdf, 0x3b, 0xe8, 0xf6, 0xd8, 0xe1, 0xc0, 0x86, 0x15, 0x01, 0x7b, + 0x7b, 0x08, 0xc9, 0x18, 0x0d, 0xa8, 0x3f, 0xa1, 0x61, 0xd7, 0x3d, 0xf5, 0xc4, 0x60, 0x18, 0x7f, + 0x61, 0x09, 0x96, 0x15, 0x28, 0xb2, 0xf5, 0x5c, 0x50, 0x3f, 0x70, 0x3c, 0x17, 0xd7, 0x49, 0xd9, + 0x94, 0x49, 0xc6, 0xde, 0x84, 0x96, 0x86, 0x62, 0xc6, 0x1a, 0x62, 0x85, 0x5e, 0x87, 0x32, 0xc6, + 0x3b, 0xb0, 0xec, 0x8c, 0xa9, 0x1b, 0x3a, 0xe1, 0x95, 0x15, 0xb3, 0x32, 0xd7, 0x25, 0x58, 0xc8, + 0x19, 0x6b, 0x50, 0xb0, 0x27, 0x8e, 0x2d, 0xa3, 0x1e, 0x78, 0x82, 0x41, 0x47, 0xde, 0xc4, 0xf3, + 0x51, 0x6f, 0x29, 0x9b, 0x3c, 0x41, 0x1e, 0xc0, 0x1a, 0xd3, 0xa1, 0x74, 0xd3, 0x3f, 0x72, 0x28, + 0x6e, 0xf0, 0x26, 0xee, 0x7c, 0x7a, 0x14, 0x99, 0xff, 0x19, 0x86, 0x49, 0x17, 0x2c, 0x87, 0x10, + 0x27, 0x55, 0x06, 0x6e, 0x95, 0x58, 0x71, 0xe7, 0xd3, 0x16, 0x62, 0x14, 0xfd, 0x43, 0x58, 0x67, + 0xf4, 0x4a, 0x00, 0x55, 0x39, 0x96, 0x31, 0x07, 0x2b, 0xac, 0x2b, 0x70, 0x2a, 0xcf, 0x2d, 0x28, + 0xf3, 0x56, 0xb1, 0x25, 0x51, 0xe0, 0x36, 0x0b, 0x6c, 0x0a, 0xf5, 0x83, 0x85, 0x00, 0x05, 0x6e, + 0x08, 0x48, 0x06, 0x28, 0x68, 0x21, 0x0e, 0xa5, 0x64, 0x88, 0xc3, 0x43, 0x58, 0x3f, 0x61, 0x6b, + 0xf4, 0x9c, 0xda, 0x63, 0xea, 0x5b, 0xd1, 0xca, 0xe7, 0xea, 0xe6, 0x2a, 0x43, 0xee, 0x23, 0x4e, + 0x6d, 0x14, 0x26, 0x09, 0x32, 0xc6, 0x43, 0xc7, 0x56, 0xe8, 0x59, 0x28, 0x20, 0x22, 0x0b, 0x2b, + 0x99, 0x35, 0x0e, 0x1e, 0x7a, 0x6d, 0x06, 0x8c, 0xd3, 0x9d, 0xf9, 0xf6, 0xec, 0x5c, 0x28, 0x83, + 0x8a, 0xee, 0x09, 0x03, 0x92, 0xd7, 0xa0, 0xc8, 0xf6, 0x84, 0x4b, 0xb9, 0xbf, 0x97, 0xab, 0x59, + 0x12, 0x44, 0xde, 0x82, 0x25, 0xac, 0x23, 0x68, 0x36, 0x70, 0x43, 0x54, 0xa3, 0xa3, 0xc2, 0x71, + 0x4d, 0x81, 0x63, 0xe2, 0xf6, 0xdc, 0x77, 0x38, 0x1f, 0x2b, 0x9b, 0xf8, 0x9b, 0xfc, 0x50, 0x63, + 0x8a, 0xab, 0x98, 0xf7, 0x2d, 0x91, 0x37, 0xb1, 0x14, 0xaf, 0xe3, 0x8f, 0xdf, 0x2a, 0xb7, 0xfa, + 0x51, 0xbe, 0x54, 0x69, 0x54, 0x8d, 0x26, 0xc6, 0x65, 0x98, 0x74, 0xe4, 0x5d, 0x50, 0xff, 0x2a, + 0xb6, 0x47, 0x32, 0xb0, 0xb9, 0x80, 0x8a, 0xdc, 0xbb, 0xbe, 0x80, 0x5b, 0x53, 0x6f, 0x2c, 0x85, + 0x82, 0xaa, 0x04, 0x1e, 0x7a, 0x63, 0x26, 0xbc, 0xac, 0x28, 0xa2, 0x53, 0xc7, 0x75, 0x82, 0x73, + 0x3a, 0x16, 0xb2, 0x41, 0x43, 0x22, 0xf6, 0x04, 0x9c, 0x49, 0xe0, 0x33, 0xdf, 0x3b, 0x53, 0x47, + 0x65, 0xc6, 0x54, 0x69, 0xe3, 0x13, 0x28, 0xf0, 0x19, 0x64, 0x1b, 0x05, 0xe7, 0x37, 0x23, 0x36, + 0x0a, 0x42, 0x9b, 0x50, 0x74, 0x69, 0xf8, 0xdc, 0xf3, 0x9f, 0x49, 0x5f, 0x91, 0x48, 0x1a, 0x3f, + 0x41, 0xc3, 0xa5, 0x0a, 0xb0, 0xe1, 0xc6, 0x07, 0xb6, 0x84, 0xf9, 0x12, 0x0c, 0xce, 0x6d, 0x61, + 0x4b, 0x2d, 0x21, 0x60, 0x70, 0x6e, 0x2f, 0x2c, 0xe1, 0xec, 0x62, 0x8c, 0xcd, 0x5b, 0x50, 0x97, + 0x21, 0x3d, 0x81, 0x35, 0xa1, 0xa7, 0xa1, 0xd8, 0x92, 0x55, 0x11, 0xcf, 0x13, 0x1c, 0xd0, 0xd3, + 0xd0, 0x38, 0x84, 0x15, 0xb1, 0x69, 0xfa, 0x33, 0x2a, 0xab, 0xfe, 0x34, 0x4d, 0x2b, 0xaa, 0x3c, + 0x5c, 0x8d, 0x8b, 0x1b, 0x5c, 0xb0, 0x8b, 0xa9, 0x4a, 0xc6, 0x8f, 0x23, 0x0b, 0x22, 0x13, 0x46, + 0x44, 0x79, 0x42, 0x37, 0x91, 0x2e, 0x36, 0xe9, 0xa9, 0x56, 0x1a, 0x90, 0x33, 0x66, 0xa3, 0x13, + 0xcc, 0x47, 0x23, 0x19, 0x6a, 0x55, 0x32, 0x65, 0xd2, 0xf8, 0x77, 0x19, 0x58, 0xc5, 0xc2, 0xa4, + 0x56, 0x27, 0x4e, 0x8a, 0x9f, 0xba, 0x91, 0x6c, 0x7e, 0x74, 0x09, 0x90, 0x27, 0xbe, 0xb9, 0xfb, + 0x22, 0xbf, 0xe0, 0xbe, 0xf8, 0x2e, 0x34, 0xc6, 0x74, 0xe2, 0xe0, 0x52, 0x92, 0x02, 0x15, 0x97, + 0x60, 0x97, 0x25, 0x5c, 0x58, 0x19, 0x8c, 0xbf, 0x9a, 0x81, 0x15, 0x2e, 0xaf, 0xa1, 0xdd, 0x46, + 0x0c, 0xd4, 0x63, 0x69, 0xa0, 0x10, 0xec, 0x54, 0xf4, 0x29, 0x92, 0x63, 0x10, 0xca, 0x89, 0xf7, + 0x6f, 0x08, 0xc3, 0x85, 0x80, 0x92, 0xef, 0xa3, 0x26, 0xea, 0x5a, 0x08, 0x14, 0x72, 0xf8, 0xcd, + 0x14, 0x09, 0x51, 0x65, 0x67, 0x6a, 0xaa, 0x8b, 0xa0, 0x9d, 0x12, 0x2c, 0x71, 0x2b, 0x98, 0xb1, + 0x07, 0xb5, 0x58, 0x35, 0x31, 0x6f, 0x4a, 0x95, 0x7b, 0x53, 0x16, 0xbc, 0x9b, 0xd9, 0x45, 0xef, + 0xe6, 0x15, 0xac, 0x9a, 0xd4, 0x1e, 0x5f, 0xed, 0x79, 0xfe, 0x51, 0x70, 0x12, 0xee, 0x71, 0x21, + 0x98, 0x9d, 0x41, 0xca, 0x65, 0x1f, 0x73, 0x59, 0x48, 0xcf, 0xad, 0x34, 0xc3, 0x7c, 0x07, 0xea, + 0x91, 0x6f, 0x5f, 0x33, 0x7b, 0xd7, 0x94, 0x7b, 0x1f, 0x65, 0x27, 0x02, 0xf9, 0x59, 0x70, 0x12, + 0x0a, 0xc3, 0x37, 0xfe, 0x36, 0xfe, 0x77, 0x1e, 0x08, 0x5b, 0xcd, 0x89, 0x05, 0x93, 0x88, 0x4a, + 0xc8, 0x2e, 0x44, 0x25, 0x3c, 0x00, 0xa2, 0x11, 0xc8, 0x60, 0x89, 0x9c, 0x0a, 0x96, 0x68, 0x44, + 0xb4, 0x22, 0x56, 0xe2, 0x01, 0xac, 0x09, 0x8d, 0x22, 0xde, 0x54, 0xbe, 0x34, 0x08, 0x57, 0x2d, + 0x62, 0xed, 0x95, 0x11, 0x09, 0xd2, 0x52, 0x9d, 0xe3, 0x11, 0x09, 0xd2, 0xa0, 0xa4, 0x2d, 0xc0, + 0xa5, 0x97, 0x2e, 0xc0, 0xe2, 0xc2, 0x02, 0xd4, 0x8c, 0x8b, 0xa5, 0xb8, 0x71, 0x71, 0xc1, 0x4c, + 0xce, 0xc5, 0xe7, 0x98, 0x99, 0xfc, 0x1e, 0x34, 0xa4, 0xa1, 0x49, 0x99, 0x30, 0x79, 0x28, 0x91, + 0x30, 0x22, 0xb7, 0xa5, 0x11, 0x33, 0xe6, 0x37, 0xab, 0x24, 0xfc, 0x66, 0xef, 0xc2, 0x4a, 0xc0, + 0xd6, 0xaf, 0x35, 0x77, 0x45, 0x3c, 0x21, 0x1d, 0xa3, 0x3e, 0x5e, 0x32, 0x1b, 0x88, 0x38, 0x8e, + 0xe0, 0x8b, 0x26, 0xb9, 0x5a, 0x8a, 0x49, 0xee, 0x51, 0xe4, 0xa2, 0x0f, 0xce, 0x9d, 0x29, 0x0a, + 0x3e, 0x51, 0x8c, 0x9c, 0x18, 0xe0, 0xc1, 0xb9, 0x33, 0x35, 0x65, 0x3c, 0x08, 0x4b, 0x90, 0x36, + 0xdc, 0x11, 0xfd, 0x49, 0x09, 0xe5, 0xe0, 0xa3, 0xb0, 0x8c, 0x92, 0xea, 0x16, 0x27, 0x3b, 0x4c, + 0x44, 0x75, 0x24, 0x06, 0x85, 0x15, 0xc2, 0xad, 0xc0, 0x0d, 0x7d, 0x50, 0x0e, 0xed, 0x4b, 0xee, + 0x37, 0xf8, 0x5f, 0x19, 0x68, 0xb0, 0x65, 0x17, 0xdb, 0xd1, 0x9f, 0x01, 0xf2, 0x9e, 0x57, 0xdc, + 0xd0, 0x15, 0x46, 0x2b, 0xf7, 0xf3, 0x27, 0x80, 0x1b, 0xd4, 0xf2, 0x66, 0xd4, 0x15, 0xdb, 0xb9, + 0x19, 0xdf, 0xce, 0x11, 0xcb, 0xde, 0xbf, 0xc1, 0x15, 0x3e, 0x06, 0x21, 0x9f, 0x41, 0x99, 0xed, + 0x03, 0x5c, 0x94, 0x22, 0xc2, 0x74, 0x4b, 0x29, 0xf1, 0x0b, 0x5b, 0x92, 0x65, 0x9d, 0x89, 0x64, + 0x5a, 0x10, 0x47, 0x3e, 0x25, 0x88, 0x43, 0xe3, 0x17, 0xfb, 0x00, 0x4f, 0xe9, 0xd5, 0x81, 0x37, + 0x42, 0x73, 0xca, 0x6d, 0x00, 0xb6, 0x75, 0x4e, 0xed, 0xa9, 0x23, 0x0c, 0x89, 0x05, 0xb3, 0xfc, + 0x8c, 0x5e, 0xed, 0x21, 0x80, 0xad, 0x1b, 0x86, 0x8e, 0x98, 0x46, 0xc1, 0x2c, 0x3d, 0xa3, 0x57, + 0x9c, 0x63, 0x58, 0x50, 0x7b, 0x4a, 0xaf, 0x76, 0x29, 0x17, 0xcc, 0x3d, 0x9f, 0xad, 0x59, 0xdf, + 0x7e, 0xce, 0x24, 0xf1, 0x58, 0x00, 0x46, 0xc5, 0xb7, 0x9f, 0x3f, 0xa5, 0x57, 0x32, 0x18, 0xa4, + 0xc8, 0xf0, 0x13, 0x6f, 0x24, 0x44, 0x09, 0x69, 0xbb, 0x89, 0x1a, 0x65, 0x2e, 0x3d, 0xc3, 0xdf, + 0xc6, 0x9f, 0x64, 0xa0, 0xc6, 0xda, 0x8f, 0xa7, 0x00, 0xae, 0x10, 0x11, 0x91, 0x98, 0x89, 0x22, + 0x12, 0x1f, 0x0a, 0x26, 0xca, 0x8f, 0x94, 0xec, 0xf5, 0x47, 0x0a, 0xce, 0x0d, 0x3f, 0x4f, 0x3e, + 0x80, 0x32, 0xe7, 0x02, 0x8c, 0xad, 0xe4, 0x62, 0x13, 0x1c, 0xeb, 0x90, 0x59, 0x42, 0xb2, 0xa7, + 0x3c, 0x00, 0x4a, 0x33, 0x93, 0xf3, 0x21, 0x2e, 0xfb, 0xca, 0x38, 0x9e, 0x32, 0x0d, 0x85, 0x6b, + 0x02, 0xa0, 0x74, 0x1b, 0xf4, 0x52, 0xd2, 0x06, 0x6d, 0xb8, 0x50, 0x62, 0x53, 0x8d, 0x9d, 0x4d, + 0x29, 0x34, 0x93, 0x56, 0x28, 0x13, 0x3c, 0x6c, 0x76, 0x06, 0x31, 0xbe, 0x9a, 0x15, 0x82, 0x87, + 0x1d, 0x50, 0x56, 0x10, 0x6b, 0xb8, 0xeb, 0x59, 0x68, 0xd4, 0x15, 0xe6, 0xce, 0x92, 0x59, 0x76, + 0xbd, 0x23, 0x0e, 0x30, 0xfe, 0x5c, 0x06, 0x2a, 0xda, 0x7e, 0x44, 0x2b, 0xbf, 0x1a, 0x4e, 0xbe, + 0x79, 0xe3, 0x3b, 0x20, 0x36, 0x1f, 0xfb, 0x37, 0xcc, 0xda, 0x28, 0x36, 0x41, 0xdb, 0x62, 0x29, + 0x63, 0xce, 0x6c, 0xcc, 0xb4, 0x24, 0xfb, 0x25, 0xd7, 0x2f, 0xfb, 0xbd, 0xb3, 0x04, 0x79, 0x46, + 0x6a, 0x3c, 0x86, 0x15, 0xad, 0x19, 0xdc, 0xf4, 0xf2, 0xaa, 0x03, 0x60, 0xfc, 0xb2, 0xca, 0xcc, + 0xea, 0xe0, 0xae, 0x69, 0x19, 0x6b, 0x46, 0xc7, 0x7c, 0x5c, 0x44, 0x4c, 0x1b, 0x07, 0xe1, 0xc8, + 0xbc, 0x6a, 0xfc, 0xd3, 0xaf, 0xc2, 0xaa, 0x56, 0xfa, 0x9e, 0xe3, 0xda, 0x13, 0xe7, 0x27, 0x28, + 0x7e, 0x04, 0xce, 0x99, 0x9b, 0x28, 0x9f, 0x83, 0xbe, 0x51, 0xf9, 0x7f, 0x2d, 0x0b, 0x6b, 0xa2, + 0x02, 0x8c, 0x1e, 0x76, 0x98, 0x4c, 0x79, 0x18, 0x9c, 0x91, 0xcf, 0xa0, 0xc6, 0xc6, 0xc6, 0xf2, + 0xe9, 0x99, 0x13, 0x84, 0x54, 0xba, 0xc4, 0x53, 0xd8, 0x28, 0x13, 0x2d, 0x18, 0xa9, 0x29, 0x28, + 0xc9, 0x63, 0xa8, 0x60, 0x56, 0x6e, 0xda, 0x12, 0x13, 0xd1, 0x5c, 0xcc, 0xc8, 0x07, 0x7a, 0xff, + 0x86, 0x09, 0x41, 0x34, 0xec, 0x8f, 0xa1, 0x82, 0x73, 0x78, 0x81, 0x03, 0x99, 0xe0, 0x64, 0x0b, + 0x03, 0xcd, 0x32, 0xcf, 0xa2, 0x61, 0x6f, 0x41, 0x8d, 0xf3, 0x32, 0x31, 0x4e, 0x22, 0x2a, 0x71, + 0x6b, 0x31, 0xbb, 0x1c, 0x49, 0xd6, 0xf8, 0x99, 0x96, 0xde, 0x29, 0x43, 0x31, 0xf4, 0x9d, 0xb3, + 0x33, 0xea, 0x1b, 0x1b, 0x6a, 0x68, 0x18, 0x93, 0xa6, 0x83, 0x90, 0xce, 0x98, 0xb2, 0x60, 0xfc, + 0xcb, 0x0c, 0x54, 0x04, 0xdb, 0xfd, 0xa9, 0xfd, 0xf0, 0x5b, 0x09, 0x23, 0x68, 0x59, 0xb3, 0x79, + 0xbe, 0x03, 0xcb, 0x53, 0xa6, 0xd9, 0x30, 0xcd, 0x3b, 0xe6, 0x84, 0xaf, 0x4b, 0xb0, 0x10, 0xda, + 0xb7, 0x61, 0x15, 0x65, 0xf8, 0xc0, 0x0a, 0x9d, 0x89, 0x25, 0x91, 0x22, 0x84, 0x7e, 0x85, 0xa3, + 0x86, 0xce, 0xe4, 0x50, 0x20, 0x98, 0x28, 0x1b, 0x84, 0xf6, 0x19, 0x15, 0x5b, 0x9f, 0x27, 0x98, + 0xb6, 0x94, 0x50, 0xba, 0xa5, 0xb6, 0xf4, 0x7f, 0x57, 0x60, 0x73, 0x01, 0x25, 0xb4, 0x25, 0xe5, + 0x75, 0x9d, 0x38, 0xd3, 0x13, 0x4f, 0x59, 0xfd, 0x33, 0x9a, 0xd7, 0xf5, 0x80, 0x61, 0xa4, 0xd5, + 0x9f, 0xc2, 0xba, 0x5c, 0x90, 0x68, 0xb6, 0x57, 0x7a, 0x79, 0x16, 0xb5, 0xc6, 0x0f, 0xe2, 0x67, + 0x5c, 0xb2, 0x3a, 0x09, 0xd7, 0x05, 0xb5, 0xd5, 0xd9, 0x02, 0x2c, 0x20, 0x7f, 0x1a, 0x9a, 0x6a, + 0xdd, 0x0b, 0x25, 0x42, 0x33, 0x32, 0xb0, 0x9a, 0xde, 0x7b, 0x49, 0x4d, 0x31, 0x7b, 0x2a, 0x4a, + 0x72, 0x1b, 0x72, 0xcb, 0xf0, 0x02, 0x55, 0x5d, 0x17, 0xf0, 0xba, 0xac, 0x0b, 0x95, 0x82, 0xc5, + 0x1a, 0xf3, 0xaf, 0xd4, 0x37, 0xb4, 0x15, 0xc7, 0xaa, 0x35, 0x6f, 0x89, 0x82, 0x15, 0x4a, 0xaf, + 0xf7, 0x1c, 0x36, 0x9e, 0xdb, 0x4e, 0x28, 0xfb, 0xa8, 0xd9, 0x38, 0x0a, 0x58, 0xdf, 0xc3, 0x97, + 0xd4, 0xf7, 0x25, 0xcf, 0x1c, 0x53, 0x93, 0xd6, 0x9e, 0x2f, 0x02, 0x83, 0xad, 0xbf, 0x93, 0x83, + 0x7a, 0xbc, 0x14, 0xc6, 0x58, 0xc4, 0x59, 0x24, 0xa5, 0x5f, 0x21, 0x92, 0x0b, 0x8f, 0x54, 0x8f, + 0x4b, 0xbd, 0x8b, 0xbe, 0xb2, 0x6c, 0x8a, 0xaf, 0x4c, 0x77, 0x51, 0xe5, 0x5e, 0x16, 0xb1, 0x90, + 0x7f, 0xa5, 0x88, 0x85, 0x42, 0x5a, 0xc4, 0xc2, 0x87, 0xd7, 0xba, 0xb8, 0xb9, 0xa1, 0x39, 0xd5, + 0xbd, 0xfd, 0xe8, 0x7a, 0xf7, 0x36, 0x97, 0xa5, 0xaf, 0x73, 0x6d, 0x6b, 0x8e, 0xf9, 0xd2, 0x35, + 0x8e, 0x25, 0xcd, 0x55, 0x9f, 0xe2, 0xda, 0x2e, 0x7f, 0x03, 0xd7, 0xf6, 0xd6, 0x9f, 0x64, 0x80, + 0x2c, 0xee, 0x0e, 0xf2, 0x84, 0xbb, 0x21, 0x5d, 0x3a, 0x11, 0x9c, 0xfb, 0x7b, 0xaf, 0xb6, 0xc3, + 0xe4, 0x82, 0x90, 0xb9, 0xc9, 0xfb, 0xb0, 0xaa, 0x5f, 0xf4, 0xd1, 0x6d, 0x08, 0x35, 0x93, 0xe8, + 0xa8, 0xc8, 0x1a, 0xa6, 0x85, 0x87, 0xe4, 0x5f, 0x1a, 0x1e, 0x52, 0x78, 0x69, 0x78, 0xc8, 0x52, + 0x3c, 0x3c, 0x64, 0xeb, 0xdf, 0x66, 0x60, 0x35, 0x65, 0x11, 0x7f, 0x7b, 0x7d, 0x66, 0x6b, 0x2f, + 0xc6, 0xd6, 0xb2, 0x62, 0xed, 0xe9, 0x1c, 0xed, 0x40, 0x5a, 0x50, 0xd9, 0x54, 0x04, 0xe2, 0xa4, + 0xba, 0xff, 0x32, 0xee, 0x12, 0xe5, 0x30, 0xf5, 0xec, 0x5b, 0x7f, 0x2f, 0x0b, 0x15, 0x0d, 0xc9, + 0x46, 0x91, 0x2f, 0x59, 0x2d, 0x38, 0x91, 0x0b, 0x8e, 0x68, 0x01, 0xb9, 0x03, 0xc2, 0xd1, 0xc4, + 0xf1, 0x7c, 0x73, 0x09, 0x29, 0x11, 0x09, 0xb6, 0x61, 0x55, 0xba, 0x88, 0x69, 0x14, 0xaf, 0x2c, + 0xce, 0x1a, 0xe1, 0xed, 0x17, 0x8d, 0x44, 0xfa, 0xf7, 0xa5, 0x72, 0x1a, 0xcd, 0x9d, 0xe6, 0x72, + 0x5b, 0x11, 0x71, 0x06, 0x62, 0x12, 0xd9, 0x3a, 0xff, 0x00, 0xd6, 0x55, 0xa0, 0x41, 0x2c, 0x07, + 0x77, 0xec, 0x10, 0x19, 0x50, 0xa0, 0x65, 0xf9, 0x21, 0xdc, 0x4e, 0xb4, 0x29, 0x91, 0x95, 0x07, + 0xd6, 0xdf, 0x8c, 0xb5, 0x4e, 0x2f, 0x61, 0xeb, 0xcf, 0x40, 0x2d, 0xc6, 0x28, 0xbf, 0xbd, 0x29, + 0x4f, 0x5a, 0x9d, 0xf8, 0x88, 0xea, 0x56, 0xa7, 0xad, 0xff, 0x99, 0x03, 0xb2, 0xc8, 0xab, 0x7f, + 0x9e, 0x4d, 0x58, 0x5c, 0x98, 0xb9, 0x94, 0x85, 0xf9, 0xff, 0x4d, 0x7e, 0x88, 0x8c, 0x9f, 0x9a, + 0x9f, 0x9f, 0x6f, 0xce, 0x86, 0x42, 0xc8, 0x56, 0x7c, 0x92, 0x8c, 0x86, 0x2a, 0xc5, 0xee, 0xaa, + 0x69, 0x02, 0x54, 0x22, 0x28, 0xea, 0x18, 0x96, 0x6c, 0x77, 0x74, 0xee, 0xf9, 0x82, 0x0f, 0xfe, + 0xc2, 0x37, 0x3e, 0x3e, 0xb7, 0x5b, 0x98, 0x1f, 0xa5, 0x36, 0x53, 0x14, 0x66, 0x7c, 0x00, 0x15, + 0x0d, 0x4c, 0xca, 0x50, 0x38, 0xe8, 0x1e, 0xee, 0xf4, 0x1b, 0x37, 0x48, 0x0d, 0xca, 0x66, 0xa7, + 0xdd, 0xff, 0xa2, 0x63, 0x76, 0x76, 0x1b, 0x19, 0x52, 0x82, 0xfc, 0x41, 0x7f, 0x30, 0x6c, 0x64, + 0x8d, 0x2d, 0x68, 0x8a, 0x12, 0x17, 0xdd, 0x40, 0xbf, 0x95, 0x57, 0xc6, 0x4b, 0x44, 0x0a, 0x0d, + 0xfe, 0x43, 0xa8, 0xea, 0xe2, 0x8d, 0x58, 0x11, 0x89, 0x50, 0x13, 0xa6, 0xbb, 0x7b, 0x1a, 0xaf, + 0x6e, 0x03, 0x0f, 0x34, 0x18, 0xab, 0x6c, 0xd9, 0x98, 0xdc, 0x9a, 0xe2, 0xb1, 0x45, 0xe5, 0x27, + 0xb6, 0x0c, 0xff, 0x14, 0xd4, 0xe3, 0x2e, 0x0f, 0xc1, 0x91, 0xd2, 0xf4, 0x51, 0x96, 0x3b, 0xe6, + 0x03, 0x21, 0x3f, 0x84, 0x46, 0xd2, 0x65, 0x22, 0x84, 0xe7, 0x6b, 0xf2, 0x2f, 0x3b, 0x71, 0x2f, + 0x0a, 0xd9, 0x87, 0xb5, 0x34, 0x01, 0x0f, 0xd7, 0xc7, 0xf5, 0x36, 0x0c, 0xb2, 0x28, 0xc4, 0x91, + 0x4f, 0x85, 0xeb, 0xac, 0x80, 0xd3, 0xff, 0x56, 0xbc, 0x7e, 0x6d, 0xb0, 0xb7, 0xf9, 0x3f, 0xcd, + 0x89, 0x76, 0x01, 0x10, 0xc1, 0x48, 0x03, 0xaa, 0xfd, 0xa3, 0x4e, 0xcf, 0x6a, 0xef, 0xb7, 0x7a, + 0xbd, 0xce, 0x41, 0xe3, 0x06, 0x21, 0x50, 0xc7, 0x68, 0x89, 0x5d, 0x05, 0xcb, 0x30, 0x98, 0x70, + 0x61, 0x4a, 0x58, 0x96, 0xac, 0x41, 0xa3, 0xdb, 0x4b, 0x40, 0x73, 0xa4, 0x09, 0x6b, 0x47, 0x1d, + 0x1e, 0x60, 0x11, 0x2b, 0x37, 0xcf, 0x94, 0x06, 0xd1, 0x5d, 0xa6, 0x34, 0x7c, 0x69, 0x4f, 0x26, + 0x34, 0x14, 0xfb, 0x40, 0xca, 0xd2, 0x7f, 0x3d, 0x03, 0xeb, 0x09, 0x44, 0xe4, 0x77, 0xe0, 0x92, + 0x74, 0x5c, 0x86, 0xae, 0x22, 0x50, 0xee, 0xa6, 0x77, 0x61, 0x45, 0x99, 0xc1, 0x12, 0xa7, 0x52, + 0x43, 0x21, 0x24, 0xf1, 0xfb, 0xb0, 0xaa, 0x59, 0xd3, 0x12, 0xbc, 0x82, 0x68, 0x28, 0x91, 0xc1, + 0xd8, 0x54, 0xd7, 0x77, 0x12, 0xad, 0x1e, 0xc3, 0x46, 0x12, 0x11, 0x79, 0x16, 0xe3, 0xed, 0x95, + 0x49, 0xf2, 0x20, 0xb1, 0x10, 0xe2, 0xad, 0xd5, 0x27, 0x5c, 0x56, 0xff, 0x3b, 0x4b, 0x40, 0x7e, + 0x3c, 0xa7, 0xfe, 0x15, 0x5e, 0x1b, 0x0b, 0x5e, 0x16, 0x1b, 0x2d, 0x0d, 0x31, 0xd9, 0x57, 0xba, + 0x1a, 0x9a, 0x76, 0x35, 0x33, 0xff, 0xf2, 0xab, 0x99, 0x85, 0x97, 0x5d, 0xcd, 0x7c, 0x13, 0x6a, + 0xce, 0x99, 0xeb, 0x31, 0x56, 0xc8, 0x24, 0xe1, 0xa0, 0xb9, 0x74, 0x37, 0x77, 0xaf, 0x6a, 0x56, + 0x05, 0x90, 0xc9, 0xc1, 0x01, 0x79, 0x1c, 0x11, 0xd1, 0xf1, 0x19, 0x5e, 0x4f, 0xd6, 0x99, 0x60, + 0x67, 0x7c, 0x46, 0x85, 0xdd, 0x09, 0x35, 0x0d, 0x99, 0x99, 0xc1, 0x03, 0xf2, 0x16, 0xd4, 0x03, + 0x6f, 0xce, 0x14, 0x0b, 0x39, 0x0c, 0xdc, 0xb5, 0x58, 0xe5, 0xd0, 0x23, 0xe9, 0x68, 0x5e, 0x9d, + 0x07, 0xd4, 0x9a, 0x3a, 0x41, 0xc0, 0xc4, 0xb3, 0x91, 0xe7, 0x86, 0xbe, 0x37, 0x11, 0xde, 0xc2, + 0x95, 0x79, 0x40, 0x0f, 0x39, 0xa6, 0xcd, 0x11, 0xe4, 0xa3, 0xa8, 0x49, 0x33, 0xdb, 0xf1, 0x83, + 0x26, 0x60, 0x93, 0x64, 0x4f, 0x51, 0x7e, 0xb7, 0x1d, 0x5f, 0xb5, 0x85, 0x25, 0x82, 0xc4, 0x95, + 0xd1, 0x4a, 0xf2, 0xca, 0xe8, 0xaf, 0xa5, 0x5f, 0x19, 0xe5, 0x01, 0x52, 0x0f, 0x44, 0xd1, 0x8b, + 0x53, 0xfc, 0x8d, 0x6e, 0x8e, 0x2e, 0xde, 0x84, 0xad, 0x7f, 0x93, 0x9b, 0xb0, 0xcb, 0x69, 0x37, + 0x61, 0x3f, 0x80, 0x0a, 0xde, 0x51, 0xb4, 0xce, 0x31, 0x4c, 0x92, 0x7b, 0x3f, 0x1b, 0xfa, 0x25, + 0xc6, 0x7d, 0xc7, 0x0d, 0x4d, 0xf0, 0xe5, 0xcf, 0x60, 0xf1, 0x52, 0xea, 0xca, 0xcf, 0xf1, 0x52, + 0xaa, 0xb8, 0x4b, 0xb9, 0x0d, 0x25, 0x39, 0x4f, 0x84, 0x40, 0xfe, 0xd4, 0xf7, 0xa6, 0xd2, 0xe3, + 0xc2, 0x7e, 0x93, 0x3a, 0x64, 0x43, 0x4f, 0x64, 0xce, 0x86, 0x9e, 0xf1, 0x2b, 0x50, 0xd1, 0x96, + 0x1a, 0x79, 0x83, 0x9b, 0x2d, 0x99, 0x6e, 0x26, 0x64, 0x4b, 0x3e, 0x8a, 0x65, 0x01, 0xed, 0x8e, + 0x19, 0xbf, 0x19, 0x3b, 0x3e, 0xc5, 0xeb, 0xe3, 0x96, 0x4f, 0x2f, 0xa8, 0x1f, 0x48, 0x0f, 0x58, + 0x43, 0x21, 0x4c, 0x0e, 0x37, 0x7e, 0x15, 0x56, 0x63, 0x73, 0x2b, 0x58, 0xc4, 0x5b, 0xb0, 0x84, + 0xe3, 0x26, 0xc3, 0x2c, 0xe2, 0x97, 0x43, 0x05, 0x0e, 0xaf, 0xca, 0x73, 0xe7, 0x9d, 0x35, 0xf3, + 0xbd, 0x13, 0xac, 0x24, 0x63, 0x56, 0x04, 0xec, 0xc8, 0xf7, 0x4e, 0x8c, 0x3f, 0xcc, 0x41, 0x6e, + 0xdf, 0x9b, 0xe9, 0xa1, 0x95, 0x99, 0x85, 0xd0, 0x4a, 0xa1, 0x70, 0x5a, 0x4a, 0xa1, 0x14, 0x32, + 0x3b, 0xba, 0xad, 0xa4, 0x52, 0x79, 0x0f, 0xea, 0x8c, 0x4f, 0x84, 0x1e, 0xd3, 0xd8, 0x9f, 0xdb, + 0x3e, 0x17, 0x88, 0x79, 0xa4, 0x72, 0xd5, 0x9e, 0x86, 0x43, 0x6f, 0x8f, 0xc3, 0xc9, 0x1a, 0xe4, + 0x94, 0xfa, 0x82, 0x68, 0x96, 0x24, 0x1b, 0xb0, 0x84, 0x17, 0x21, 0xae, 0x44, 0x98, 0x80, 0x48, + 0x91, 0xef, 0xc1, 0x6a, 0xbc, 0x5c, 0xce, 0x8a, 0x84, 0x6c, 0xa4, 0x17, 0x8c, 0x3c, 0xe9, 0x26, + 0x30, 0x3e, 0xc2, 0x69, 0x44, 0x3c, 0xd3, 0x29, 0xa5, 0x88, 0xd2, 0x98, 0x5e, 0x29, 0xc6, 0xf4, + 0xee, 0x40, 0x25, 0x9c, 0x5c, 0x58, 0x33, 0xfb, 0x6a, 0xe2, 0xd9, 0x63, 0xb1, 0xbf, 0x21, 0x9c, + 0x5c, 0x1c, 0x71, 0x08, 0x79, 0x1f, 0x60, 0x3a, 0x9b, 0x89, 0xbd, 0x87, 0xae, 0x98, 0x68, 0x29, + 0x1f, 0x1e, 0x1d, 0xf1, 0x25, 0x67, 0x96, 0xa7, 0xb3, 0x19, 0xff, 0x49, 0x76, 0xa1, 0x9e, 0x7a, + 0xc5, 0xfb, 0xb6, 0x0c, 0x58, 0xf7, 0x66, 0xdb, 0x29, 0x9b, 0xb3, 0x36, 0xd2, 0x61, 0x5b, 0x3f, + 0x04, 0xf2, 0x33, 0x5e, 0xb4, 0x1e, 0x42, 0x59, 0xb5, 0x4f, 0xbf, 0xa7, 0x8c, 0x37, 0x71, 0x2a, + 0xb1, 0x7b, 0xca, 0xad, 0xf1, 0xd8, 0x67, 0x7c, 0x91, 0x1f, 0x98, 0x8a, 0xe5, 0x83, 0x76, 0x62, + 0x8a, 0xab, 0x1e, 0xc6, 0x7f, 0xc9, 0x40, 0x81, 0x5f, 0x9a, 0x7e, 0x1b, 0x96, 0x39, 0xbd, 0x0a, + 0x53, 0x15, 0xc1, 0x05, 0xfc, 0xdc, 0x1d, 0x8a, 0x08, 0x55, 0xb6, 0x2d, 0xb4, 0x87, 0x24, 0xb2, + 0x6a, 0xe6, 0xb5, 0xc7, 0x24, 0xee, 0x40, 0x59, 0x55, 0xad, 0x2d, 0x9d, 0x92, 0xac, 0x99, 0xbc, + 0x0e, 0xf9, 0x73, 0x6f, 0x26, 0x2d, 0x3f, 0x10, 0x8d, 0xa4, 0x89, 0xf0, 0xa8, 0x2d, 0xac, 0x8e, + 0xe8, 0x0a, 0x4a, 0x4e, 0xb4, 0x85, 0x55, 0x82, 0xcb, 0x60, 0xb1, 0x8f, 0x4b, 0x29, 0x7d, 0x3c, + 0x86, 0x65, 0xc6, 0x07, 0xb4, 0x08, 0x87, 0xeb, 0x0f, 0xcd, 0xef, 0x32, 0x09, 0x6f, 0x34, 0x99, + 0x8f, 0xa9, 0x6e, 0x7b, 0xc3, 0x98, 0x43, 0x01, 0x97, 0x92, 0xb5, 0xf1, 0x3b, 0x19, 0xce, 0x5f, + 0x58, 0xb9, 0xe4, 0x1e, 0xe4, 0x5d, 0x19, 0x0d, 0x11, 0xc9, 0x71, 0xea, 0x4a, 0x14, 0xa3, 0x33, + 0x91, 0x82, 0x4d, 0x1d, 0xc6, 0x10, 0xe8, 0xa5, 0xd7, 0xcc, 0x8a, 0x3b, 0x9f, 0x2a, 0xd3, 0xd5, + 0x77, 0x64, 0xb7, 0x12, 0x66, 0x1f, 0xde, 0x7b, 0xb5, 0x4d, 0xb7, 0xb5, 0xe0, 0xc5, 0x7c, 0xec, + 0xc4, 0x94, 0x52, 0xe0, 0xf8, 0x8c, 0x6a, 0x41, 0x8b, 0xbf, 0x97, 0x85, 0x5a, 0xac, 0x45, 0x18, + 0xbd, 0xc9, 0x0e, 0x00, 0xee, 0x77, 0x12, 0xf3, 0x8d, 0x41, 0x72, 0x42, 0x50, 0xd7, 0xc6, 0x29, + 0x1b, 0x1b, 0x27, 0x15, 0xce, 0x94, 0xd3, 0xc3, 0x99, 0x1e, 0x40, 0x39, 0x7a, 0x40, 0x24, 0xde, + 0x24, 0x56, 0x9f, 0xbc, 0x18, 0x16, 0x11, 0x45, 0x01, 0x50, 0x05, 0x3d, 0x00, 0xea, 0x73, 0x2d, + 0x5e, 0x66, 0x09, 0x8b, 0x31, 0xd2, 0x46, 0xf4, 0xe7, 0x12, 0x2d, 0x63, 0x3c, 0x86, 0x8a, 0xd6, + 0x78, 0x3d, 0xe6, 0x24, 0x13, 0x8b, 0x39, 0x51, 0x17, 0x3b, 0xb3, 0xd1, 0xc5, 0x4e, 0xe3, 0xcf, + 0x67, 0xa1, 0xc6, 0xf6, 0x97, 0xe3, 0x9e, 0x1d, 0x79, 0x13, 0x67, 0x84, 0x7e, 0x28, 0xb5, 0xc3, + 0x84, 0xa0, 0x25, 0xf7, 0x99, 0xd8, 0x62, 0x5c, 0xce, 0xd2, 0x6f, 0xb5, 0x73, 0x26, 0xad, 0x6e, + 0xb5, 0x1b, 0x50, 0x63, 0x8c, 0x11, 0x3d, 0x4a, 0xd1, 0x33, 0x24, 0x66, 0xe5, 0x94, 0xd2, 0x1d, + 0x3b, 0xe0, 0x1c, 0xf2, 0x7b, 0xb0, 0xca, 0x68, 0xf0, 0x42, 0xef, 0xd4, 0x99, 0x4c, 0x9c, 0xe8, + 0xce, 0x57, 0xce, 0x6c, 0x9c, 0x52, 0x6a, 0xda, 0x21, 0x3d, 0x64, 0x08, 0xf1, 0x6a, 0x49, 0x69, + 0xec, 0x04, 0xf6, 0x49, 0x14, 0x63, 0xab, 0xd2, 0xe8, 0xe7, 0x16, 0x7e, 0xda, 0x68, 0x93, 0xe5, + 0xcd, 0xca, 0x94, 0x7b, 0x69, 0x31, 0x7f, 0x62, 0x25, 0x15, 0x93, 0x2b, 0xc9, 0xf8, 0x67, 0x59, + 0xa8, 0x68, 0xcb, 0xf2, 0x55, 0x4e, 0xd7, 0xdb, 0x0b, 0x7e, 0xc3, 0xb2, 0xee, 0x22, 0x7c, 0x33, + 0x5e, 0x65, 0x4e, 0x5d, 0x0c, 0xd2, 0x17, 0xf0, 0x2d, 0x28, 0xb3, 0x5d, 0xf7, 0x01, 0x9a, 0x60, + 0xc5, 0xab, 0x41, 0x08, 0x38, 0x9a, 0x9f, 0x48, 0xe4, 0x43, 0x44, 0x16, 0x22, 0xe4, 0x43, 0x86, + 0x7c, 0xd1, 0xc5, 0x80, 0x4f, 0xa0, 0x2a, 0x4a, 0xc5, 0x39, 0xc5, 0xee, 0x46, 0xbb, 0x3e, 0x36, + 0xdf, 0x66, 0x85, 0x57, 0xc7, 0x27, 0x5f, 0x64, 0x7c, 0x28, 0x33, 0x96, 0x5e, 0x96, 0xf1, 0x21, + 0x4f, 0x18, 0x7b, 0xea, 0xae, 0x05, 0x46, 0xaa, 0x49, 0x3e, 0xf6, 0x3e, 0xac, 0x4a, 0x76, 0x35, + 0x77, 0x6d, 0xd7, 0xf5, 0xe6, 0xee, 0x88, 0xca, 0xbb, 0x9d, 0x44, 0xa0, 0x8e, 0x23, 0x8c, 0x31, + 0x56, 0x0f, 0x05, 0xf0, 0x88, 0xb7, 0xfb, 0x50, 0xe0, 0x72, 0x39, 0x17, 0x3e, 0xd2, 0x19, 0x17, + 0x27, 0x21, 0xf7, 0xa0, 0xc0, 0xc5, 0xf3, 0xec, 0xb5, 0xcc, 0x86, 0x13, 0x18, 0x2d, 0x20, 0x2c, + 0xe3, 0x21, 0x0d, 0x7d, 0x67, 0x14, 0x44, 0xd7, 0x46, 0x0b, 0x4c, 0xff, 0xe4, 0x75, 0x45, 0x96, + 0xdb, 0x88, 0x12, 0x75, 0x54, 0x4e, 0xc3, 0x0e, 0xa6, 0xd5, 0x58, 0x19, 0x42, 0x5c, 0x9a, 0xc0, + 0xc6, 0x09, 0x0d, 0x9f, 0x53, 0xea, 0xba, 0x4c, 0x18, 0x1a, 0x51, 0x37, 0xf4, 0xed, 0x09, 0x9b, + 0x24, 0xde, 0x83, 0x47, 0x0b, 0xa5, 0x46, 0x36, 0x90, 0x9d, 0x28, 0x63, 0x5b, 0xe5, 0xe3, 0xbc, + 0x63, 0xfd, 0x24, 0x0d, 0xb7, 0xf5, 0xcb, 0xb0, 0x75, 0x7d, 0xa6, 0x94, 0x2b, 0xe3, 0xf7, 0xe2, + 0x5c, 0x45, 0xf9, 0x01, 0x27, 0x9e, 0x1d, 0xf2, 0xd6, 0xe8, 0x9c, 0xa5, 0x07, 0x15, 0x0d, 0x13, + 0x9d, 0xfd, 0x19, 0x14, 0xee, 0x78, 0x82, 0x9d, 0x48, 0xae, 0xe7, 0x4f, 0xd1, 0xef, 0x36, 0xb6, + 0xa2, 0xd2, 0x33, 0xe6, 0x72, 0x04, 0xc7, 0x18, 0x0b, 0x63, 0x1b, 0x96, 0x51, 0xb2, 0xd7, 0x0e, + 0xba, 0x17, 0x09, 0x83, 0xc6, 0x1a, 0x90, 0x1e, 0xe7, 0x5d, 0x7a, 0xf4, 0xdf, 0xbf, 0xcf, 0x41, + 0x45, 0x03, 0xb3, 0xd3, 0x08, 0x43, 0x26, 0xad, 0xb1, 0x63, 0x4f, 0xa9, 0x74, 0x72, 0xd6, 0xcc, + 0x1a, 0x42, 0x77, 0x05, 0x90, 0x9d, 0xc5, 0xf6, 0xc5, 0x99, 0xe5, 0xcd, 0x43, 0x6b, 0x4c, 0xcf, + 0x7c, 0x2a, 0x5b, 0x59, 0xb5, 0x2f, 0xce, 0xfa, 0xf3, 0x70, 0x17, 0x61, 0x8c, 0x8a, 0xf1, 0x12, + 0x8d, 0x4a, 0x44, 0xd0, 0x4d, 0xed, 0xcb, 0x88, 0x4a, 0x84, 0x9a, 0xf2, 0x95, 0x99, 0x57, 0xa1, + 0xa6, 0x5c, 0x5b, 0x4c, 0x1e, 0xa0, 0x85, 0xc5, 0x03, 0xf4, 0x23, 0xd8, 0xe0, 0x07, 0xa8, 0x60, + 0xcd, 0x56, 0x62, 0x27, 0xaf, 0x21, 0x56, 0x74, 0x52, 0x13, 0x7b, 0x1b, 0xac, 0x07, 0x92, 0x2d, + 0x05, 0xce, 0x4f, 0x38, 0x23, 0xcb, 0x98, 0xac, 0x67, 0xa2, 0xf0, 0x81, 0xf3, 0x13, 0xca, 0x28, + 0x31, 0x56, 0x47, 0xa7, 0x14, 0xd7, 0x7e, 0xa6, 0x8e, 0x9b, 0xa4, 0xb4, 0x2f, 0xe3, 0x94, 0x65, + 0x41, 0x69, 0x5f, 0xea, 0x94, 0x8f, 0x60, 0x73, 0x4a, 0xc7, 0x8e, 0x1d, 0x2f, 0xd6, 0x8a, 0x04, + 0xb7, 0x35, 0x8e, 0xd6, 0xf2, 0x0c, 0xb8, 0xe2, 0xce, 0x46, 0xe3, 0x27, 0xde, 0xf4, 0xc4, 0xe1, + 0x32, 0x0b, 0x8f, 0x1e, 0xca, 0x9b, 0x75, 0x77, 0x3e, 0xfd, 0x25, 0x04, 0xb3, 0x2c, 0x81, 0x51, + 0x83, 0xca, 0x20, 0xf4, 0x66, 0x72, 0x9a, 0xeb, 0x50, 0xe5, 0x49, 0x71, 0x61, 0xfa, 0x16, 0xdc, + 0x44, 0x96, 0x30, 0xf4, 0x66, 0xde, 0xc4, 0x3b, 0xbb, 0x8a, 0xd9, 0xf1, 0xfe, 0x55, 0x06, 0x56, + 0x63, 0x58, 0xc1, 0x5e, 0x3f, 0xe2, 0xfc, 0x4c, 0x5d, 0xf7, 0xcc, 0xc4, 0xee, 0xfa, 0xb0, 0xf9, + 0xe2, 0x84, 0x9c, 0x99, 0xc9, 0x2b, 0xa0, 0xad, 0xe8, 0x65, 0x17, 0x99, 0x91, 0xb3, 0x94, 0xe6, + 0x22, 0x4b, 0x11, 0xf9, 0xe5, 0x9b, 0x2f, 0xb2, 0x88, 0x5f, 0x10, 0x57, 0xb3, 0xc6, 0xa2, 0xcb, + 0xb9, 0xf8, 0xe5, 0x0d, 0xdd, 0xe6, 0x27, 0x5b, 0x10, 0x19, 0x02, 0x03, 0xe3, 0xef, 0x66, 0x00, + 0xa2, 0xd6, 0xe1, 0xf5, 0x11, 0x25, 0xb7, 0x64, 0x30, 0x70, 0x57, 0x93, 0x51, 0xde, 0x80, 0xaa, + 0x8a, 0xf1, 0x8e, 0x24, 0xa1, 0x8a, 0x84, 0x31, 0x71, 0xe8, 0x1d, 0x58, 0x3e, 0x9b, 0x78, 0x27, + 0x28, 0xb1, 0x0a, 0xb9, 0x85, 0x47, 0xcf, 0xd5, 0x39, 0x58, 0x4a, 0x23, 0x91, 0xdc, 0x94, 0x4f, + 0x0d, 0x03, 0xd7, 0xa5, 0x20, 0xe3, 0x2f, 0x67, 0x55, 0x20, 0x69, 0x34, 0x12, 0x2f, 0x56, 0xef, + 0x7e, 0x9a, 0x50, 0x9b, 0x17, 0xb9, 0x17, 0x1f, 0x43, 0xdd, 0xe7, 0x87, 0x92, 0x3c, 0xb1, 0xf2, + 0x2f, 0x38, 0xb1, 0x6a, 0x7e, 0x4c, 0xd2, 0xf9, 0x2e, 0x34, 0xec, 0xf1, 0x05, 0xf5, 0x43, 0x07, + 0xad, 0xf5, 0x28, 0x1f, 0x8b, 0xd0, 0x4d, 0x0d, 0x8e, 0x82, 0xe8, 0x3b, 0xb0, 0x2c, 0x2e, 0xf1, + 0x2b, 0x4a, 0xf1, 0x84, 0x58, 0x04, 0x66, 0x84, 0xc6, 0x3f, 0x94, 0x91, 0xab, 0xf1, 0xd9, 0x7d, + 0xf1, 0xa8, 0xe8, 0x3d, 0xcc, 0x2e, 0x3a, 0x50, 0xc5, 0x42, 0x12, 0x4e, 0x00, 0xc1, 0x8f, 0x38, + 0x50, 0xb8, 0x00, 0xe2, 0xc3, 0x9a, 0x7f, 0x95, 0x61, 0x35, 0xfe, 0x4d, 0x06, 0x8a, 0xfb, 0xde, + 0x6c, 0xdf, 0xe1, 0xf7, 0x1f, 0x70, 0x9b, 0x28, 0x1f, 0xd5, 0x12, 0x4b, 0x62, 0x5c, 0xd0, 0x0b, + 0xae, 0x41, 0xa6, 0x8a, 0x79, 0xb5, 0xb8, 0x98, 0xf7, 0x39, 0xdc, 0x42, 0x17, 0xa0, 0xef, 0xcd, + 0x3c, 0x9f, 0x6d, 0x55, 0x7b, 0xc2, 0xc5, 0x3d, 0xcf, 0x0d, 0xcf, 0x25, 0xef, 0xbc, 0x79, 0x4a, + 0xe9, 0x91, 0x46, 0x71, 0xa8, 0x08, 0xf0, 0x0a, 0xf4, 0x24, 0xbc, 0xb0, 0xb8, 0x86, 0x2e, 0xe4, + 0x51, 0xce, 0x51, 0x97, 0x19, 0xa2, 0x83, 0x70, 0x94, 0x48, 0x8d, 0x4f, 0xa1, 0xac, 0x8c, 0x3d, + 0xe4, 0x5d, 0x28, 0x9f, 0x7b, 0x33, 0x61, 0x11, 0xca, 0xc4, 0xae, 0x8a, 0x8a, 0x5e, 0x9b, 0xa5, + 0x73, 0xfe, 0x23, 0x30, 0xfe, 0xb0, 0x08, 0xc5, 0xae, 0x7b, 0xe1, 0x39, 0x23, 0x8c, 0x7d, 0x9d, + 0xd2, 0xa9, 0x27, 0x5f, 0x12, 0x61, 0xbf, 0x31, 0x74, 0x2b, 0x7a, 0x08, 0x2c, 0x27, 0x42, 0xb7, + 0xd4, 0x13, 0x60, 0xeb, 0xb0, 0xe4, 0xeb, 0x2f, 0x79, 0x15, 0x7c, 0xbc, 0x31, 0xa0, 0xce, 0xcb, + 0x82, 0xf6, 0x3e, 0x0b, 0x2b, 0x8b, 0x87, 0x25, 0xe2, 0x90, 0xf1, 0x6b, 0xcc, 0x65, 0x84, 0xe0, + 0x80, 0xbd, 0x06, 0x45, 0x71, 0x33, 0x93, 0xdf, 0x13, 0xe3, 0x21, 0xfe, 0x02, 0x84, 0xab, 0xc1, + 0xa7, 0xdc, 0x85, 0xab, 0x04, 0xd9, 0x9c, 0x59, 0x95, 0xc0, 0x5d, 0xb6, 0xd6, 0xee, 0x40, 0x85, + 0xd3, 0x73, 0x92, 0x92, 0x08, 0x19, 0x45, 0x10, 0x12, 0xa4, 0x3c, 0x88, 0x57, 0x4e, 0x7d, 0x10, + 0x0f, 0x83, 0x9b, 0x15, 0x97, 0xe5, 0x5d, 0x04, 0xfe, 0x0c, 0x9a, 0x06, 0x97, 0xaf, 0x4c, 0x0a, + 0x9b, 0x0a, 0xbf, 0xe1, 0x2f, 0x6d, 0x2a, 0x6f, 0x42, 0xed, 0xd4, 0x9e, 0x4c, 0x4e, 0xec, 0xd1, + 0x33, 0x6e, 0x0a, 0xa8, 0x72, 0xeb, 0xa7, 0x04, 0xa2, 0x2d, 0xe0, 0x0e, 0x54, 0xb4, 0x59, 0xc6, + 0x78, 0xd0, 0xbc, 0x09, 0xd1, 0xfc, 0x26, 0x2d, 0x7c, 0xf5, 0x57, 0xb0, 0xf0, 0x69, 0x71, 0xb1, + 0xcb, 0xf1, 0xb8, 0xd8, 0x5b, 0xc8, 0x4d, 0x45, 0x44, 0x62, 0x83, 0xbf, 0xb9, 0x65, 0x8f, 0xc7, + 0x18, 0x91, 0x88, 0x86, 0x2c, 0x3e, 0x78, 0x1c, 0xbf, 0xc2, 0x75, 0x09, 0x0e, 0xe3, 0x24, 0xb7, + 0xb9, 0x99, 0x7a, 0x66, 0x3b, 0x63, 0xbc, 0xa6, 0xc1, 0xad, 0x07, 0x45, 0x7b, 0x1a, 0x1e, 0xd9, + 0xce, 0x98, 0xdc, 0x85, 0xaa, 0x44, 0xe3, 0xe9, 0xb8, 0xca, 0xc7, 0x5f, 0xa0, 0x07, 0xfc, 0xfd, + 0x0a, 0x45, 0x31, 0x55, 0x57, 0xf4, 0xcd, 0x8a, 0x20, 0xc1, 0x75, 0xf0, 0x01, 0x46, 0xf9, 0x84, + 0x14, 0x2f, 0xe1, 0xd7, 0x1f, 0xde, 0x52, 0xc1, 0x07, 0xb8, 0x4a, 0xe5, 0x7f, 0xee, 0x1c, 0xe3, + 0x94, 0x4c, 0xb8, 0xe3, 0x3e, 0xba, 0x8d, 0x98, 0xfc, 0x2b, 0x48, 0xd1, 0x47, 0xc7, 0x09, 0xc8, + 0xa7, 0x9a, 0xfe, 0xda, 0x44, 0xe2, 0xd7, 0x12, 0xe5, 0x5f, 0x77, 0x0f, 0xee, 0x36, 0x80, 0x13, + 0xb0, 0x53, 0x26, 0xa0, 0xee, 0x18, 0xef, 0xd2, 0x97, 0xcc, 0xb2, 0x13, 0x3c, 0xe5, 0x80, 0x6f, + 0x57, 0xb1, 0x6d, 0x41, 0x55, 0xef, 0x26, 0x29, 0x41, 0xbe, 0x7f, 0xd4, 0xe9, 0x35, 0x6e, 0x90, + 0x0a, 0x14, 0x07, 0x9d, 0xe1, 0xf0, 0x00, 0x3d, 0x7d, 0x55, 0x28, 0xa9, 0x9b, 0xb2, 0x59, 0x96, + 0x6a, 0xb5, 0xdb, 0x9d, 0xa3, 0x61, 0x67, 0xb7, 0x91, 0xfb, 0x51, 0xbe, 0x94, 0x6d, 0xe4, 0x8c, + 0x3f, 0xca, 0x41, 0x45, 0x1b, 0x85, 0x17, 0x33, 0xe3, 0xdb, 0x00, 0xa8, 0x49, 0x46, 0x01, 0xab, + 0x79, 0xb3, 0xcc, 0x20, 0x7c, 0xf2, 0x75, 0x1f, 0x05, 0x7f, 0xcc, 0x44, 0xf9, 0x28, 0xde, 0x84, + 0x1a, 0x7f, 0x6e, 0x44, 0xf7, 0xd7, 0x16, 0xcc, 0x2a, 0x07, 0x0a, 0x56, 0x8d, 0xf7, 0xee, 0x91, + 0x08, 0x6f, 0x34, 0x8a, 0xf7, 0x90, 0x38, 0x08, 0xef, 0x34, 0xe2, 0x85, 0xd4, 0xc0, 0x9b, 0x5c, + 0x50, 0x4e, 0xc1, 0x25, 0xc2, 0x8a, 0x80, 0x0d, 0xc5, 0x9b, 0x06, 0x82, 0x1f, 0x6a, 0x17, 0xbf, + 0x0b, 0x66, 0x95, 0x03, 0x45, 0x45, 0xdf, 0x93, 0x0b, 0x88, 0x47, 0xaf, 0x6c, 0x2e, 0xae, 0x86, + 0xd8, 0xe2, 0x39, 0x58, 0x30, 0x23, 0x96, 0x71, 0x61, 0x7c, 0x67, 0x31, 0xdf, 0xcb, 0xcd, 0x89, + 0xe4, 0x5d, 0x20, 0xd3, 0xd9, 0xcc, 0x4a, 0x31, 0xf0, 0xe5, 0xcd, 0xe5, 0xe9, 0x6c, 0x36, 0xd4, + 0xec, 0x5f, 0xdf, 0x82, 0xed, 0xf1, 0x6b, 0x20, 0x2d, 0xb6, 0x81, 0xb1, 0x89, 0x4a, 0x15, 0x8b, + 0xd8, 0x72, 0x46, 0x67, 0xcb, 0x29, 0xdc, 0x2f, 0x9b, 0xca, 0xfd, 0x5e, 0xc4, 0x27, 0x8c, 0x3d, + 0xa8, 0x1c, 0x69, 0xaf, 0x2e, 0xde, 0x65, 0x27, 0x84, 0x7c, 0x6f, 0x91, 0x9f, 0x1d, 0xdc, 0xa6, + 0xe8, 0x8b, 0x67, 0x16, 0xb5, 0xd6, 0x64, 0xb5, 0xd6, 0x18, 0x7f, 0x3b, 0xc3, 0x5f, 0xa9, 0x52, + 0x8d, 0x8f, 0x1e, 0x7a, 0x94, 0xee, 0xb7, 0xe8, 0x7d, 0x86, 0x8a, 0x74, 0xbb, 0x89, 0xa7, 0x15, + 0xb0, 0x69, 0x96, 0x77, 0x7a, 0x1a, 0x50, 0x19, 0xe3, 0x51, 0x41, 0x58, 0x1f, 0x41, 0x52, 0xf8, + 0x66, 0x12, 0xbe, 0xc3, 0xcb, 0x0f, 0x44, 0x60, 0x07, 0x13, 0xbe, 0x0f, 0xed, 0x4b, 0x51, 0x6b, + 0xc0, 0x44, 0x10, 0xe1, 0x1f, 0x90, 0xf7, 0x93, 0x55, 0xda, 0xf8, 0x1b, 0xe2, 0x09, 0x89, 0xe4, + 0xf8, 0xde, 0x87, 0x92, 0x2a, 0x35, 0x7e, 0xc2, 0x4a, 0x4a, 0x85, 0x67, 0xe7, 0x38, 0x1a, 0x43, + 0x62, 0x2d, 0xe6, 0x9b, 0x0b, 0x7d, 0x3c, 0x5d, 0xad, 0xd5, 0xef, 0x01, 0x39, 0x75, 0xfc, 0x24, + 0x31, 0xdf, 0x6c, 0x0d, 0xc4, 0x68, 0xd4, 0xc6, 0x31, 0xac, 0x4a, 0x2e, 0xa1, 0x69, 0x04, 0xf1, + 0xc9, 0xcb, 0xbc, 0x84, 0xc9, 0x67, 0x17, 0x98, 0xbc, 0xf1, 0x1b, 0x05, 0x28, 0xca, 0x17, 0x4c, + 0xd3, 0x5e, 0xdd, 0x2c, 0xc7, 0x5f, 0xdd, 0x6c, 0xc6, 0xde, 0x62, 0xc3, 0xa9, 0x17, 0xe7, 0xfd, + 0x3b, 0xc9, 0x23, 0x5b, 0xf3, 0x55, 0xc4, 0x8e, 0x6d, 0xe1, 0xab, 0x28, 0xc4, 0x7d, 0x15, 0x69, + 0x2f, 0x91, 0x72, 0xd1, 0x73, 0xe1, 0x25, 0xd2, 0x5b, 0xc0, 0xe5, 0x08, 0x2d, 0xb8, 0xad, 0x84, + 0x00, 0x71, 0xc7, 0x5e, 0x13, 0x3b, 0x4a, 0x49, 0xb1, 0xe3, 0x95, 0x45, 0x82, 0x8f, 0x60, 0x89, + 0x3f, 0x47, 0x23, 0xee, 0x5b, 0xcb, 0x83, 0x43, 0x8c, 0x95, 0xfc, 0xcf, 0x2f, 0x44, 0x98, 0x82, + 0x56, 0x7f, 0xd6, 0xaf, 0x12, 0x7b, 0xd6, 0x4f, 0xf7, 0xa1, 0x54, 0xe3, 0x3e, 0x94, 0x7b, 0xd0, + 0x50, 0x03, 0x87, 0x16, 0x49, 0x37, 0x10, 0x77, 0x2d, 0xeb, 0x12, 0xce, 0xb8, 0x61, 0x2f, 0x88, + 0x0e, 0xbe, 0x7a, 0xec, 0xe0, 0x63, 0xbc, 0xaa, 0x15, 0x86, 0x74, 0x3a, 0x0b, 0xe5, 0xc1, 0xa7, + 0x3d, 0xfe, 0xca, 0x67, 0x9e, 0x5f, 0x06, 0x91, 0xd3, 0xcb, 0x57, 0xc7, 0x0e, 0xd4, 0x4f, 0x6d, + 0x67, 0x32, 0xf7, 0xa9, 0xe5, 0x53, 0x3b, 0xf0, 0x5c, 0xdc, 0xfc, 0xd1, 0x19, 0x2c, 0xba, 0xb8, + 0xc7, 0x69, 0x4c, 0x24, 0x31, 0x6b, 0xa7, 0x7a, 0x12, 0xaf, 0x54, 0xe9, 0x23, 0xc1, 0x8e, 0x2c, + 0x71, 0xeb, 0x9a, 0xc7, 0xaa, 0x74, 0x7b, 0xd6, 0xde, 0x41, 0xf7, 0xc9, 0xfe, 0xb0, 0x91, 0x61, + 0xc9, 0xc1, 0x71, 0xbb, 0xdd, 0xe9, 0xec, 0xe2, 0x11, 0x06, 0xb0, 0xb4, 0xd7, 0xea, 0x1e, 0x88, + 0x03, 0x2c, 0xdf, 0x28, 0x18, 0xff, 0x34, 0x0b, 0x15, 0xad, 0x37, 0xe4, 0x91, 0x9a, 0x04, 0xfe, + 0xce, 0xc3, 0xed, 0xc5, 0x1e, 0x6f, 0x4b, 0x0e, 0xaf, 0xcd, 0x82, 0x7a, 0xe6, 0x35, 0x7b, 0xed, + 0x33, 0xaf, 0xe4, 0x6d, 0x58, 0xb6, 0x79, 0x09, 0x6a, 0xd0, 0x85, 0x71, 0x5f, 0x80, 0xc5, 0x98, + 0xbf, 0x2d, 0xde, 0x9c, 0x10, 0xc7, 0x14, 0xa3, 0xcb, 0xcb, 0xa0, 0x4d, 0x75, 0x52, 0xe1, 0xdc, + 0x14, 0xc5, 0xc8, 0x08, 0x67, 0xbc, 0x3a, 0xf0, 0xc5, 0x78, 0x49, 0x34, 0xbf, 0x67, 0xa9, 0xad, + 0xf0, 0xaa, 0xa9, 0xd2, 0xc6, 0xc7, 0x00, 0x51, 0x7f, 0xe2, 0xc3, 0x77, 0x23, 0x3e, 0x7c, 0x19, + 0x6d, 0xf8, 0xb2, 0xc6, 0x3f, 0x10, 0xac, 0x4b, 0xcc, 0x85, 0x32, 0xf5, 0x7d, 0x0f, 0xa4, 0xf1, + 0xd1, 0xc2, 0x20, 0xef, 0xd9, 0x84, 0x86, 0xf2, 0xaa, 0xe8, 0x8a, 0xc0, 0x74, 0x15, 0x62, 0x81, + 0xd5, 0x66, 0x17, 0x59, 0xed, 0x1b, 0x50, 0xc5, 0x47, 0xcc, 0x44, 0x45, 0x82, 0x5d, 0x55, 0xa6, + 0xf6, 0xa5, 0xac, 0x3b, 0xc6, 0x63, 0xf3, 0x09, 0x1e, 0xfb, 0x37, 0x33, 0xfc, 0xc5, 0x9b, 0xa8, + 0xa1, 0x11, 0x93, 0x55, 0x65, 0xc6, 0x99, 0xac, 0x20, 0x35, 0x15, 0xfe, 0x1a, 0xc6, 0x99, 0x4d, + 0x67, 0x9c, 0xe9, 0x2c, 0x39, 0x97, 0xca, 0x92, 0x8d, 0x2d, 0x68, 0xee, 0x52, 0x36, 0x14, 0xad, + 0xc9, 0x24, 0x31, 0x96, 0xc6, 0x2d, 0xb8, 0x99, 0x82, 0x13, 0x56, 0x9b, 0xdf, 0xcc, 0xc0, 0x7a, + 0x8b, 0x3f, 0x74, 0xf1, 0xad, 0xdd, 0xe5, 0xfc, 0x0c, 0x6e, 0xaa, 0x88, 0x6d, 0xed, 0x8a, 0x98, + 0xfe, 0x4a, 0x91, 0x0c, 0xf6, 0xd6, 0xee, 0x29, 0xb0, 0x33, 0xd3, 0x68, 0xc2, 0x46, 0xb2, 0x35, + 0xa2, 0xa1, 0x7b, 0xb0, 0xb2, 0x4b, 0x4f, 0xe6, 0x67, 0x07, 0xf4, 0x22, 0x6a, 0x23, 0x81, 0x7c, + 0x70, 0xee, 0x3d, 0x17, 0x0b, 0x03, 0x7f, 0x63, 0x48, 0x27, 0xa3, 0xb1, 0x82, 0x19, 0x1d, 0x49, + 0xab, 0x3f, 0x42, 0x06, 0x33, 0x3a, 0x32, 0x1e, 0x01, 0xd1, 0xcb, 0x11, 0xb3, 0xc8, 0x54, 0xb2, + 0xf9, 0x89, 0x15, 0x5c, 0x05, 0x21, 0x9d, 0xca, 0xeb, 0x8f, 0x10, 0xcc, 0x4f, 0x06, 0x1c, 0x62, + 0xbc, 0x03, 0xd5, 0x23, 0xfb, 0xca, 0xa4, 0x5f, 0x8b, 0x5b, 0x86, 0x9b, 0x50, 0x9c, 0xd9, 0x57, + 0x8c, 0x17, 0x2b, 0x07, 0x20, 0xa2, 0x8d, 0x7f, 0x94, 0x87, 0x25, 0x4e, 0x49, 0xee, 0xf2, 0x07, + 0xd8, 0x1d, 0x17, 0x79, 0xa1, 0x3c, 0x95, 0x34, 0xd0, 0xc2, 0xc1, 0x95, 0x5d, 0x3c, 0xb8, 0x84, + 0xb5, 0x52, 0xbe, 0xa2, 0x26, 0x5d, 0x35, 0xee, 0x7c, 0x2a, 0x9f, 0x4e, 0x8b, 0xbf, 0xf3, 0x90, + 0x8f, 0x1e, 0xee, 0xe7, 0x77, 0xdc, 0xe3, 0xce, 0xf4, 0x48, 0xf1, 0xe3, 0xad, 0x93, 0xe7, 0xb1, + 0x38, 0xb3, 0x74, 0x50, 0xaa, 0x76, 0x59, 0x94, 0x57, 0x67, 0xe3, 0xda, 0xe5, 0x82, 0x16, 0x59, + 0x7a, 0xb9, 0x16, 0xc9, 0xcd, 0x98, 0x2f, 0xd0, 0x22, 0xe1, 0x15, 0xb4, 0xc8, 0x57, 0x70, 0x64, + 0xdf, 0x84, 0x12, 0x0a, 0x59, 0xda, 0x11, 0xc6, 0x84, 0x2b, 0x76, 0x84, 0x7d, 0xa2, 0xe9, 0x59, + 0x3c, 0x8a, 0x46, 0x3b, 0x43, 0x4c, 0xfa, 0xf5, 0xcf, 0xc7, 0x41, 0xf8, 0x15, 0x14, 0x05, 0x94, + 0x2d, 0x68, 0xd7, 0x9e, 0xca, 0xf7, 0x38, 0xf1, 0x37, 0x1b, 0x36, 0x7c, 0x3d, 0xef, 0xeb, 0xb9, + 0xe3, 0xd3, 0xb1, 0x7c, 0xc3, 0xcb, 0xc1, 0xfd, 0xcd, 0x20, 0xac, 0x83, 0x4c, 0xe7, 0x73, 0xbd, + 0xe7, 0xae, 0xe0, 0x5b, 0x45, 0x27, 0x78, 0xca, 0x92, 0x06, 0x81, 0x06, 0xbe, 0xde, 0x3b, 0xf3, + 0x7c, 0x29, 0x21, 0x18, 0xbf, 0x9b, 0x81, 0x86, 0xd8, 0x5d, 0x0a, 0xa7, 0xab, 0x5c, 0x85, 0xeb, + 0x82, 0x3e, 0x5e, 0xfc, 0x22, 0x97, 0x01, 0x35, 0xb4, 0x34, 0x29, 0x71, 0x81, 0x5b, 0xca, 0x2a, + 0x0c, 0xb8, 0x27, 0x44, 0x86, 0xd7, 0xa1, 0x22, 0x03, 0xce, 0xa7, 0xce, 0x44, 0x7e, 0xa3, 0x83, + 0x47, 0x9c, 0x1f, 0x3a, 0x13, 0x29, 0x6d, 0xf8, 0xb6, 0xb8, 0xca, 0x9d, 0x41, 0x69, 0xc3, 0xb4, + 0x43, 0x6a, 0xfc, 0x93, 0x0c, 0xac, 0x68, 0x5d, 0x11, 0xfb, 0xf6, 0xfb, 0x50, 0x55, 0xcf, 0x66, + 0x53, 0x25, 0xe6, 0x6e, 0xc6, 0x79, 0x54, 0x94, 0xad, 0x32, 0x52, 0x90, 0x80, 0x35, 0x66, 0x6c, + 0x5f, 0xf1, 0xa8, 0xe8, 0xf9, 0x54, 0x6a, 0x92, 0x63, 0xfb, 0x6a, 0x8f, 0xd2, 0xc1, 0x7c, 0x4a, + 0xee, 0x42, 0xf5, 0x39, 0xa5, 0xcf, 0x14, 0x01, 0x67, 0xbd, 0xc0, 0x60, 0x82, 0xc2, 0x80, 0xda, + 0xd4, 0x73, 0xc3, 0x73, 0x45, 0x22, 0x44, 0x7c, 0x04, 0x72, 0x1a, 0xe3, 0x0f, 0xb2, 0xb0, 0xca, + 0xed, 0x99, 0xc2, 0x8e, 0x2c, 0x58, 0x57, 0x13, 0x96, 0xb8, 0x69, 0x97, 0x33, 0xaf, 0xfd, 0x1b, + 0xa6, 0x48, 0x93, 0x8f, 0x5e, 0xd1, 0x06, 0x2b, 0x6f, 0x8b, 0x5f, 0x33, 0xfc, 0xb9, 0xc5, 0xe1, + 0xbf, 0x7e, 0x78, 0xd3, 0xbc, 0xca, 0x85, 0x34, 0xaf, 0xf2, 0xab, 0xf8, 0x72, 0x17, 0xee, 0x35, + 0x17, 0x17, 0x9f, 0xff, 0x7c, 0x04, 0x9b, 0x31, 0x1a, 0xe4, 0xd6, 0xce, 0xa9, 0x43, 0xe5, 0x03, + 0x43, 0x6b, 0x1a, 0xf5, 0x40, 0xe2, 0x76, 0x8a, 0x50, 0x08, 0x46, 0xde, 0x8c, 0x1a, 0x1b, 0xb0, + 0x16, 0x1f, 0x55, 0x71, 0x4c, 0xfc, 0x76, 0x06, 0x9a, 0x22, 0x06, 0xc8, 0x71, 0xcf, 0xf6, 0x9d, + 0x20, 0xf4, 0x7c, 0xf5, 0xbc, 0xf4, 0x6d, 0x00, 0xfe, 0xbd, 0x10, 0x54, 0xdc, 0xc5, 0x8b, 0x38, + 0x08, 0x41, 0xb5, 0xfd, 0x26, 0x94, 0xa8, 0x3b, 0xe6, 0x48, 0xbe, 0x1a, 0x8a, 0xd4, 0x1d, 0x4b, + 0xa5, 0x7f, 0xe1, 0x18, 0xae, 0xc5, 0x05, 0x0c, 0xf1, 0xb6, 0x03, 0x1b, 0x1d, 0x7a, 0x81, 0xe2, + 0x40, 0x5e, 0xbd, 0xed, 0x70, 0x68, 0x5f, 0x62, 0x44, 0x6d, 0x60, 0xfc, 0x95, 0x2c, 0x2c, 0x47, + 0xed, 0xe3, 0xaf, 0xdb, 0xbc, 0xf8, 0x9d, 0x9e, 0xbb, 0x62, 0x39, 0x38, 0x4c, 0x59, 0xd2, 0xac, + 0xbc, 0x25, 0xbe, 0x39, 0xbb, 0x2e, 0x31, 0xa0, 0x22, 0x29, 0xbc, 0x79, 0xa8, 0x3d, 0x59, 0x5a, + 0xe6, 0x24, 0xfd, 0x79, 0xc8, 0xb4, 0x5b, 0xa6, 0xe6, 0x3b, 0xae, 0xd0, 0x2f, 0x0b, 0xf6, 0x34, + 0xec, 0xe2, 0x47, 0x69, 0x18, 0x98, 0x65, 0xe3, 0x13, 0xc9, 0xa8, 0x18, 0x7d, 0x83, 0x2b, 0x3b, + 0x7c, 0xe6, 0x50, 0xd1, 0xd1, 0x35, 0x01, 0xfe, 0x8e, 0xbe, 0xd2, 0x04, 0x5e, 0x87, 0x0a, 0x2f, + 0x3c, 0xba, 0xc6, 0x8e, 0xef, 0x87, 0x85, 0x5d, 0x17, 0xf1, 0xc2, 0xe2, 0xe6, 0xcd, 0x63, 0x76, + 0x06, 0xe0, 0x55, 0x61, 0x88, 0xcd, 0x6f, 0x66, 0xe0, 0x66, 0xca, 0xb4, 0x89, 0x5d, 0xde, 0x86, + 0x95, 0x53, 0x85, 0x94, 0xa3, 0xcb, 0xb7, 0xfa, 0x86, 0x64, 0xab, 0xf1, 0x31, 0x35, 0x1b, 0xa7, + 0x71, 0x40, 0xa4, 0xe1, 0xf2, 0x19, 0x8c, 0x3d, 0x92, 0x80, 0xe2, 0x14, 0x9f, 0x46, 0xae, 0x5c, + 0x1e, 0xc1, 0x56, 0xe7, 0x92, 0x71, 0x0c, 0x15, 0x96, 0x3b, 0x7a, 0x36, 0x97, 0x9e, 0xaf, 0x84, + 0x35, 0x3f, 0xf3, 0x4a, 0xd6, 0xfc, 0x31, 0xbf, 0xe6, 0xac, 0xca, 0xfa, 0x69, 0x0a, 0xc1, 0x03, + 0x94, 0xe5, 0x39, 0xc1, 0x22, 0xe4, 0x6b, 0x09, 0x0c, 0xc4, 0x0b, 0x35, 0x02, 0x58, 0x3e, 0x9c, + 0x4f, 0x42, 0xa7, 0xad, 0x40, 0xe4, 0x23, 0x91, 0x07, 0xeb, 0x91, 0xa3, 0x96, 0x5a, 0x11, 0xa8, + 0x8a, 0x70, 0xb0, 0xa6, 0xac, 0x20, 0x6b, 0xb1, 0xbe, 0xe5, 0x69, 0xbc, 0x06, 0xe3, 0x26, 0x6c, + 0x46, 0x29, 0x3e, 0x6c, 0xf2, 0xa8, 0xf9, 0x5b, 0x19, 0x1e, 0xbe, 0xcf, 0x71, 0x03, 0xd7, 0x9e, + 0x05, 0xe7, 0x5e, 0x48, 0x3a, 0xb0, 0x1a, 0x38, 0xee, 0xd9, 0x84, 0xea, 0xc5, 0x07, 0x62, 0x10, + 0xd6, 0xe3, 0x6d, 0xe3, 0x59, 0x03, 0x73, 0x85, 0xe7, 0x88, 0x4a, 0x0b, 0xc8, 0xce, 0x75, 0x8d, + 0x8c, 0x96, 0x45, 0x62, 0x34, 0x16, 0x1b, 0xdf, 0x85, 0x7a, 0xbc, 0x22, 0xf2, 0x89, 0x78, 0x1d, + 0x20, 0x6a, 0x55, 0x2e, 0x71, 0x37, 0x3a, 0x5a, 0x10, 0x95, 0x68, 0xec, 0x03, 0xe3, 0x2f, 0x65, + 0xa0, 0x69, 0x52, 0xb6, 0x72, 0xb5, 0x56, 0xca, 0x35, 0xf3, 0xfd, 0x85, 0x52, 0xaf, 0xef, 0xab, + 0x7c, 0x74, 0x40, 0xb6, 0xe8, 0xbd, 0x6b, 0x27, 0x63, 0xff, 0xc6, 0x42, 0x8f, 0x76, 0x4a, 0xb0, + 0xc4, 0x49, 0x8c, 0x4d, 0x58, 0x17, 0xed, 0x91, 0x6d, 0x89, 0x5c, 0xb5, 0xb1, 0x1a, 0x63, 0xae, + 0xda, 0x2d, 0x68, 0xf2, 0x7b, 0xbe, 0x7a, 0x27, 0x44, 0xc6, 0x5d, 0x20, 0x87, 0xf6, 0xc8, 0xf6, + 0x3d, 0xcf, 0x3d, 0xa2, 0xbe, 0x08, 0x86, 0x46, 0x09, 0x13, 0x3d, 0x99, 0x52, 0x14, 0xe6, 0x29, + 0xf9, 0x50, 0xb3, 0xe7, 0xca, 0xd8, 0x2f, 0x9e, 0x32, 0x7c, 0x58, 0xdd, 0xb1, 0x9f, 0x51, 0x59, + 0x92, 0x1c, 0xa2, 0xc7, 0x50, 0x99, 0xa9, 0x42, 0xe5, 0xb8, 0xcb, 0xc7, 0x52, 0x16, 0xab, 0x35, + 0x75, 0x6a, 0xc6, 0x82, 0x7c, 0xcf, 0x0b, 0xf1, 0x61, 0x02, 0xe9, 0x0c, 0x33, 0xcb, 0x0c, 0xf4, + 0x94, 0x5e, 0x75, 0xc7, 0xc6, 0x43, 0x58, 0x8b, 0xd7, 0x29, 0x58, 0xcb, 0x16, 0x94, 0xa6, 0x02, + 0x26, 0x5a, 0xaf, 0xd2, 0x4c, 0x19, 0x61, 0x2a, 0x9f, 0xcc, 0xd3, 0xdd, 0x55, 0x2a, 0xd5, 0x63, + 0xd8, 0x5c, 0xc0, 0x88, 0x02, 0xef, 0x42, 0x55, 0x6b, 0x08, 0xef, 0x46, 0x9e, 0x89, 0xac, 0xa2, + 0x25, 0x81, 0xf1, 0x19, 0x6c, 0x72, 0x7d, 0x2c, 0xca, 0x2e, 0x87, 0x20, 0xd1, 0x8b, 0x4c, 0xb2, + 0x17, 0x1f, 0x49, 0x35, 0x4f, 0xcf, 0x1a, 0x5d, 0x15, 0x18, 0x23, 0x4e, 0x86, 0xef, 0xc8, 0xa4, + 0x71, 0x0c, 0x1b, 0x8b, 0xc3, 0xc7, 0xda, 0xff, 0x33, 0x0d, 0xb9, 0x1c, 0x9e, 0x08, 0xad, 0x86, + 0xe7, 0xbf, 0x66, 0xf8, 0xf8, 0xc4, 0x50, 0xa2, 0x99, 0x63, 0x20, 0x53, 0x1a, 0x9e, 0x7b, 0x63, + 0x6b, 0xb1, 0xe6, 0x47, 0x2a, 0x7a, 0x28, 0x35, 0xef, 0xf6, 0x21, 0x66, 0xd4, 0x30, 0x22, 0x8e, + 0x7d, 0x9a, 0x84, 0x6f, 0x8d, 0x60, 0x23, 0x9d, 0x38, 0x25, 0xe6, 0xe6, 0xc3, 0xb8, 0xa0, 0x7e, + 0xfb, 0xda, 0xee, 0xb3, 0x66, 0xe9, 0x72, 0xfb, 0x6f, 0x95, 0xa0, 0x28, 0xac, 0x24, 0x64, 0x1b, + 0xf2, 0x23, 0x19, 0xbf, 0x19, 0x3d, 0x44, 0x27, 0xb0, 0xf2, 0x7f, 0x1b, 0xa3, 0x38, 0x19, 0x1d, + 0x79, 0x0c, 0xf5, 0x78, 0x08, 0x43, 0xe2, 0x91, 0x8a, 0x78, 0xec, 0x41, 0x6d, 0x94, 0x70, 0x56, + 0x97, 0x23, 0xe1, 0x8a, 0xcb, 0x9c, 0xa5, 0x73, 0x4d, 0xfa, 0xf2, 0x5c, 0xa6, 0xaf, 0x05, 0xe7, + 0xb6, 0xf5, 0xf0, 0xd1, 0xc7, 0xe2, 0x95, 0x8a, 0x0a, 0x02, 0x07, 0xe7, 0xf6, 0xc3, 0x47, 0x1f, + 0x27, 0x35, 0x31, 0xf1, 0x46, 0x85, 0xa6, 0x89, 0xad, 0x41, 0x81, 0xbf, 0x66, 0xcd, 0x03, 0xf1, + 0x78, 0x82, 0x3c, 0x80, 0x35, 0x69, 0x78, 0x13, 0x57, 0x26, 0xf8, 0x29, 0x5a, 0xe2, 0xb7, 0x54, + 0x05, 0x6e, 0x80, 0x28, 0x6e, 0xaa, 0xdb, 0x80, 0xa5, 0xf3, 0xe8, 0x79, 0xf2, 0x9a, 0x29, 0x52, + 0xc6, 0x1f, 0x14, 0xa0, 0xa2, 0x0d, 0x0a, 0xa9, 0x42, 0xc9, 0xec, 0x0c, 0x3a, 0xe6, 0x17, 0x9d, + 0xdd, 0xc6, 0x0d, 0x72, 0x0f, 0xde, 0xea, 0xf6, 0xda, 0x7d, 0xd3, 0xec, 0xb4, 0x87, 0x56, 0xdf, + 0xb4, 0xe4, 0x73, 0x88, 0x47, 0xad, 0xaf, 0x0e, 0x3b, 0xbd, 0xa1, 0xb5, 0xdb, 0x19, 0xb6, 0xba, + 0x07, 0x83, 0x46, 0x86, 0xbc, 0x06, 0xcd, 0x88, 0x52, 0xa2, 0x5b, 0x87, 0xfd, 0xe3, 0xde, 0xb0, + 0x91, 0x25, 0x77, 0xe0, 0xd6, 0x5e, 0xb7, 0xd7, 0x3a, 0xb0, 0x22, 0x9a, 0xf6, 0xc1, 0xf0, 0x0b, + 0xab, 0xf3, 0x8b, 0x47, 0x5d, 0xf3, 0xab, 0x46, 0x2e, 0x8d, 0x60, 0x7f, 0x78, 0xd0, 0x96, 0x25, + 0xe4, 0xc9, 0x4d, 0x58, 0xe7, 0x04, 0x3c, 0x8b, 0x35, 0xec, 0xf7, 0xad, 0x41, 0xbf, 0xdf, 0x6b, + 0x14, 0xc8, 0x0a, 0xd4, 0xba, 0xbd, 0x2f, 0x5a, 0x07, 0xdd, 0x5d, 0xcb, 0xec, 0xb4, 0x0e, 0x0e, + 0x1b, 0x4b, 0x64, 0x15, 0x96, 0x93, 0x74, 0x45, 0x56, 0x84, 0xa4, 0xeb, 0xf7, 0xba, 0xfd, 0x9e, + 0xf5, 0x45, 0xc7, 0x1c, 0x74, 0xfb, 0xbd, 0x46, 0x89, 0x6c, 0x00, 0x89, 0xa3, 0xf6, 0x0f, 0x5b, + 0xed, 0x46, 0x99, 0xac, 0xc3, 0x4a, 0x1c, 0xfe, 0xb4, 0xf3, 0x55, 0x03, 0x48, 0x13, 0xd6, 0x78, + 0xc3, 0xac, 0x9d, 0xce, 0x41, 0xff, 0x4b, 0xeb, 0xb0, 0xdb, 0xeb, 0x1e, 0x1e, 0x1f, 0x36, 0x2a, + 0xf8, 0x28, 0x6d, 0xa7, 0x63, 0x75, 0x7b, 0x83, 0xe3, 0xbd, 0xbd, 0x6e, 0xbb, 0xdb, 0xe9, 0x0d, + 0x1b, 0x55, 0x5e, 0x73, 0x5a, 0xc7, 0x6b, 0x2c, 0x83, 0xb8, 0x57, 0x65, 0xed, 0x76, 0x07, 0xad, + 0x9d, 0x83, 0xce, 0x6e, 0xa3, 0x4e, 0x6e, 0xc3, 0xcd, 0x61, 0xe7, 0xf0, 0xa8, 0x6f, 0xb6, 0xcc, + 0xaf, 0xe4, 0xbd, 0x2b, 0x6b, 0xaf, 0xd5, 0x3d, 0x38, 0x36, 0x3b, 0x8d, 0x65, 0xf2, 0x06, 0xdc, + 0x36, 0x3b, 0x3f, 0x3e, 0xee, 0x9a, 0x9d, 0x5d, 0xab, 0xd7, 0xdf, 0xed, 0x58, 0x7b, 0x9d, 0xd6, + 0xf0, 0xd8, 0xec, 0x58, 0x87, 0xdd, 0xc1, 0xa0, 0xdb, 0x7b, 0xd2, 0x68, 0x90, 0xb7, 0xe0, 0xae, + 0x22, 0x51, 0x05, 0x24, 0xa8, 0x56, 0x58, 0xff, 0xe4, 0x94, 0xf6, 0x3a, 0xbf, 0x38, 0xb4, 0x8e, + 0x3a, 0x1d, 0xb3, 0x41, 0xc8, 0x16, 0x6c, 0x44, 0xd5, 0xf3, 0x0a, 0x44, 0xdd, 0xab, 0x0c, 0x77, + 0xd4, 0x31, 0x0f, 0x5b, 0x3d, 0x36, 0xc1, 0x31, 0xdc, 0x1a, 0x6b, 0x76, 0x84, 0x4b, 0x36, 0x7b, + 0x9d, 0x10, 0xa8, 0x6b, 0xb3, 0xb2, 0xd7, 0x32, 0x1b, 0x1b, 0x64, 0x19, 0x2a, 0x87, 0x47, 0x47, + 0xd6, 0xb0, 0x7b, 0xd8, 0xe9, 0x1f, 0x0f, 0x1b, 0x9b, 0x64, 0x1d, 0x1a, 0xdd, 0xde, 0xb0, 0x63, + 0xb2, 0xb9, 0x96, 0x59, 0xff, 0x5b, 0x91, 0xac, 0xc1, 0xb2, 0x6c, 0xa9, 0x84, 0xfe, 0x71, 0x91, + 0x6c, 0x02, 0x39, 0xee, 0x99, 0x9d, 0xd6, 0x2e, 0x1b, 0x38, 0x85, 0xf8, 0xef, 0x45, 0xe1, 0xce, + 0xfc, 0xdd, 0x9c, 0x12, 0xf6, 0xa2, 0xf8, 0xa0, 0xf8, 0x37, 0x3b, 0xaa, 0xda, 0xb7, 0x36, 0x5e, + 0xf6, 0xe5, 0x2d, 0x4d, 0x35, 0xcf, 0x2d, 0xa8, 0xe6, 0x0b, 0xb6, 0x9f, 0x9a, 0xae, 0x3b, 0xbc, + 0x09, 0xb5, 0x29, 0xff, 0x7e, 0x87, 0x78, 0x9c, 0x1e, 0x44, 0xb0, 0x1c, 0x07, 0xf2, 0x97, 0xe9, + 0x17, 0x3e, 0x3d, 0x55, 0x58, 0xfc, 0xf4, 0x54, 0x9a, 0x7e, 0xb8, 0x94, 0xa6, 0x1f, 0xde, 0x87, + 0x15, 0xce, 0x9a, 0x1c, 0xd7, 0x99, 0x4a, 0xab, 0x0b, 0xd7, 0x22, 0x96, 0x91, 0x45, 0x71, 0xb8, + 0x54, 0x47, 0xa5, 0xca, 0x2a, 0x58, 0x48, 0x51, 0x68, 0xab, 0x31, 0x4d, 0x95, 0x73, 0x0e, 0xa5, + 0xa9, 0xaa, 0x1a, 0xec, 0xcb, 0xa8, 0x86, 0x8a, 0x56, 0x03, 0x87, 0x63, 0x0d, 0xf7, 0x61, 0x85, + 0x5e, 0x86, 0xbe, 0x6d, 0x79, 0x33, 0xfb, 0xeb, 0x39, 0xc6, 0x5b, 0xd8, 0x68, 0x03, 0xaa, 0x9a, + 0xcb, 0x88, 0xe8, 0x23, 0x7c, 0xd7, 0x0e, 0x6d, 0xe3, 0x57, 0x00, 0xd4, 0xa9, 0x3a, 0x66, 0x0c, + 0xd0, 0xf5, 0xe4, 0xb5, 0xbb, 0xaa, 0xc9, 0x13, 0x38, 0x8f, 0xa1, 0xe7, 0xdb, 0x67, 0xb4, 0x2b, + 0x5f, 0x7a, 0x89, 0x00, 0xe4, 0x16, 0xe4, 0xbc, 0x99, 0x0c, 0x25, 0x2b, 0xcb, 0xd7, 0x96, 0x67, + 0x26, 0x83, 0x1a, 0x1f, 0x43, 0xb6, 0x3f, 0xbb, 0x56, 0x54, 0x6a, 0x42, 0x51, 0x7e, 0x6c, 0x32, + 0x8b, 0xe1, 0x63, 0x32, 0x79, 0xff, 0xcf, 0x42, 0x45, 0xfb, 0xe4, 0x0c, 0xd9, 0x84, 0xd5, 0x2f, + 0xbb, 0xc3, 0x5e, 0x67, 0x30, 0xb0, 0x8e, 0x8e, 0x77, 0x9e, 0x76, 0xbe, 0xb2, 0xf6, 0x5b, 0x83, + 0xfd, 0xc6, 0x0d, 0xc6, 0x4b, 0x7a, 0x9d, 0xc1, 0xb0, 0xb3, 0x1b, 0x83, 0x67, 0xc8, 0xeb, 0xb0, + 0x75, 0xdc, 0x3b, 0x1e, 0x74, 0x76, 0xad, 0xb4, 0x7c, 0x59, 0xb6, 0x79, 0x04, 0x3e, 0x25, 0x7b, + 0xee, 0xfe, 0xaf, 0x42, 0x3d, 0xfe, 0x32, 0x02, 0x01, 0x58, 0x3a, 0xe8, 0x3c, 0x69, 0xb5, 0xbf, + 0xe2, 0xaf, 0x69, 0x0f, 0x86, 0xad, 0x61, 0xb7, 0x6d, 0x89, 0xd7, 0xb3, 0x19, 0xa3, 0xca, 0x90, + 0x0a, 0x14, 0x5b, 0xbd, 0xf6, 0x7e, 0xdf, 0x1c, 0x34, 0xb2, 0xe4, 0x35, 0xd8, 0x94, 0x5b, 0xa8, + 0xdd, 0x3f, 0x3c, 0xec, 0x0e, 0x91, 0x47, 0x0f, 0xbf, 0x3a, 0x62, 0x3b, 0xe6, 0xbe, 0x0d, 0xe5, + 0xe8, 0xe1, 0x6f, 0xe4, 0x7b, 0xdd, 0x61, 0xb7, 0x35, 0x8c, 0x98, 0x7e, 0xe3, 0x06, 0x63, 0xab, + 0x11, 0x18, 0x5f, 0xef, 0x6e, 0x64, 0xf8, 0xe5, 0x51, 0x09, 0xe4, 0xb5, 0x37, 0xb2, 0x6c, 0xaf, + 0x47, 0xd0, 0x9d, 0xfe, 0x90, 0x75, 0xe1, 0xd7, 0xa0, 0x1e, 0x7f, 0x5f, 0x9b, 0x34, 0xa0, 0xca, + 0xea, 0xd7, 0xaa, 0x00, 0x58, 0xe2, 0x2d, 0x6e, 0x64, 0x38, 0x63, 0x6f, 0xf7, 0x0f, 0xbb, 0xbd, + 0x27, 0x78, 0x1a, 0x34, 0xb2, 0x0c, 0xd4, 0x3f, 0x1e, 0x3e, 0xe9, 0x2b, 0x50, 0x8e, 0xe5, 0xe0, + 0xdd, 0x69, 0xe4, 0xef, 0x7f, 0x0d, 0x2b, 0x0b, 0x2f, 0x71, 0xb3, 0x56, 0xf7, 0x8f, 0x87, 0xed, + 0xfe, 0xa1, 0x5e, 0x4f, 0x05, 0x8a, 0xed, 0x83, 0x56, 0xf7, 0x10, 0x1d, 0x21, 0x35, 0x28, 0x1f, + 0xf7, 0x64, 0x32, 0x1b, 0x7f, 0x43, 0x3c, 0xc7, 0x58, 0xd4, 0x5e, 0xd7, 0x1c, 0x0c, 0xad, 0xc1, + 0xb0, 0xf5, 0xa4, 0xd3, 0xc8, 0xb3, 0xbc, 0x92, 0x5f, 0x15, 0xee, 0x7f, 0x06, 0xf5, 0x78, 0xdc, + 0x73, 0xdc, 0x81, 0xb5, 0x05, 0x1b, 0x3b, 0x9d, 0xe1, 0x97, 0x9d, 0x4e, 0x0f, 0xa7, 0xbc, 0xdd, + 0xe9, 0x0d, 0xcd, 0xd6, 0x41, 0x77, 0xf8, 0x55, 0x23, 0x73, 0xff, 0x31, 0x34, 0x92, 0x41, 0x06, + 0xb1, 0xa8, 0x8c, 0x17, 0x85, 0x6f, 0xdc, 0xff, 0x4f, 0x19, 0x58, 0x4b, 0xf3, 0xaf, 0xb1, 0x85, + 0x29, 0x18, 0x21, 0x3b, 0x0e, 0x07, 0xfd, 0x9e, 0xd5, 0xeb, 0xe3, 0xa3, 0xba, 0x5b, 0xb0, 0x91, + 0x40, 0xc8, 0x5e, 0x64, 0xc8, 0x2d, 0xd8, 0x5c, 0xc8, 0x64, 0x99, 0xfd, 0x63, 0x9c, 0xcb, 0x26, + 0xac, 0x25, 0x90, 0x1d, 0xd3, 0xec, 0x9b, 0x8d, 0x1c, 0x79, 0x0f, 0xee, 0x25, 0x30, 0x8b, 0x42, + 0x80, 0x94, 0x11, 0xf2, 0xe4, 0x1d, 0x78, 0x73, 0x81, 0x3a, 0x3a, 0x27, 0xad, 0x9d, 0xd6, 0x01, + 0xeb, 0x5e, 0xa3, 0x70, 0xff, 0xef, 0xe7, 0x00, 0xa2, 0x8b, 0x85, 0xac, 0xfe, 0xdd, 0xd6, 0xb0, + 0x75, 0xd0, 0x67, 0x7b, 0xc6, 0xec, 0x0f, 0x59, 0xe9, 0x66, 0xe7, 0xc7, 0x8d, 0x1b, 0xa9, 0x98, + 0xfe, 0x11, 0xeb, 0xd0, 0x26, 0xac, 0xf2, 0xf5, 0x77, 0xc0, 0xba, 0xc1, 0x96, 0x0b, 0xbe, 0xcf, + 0x8c, 0x92, 0xc6, 0xf1, 0xd1, 0x9e, 0xd9, 0xef, 0x0d, 0xad, 0xc1, 0xfe, 0xf1, 0x70, 0x17, 0x5f, + 0x77, 0x6e, 0x9b, 0xdd, 0x23, 0x5e, 0x66, 0xfe, 0x45, 0x04, 0xac, 0xe8, 0x02, 0xdb, 0xe0, 0x4f, + 0xfa, 0x83, 0x41, 0xf7, 0xc8, 0xfa, 0xf1, 0x71, 0xc7, 0xec, 0x76, 0x06, 0x98, 0x71, 0x29, 0x05, + 0xce, 0xe8, 0x8b, 0x6c, 0xcd, 0x0e, 0x0f, 0xbe, 0x10, 0x02, 0x04, 0x23, 0x2d, 0xc5, 0x41, 0x8c, + 0xaa, 0xcc, 0x66, 0x87, 0x9d, 0xc0, 0x29, 0x25, 0xc3, 0x35, 0x38, 0x96, 0xaf, 0xc2, 0x64, 0x8b, + 0x85, 0x9d, 0x8f, 0xd9, 0xaa, 0xe9, 0x28, 0x96, 0x0b, 0xc5, 0x0e, 0x25, 0xa4, 0xed, 0xee, 0x9a, + 0x98, 0xa1, 0xbe, 0x00, 0x65, 0xb4, 0xcb, 0x6c, 0x11, 0xb2, 0x23, 0x9a, 0x91, 0x34, 0x64, 0x82, + 0x61, 0x56, 0x1e, 0xfe, 0x8b, 0x37, 0xa0, 0xac, 0x2e, 0x18, 0x90, 0x1f, 0x41, 0x2d, 0x76, 0xe3, + 0x9b, 0x48, 0x13, 0x7e, 0xda, 0x05, 0xf1, 0xad, 0xd7, 0xd2, 0x91, 0x42, 0x39, 0x39, 0xd4, 0xac, + 0x01, 0xbc, 0xb0, 0xd7, 0x92, 0x1a, 0x7a, 0xac, 0xb4, 0xdb, 0xd7, 0x60, 0x45, 0x71, 0x4f, 0xf1, + 0xa9, 0x68, 0xfd, 0xcb, 0xc5, 0xe4, 0x76, 0xf4, 0x6e, 0x6f, 0xca, 0x17, 0x8d, 0xb7, 0x6e, 0x2e, + 0x7e, 0x63, 0x58, 0x7e, 0x94, 0x78, 0x17, 0x2a, 0xda, 0x07, 0xf9, 0xc8, 0xcd, 0x6b, 0x3f, 0x1e, + 0xb8, 0xb5, 0x95, 0x86, 0x12, 0x4d, 0xfa, 0x1c, 0xca, 0xea, 0xe3, 0x6c, 0x64, 0x53, 0xfb, 0xb0, + 0x9e, 0xfe, 0x89, 0xb9, 0xad, 0xe6, 0x22, 0x42, 0xe4, 0xdf, 0x85, 0x8a, 0xf6, 0x8d, 0x35, 0xd5, + 0x8a, 0xc5, 0xef, 0xb8, 0xa9, 0x56, 0xa4, 0x7d, 0x92, 0xed, 0x00, 0xd6, 0x85, 0xcd, 0xe1, 0x84, + 0x7e, 0x93, 0xe1, 0x49, 0xf9, 0x04, 0xf3, 0x83, 0x0c, 0x79, 0x0c, 0x25, 0xf9, 0x35, 0x3d, 0xb2, + 0x91, 0xfe, 0xad, 0xc0, 0xad, 0xcd, 0x05, 0xb8, 0x68, 0x4a, 0x0b, 0x20, 0xfa, 0x7a, 0x1b, 0x91, + 0x1d, 0x5f, 0xf8, 0x1a, 0x9c, 0x9a, 0x99, 0x94, 0x4f, 0xbd, 0xed, 0x42, 0x45, 0xfb, 0x50, 0x9b, + 0x1a, 0x93, 0xc5, 0x8f, 0xbc, 0xa9, 0x31, 0x49, 0xfb, 0xae, 0xdb, 0x8f, 0xa0, 0x16, 0xfb, 0xe2, + 0x9a, 0x5a, 0xc7, 0x69, 0xdf, 0x73, 0x53, 0xeb, 0x38, 0xfd, 0x23, 0x6d, 0xbb, 0x50, 0xd1, 0xbe, + 0x8f, 0xa6, 0x5a, 0xb4, 0xf8, 0x29, 0x36, 0xd5, 0xa2, 0x94, 0xcf, 0xa9, 0xb1, 0xdd, 0x10, 0xff, + 0x38, 0x9a, 0xda, 0x0d, 0xa9, 0x5f, 0x59, 0x53, 0xbb, 0x21, 0xfd, 0x8b, 0x6a, 0x6c, 0xe9, 0xa9, + 0x37, 0xe2, 0xc9, 0x66, 0x4c, 0xd5, 0x8f, 0x1e, 0x9b, 0x57, 0x4b, 0x6f, 0xf1, 0x39, 0xf9, 0x27, + 0xb0, 0xaa, 0x16, 0x8d, 0x7a, 0xe1, 0x3d, 0x50, 0x6d, 0x4a, 0x7d, 0x47, 0x7e, 0xab, 0x91, 0xc4, + 0x3e, 0xc8, 0x90, 0x4f, 0xa1, 0x28, 0x9e, 0xcd, 0x26, 0xeb, 0xc9, 0x67, 0xb4, 0x79, 0x23, 0x36, + 0xd2, 0x5f, 0xd7, 0x26, 0x47, 0xb8, 0xa1, 0xf5, 0x77, 0xad, 0xf5, 0x15, 0x9b, 0xf2, 0x14, 0xf6, + 0xd6, 0xeb, 0xd7, 0xa1, 0xa3, 0x12, 0x93, 0x6f, 0xb1, 0xdf, 0xbe, 0xee, 0x25, 0x96, 0x78, 0x89, + 0xd7, 0x3d, 0x19, 0xf7, 0x04, 0xaa, 0xfa, 0xa7, 0x79, 0x88, 0xbe, 0x0f, 0x93, 0x65, 0xdd, 0x4a, + 0xc5, 0x89, 0x82, 0xbe, 0x80, 0x0d, 0x35, 0xde, 0xfa, 0xb3, 0x20, 0x01, 0xb9, 0x93, 0xf2, 0x58, + 0x48, 0x6c, 0xd4, 0x6f, 0x5e, 0xfb, 0x9a, 0xc8, 0x83, 0x0c, 0x32, 0xd9, 0xd8, 0xd7, 0x34, 0x22, + 0x26, 0x9b, 0xf6, 0x11, 0x91, 0x88, 0xc9, 0xa6, 0x7f, 0x82, 0xa3, 0x05, 0xcb, 0xda, 0xb3, 0x26, + 0x83, 0x2b, 0x77, 0xa4, 0xd6, 0xfb, 0xe2, 0x83, 0xc3, 0x5b, 0x69, 0x96, 0x6f, 0xd2, 0x86, 0x8a, + 0xfe, 0x32, 0xca, 0x0b, 0xb2, 0x6f, 0x6a, 0x28, 0xfd, 0x4d, 0xd9, 0x07, 0x19, 0x72, 0x00, 0x8d, + 0xe4, 0x3b, 0x86, 0x6a, 0x0b, 0xa7, 0xbd, 0xfd, 0xb8, 0x95, 0x40, 0xc6, 0x5e, 0x3f, 0x64, 0xeb, + 0x22, 0xf6, 0xa9, 0x5f, 0xcf, 0x4f, 0x1e, 0x45, 0xf1, 0x4f, 0x00, 0xab, 0xd2, 0xd2, 0x3e, 0xfe, + 0x7c, 0x2f, 0xf3, 0x20, 0x43, 0xf6, 0xa0, 0x1a, 0x7b, 0xc6, 0x2b, 0x76, 0xd7, 0x25, 0xd1, 0xcd, + 0xa6, 0x8e, 0x4b, 0xf4, 0xf3, 0x10, 0xea, 0xf1, 0x10, 0x0d, 0xd5, 0xb0, 0xd4, 0x38, 0x12, 0x35, + 0x7d, 0xe9, 0x71, 0x1d, 0xe4, 0x07, 0xfc, 0x43, 0xf6, 0x32, 0x94, 0x8f, 0x2c, 0x7e, 0xf8, 0x5c, + 0xcd, 0x99, 0xfe, 0x99, 0x70, 0x23, 0xf7, 0x17, 0xb3, 0x19, 0xec, 0xd7, 0xf7, 0xf9, 0x67, 0x64, + 0x65, 0x34, 0x17, 0x9b, 0xff, 0x57, 0x2d, 0x84, 0xec, 0xf1, 0xca, 0xc5, 0x47, 0xbc, 0x23, 0xce, + 0xbd, 0xf0, 0x61, 0xef, 0x97, 0xb4, 0xa1, 0xc5, 0xdb, 0x20, 0xf2, 0xc4, 0xd6, 0xe0, 0x2b, 0x96, + 0x45, 0x3e, 0x01, 0x88, 0x42, 0x64, 0x49, 0x22, 0x50, 0x53, 0x6d, 0xa8, 0x94, 0x28, 0xda, 0x0e, + 0xdf, 0xef, 0x2a, 0x52, 0x54, 0x3f, 0x92, 0xe3, 0x41, 0xab, 0xb1, 0x23, 0x39, 0x59, 0xcc, 0x87, + 0x50, 0x3b, 0xf0, 0xbc, 0x67, 0xf3, 0x99, 0xba, 0x67, 0x11, 0x0f, 0x63, 0xda, 0xb7, 0x83, 0xf3, + 0xad, 0x44, 0xb3, 0x48, 0x0b, 0x56, 0x14, 0x8b, 0x88, 0x42, 0x55, 0xe3, 0x44, 0x31, 0xc6, 0x90, + 0x28, 0xe0, 0x41, 0x86, 0x3c, 0x84, 0xea, 0x2e, 0x1d, 0xe1, 0x33, 0x1b, 0x18, 0x34, 0xb3, 0x1a, + 0x0b, 0xc0, 0xe0, 0xd1, 0x36, 0x5b, 0xb5, 0x18, 0x50, 0xb2, 0xb8, 0x28, 0x70, 0x4b, 0x3f, 0x33, + 0xe2, 0xd1, 0x4f, 0x31, 0x16, 0xb7, 0x10, 0xbc, 0xf5, 0x05, 0xac, 0x2c, 0x84, 0x46, 0x29, 0xee, + 0x76, 0x5d, 0x40, 0xd5, 0xd6, 0xdd, 0xeb, 0x09, 0x44, 0xb9, 0x3f, 0x84, 0x1a, 0x7f, 0x62, 0xf8, + 0x84, 0xf2, 0x6b, 0xb2, 0x89, 0x37, 0xa6, 0xf4, 0x3b, 0xb8, 0x49, 0x96, 0xc4, 0x33, 0x3c, 0xc1, + 0x0f, 0x8f, 0x68, 0x97, 0x50, 0xd5, 0xbc, 0x2e, 0x5e, 0x8c, 0x55, 0xf3, 0x9a, 0x76, 0xdf, 0xf5, + 0x33, 0xa8, 0x3c, 0xa1, 0xa1, 0xbc, 0xd6, 0xa9, 0xe4, 0xa3, 0xc4, 0x3d, 0xcf, 0xad, 0x94, 0xcb, + 0xb8, 0xe4, 0x63, 0xcc, 0xaa, 0x9e, 0x28, 0xd8, 0xd0, 0x6a, 0xd1, 0xb3, 0x2e, 0x27, 0xe0, 0x4c, + 0xfa, 0xd0, 0x1e, 0x2a, 0x51, 0x0d, 0x5f, 0x7c, 0x98, 0x46, 0x35, 0x3c, 0xed, 0x5d, 0x93, 0x1f, + 0xf0, 0x11, 0xd0, 0x2e, 0x92, 0x46, 0x22, 0x58, 0xf2, 0xce, 0xa9, 0x6a, 0xbe, 0x4e, 0xfe, 0x08, + 0x60, 0x10, 0x7a, 0xb3, 0x5d, 0x9b, 0x4e, 0x3d, 0x37, 0xe2, 0x09, 0xd1, 0x15, 0xc6, 0x68, 0x23, + 0x6a, 0xf7, 0x18, 0xc9, 0x97, 0x9a, 0x6c, 0x1a, 0x9b, 0x12, 0x39, 0xed, 0xd7, 0xde, 0x72, 0x54, + 0xdd, 0x49, 0xb9, 0xe9, 0x88, 0x4c, 0x02, 0xa2, 0xc8, 0x33, 0x25, 0x69, 0x2e, 0x04, 0xb5, 0xa9, + 0xbd, 0x9e, 0x12, 0xa6, 0xf6, 0x39, 0x94, 0xa3, 0x90, 0x9d, 0xcd, 0xe8, 0xd5, 0xa4, 0x58, 0x80, + 0x8f, 0xe2, 0xde, 0x8b, 0xe1, 0x32, 0x3d, 0x58, 0xe5, 0xcd, 0x51, 0xc7, 0x1f, 0x5e, 0xb4, 0x53, + 0xdf, 0xcd, 0x59, 0x8c, 0x53, 0x51, 0xfb, 0x27, 0x2d, 0xda, 0x82, 0xed, 0x9f, 0x05, 0xaf, 0xbd, + 0xda, 0x3f, 0xd7, 0x85, 0x61, 0xa8, 0xfd, 0x73, 0xbd, 0xc3, 0xbf, 0x07, 0xab, 0x29, 0xfe, 0x77, + 0xf2, 0x86, 0x54, 0x6c, 0xae, 0xf5, 0xcd, 0x6f, 0xa5, 0xfa, 0x69, 0xc9, 0x10, 0x36, 0x79, 0x9e, + 0xd6, 0x64, 0x92, 0x70, 0xf7, 0xbe, 0xae, 0x65, 0x48, 0x71, 0x61, 0xc7, 0x44, 0x99, 0x84, 0x1b, + 0xbb, 0x07, 0x8d, 0xa4, 0xa7, 0x94, 0x5c, 0x4f, 0xbe, 0x75, 0x27, 0x26, 0xb2, 0x2f, 0x7a, 0x57, + 0xc9, 0x17, 0xca, 0x5f, 0x9b, 0x68, 0xe3, 0x9d, 0xe8, 0x73, 0x6f, 0xa9, 0xde, 0x65, 0xa5, 0x0d, + 0xa4, 0xba, 0x7b, 0xc9, 0x2f, 0xc2, 0x66, 0x72, 0x45, 0xcb, 0x92, 0xef, 0xa6, 0x0d, 0xd7, 0xb5, + 0xa2, 0x5c, 0xbc, 0x43, 0x0f, 0x32, 0x8c, 0x11, 0xeb, 0x5e, 0x55, 0xb5, 0x90, 0x52, 0xdc, 0xbb, + 0x6a, 0x21, 0xa5, 0xba, 0x61, 0x8f, 0x60, 0x39, 0xe1, 0x50, 0x55, 0x62, 0x70, 0xba, 0x0b, 0x56, + 0x89, 0xc1, 0xd7, 0xf9, 0x61, 0x07, 0xd0, 0x48, 0xba, 0x4a, 0xd5, 0x5c, 0x5f, 0xe3, 0x7e, 0xdd, + 0xba, 0x73, 0x2d, 0x3e, 0xde, 0x4c, 0xcd, 0xa9, 0x18, 0x6b, 0xe6, 0xa2, 0x2b, 0x34, 0xd6, 0xcc, + 0x14, 0x97, 0xe6, 0xce, 0x3b, 0xbf, 0xf4, 0x9d, 0x33, 0x27, 0x3c, 0x9f, 0x9f, 0x6c, 0x8f, 0xbc, + 0xe9, 0xfb, 0x13, 0x69, 0xd5, 0x10, 0xf7, 0xce, 0xdf, 0x9f, 0xb8, 0xe3, 0xf7, 0xb1, 0x80, 0x93, + 0xa5, 0x99, 0xef, 0x85, 0xde, 0x87, 0xff, 0x2f, 0x00, 0x00, 0xff, 0xff, 0xe7, 0x1e, 0xb6, 0xde, + 0x52, 0x8b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/lnrpc/rpc.proto b/lnrpc/rpc.proto index ac07a40f..c95fb3f1 100644 --- a/lnrpc/rpc.proto +++ b/lnrpc/rpc.proto @@ -1415,6 +1415,20 @@ message Peer { spamming us with errors at no cost. */ repeated TimestampedError errors = 12; + + /* + The number of times we have recorded this peer going offline or coming + online, recorded across restarts. Note that this value is decreased over + time if the peer has not recently flapped, so that we can forgive peers + with historically high flap counts. + */ + int32 flap_count = 13; + + /* + The timestamp of the last flap we observed for this peer. If this value is + zero, we have not observed any flaps for this peer. + */ + int64 last_flap_ns = 14; } message TimestampedError { diff --git a/lnrpc/rpc.swagger.json b/lnrpc/rpc.swagger.json index 81c55a86..a401ebfa 100644 --- a/lnrpc/rpc.swagger.json +++ b/lnrpc/rpc.swagger.json @@ -4800,6 +4800,16 @@ "$ref": "#/definitions/lnrpcTimestampedError" }, "description": "The latest errors received from our peer with timestamps, limited to the 10\nmost recent errors. These errors are tracked across peer connections, but\nare not persisted across lnd restarts. Note that these errors are only\nstored for peers that we have channels open with, to prevent peers from\nspamming us with errors at no cost." + }, + "flap_count": { + "type": "integer", + "format": "int32", + "description": "The number of times we have recorded this peer going offline or coming\nonline, recorded across restarts. Note that this value is decreased over\ntime if the peer has not recently flapped, so that we can forgive peers\nwith historically high flap counts." + }, + "last_flap_ns": { + "type": "string", + "format": "int64", + "description": "The timestamp of the last flap we observed for this peer. If this value is\nzero, we have not observed any flaps for this peer." } } }, diff --git a/rpcserver.go b/rpcserver.go index e1e7cd0d..5923b560 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2701,6 +2701,30 @@ func (r *rpcServer) ListPeers(ctx context.Context, rpcPeer.Errors = append(rpcPeer.Errors, rpcErr) } + // If the server has started, we can query the event store + // for our peer's flap count. If we do so when the server has + // not started, the request will block. + if r.server.Started() { + vertex, err := route.NewVertexFromBytes(nodePub[:]) + if err != nil { + return nil, err + } + + flap, ts, err := r.server.chanEventStore.FlapCount( + vertex, + ) + if err != nil { + return nil, err + } + + // If our timestamp is non-nil, we have values for our + // peer's flap count, so we set them. + if ts != nil { + rpcPeer.FlapCount = int32(flap) + rpcPeer.LastFlapNs = ts.UnixNano() + } + } + resp.Peers = append(resp.Peers, rpcPeer) }