mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2025-02-23 06:43:14 +08:00
feat: add mrs
format ipcidr ruleset
This commit is contained in:
parent
303f6e4567
commit
4f8a5a5f54
77
component/cidr/ipcidr_set_bin.go
Normal file
77
component/cidr/ipcidr_set_bin.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package cidr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ss *IpCidrSet) WriteBin(w io.Writer) (err error) {
|
||||||
|
// version
|
||||||
|
_, err = w.Write([]byte{1})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// rr
|
||||||
|
err = binary.Write(w, binary.BigEndian, int64(len(ss.rr)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, r := range ss.rr {
|
||||||
|
err = binary.Write(w, binary.BigEndian, r.From().As16())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(w, binary.BigEndian, r.To().As16())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadIpCidrSet(r io.Reader) (ss *IpCidrSet, err error) {
|
||||||
|
// version
|
||||||
|
version := make([]byte, 1)
|
||||||
|
_, err = io.ReadFull(r, version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if version[0] != 1 {
|
||||||
|
return nil, errors.New("version is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
ss = NewIpCidrSet()
|
||||||
|
var length int64
|
||||||
|
|
||||||
|
// rr
|
||||||
|
err = binary.Read(r, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if length < 1 {
|
||||||
|
return nil, errors.New("length is invalid")
|
||||||
|
}
|
||||||
|
ss.rr = make([]netipx.IPRange, length)
|
||||||
|
for i := int64(0); i < length; i++ {
|
||||||
|
var a16 [16]byte
|
||||||
|
err = binary.Read(r, binary.BigEndian, &a16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
from := netip.AddrFrom16(a16).Unmap()
|
||||||
|
err = binary.Read(r, binary.BigEndian, &a16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
to := netip.AddrFrom16(a16).Unmap()
|
||||||
|
ss.rr[i] = netipx.IPRangeFrom(from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ss, nil
|
||||||
|
}
|
@ -6,19 +6,13 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ss *DomainSet) WriteBin(w io.Writer, count int64) (err error) {
|
func (ss *DomainSet) WriteBin(w io.Writer) (err error) {
|
||||||
// version
|
// version
|
||||||
_, err = w.Write([]byte{1})
|
_, err = w.Write([]byte{1})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// count
|
|
||||||
err = binary.Write(w, binary.BigEndian, count)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// leaves
|
// leaves
|
||||||
err = binary.Write(w, binary.BigEndian, int64(len(ss.leaves)))
|
err = binary.Write(w, binary.BigEndian, int64(len(ss.leaves)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -56,21 +50,15 @@ func (ss *DomainSet) WriteBin(w io.Writer, count int64) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadDomainSetBin(r io.Reader) (ds *DomainSet, count int64, err error) {
|
func ReadDomainSetBin(r io.Reader) (ds *DomainSet, err error) {
|
||||||
// version
|
// version
|
||||||
version := make([]byte, 1)
|
version := make([]byte, 1)
|
||||||
_, err = io.ReadFull(r, version)
|
_, err = io.ReadFull(r, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if version[0] != 1 {
|
if version[0] != 1 {
|
||||||
return nil, 0, errors.New("version is invalid")
|
return nil, errors.New("version is invalid")
|
||||||
}
|
|
||||||
|
|
||||||
// count
|
|
||||||
err = binary.Read(r, binary.BigEndian, &count)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ds = &DomainSet{}
|
ds = &DomainSet{}
|
||||||
@ -79,49 +67,49 @@ func ReadDomainSetBin(r io.Reader) (ds *DomainSet, count int64, err error) {
|
|||||||
// leaves
|
// leaves
|
||||||
err = binary.Read(r, binary.BigEndian, &length)
|
err = binary.Read(r, binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if length < 1 {
|
if length < 1 {
|
||||||
return nil, 0, errors.New("length is invalid")
|
return nil, errors.New("length is invalid")
|
||||||
}
|
}
|
||||||
ds.leaves = make([]uint64, length)
|
ds.leaves = make([]uint64, length)
|
||||||
for i := int64(0); i < length; i++ {
|
for i := int64(0); i < length; i++ {
|
||||||
err = binary.Read(r, binary.BigEndian, &ds.leaves[i])
|
err = binary.Read(r, binary.BigEndian, &ds.leaves[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// labelBitmap
|
// labelBitmap
|
||||||
err = binary.Read(r, binary.BigEndian, &length)
|
err = binary.Read(r, binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if length < 1 {
|
if length < 1 {
|
||||||
return nil, 0, errors.New("length is invalid")
|
return nil, errors.New("length is invalid")
|
||||||
}
|
}
|
||||||
ds.labelBitmap = make([]uint64, length)
|
ds.labelBitmap = make([]uint64, length)
|
||||||
for i := int64(0); i < length; i++ {
|
for i := int64(0); i < length; i++ {
|
||||||
err = binary.Read(r, binary.BigEndian, &ds.labelBitmap[i])
|
err = binary.Read(r, binary.BigEndian, &ds.labelBitmap[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// labels
|
// labels
|
||||||
err = binary.Read(r, binary.BigEndian, &length)
|
err = binary.Read(r, binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if length < 1 {
|
if length < 1 {
|
||||||
return nil, 0, errors.New("length is invalid")
|
return nil, errors.New("length is invalid")
|
||||||
}
|
}
|
||||||
ds.labels = make([]byte, length)
|
ds.labels = make([]byte, length)
|
||||||
_, err = io.ReadFull(r, ds.labels)
|
_, err = io.ReadFull(r, ds.labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ds.init()
|
ds.init()
|
||||||
return ds, count, nil
|
return ds, nil
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,19 @@ func (rt RuleBehavior) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rt RuleBehavior) Byte() byte {
|
||||||
|
switch rt {
|
||||||
|
case Domain:
|
||||||
|
return 0
|
||||||
|
case IPCIDR:
|
||||||
|
return 1
|
||||||
|
case Classical:
|
||||||
|
return 2
|
||||||
|
default:
|
||||||
|
return 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ParseBehavior(s string) (behavior RuleBehavior, err error) {
|
func ParseBehavior(s string) (behavior RuleBehavior, err error) {
|
||||||
switch s {
|
switch s {
|
||||||
case "domain":
|
case "domain":
|
||||||
|
@ -942,7 +942,7 @@ rule-providers:
|
|||||||
interval: 259200
|
interval: 259200
|
||||||
path: /path/to/save/file.yaml
|
path: /path/to/save/file.yaml
|
||||||
type: file
|
type: file
|
||||||
rule3: # mrs类型ruleset,目前仅支持domain,可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换得到
|
rule3: # mrs类型ruleset,目前仅支持domain和ipcidr,可以通过“mihomo convert-ruleset domain yaml XXX.yaml XXX.mrs”转换得到
|
||||||
type: http
|
type: http
|
||||||
url: "url"
|
url: "url"
|
||||||
format: mrs
|
format: mrs
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
P "github.com/metacubex/mihomo/constant/provider"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,6 +17,10 @@ type classicalStrategy struct {
|
|||||||
parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)
|
parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *classicalStrategy) Behavior() P.RuleBehavior {
|
||||||
|
return P.Classical
|
||||||
|
}
|
||||||
|
|
||||||
func (c *classicalStrategy) Match(metadata *C.Metadata) bool {
|
func (c *classicalStrategy) Match(metadata *C.Metadata) bool {
|
||||||
for _, rule := range c.rules {
|
for _, rule := range c.rules {
|
||||||
if m, _ := rule.Match(metadata); m {
|
if m, _ := rule.Match(metadata); m {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/metacubex/mihomo/component/trie"
|
"github.com/metacubex/mihomo/component/trie"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
P "github.com/metacubex/mihomo/constant/provider"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,6 +16,10 @@ type domainStrategy struct {
|
|||||||
domainSet *trie.DomainSet
|
domainSet *trie.DomainSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *domainStrategy) Behavior() P.RuleBehavior {
|
||||||
|
return P.Domain
|
||||||
|
}
|
||||||
|
|
||||||
func (d *domainStrategy) ShouldFindProcess() bool {
|
func (d *domainStrategy) ShouldFindProcess() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -51,12 +56,12 @@ func (d *domainStrategy) FinishInsert() {
|
|||||||
d.domainTrie = nil
|
d.domainTrie = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *domainStrategy) FromMrs(r io.Reader) error {
|
func (d *domainStrategy) FromMrs(r io.Reader, count int) error {
|
||||||
domainSet, count, err := trie.ReadDomainSetBin(r)
|
domainSet, err := trie.ReadDomainSetBin(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.count = int(count)
|
d.count = count
|
||||||
d.domainSet = domainSet
|
d.domainSet = domainSet
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -65,7 +70,7 @@ func (d *domainStrategy) WriteMrs(w io.Writer) error {
|
|||||||
if d.domainSet == nil {
|
if d.domainSet == nil {
|
||||||
return errors.New("nil domainSet")
|
return errors.New("nil domainSet")
|
||||||
}
|
}
|
||||||
return d.domainSet.WriteBin(w, int64(d.count))
|
return d.domainSet.WriteBin(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ mrsRuleStrategy = (*domainStrategy)(nil)
|
var _ mrsRuleStrategy = (*domainStrategy)(nil)
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/cidr"
|
"github.com/metacubex/mihomo/component/cidr"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
P "github.com/metacubex/mihomo/constant/provider"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
@ -15,6 +19,10 @@ type ipcidrStrategy struct {
|
|||||||
//trie *trie.IpCidrTrie
|
//trie *trie.IpCidrTrie
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *ipcidrStrategy) Behavior() P.RuleBehavior {
|
||||||
|
return P.IPCIDR
|
||||||
|
}
|
||||||
|
|
||||||
func (i *ipcidrStrategy) ShouldFindProcess() bool {
|
func (i *ipcidrStrategy) ShouldFindProcess() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -54,6 +62,26 @@ func (i *ipcidrStrategy) FinishInsert() {
|
|||||||
i.cidrSet.Merge()
|
i.cidrSet.Merge()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *ipcidrStrategy) FromMrs(r io.Reader, count int) error {
|
||||||
|
cidrSet, err := cidr.ReadIpCidrSet(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.count = count
|
||||||
|
i.cidrSet = cidrSet
|
||||||
|
if i.count > 0 {
|
||||||
|
i.shouldResolveIP = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipcidrStrategy) WriteMrs(w io.Writer) error {
|
||||||
|
if i.cidrSet == nil {
|
||||||
|
return errors.New("nil cidrSet")
|
||||||
|
}
|
||||||
|
return i.cidrSet.WriteBin(w)
|
||||||
|
}
|
||||||
|
|
||||||
func (i *ipcidrStrategy) ToIpCidr() *netipx.IPSet {
|
func (i *ipcidrStrategy) ToIpCidr() *netipx.IPSet {
|
||||||
return i.cidrSet.ToIPSet()
|
return i.cidrSet.ToIPSet()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@ -27,6 +28,38 @@ func ConvertToMrs(buf []byte, behavior P.RuleBehavior, format P.RuleFormat, w io
|
|||||||
err = zstdErr
|
err = zstdErr
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// header
|
||||||
|
_, err = encoder.Write(MrsMagicBytes[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// behavior
|
||||||
|
_behavior := []byte{behavior.Byte()}
|
||||||
|
_, err = encoder.Write(_behavior[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// count
|
||||||
|
count := int64(_strategy.Count())
|
||||||
|
err = binary.Write(encoder, binary.BigEndian, count)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// extra (reserved for future using)
|
||||||
|
var extra []byte
|
||||||
|
err = binary.Write(encoder, binary.BigEndian, int64(len(extra)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = encoder.Write(extra)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return _strategy.WriteMrs(encoder)
|
return _strategy.WriteMrs(encoder)
|
||||||
} else {
|
} else {
|
||||||
return ErrInvalidFormat
|
return ErrInvalidFormat
|
||||||
|
72
rules/provider/mrs_reader.go
Normal file
72
rules/provider/mrs_reader.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/zstd"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MrsMagicBytes = [4]byte{'M', 'R', 'S', 1} // MRSv1
|
||||||
|
|
||||||
|
func rulesMrsParse(buf []byte, strategy ruleStrategy) (ruleStrategy, error) {
|
||||||
|
if _strategy, ok := strategy.(mrsRuleStrategy); ok {
|
||||||
|
reader, err := zstd.NewReader(bytes.NewReader(buf))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
// header
|
||||||
|
var header [4]byte
|
||||||
|
_, err = io.ReadFull(reader, header[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if header != MrsMagicBytes {
|
||||||
|
return nil, fmt.Errorf("invalid MrsMagic bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// behavior
|
||||||
|
var _behavior [1]byte
|
||||||
|
_, err = io.ReadFull(reader, _behavior[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _behavior[0] != strategy.Behavior().Byte() {
|
||||||
|
return nil, fmt.Errorf("invalid behavior")
|
||||||
|
}
|
||||||
|
|
||||||
|
// count
|
||||||
|
var count int64
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// extra (reserved for future using)
|
||||||
|
var length int64
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if length < 0 {
|
||||||
|
return nil, errors.New("length is invalid")
|
||||||
|
}
|
||||||
|
if length > 0 {
|
||||||
|
extra := make([]byte, length)
|
||||||
|
_, err = io.ReadFull(reader, extra)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = _strategy.FromMrs(reader, int(count))
|
||||||
|
return strategy, err
|
||||||
|
} else {
|
||||||
|
return nil, ErrInvalidFormat
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,6 @@ import (
|
|||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
P "github.com/metacubex/mihomo/constant/provider"
|
P "github.com/metacubex/mihomo/constant/provider"
|
||||||
|
|
||||||
"github.com/klauspost/compress/zstd"
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,6 +44,7 @@ type RulePayload struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ruleStrategy interface {
|
type ruleStrategy interface {
|
||||||
|
Behavior() P.RuleBehavior
|
||||||
Match(metadata *C.Metadata) bool
|
Match(metadata *C.Metadata) bool
|
||||||
Count() int
|
Count() int
|
||||||
ShouldResolveIP() bool
|
ShouldResolveIP() bool
|
||||||
@ -56,7 +56,7 @@ type ruleStrategy interface {
|
|||||||
|
|
||||||
type mrsRuleStrategy interface {
|
type mrsRuleStrategy interface {
|
||||||
ruleStrategy
|
ruleStrategy
|
||||||
FromMrs(r io.Reader) error
|
FromMrs(r io.Reader, count int) error
|
||||||
WriteMrs(w io.Writer) error
|
WriteMrs(w io.Writer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,17 +165,7 @@ var ErrInvalidFormat = errors.New("invalid format")
|
|||||||
func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStrategy, error) {
|
func rulesParse(buf []byte, strategy ruleStrategy, format P.RuleFormat) (ruleStrategy, error) {
|
||||||
strategy.Reset()
|
strategy.Reset()
|
||||||
if format == P.MrsRule {
|
if format == P.MrsRule {
|
||||||
if _strategy, ok := strategy.(mrsRuleStrategy); ok {
|
return rulesMrsParse(buf, strategy)
|
||||||
reader, err := zstd.NewReader(bytes.NewReader(buf))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
err = _strategy.FromMrs(reader)
|
|
||||||
return strategy, err
|
|
||||||
} else {
|
|
||||||
return nil, ErrInvalidFormat
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
schema := &RulePayload{}
|
schema := &RulePayload{}
|
||||||
|
Loading…
Reference in New Issue
Block a user