diff --git a/infra/conf/freedom.go b/infra/conf/freedom.go index c3510477..60c5b7f1 100644 --- a/infra/conf/freedom.go +++ b/infra/conf/freedom.go @@ -39,91 +39,89 @@ func (c *FreedomConfig) Build() (proto.Message, error) { } if c.Fragment != nil { - if len(c.Fragment.Interval) == 0 || len(c.Fragment.Length) == 0 { - return nil, newError("Invalid interval or length") - } - intervalMinMax := strings.Split(c.Fragment.Interval, "-") - var minInterval, maxInterval int64 + config.Fragment = new(freedom.Fragment) var err, err2 error - if len(intervalMinMax) == 2 { - minInterval, err = strconv.ParseInt(intervalMinMax[0], 10, 64) - maxInterval, err2 = strconv.ParseInt(intervalMinMax[1], 10, 64) - } else { - minInterval, err = strconv.ParseInt(intervalMinMax[0], 10, 64) - maxInterval = minInterval - } - if err != nil { - return nil, newError("Invalid minimum interval: ", err).Base(err) - } - if err2 != nil { - return nil, newError("Invalid maximum interval: ", err2).Base(err2) - } - - lengthMinMax := strings.Split(c.Fragment.Length, "-") - var minLength, maxLength int64 - if len(lengthMinMax) == 2 { - minLength, err = strconv.ParseInt(lengthMinMax[0], 10, 64) - maxLength, err2 = strconv.ParseInt(lengthMinMax[1], 10, 64) - - } else { - minLength, err = strconv.ParseInt(lengthMinMax[0], 10, 64) - maxLength = minLength - } - if err != nil { - return nil, newError("Invalid minimum length: ", err).Base(err) - } - if err2 != nil { - return nil, newError("Invalid maximum length: ", err2).Base(err2) - } - - if minInterval > maxInterval { - minInterval, maxInterval = maxInterval, minInterval - } - if minLength > maxLength { - minLength, maxLength = maxLength, minLength - } - - config.Fragment = &freedom.Fragment{ - MinInterval: int32(minInterval), - MaxInterval: int32(maxInterval), - MinLength: int32(minLength), - MaxLength: int32(maxLength), - } switch strings.ToLower(c.Fragment.Packets) { case "tlshello": // TLS Hello Fragmentation (into multiple handshake messages) - config.Fragment.StartPacket = 0 - config.Fragment.EndPacket = 1 + config.Fragment.PacketsFrom = 0 + config.Fragment.PacketsTo = 1 case "": // TCP Segmentation (all packets) - config.Fragment.StartPacket = 0 - config.Fragment.EndPacket = 0 + config.Fragment.PacketsFrom = 0 + config.Fragment.PacketsTo = 0 default: // TCP Segmentation (range) - packetRange := strings.Split(c.Fragment.Packets, "-") - var startPacket, endPacket int64 - if len(packetRange) == 2 { - startPacket, err = strconv.ParseInt(packetRange[0], 10, 64) - endPacket, err2 = strconv.ParseInt(packetRange[1], 10, 64) + packetsFromTo := strings.Split(c.Fragment.Packets, "-") + if len(packetsFromTo) == 2 { + config.Fragment.PacketsFrom, err = strconv.ParseUint(packetsFromTo[0], 10, 64) + config.Fragment.PacketsTo, err2 = strconv.ParseUint(packetsFromTo[1], 10, 64) } else { - startPacket, err = strconv.ParseInt(packetRange[0], 10, 64) - endPacket = startPacket + config.Fragment.PacketsFrom, err = strconv.ParseUint(packetsFromTo[0], 10, 64) + config.Fragment.PacketsTo = config.Fragment.PacketsFrom } if err != nil { - return nil, newError("Invalid start packet: ", err).Base(err) + return nil, newError("Invalid PacketsFrom").Base(err) } if err2 != nil { - return nil, newError("Invalid end packet: ", err2).Base(err2) + return nil, newError("Invalid PacketsTo").Base(err2) } - if startPacket > endPacket { - return nil, newError("Invalid packet range: ", c.Fragment.Packets) + if config.Fragment.PacketsFrom > config.Fragment.PacketsTo { + config.Fragment.PacketsFrom, config.Fragment.PacketsTo = config.Fragment.PacketsTo, config.Fragment.PacketsFrom } - if startPacket < 1 { - return nil, newError("Cannot start from packet 0") + if config.Fragment.PacketsFrom == 0 { + return nil, newError("PacketsFrom can't be 0") + } + } + + { + if c.Fragment.Length == "" { + return nil, newError("Length can't be empty") + } + lengthMinMax := strings.Split(c.Fragment.Length, "-") + if len(lengthMinMax) == 2 { + config.Fragment.LengthMin, err = strconv.ParseUint(lengthMinMax[0], 10, 64) + config.Fragment.LengthMax, err2 = strconv.ParseUint(lengthMinMax[1], 10, 64) + } else { + config.Fragment.LengthMin, err = strconv.ParseUint(lengthMinMax[0], 10, 64) + config.Fragment.LengthMax = config.Fragment.LengthMin + } + if err != nil { + return nil, newError("Invalid LengthMin").Base(err) + } + if err2 != nil { + return nil, newError("Invalid LengthMax").Base(err2) + } + if config.Fragment.LengthMin > config.Fragment.LengthMax { + config.Fragment.LengthMin, config.Fragment.LengthMax = config.Fragment.LengthMax, config.Fragment.LengthMin + } + if config.Fragment.LengthMin == 0 { + return nil, newError("LengthMin can't be 0") + } + } + + { + if c.Fragment.Interval == "" { + return nil, newError("Interval can't be empty") + } + intervalMinMax := strings.Split(c.Fragment.Interval, "-") + if len(intervalMinMax) == 2 { + config.Fragment.IntervalMin, err = strconv.ParseUint(intervalMinMax[0], 10, 64) + config.Fragment.IntervalMax, err2 = strconv.ParseUint(intervalMinMax[1], 10, 64) + } else { + config.Fragment.IntervalMin, err = strconv.ParseUint(intervalMinMax[0], 10, 64) + config.Fragment.IntervalMax = config.Fragment.IntervalMin + } + if err != nil { + return nil, newError("Invalid IntervalMin").Base(err) + } + if err2 != nil { + return nil, newError("Invalid IntervalMax").Base(err2) + } + if config.Fragment.IntervalMin > config.Fragment.IntervalMax { + config.Fragment.IntervalMin, config.Fragment.IntervalMax = config.Fragment.IntervalMax, config.Fragment.IntervalMin } - config.Fragment.StartPacket = int32(startPacket) - config.Fragment.EndPacket = int32(endPacket) } } diff --git a/proxy/freedom/config.pb.go b/proxy/freedom/config.pb.go index 2e2fb403..7561f7fd 100644 --- a/proxy/freedom/config.pb.go +++ b/proxy/freedom/config.pb.go @@ -125,12 +125,12 @@ type Fragment struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - MinInterval int32 `protobuf:"varint,1,opt,name=min_interval,json=minInterval,proto3" json:"min_interval,omitempty"` - MaxInterval int32 `protobuf:"varint,2,opt,name=max_interval,json=maxInterval,proto3" json:"max_interval,omitempty"` - MinLength int32 `protobuf:"varint,3,opt,name=min_length,json=minLength,proto3" json:"min_length,omitempty"` - MaxLength int32 `protobuf:"varint,4,opt,name=max_length,json=maxLength,proto3" json:"max_length,omitempty"` - StartPacket int32 `protobuf:"varint,5,opt,name=start_packet,json=startPacket,proto3" json:"start_packet,omitempty"` - EndPacket int32 `protobuf:"varint,6,opt,name=end_packet,json=endPacket,proto3" json:"end_packet,omitempty"` + PacketsFrom uint64 `protobuf:"varint,1,opt,name=packets_from,json=packetsFrom,proto3" json:"packets_from,omitempty"` + PacketsTo uint64 `protobuf:"varint,2,opt,name=packets_to,json=packetsTo,proto3" json:"packets_to,omitempty"` + LengthMin uint64 `protobuf:"varint,3,opt,name=length_min,json=lengthMin,proto3" json:"length_min,omitempty"` + LengthMax uint64 `protobuf:"varint,4,opt,name=length_max,json=lengthMax,proto3" json:"length_max,omitempty"` + IntervalMin uint64 `protobuf:"varint,5,opt,name=interval_min,json=intervalMin,proto3" json:"interval_min,omitempty"` + IntervalMax uint64 `protobuf:"varint,6,opt,name=interval_max,json=intervalMax,proto3" json:"interval_max,omitempty"` } func (x *Fragment) Reset() { @@ -165,44 +165,44 @@ func (*Fragment) Descriptor() ([]byte, []int) { return file_proxy_freedom_config_proto_rawDescGZIP(), []int{1} } -func (x *Fragment) GetMinInterval() int32 { +func (x *Fragment) GetPacketsFrom() uint64 { if x != nil { - return x.MinInterval + return x.PacketsFrom } return 0 } -func (x *Fragment) GetMaxInterval() int32 { +func (x *Fragment) GetPacketsTo() uint64 { if x != nil { - return x.MaxInterval + return x.PacketsTo } return 0 } -func (x *Fragment) GetMinLength() int32 { +func (x *Fragment) GetLengthMin() uint64 { if x != nil { - return x.MinLength + return x.LengthMin } return 0 } -func (x *Fragment) GetMaxLength() int32 { +func (x *Fragment) GetLengthMax() uint64 { if x != nil { - return x.MaxLength + return x.LengthMax } return 0 } -func (x *Fragment) GetStartPacket() int32 { +func (x *Fragment) GetIntervalMin() uint64 { if x != nil { - return x.StartPacket + return x.IntervalMin } return 0 } -func (x *Fragment) GetEndPacket() int32 { +func (x *Fragment) GetIntervalMax() uint64 { if x != nil { - return x.EndPacket + return x.IntervalMax } return 0 } @@ -302,19 +302,19 @@ var file_proxy_freedom_config_proto_rawDesc = []byte{ 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0xd0, 0x01, 0x0a, 0x08, 0x46, 0x72, 0x61, - 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6d, 0x69, 0x6e, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x61, 0x78, 0x5f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, - 0x6d, 0x61, 0x78, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, - 0x69, 0x6e, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x09, 0x6d, 0x69, 0x6e, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x61, - 0x78, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, - 0x6d, 0x61, 0x78, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, - 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x09, 0x65, 0x6e, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x22, 0xf2, 0x02, 0x0a, 0x06, + 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, + 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x70, 0x61, 0x63, + 0x6b, 0x65, 0x74, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x63, 0x6b, + 0x65, 0x74, 0x73, 0x5f, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x70, 0x61, + 0x63, 0x6b, 0x65, 0x74, 0x73, 0x54, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x65, 0x6e, 0x67, 0x74, + 0x68, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6c, 0x65, 0x6e, + 0x67, 0x74, 0x68, 0x4d, 0x69, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, + 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6c, 0x65, 0x6e, 0x67, + 0x74, 0x68, 0x4d, 0x61, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, + 0x6c, 0x5f, 0x6d, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x61, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x61, 0x78, 0x22, 0xf2, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x52, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x66, 0x72, 0x65, diff --git a/proxy/freedom/config.proto b/proxy/freedom/config.proto index 4422edd3..53524e19 100644 --- a/proxy/freedom/config.proto +++ b/proxy/freedom/config.proto @@ -13,12 +13,12 @@ message DestinationOverride { } message Fragment { - int32 min_interval = 1; - int32 max_interval = 2; - int32 min_length = 3; - int32 max_length = 4; - int32 start_packet = 5; - int32 end_packet = 6; + uint64 packets_from = 1; + uint64 packets_to = 2; + uint64 length_min = 3; + uint64 length_max = 4; + uint64 interval_min = 5; + uint64 interval_max = 6; } message Config { diff --git a/proxy/freedom/freedom.go b/proxy/freedom/freedom.go index b881ffde..c6907b4c 100644 --- a/proxy/freedom/freedom.go +++ b/proxy/freedom/freedom.go @@ -5,7 +5,6 @@ package freedom import ( "context" "crypto/rand" - "encoding/binary" "io" "math/big" "time" @@ -13,7 +12,6 @@ import ( "github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/dice" - "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/retry" "github.com/xtls/xray-core/common/session" @@ -175,28 +173,12 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte var writer buf.Writer if destination.Network == net.Network_TCP { if h.config.Fragment != nil { - if h.config.Fragment.StartPacket == 0 && h.config.Fragment.EndPacket == 1 { - newError("FRAGMENT", int(h.config.Fragment.MaxLength)).WriteToLog(session.ExportIDToError(ctx)) - writer = buf.NewWriter( - &FragmentedClientHelloConn{ - Conn: conn, - maxLength: int(h.config.Fragment.MaxLength), - minInterval: time.Duration(h.config.Fragment.MinInterval) * time.Millisecond, - maxInterval: time.Duration(h.config.Fragment.MaxInterval) * time.Millisecond, - }) - } else { - writer = buf.NewWriter( - &FragmentWriter{ - Writer: conn, - minLength: int(h.config.Fragment.MinLength), - maxLength: int(h.config.Fragment.MaxLength), - minInterval: time.Duration(h.config.Fragment.MinInterval) * time.Millisecond, - maxInterval: time.Duration(h.config.Fragment.MaxInterval) * time.Millisecond, - startPacket: int(h.config.Fragment.StartPacket), - endPacket: int(h.config.Fragment.EndPacket), - PacketCount: 0, - }) - } + newError("FRAGMENT", h.config.Fragment.PacketsFrom, h.config.Fragment.PacketsTo, h.config.Fragment.LengthMin, h.config.Fragment.LengthMax, + h.config.Fragment.IntervalMin, h.config.Fragment.IntervalMax).AtDebug().WriteToLog(session.ExportIDToError(ctx)) + writer = buf.NewWriter(&FragmentWriter{ + fragment: h.config.Fragment, + writer: conn, + }) } else { writer = buf.NewWriter(conn) } @@ -356,40 +338,66 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { } type FragmentWriter struct { - io.Writer - minLength int - maxLength int - minInterval time.Duration - maxInterval time.Duration - startPacket int - endPacket int - PacketCount int + fragment *Fragment + writer io.Writer + count uint64 } -func (w *FragmentWriter) Write(buf []byte) (int, error) { - w.PacketCount += 1 - if (w.startPacket != 0 && (w.PacketCount < w.startPacket || w.PacketCount > w.endPacket)) || len(buf) <= w.minLength { - return w.Writer.Write(buf) +func (f *FragmentWriter) Write(b []byte) (int, error) { + f.count++ + + if f.fragment.PacketsFrom == 0 && f.fragment.PacketsTo == 1 { + if f.count != 1 || len(b) <= 5 || b[0] != 22 { + return f.writer.Write(b) + } + recordLen := 5 + ((int(b[3]) << 8) | int(b[4])) + data := b[5:recordLen] + buf := make([]byte, 1024) + for from := 0; ; { + to := from + int(randBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax))) + if to > len(data) { + to = len(data) + } + copy(buf[:3], b) + copy(buf[5:], data[from:to]) + l := to - from + from = to + buf[3] = byte(l >> 8) + buf[4] = byte(l) + _, err := f.writer.Write(buf[:5+l]) + time.Sleep(time.Duration(randBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond) + if err != nil { + return 0, err + } + if from == len(data) { + if len(b) > recordLen { + n, err := f.writer.Write(b[recordLen:]) + if err != nil { + return recordLen + n, err + } + } + return len(b), nil + } + } } - nTotal := 0 - for { - randomBytesTo := int(randBetween(int64(w.minLength), int64(w.maxLength))) + nTotal - if randomBytesTo > len(buf) { - randomBytesTo = len(buf) + if f.fragment.PacketsFrom != 0 && (f.count < f.fragment.PacketsFrom || f.count > f.fragment.PacketsTo) { + return f.writer.Write(b) + } + for from := 0; ; { + to := from + int(randBetween(int64(f.fragment.LengthMin), int64(f.fragment.LengthMax))) + if to > len(b) { + to = len(b) } - n, err := w.Writer.Write(buf[nTotal:randomBytesTo]) + n, err := f.writer.Write(b[from:to]) + from += n + time.Sleep(time.Duration(randBetween(int64(f.fragment.IntervalMin), int64(f.fragment.IntervalMax))) * time.Millisecond) if err != nil { - return nTotal + n, err + return from, err } - nTotal += n - - if nTotal >= len(buf) { - return nTotal, nil + if from >= len(b) { + return from, nil } - - randomInterval := randBetween(int64(w.minInterval), int64(w.maxInterval)) - time.Sleep(time.Duration(randomInterval)) } } @@ -401,66 +409,3 @@ func randBetween(left int64, right int64) int64 { bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left)) return left + bigInt.Int64() } - -type FragmentedClientHelloConn struct { - net.Conn - PacketCount int - minLength int - maxLength int - minInterval time.Duration - maxInterval time.Duration -} - -func (c *FragmentedClientHelloConn) Write(b []byte) (n int, err error) { - if len(b) >= 5 && b[0] == 22 && c.PacketCount == 0 { - n, err = sendFragmentedClientHello(c, b, c.minLength, c.maxLength) - - if err == nil { - c.PacketCount++ - return n, err - } - } - - return c.Conn.Write(b) -} - -func sendFragmentedClientHello(conn *FragmentedClientHelloConn, clientHello []byte, minFragmentSize, maxFragmentSize int) (n int, err error) { - if len(clientHello) < 5 || clientHello[0] != 22 { - return 0, errors.New("not a valid TLS ClientHello message") - } - - clientHelloLen := (int(clientHello[3]) << 8) | int(clientHello[4]) - - clientHelloData := clientHello[5:] - for i := 0; i < clientHelloLen; { - fragmentEnd := i + int(randBetween(int64(minFragmentSize), int64(maxFragmentSize))) - if fragmentEnd > clientHelloLen { - fragmentEnd = clientHelloLen - } - - fragment := clientHelloData[i:fragmentEnd] - i = fragmentEnd - - err = writeFragmentedRecord(conn, 22, fragment, clientHello) - if err != nil { - return 0, err - } - } - - return len(clientHello), nil -} - -func writeFragmentedRecord(c *FragmentedClientHelloConn, contentType uint8, data []byte, clientHello []byte) error { - header := make([]byte, 5) - header[0] = byte(clientHello[0]) - - tlsVersion := (int(clientHello[1]) << 8) | int(clientHello[2]) - binary.BigEndian.PutUint16(header[1:], uint16(tlsVersion)) - - binary.BigEndian.PutUint16(header[3:], uint16(len(data))) - _, err := c.Conn.Write(append(header, data...)) - randomInterval := randBetween(int64(c.minInterval), int64(c.maxInterval)) - time.Sleep(time.Duration(randomInterval)) - - return err -}