2020-11-25 19:01:53 +08:00
package dispatcher
import (
"context"
2024-06-16 22:51:52 +08:00
"regexp"
2020-11-25 19:01:53 +08:00
"strings"
"sync"
"time"
2020-12-04 09:36:16 +08:00
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
2024-09-14 01:05:19 +08:00
"github.com/xtls/xray-core/common/errors"
2020-12-04 09:36:16 +08:00
"github.com/xtls/xray-core/common/log"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/core"
2021-12-14 19:28:47 -05:00
"github.com/xtls/xray-core/features/dns"
2020-12-04 09:36:16 +08:00
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/features/policy"
"github.com/xtls/xray-core/features/routing"
routing_session "github.com/xtls/xray-core/features/routing/session"
"github.com/xtls/xray-core/features/stats"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/pipe"
2020-11-25 19:01:53 +08:00
)
2024-06-29 14:32:57 -04:00
var errSniffingTimeout = errors . New ( "timeout on sniffing" )
2020-11-25 19:01:53 +08:00
type cachedReader struct {
sync . Mutex
2025-08-29 12:35:56 +00:00
reader buf . TimeoutReader // *pipe.Reader or *buf.TimeoutWrapperReader
2020-11-25 19:01:53 +08:00
cache buf . MultiBuffer
}
2025-04-28 18:03:03 +08:00
func ( r * cachedReader ) Cache ( b * buf . Buffer , deadline time . Duration ) error {
mb , err := r . reader . ReadMultiBufferTimeout ( deadline )
if err != nil {
return err
}
2020-11-25 19:01:53 +08:00
r . Lock ( )
if ! mb . IsEmpty ( ) {
r . cache , _ = buf . MergeMulti ( r . cache , mb )
}
2025-04-28 18:03:03 +08:00
b . Clear ( )
2025-04-29 16:04:04 +08:00
rawBytes := b . Extend ( min ( r . cache . Len ( ) , b . Cap ( ) ) )
2020-11-25 19:01:53 +08:00
n := r . cache . Copy ( rawBytes )
b . Resize ( 0 , int32 ( n ) )
r . Unlock ( )
2025-04-28 18:03:03 +08:00
return nil
2020-11-25 19:01:53 +08:00
}
func ( r * cachedReader ) readInternal ( ) buf . MultiBuffer {
r . Lock ( )
defer r . Unlock ( )
if r . cache != nil && ! r . cache . IsEmpty ( ) {
mb := r . cache
r . cache = nil
return mb
}
return nil
}
func ( r * cachedReader ) ReadMultiBuffer ( ) ( buf . MultiBuffer , error ) {
mb := r . readInternal ( )
if mb != nil {
return mb , nil
}
return r . reader . ReadMultiBuffer ( )
}
func ( r * cachedReader ) ReadMultiBufferTimeout ( timeout time . Duration ) ( buf . MultiBuffer , error ) {
mb := r . readInternal ( )
if mb != nil {
return mb , nil
}
return r . reader . ReadMultiBufferTimeout ( timeout )
}
func ( r * cachedReader ) Interrupt ( ) {
r . Lock ( )
if r . cache != nil {
r . cache = buf . ReleaseMulti ( r . cache )
}
r . Unlock ( )
2025-08-29 12:35:56 +00:00
if p , ok := r . reader . ( * pipe . Reader ) ; ok {
p . Interrupt ( )
}
2020-11-25 19:01:53 +08:00
}
// DefaultDispatcher is a default implementation of Dispatcher.
type DefaultDispatcher struct {
ohm outbound . Manager
router routing . Router
policy policy . Manager
stats stats . Manager
2022-04-23 19:24:46 -04:00
fdns dns . FakeDNSEngine
2020-11-25 19:01:53 +08:00
}
func init ( ) {
common . Must ( common . RegisterConfig ( ( * Config ) ( nil ) , func ( ctx context . Context , config interface { } ) ( interface { } , error ) {
d := new ( DefaultDispatcher )
2021-09-28 14:41:31 +08:00
if err := core . RequireFeatures ( ctx , func ( om outbound . Manager , router routing . Router , pm policy . Manager , sm stats . Manager , dc dns . Client ) error {
2024-12-26 07:55:12 -05:00
core . OptionalFeatures ( ctx , func ( fdns dns . FakeDNSEngine ) {
2022-04-23 19:24:46 -04:00
d . fdns = fdns
} )
2025-05-15 17:45:03 +03:30
return d . Init ( config . ( * Config ) , om , router , pm , sm )
2020-11-25 19:01:53 +08:00
} ) ; err != nil {
return nil , err
}
return d , nil
} ) )
}
// Init initializes DefaultDispatcher.
2025-05-15 17:45:03 +03:30
func ( d * DefaultDispatcher ) Init ( config * Config , om outbound . Manager , router routing . Router , pm policy . Manager , sm stats . Manager ) error {
2020-11-25 19:01:53 +08:00
d . ohm = om
d . router = router
d . policy = pm
d . stats = sm
return nil
}
// Type implements common.HasType.
func ( * DefaultDispatcher ) Type ( ) interface { } {
return routing . DispatcherType ( )
}
// Start implements common.Runnable.
func ( * DefaultDispatcher ) Start ( ) error {
return nil
}
// Close implements common.Closable.
func ( * DefaultDispatcher ) Close ( ) error { return nil }
2023-08-29 15:12:36 +08:00
func ( d * DefaultDispatcher ) getLink ( ctx context . Context ) ( * transport . Link , * transport . Link ) {
opt := pipe . OptionsFromContext ( ctx )
uplinkReader , uplinkWriter := pipe . New ( opt ... )
downlinkReader , downlinkWriter := pipe . New ( opt ... )
2020-11-25 19:01:53 +08:00
inboundLink := & transport . Link {
Reader : downlinkReader ,
Writer : uplinkWriter ,
}
outboundLink := & transport . Link {
Reader : uplinkReader ,
Writer : downlinkWriter ,
}
sessionInbound := session . InboundFromContext ( ctx )
var user * protocol . MemoryUser
if sessionInbound != nil {
user = sessionInbound . User
}
if user != nil && len ( user . Email ) > 0 {
p := d . policy . ForLevel ( user . Level )
if p . Stats . UserUplink {
name := "user>>>" + user . Email + ">>>traffic>>>uplink"
if c , _ := stats . GetOrRegisterCounter ( d . stats , name ) ; c != nil {
inboundLink . Writer = & SizeStatWriter {
Counter : c ,
Writer : inboundLink . Writer ,
}
}
}
if p . Stats . UserDownlink {
name := "user>>>" + user . Email + ">>>traffic>>>downlink"
if c , _ := stats . GetOrRegisterCounter ( d . stats , name ) ; c != nil {
outboundLink . Writer = & SizeStatWriter {
Counter : c ,
Writer : outboundLink . Writer ,
}
}
}
2024-11-03 17:44:15 +04:00
if p . Stats . UserOnline {
name := "user>>>" + user . Email + ">>>online"
if om , _ := stats . GetOrRegisterOnlineMap ( d . stats , name ) ; om != nil {
sessionInbounds := session . InboundFromContext ( ctx )
userIP := sessionInbounds . Source . Address . String ( )
om . AddIP ( userIP )
// log Online user with ips
// errors.LogDebug(ctx, "user>>>" + user.Email + ">>>online", om.Count(), om.List())
}
}
2020-11-25 19:01:53 +08:00
}
return inboundLink , outboundLink
}
2025-09-03 23:25:17 +00:00
func ( d * DefaultDispatcher ) WrapLink ( ctx context . Context , link * transport . Link ) * transport . Link {
sessionInbound := session . InboundFromContext ( ctx )
var user * protocol . MemoryUser
if sessionInbound != nil {
user = sessionInbound . User
}
link . Reader = & buf . TimeoutWrapperReader { Reader : link . Reader }
if user != nil && len ( user . Email ) > 0 {
p := d . policy . ForLevel ( user . Level )
if p . Stats . UserUplink {
name := "user>>>" + user . Email + ">>>traffic>>>uplink"
if c , _ := stats . GetOrRegisterCounter ( d . stats , name ) ; c != nil {
link . Reader . ( * buf . TimeoutWrapperReader ) . Counter = c
}
}
if p . Stats . UserDownlink {
name := "user>>>" + user . Email + ">>>traffic>>>downlink"
if c , _ := stats . GetOrRegisterCounter ( d . stats , name ) ; c != nil {
link . Writer = & SizeStatWriter {
Counter : c ,
Writer : link . Writer ,
}
}
}
if p . Stats . UserOnline {
name := "user>>>" + user . Email + ">>>online"
if om , _ := stats . GetOrRegisterOnlineMap ( d . stats , name ) ; om != nil {
sessionInbounds := session . InboundFromContext ( ctx )
userIP := sessionInbounds . Source . Address . String ( )
om . AddIP ( userIP )
// log Online user with ips
// errors.LogDebug(ctx, "user>>>" + user.Email + ">>>online", om.Count(), om.List())
}
}
}
return link
}
2022-04-23 19:24:46 -04:00
func ( d * DefaultDispatcher ) shouldOverride ( ctx context . Context , result SniffResult , request session . SniffingRequest , destination net . Destination ) bool {
2021-01-22 04:50:09 +08:00
domain := result . Domain ( )
2022-05-22 23:48:10 -04:00
if domain == "" {
return false
}
2021-01-22 04:50:09 +08:00
for _ , d := range request . ExcludeForDomain {
2024-06-16 22:51:52 +08:00
if strings . HasPrefix ( d , "regexp:" ) {
pattern := d [ 7 : ]
re , err := regexp . Compile ( pattern )
if err != nil {
2024-06-29 14:32:57 -04:00
errors . LogInfo ( ctx , "Unable to compile regex" )
2024-06-16 22:51:52 +08:00
continue
}
if re . MatchString ( domain ) {
return false
}
} else {
if strings . ToLower ( domain ) == d {
return false
}
2021-01-22 04:50:09 +08:00
}
}
2021-03-06 23:39:50 -05:00
protocolString := result . Protocol ( )
if resComp , ok := result . ( SnifferResultComposite ) ; ok {
protocolString = resComp . ProtocolForDomainResult ( )
}
2021-01-22 04:50:09 +08:00
for _ , p := range request . OverrideDestinationForProtocol {
2023-08-31 19:21:35 +08:00
if strings . HasPrefix ( protocolString , p ) || strings . HasPrefix ( p , protocolString ) {
2021-03-06 23:39:50 -05:00
return true
}
2022-04-23 19:24:46 -04:00
if fkr0 , ok := d . fdns . ( dns . FakeDNSEngineRev0 ) ; ok && protocolString != "bittorrent" && p == "fakedns" &&
2024-02-20 20:24:31 -05:00
fkr0 . IsIPInIPPool ( destination . Address ) {
2024-06-29 14:32:57 -04:00
errors . LogInfo ( ctx , "Using sniffer " , protocolString , " since the fake DNS missed" )
2021-04-09 05:20:30 +08:00
return true
2021-10-20 01:15:49 -04:00
}
if resultSubset , ok := result . ( SnifferIsProtoSubsetOf ) ; ok {
if resultSubset . IsProtoSubsetOf ( p ) {
return true
}
2020-11-25 19:01:53 +08:00
}
}
2021-01-22 04:50:09 +08:00
2020-11-25 19:01:53 +08:00
return false
}
// Dispatch implements routing.Dispatcher.
func ( d * DefaultDispatcher ) Dispatch ( ctx context . Context , destination net . Destination ) ( * transport . Link , error ) {
if ! destination . IsValid ( ) {
panic ( "Dispatcher: Invalid destination." )
}
2024-05-13 21:52:24 -04:00
outbounds := session . OutboundsFromContext ( ctx )
if len ( outbounds ) == 0 {
outbounds = [ ] * session . Outbound { { } }
ctx = session . ContextWithOutbounds ( ctx , outbounds )
2020-11-25 19:01:53 +08:00
}
2024-06-16 22:51:52 +08:00
ob := outbounds [ len ( outbounds ) - 1 ]
2023-05-03 22:21:45 -04:00
ob . OriginalTarget = destination
ob . Target = destination
2020-11-25 19:01:53 +08:00
content := session . ContentFromContext ( ctx )
if content == nil {
content = new ( session . Content )
ctx = session . ContextWithContent ( ctx , content )
}
2024-02-17 22:51:37 -05:00
2020-11-25 19:01:53 +08:00
sniffingRequest := content . SniffingRequest
2023-08-29 15:12:36 +08:00
inbound , outbound := d . getLink ( ctx )
2022-05-22 23:48:10 -04:00
if ! sniffingRequest . Enabled {
2020-11-25 19:01:53 +08:00
go d . routedDispatch ( ctx , outbound , destination )
2022-05-22 23:48:10 -04:00
} else {
2020-11-25 19:01:53 +08:00
go func ( ) {
cReader := & cachedReader {
reader : outbound . Reader . ( * pipe . Reader ) ,
}
outbound . Reader = cReader
2022-05-22 23:48:10 -04:00
result , err := sniffer ( ctx , cReader , sniffingRequest . MetadataOnly , destination . Network )
2020-11-25 19:01:53 +08:00
if err == nil {
content . Protocol = result . Protocol ( )
}
2022-04-23 19:24:46 -04:00
if err == nil && d . shouldOverride ( ctx , result , sniffingRequest , destination ) {
2020-11-25 19:01:53 +08:00
domain := result . Domain ( )
2024-06-29 14:32:57 -04:00
errors . LogInfo ( ctx , "sniffed domain: " , domain )
2020-11-25 19:01:53 +08:00
destination . Address = net . ParseAddress ( domain )
2023-08-29 15:12:36 +08:00
protocol := result . Protocol ( )
if resComp , ok := result . ( SnifferResultComposite ) ; ok {
protocol = resComp . ProtocolForDomainResult ( )
}
isFakeIP := false
2024-02-20 20:24:31 -05:00
if fkr0 , ok := d . fdns . ( dns . FakeDNSEngineRev0 ) ; ok && fkr0 . IsIPInIPPool ( ob . Target . Address ) {
2023-08-29 15:12:36 +08:00
isFakeIP = true
}
if sniffingRequest . RouteOnly && protocol != "fakedns" && protocol != "fakedns+others" && ! isFakeIP {
2021-09-16 15:05:48 +08:00
ob . RouteTarget = destination
} else {
ob . Target = destination
}
2020-11-25 19:01:53 +08:00
}
d . routedDispatch ( ctx , outbound , destination )
} ( )
}
return inbound , nil
}
2021-09-28 09:42:57 +08:00
// DispatchLink implements routing.Dispatcher.
func ( d * DefaultDispatcher ) DispatchLink ( ctx context . Context , destination net . Destination , outbound * transport . Link ) error {
if ! destination . IsValid ( ) {
2024-06-29 14:32:57 -04:00
return errors . New ( "Dispatcher: Invalid destination." )
2021-09-28 09:42:57 +08:00
}
2024-05-13 21:52:24 -04:00
outbounds := session . OutboundsFromContext ( ctx )
if len ( outbounds ) == 0 {
outbounds = [ ] * session . Outbound { { } }
ctx = session . ContextWithOutbounds ( ctx , outbounds )
2021-09-28 09:42:57 +08:00
}
2024-06-16 22:51:52 +08:00
ob := outbounds [ len ( outbounds ) - 1 ]
2023-05-03 22:21:45 -04:00
ob . OriginalTarget = destination
ob . Target = destination
2021-09-28 09:42:57 +08:00
content := session . ContentFromContext ( ctx )
if content == nil {
content = new ( session . Content )
ctx = session . ContextWithContent ( ctx , content )
}
2025-09-03 23:25:17 +00:00
outbound = d . WrapLink ( ctx , outbound )
2021-09-28 09:42:57 +08:00
sniffingRequest := content . SniffingRequest
2022-05-22 23:48:10 -04:00
if ! sniffingRequest . Enabled {
2023-04-23 19:31:41 +08:00
d . routedDispatch ( ctx , outbound , destination )
2022-05-22 23:48:10 -04:00
} else {
2023-04-23 19:31:41 +08:00
cReader := & cachedReader {
2025-08-29 12:35:56 +00:00
reader : outbound . Reader . ( buf . TimeoutReader ) ,
2023-04-23 19:31:41 +08:00
}
outbound . Reader = cReader
result , err := sniffer ( ctx , cReader , sniffingRequest . MetadataOnly , destination . Network )
if err == nil {
content . Protocol = result . Protocol ( )
}
if err == nil && d . shouldOverride ( ctx , result , sniffingRequest , destination ) {
domain := result . Domain ( )
2024-06-29 14:32:57 -04:00
errors . LogInfo ( ctx , "sniffed domain: " , domain )
2023-04-23 19:31:41 +08:00
destination . Address = net . ParseAddress ( domain )
2023-08-29 15:12:36 +08:00
protocol := result . Protocol ( )
if resComp , ok := result . ( SnifferResultComposite ) ; ok {
protocol = resComp . ProtocolForDomainResult ( )
}
isFakeIP := false
2024-02-20 20:24:31 -05:00
if fkr0 , ok := d . fdns . ( dns . FakeDNSEngineRev0 ) ; ok && fkr0 . IsIPInIPPool ( ob . Target . Address ) {
2023-08-29 15:12:36 +08:00
isFakeIP = true
}
if sniffingRequest . RouteOnly && protocol != "fakedns" && protocol != "fakedns+others" && ! isFakeIP {
2023-04-23 19:31:41 +08:00
ob . RouteTarget = destination
} else {
ob . Target = destination
2021-09-28 09:42:57 +08:00
}
2023-04-23 19:31:41 +08:00
}
d . routedDispatch ( ctx , outbound , destination )
2021-09-28 09:42:57 +08:00
}
2022-05-22 23:48:10 -04:00
2021-09-28 09:42:57 +08:00
return nil
}
2022-05-22 23:48:10 -04:00
func sniffer ( ctx context . Context , cReader * cachedReader , metadataOnly bool , network net . Network ) ( SniffResult , error ) {
2025-04-28 18:03:03 +08:00
payload := buf . NewWithSize ( 32767 )
2020-11-25 19:01:53 +08:00
defer payload . Release ( )
2021-03-06 23:39:50 -05:00
sniffer := NewSniffer ( ctx )
metaresult , metadataErr := sniffer . SniffMetadata ( ctx )
if metadataOnly {
return metaresult , metadataErr
}
2020-11-25 19:01:53 +08:00
2021-03-06 23:39:50 -05:00
contentResult , contentErr := func ( ) ( SniffResult , error ) {
2025-04-28 18:03:03 +08:00
cacheDeadline := 200 * time . Millisecond
2021-03-06 23:39:50 -05:00
totalAttempt := 0
for {
select {
case <- ctx . Done ( ) :
return nil , ctx . Err ( )
default :
2025-04-28 18:03:03 +08:00
cachingStartingTimeStamp := time . Now ( )
2025-05-16 15:34:54 +03:30
err := cReader . Cache ( payload , cacheDeadline )
if err != nil {
return nil , err
}
2025-04-28 18:03:03 +08:00
cachingTimeElapsed := time . Since ( cachingStartingTimeStamp )
cacheDeadline -= cachingTimeElapsed
2021-03-06 23:39:50 -05:00
if ! payload . IsEmpty ( ) {
2022-05-22 23:48:10 -04:00
result , err := sniffer . Sniff ( ctx , payload . Bytes ( ) , network )
2025-04-28 18:03:03 +08:00
switch err {
case common . ErrNoClue : // No Clue: protocol not matches, and sniffer cannot determine whether there will be a match or not
totalAttempt ++
case protocol . ErrProtoNeedMoreData : // Protocol Need More Data: protocol matches, but need more data to complete sniffing
2025-05-16 15:34:54 +03:30
// in this case, do not add totalAttempt(allow to read until timeout)
2025-04-28 18:03:03 +08:00
default :
2021-03-06 23:39:50 -05:00
return result , err
}
2025-05-16 15:34:54 +03:30
} else {
totalAttempt ++
2021-03-06 23:39:50 -05:00
}
2025-04-28 18:03:03 +08:00
if totalAttempt >= 2 || cacheDeadline <= 0 {
return nil , errSniffingTimeout
2020-11-25 19:01:53 +08:00
}
}
}
2021-03-06 23:39:50 -05:00
} ( )
if contentErr != nil && metadataErr == nil {
return metaresult , nil
}
if contentErr == nil && metadataErr == nil {
return CompositeResult ( metaresult , contentResult ) , nil
2020-11-25 19:01:53 +08:00
}
2021-03-06 23:39:50 -05:00
return contentResult , contentErr
2020-11-25 19:01:53 +08:00
}
func ( d * DefaultDispatcher ) routedDispatch ( ctx context . Context , link * transport . Link , destination net . Destination ) {
2024-05-13 21:52:24 -04:00
outbounds := session . OutboundsFromContext ( ctx )
2024-06-16 22:51:52 +08:00
ob := outbounds [ len ( outbounds ) - 1 ]
2021-09-28 14:41:31 +08:00
2020-11-25 19:01:53 +08:00
var handler outbound . Handler
2022-04-15 18:23:49 -04:00
routingLink := routing_session . AsRoutingContext ( ctx )
inTag := routingLink . GetInboundTag ( )
isPickRoute := 0
2021-04-09 05:20:30 +08:00
if forcedOutboundTag := session . GetForcedOutboundTagFromContext ( ctx ) ; forcedOutboundTag != "" {
ctx = session . SetForcedOutboundTagToContext ( ctx , "" )
if h := d . ohm . GetHandler ( forcedOutboundTag ) ; h != nil {
2022-04-15 18:23:49 -04:00
isPickRoute = 1
2024-06-29 14:32:57 -04:00
errors . LogInfo ( ctx , "taking platform initialized detour [" , forcedOutboundTag , "] for [" , destination , "]" )
2021-04-09 05:20:30 +08:00
handler = h
} else {
2024-06-29 14:32:57 -04:00
errors . LogError ( ctx , "non existing tag for platform initialized detour: " , forcedOutboundTag )
2021-04-09 05:20:30 +08:00
common . Close ( link . Writer )
common . Interrupt ( link . Reader )
return
}
} else if d . router != nil {
2022-04-15 18:23:49 -04:00
if route , err := d . router . PickRoute ( routingLink ) ; err == nil {
outTag := route . GetOutboundTag ( )
if h := d . ohm . GetHandler ( outTag ) ; h != nil {
isPickRoute = 2
2024-09-14 01:05:19 +08:00
if route . GetRuleTag ( ) == "" {
errors . LogInfo ( ctx , "taking detour [" , outTag , "] for [" , destination , "]" )
} else {
errors . LogInfo ( ctx , "Hit route rule: [" , route . GetRuleTag ( ) , "] so taking detour [" , outTag , "] for [" , destination , "]" )
}
2020-11-25 19:01:53 +08:00
handler = h
} else {
2024-06-29 14:32:57 -04:00
errors . LogWarning ( ctx , "non existing outTag: " , outTag )
2025-09-11 14:36:22 +02:00
common . Close ( link . Writer )
common . Interrupt ( link . Reader )
2025-09-10 14:36:36 +00:00
return // DO NOT CHANGE: the traffic shouldn't be processed by default outbound if the specified outbound tag doesn't exist (yet), e.g., VLESS Reverse Proxy
2020-11-25 19:01:53 +08:00
}
} else {
2024-06-29 14:32:57 -04:00
errors . LogInfo ( ctx , "default route for " , destination )
2020-11-25 19:01:53 +08:00
}
}
if handler == nil {
handler = d . ohm . GetDefaultHandler ( )
}
if handler == nil {
2024-06-29 14:32:57 -04:00
errors . LogInfo ( ctx , "default outbound handler not exist" )
2020-11-25 19:01:53 +08:00
common . Close ( link . Writer )
common . Interrupt ( link . Reader )
return
}
2024-05-13 21:52:24 -04:00
ob . Tag = handler . Tag ( )
2020-11-25 19:01:53 +08:00
if accessMessage := log . AccessMessageFromContext ( ctx ) ; accessMessage != nil {
if tag := handler . Tag ( ) ; tag != "" {
2022-04-15 18:23:49 -04:00
if inTag == "" {
accessMessage . Detour = tag
} else if isPickRoute == 1 {
accessMessage . Detour = inTag + " ==> " + tag
} else if isPickRoute == 2 {
accessMessage . Detour = inTag + " -> " + tag
} else {
accessMessage . Detour = inTag + " >> " + tag
}
2020-11-25 19:01:53 +08:00
}
log . Record ( accessMessage )
}
handler . Dispatch ( ctx , link )
}