package option import ( "net/url" "reflect" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/schema" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badoption" ) type Hysteria2InboundOptions struct { ListenOptions UpMbps int `json:"up_mbps,omitempty"` DownMbps int `json:"down_mbps,omitempty"` Obfs *Hysteria2Obfs `json:"obfs,omitempty"` Users []Hysteria2User `json:"users,omitempty"` IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"` InboundTLSOptionsContainer QUICOptions Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"` BBRProfile string `json:"bbr_profile,omitempty" enum:"standard,conservative,aggressive"` BrutalDebug bool `json:"brutal_debug,omitempty"` Realm *Hysteria2InboundRealm `json:"realm,omitempty"` } type Hysteria2Realm struct { ServerURL string `json:"server_url"` Token string `json:"token,omitempty"` RealmID string `json:"realm_id"` STUNServers badoption.Listable[string] `json:"stun_servers"` IPVersion int `json:"ip_version,omitempty" enum:"0,4,6"` PortMapping *Hysteria2RealmPortMapping `json:"port_mapping,omitempty"` HTTPClient *HTTPClientOptions `json:"http_client,omitempty"` } type Hysteria2RealmPortMapping struct { Enabled bool `json:"enabled,omitempty"` Timeout badoption.Duration `json:"timeout,omitempty"` Lifetime badoption.Duration `json:"lifetime,omitempty"` } type Hysteria2InboundRealm struct { Hysteria2Realm STUNDomainResolver *DomainResolveOptions `json:"stun_domain_resolver,omitempty"` } type Hysteria2ObfsGecko struct { MinPacketSize int `json:"min_packet_size,omitempty"` MaxPacketSize int `json:"max_packet_size,omitempty"` } type _Hysteria2Obfs struct { Type string `json:"type,omitempty" enum:"salamander,gecko"` Password string `json:"password,omitempty"` GeckoOptions Hysteria2ObfsGecko `json:"-"` } type Hysteria2Obfs _Hysteria2Obfs func (o Hysteria2Obfs) MarshalJSON() ([]byte, error) { var v any switch o.Type { case C.Hysteria2ObfsTypeSalamander: case C.Hysteria2ObfsTypeGecko: v = o.GeckoOptions default: return nil, E.New("unknown obfs type: ", o.Type) } if v == nil { return json.Marshal((_Hysteria2Obfs)(o)) } return badjson.MarshallObjects((_Hysteria2Obfs)(o), v) } func (o *Hysteria2Obfs) UnmarshalJSON(bytes []byte) error { err := json.Unmarshal(bytes, (*_Hysteria2Obfs)(o)) if err != nil { return err } var v any switch o.Type { case C.Hysteria2ObfsTypeSalamander: case C.Hysteria2ObfsTypeGecko: v = &o.GeckoOptions default: return E.New("unknown obfs type: ", o.Type) } if v == nil { return nil } return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Obfs)(o), v) } func (o Hysteria2Obfs) DescribeSchema(builder schema.Builder) (*schema.Node, error) { return schema.DiscriminatedUnion(builder, "type", true, []schema.UnionVariant{ {Value: C.Hysteria2ObfsTypeSalamander}, {Value: C.Hysteria2ObfsTypeGecko, StructType: reflect.TypeFor[Hysteria2ObfsGecko]()}, }, func(variant *schema.Node) error { variant.Properties.Put("password", schema.StringNode()) return nil }) } type Hysteria2User struct { Name string `json:"name,omitempty"` Password string `json:"password,omitempty"` } type _Hysteria2Masquerade struct { Type string `json:"type,omitempty" enum:"file,proxy,string"` FileOptions Hysteria2MasqueradeFile `json:"-"` ProxyOptions Hysteria2MasqueradeProxy `json:"-"` StringOptions Hysteria2MasqueradeString `json:"-"` } type Hysteria2Masquerade _Hysteria2Masquerade func (m Hysteria2Masquerade) MarshalJSON() ([]byte, error) { var v any switch m.Type { case C.Hysterai2MasqueradeTypeFile: v = m.FileOptions case C.Hysterai2MasqueradeTypeProxy: v = m.ProxyOptions case C.Hysterai2MasqueradeTypeString: v = m.StringOptions default: return nil, E.New("unknown masquerade type: ", m.Type) } return badjson.MarshallObjects((_Hysteria2Masquerade)(m), v) } func (m *Hysteria2Masquerade) UnmarshalJSON(bytes []byte) error { var urlString string err := json.Unmarshal(bytes, &urlString) if err == nil { masqueradeURL, err := url.Parse(urlString) if err != nil { return E.Cause(err, "invalid masquerade URL") } switch masqueradeURL.Scheme { case "file": m.Type = C.Hysterai2MasqueradeTypeFile m.FileOptions.Directory = masqueradeURL.Path case "http", "https": m.Type = C.Hysterai2MasqueradeTypeProxy m.ProxyOptions.URL = urlString default: return E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) } return nil } err = json.Unmarshal(bytes, (*_Hysteria2Masquerade)(m)) if err != nil { return err } var v any switch m.Type { case C.Hysterai2MasqueradeTypeFile: v = &m.FileOptions case C.Hysterai2MasqueradeTypeProxy: v = &m.ProxyOptions case C.Hysterai2MasqueradeTypeString: v = &m.StringOptions default: return E.New("unknown masquerade type: ", m.Type) } return badjson.UnmarshallExcluded(bytes, (*_Hysteria2Masquerade)(m), v) } func (m Hysteria2Masquerade) DescribeSchema(builder schema.Builder) (*schema.Node, error) { union, err := schema.DiscriminatedUnion(builder, "type", true, []schema.UnionVariant{ {Value: C.Hysterai2MasqueradeTypeFile, StructType: reflect.TypeFor[Hysteria2MasqueradeFile]()}, {Value: C.Hysterai2MasqueradeTypeProxy, StructType: reflect.TypeFor[Hysteria2MasqueradeProxy]()}, {Value: C.Hysterai2MasqueradeTypeString, StructType: reflect.TypeFor[Hysteria2MasqueradeString]()}, }, nil) if err != nil { return nil, err } return schema.AnyOf(schema.StringNode(), union), nil } type Hysteria2MasqueradeFile struct { Directory string `json:"directory"` } type Hysteria2MasqueradeProxy struct { URL string `json:"url"` RewriteHost bool `json:"rewrite_host,omitempty"` } type Hysteria2MasqueradeString struct { StatusCode int `json:"status_code,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"` Content string `json:"content"` } type Hysteria2OutboundOptions struct { DialerOptions ServerOptions ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"` HopInterval badoption.Duration `json:"hop_interval,omitempty"` HopIntervalMax badoption.Duration `json:"hop_interval_max,omitempty"` UpMbps int `json:"up_mbps,omitempty"` DownMbps int `json:"down_mbps,omitempty"` Obfs *Hysteria2Obfs `json:"obfs,omitempty"` Password string `json:"password,omitempty"` Network NetworkList `json:"network,omitempty"` OutboundTLSOptionsContainer QUICOptions BBRProfile string `json:"bbr_profile,omitempty" enum:"standard,conservative,aggressive"` BrutalDebug bool `json:"brutal_debug,omitempty"` Realm *Hysteria2Realm `json:"realm,omitempty"` } type HysteriaRealmUser struct { Name string `json:"name"` Token string `json:"token"` MaxRealms int `json:"max_realms,omitempty"` } type HysteriaRealmServiceOptions struct { ListenOptions InboundTLSOptionsContainer HTTP2Options Users []HysteriaRealmUser `json:"users"` }