dorothy2 0.0.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ # "THE BEER-WARE LICENSE" (Revision 42):
2
+ # Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
3
+ # notice you can do whatever you want with this stuff. If we meet some day,
4
+ # and you think this stuff is worth it, you can buy us a beer in return.
5
+ #
6
+ # All about pcapr
7
+ # * http://www.pcapr.net
8
+ # * http://groups.google.com/group/pcapr-forum
9
+ # * http://twitter.com/pcapr
10
+ #
11
+ # Mu Dynamics
12
+ # * http://www.mudynamics.com
13
+ # * http://labs.mudynamics.com
14
+
15
+ module Mu
16
+ class Xtractr
17
+ # = Packets
18
+ # Packets is an iterator on a collection of packets determined by the search
19
+ # query. This class can also be used to report on the collection of packets
20
+ # in addition to slicing those packets into a new pcap.
21
+ #
22
+ # xtractr.packets('http.request.method:GET pkt.src:192.168.1.1').save('foo.pcap')
23
+ #
24
+ # <b>Find the top URL's in HTTP packets</b>
25
+ #
26
+ # xtractr.packets('pkt.service:HTTP').count('http.request.method')
27
+ #
28
+ # <b>Find the packet size distribution for DNS packets</b>
29
+ #
30
+ # xtractr.packets('pkt.service:DNS').count('pkt.length')
31
+ class Packets
32
+ include Enumerable
33
+
34
+ attr_reader :xtractr # :nodoc:
35
+
36
+ MAX_PAGE_SIZE = 100 # :nodoc:
37
+
38
+ def initialize xtractr, opts # :nodoc:
39
+ @xtractr = xtractr
40
+ @opts = opts
41
+ @opts[:q] ||= '*'
42
+ end
43
+
44
+ def q # :nodoc:
45
+ @opts[:q]
46
+ end
47
+
48
+ # Iterate over each packet that matches the search criteria. It's always
49
+ # better to use this with a fine-grained query instead of packets.to_a
50
+ # because it's going to try and load <em>all</em> packets from the index.
51
+ # xtractr.packets("pkt.src:192.168.1.1").each do |pkt|
52
+ # ...
53
+ # end
54
+ def each_packet() # :yields: packet
55
+ _opts = @opts.dup
56
+ _opts[:start] ||= 1
57
+ _opts[:limit] ||= MAX_PAGE_SIZE
58
+
59
+ while true
60
+ result = xtractr.json "api/packets", _opts
61
+ rows = result['rows']
62
+ break if rows.empty?
63
+
64
+ rows[0, MAX_PAGE_SIZE-1].each do |row|
65
+ packet = Packet.new xtractr, row
66
+ yield packet
67
+ end
68
+
69
+ break if rows.size < MAX_PAGE_SIZE
70
+ _opts[:start] = rows[MAX_PAGE_SIZE-1]['id']
71
+ end
72
+ return self
73
+ end
74
+
75
+ # Fetch the first packet that matched the query. Mostly used for unit
76
+ # testing.
77
+ def first
78
+ result = xtractr.json "api/packets", :start => 1, :limit => 1, :q => q
79
+ rows = result['rows']
80
+ rows.empty? ? nil : Packet.new(xtractr, rows[0])
81
+ end
82
+
83
+ # Count the unique values of the specified field amongst all the packets
84
+ # that matched the query.
85
+ # xtractr.packets('mozilla').count('http.request.uri')
86
+ def count field
87
+ Views.count xtractr, field, '/api/packets/report', @opts
88
+ end
89
+
90
+ # Return a list of Field::Value objects for the specified field, sorted
91
+ # by their frequency. This is a convenience method used in method chaining.
92
+ # xtractr.packets('index.html').values('http.request.uri')
93
+ def values field
94
+ count(field).map { |c| c.object }
95
+ end
96
+
97
+ # Sum the numeric values of vfield, keyed by the unique values of
98
+ # kfield.
99
+ # xtractr.packets('mozilla').sum('http.request.uri', 'pkt.length')
100
+ def sum kfield, vfield
101
+ Views.sum xtractr, kfield, vfield, '/api/packets/report', @opts
102
+ end
103
+
104
+ # Stich together a pcap made up of all packets that matched the query
105
+ # and save it to the filename.
106
+ # xtractr.packets('pkt.service:DNS pkt.length:>64').save('foo.pcap')
107
+ def save filename
108
+ open(filename, "w") do |ios|
109
+ pcap = xtractr.get "api/packets/slice", :q => @opts[:q]
110
+ ios.write pcap
111
+ end
112
+ return self
113
+ end
114
+
115
+ def inspect # :nodoc:
116
+ "#<packets:#{@opts[:q]}>"
117
+ end
118
+
119
+ alias_method :each, :each_packet
120
+ end
121
+ end # Xtractr
122
+ end # Mu
@@ -0,0 +1,77 @@
1
+ # "THE BEER-WARE LICENSE" (Revision 42):
2
+ # Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
3
+ # notice you can do whatever you want with this stuff. If we meet some day,
4
+ # and you think this stuff is worth it, you can buy us a beer in return.
5
+ #
6
+ # All about pcapr
7
+ # * http://www.pcapr.net
8
+ # * http://groups.google.com/group/pcapr-forum
9
+ # * http://twitter.com/pcapr
10
+ #
11
+ # Mu Dynamics
12
+ # * http://www.mudynamics.com
13
+ # * http://labs.mudynamics.com
14
+
15
+ module Mu
16
+ class Xtractr
17
+ # = Service
18
+ # The Service class represents the Wireshark-assigned protocol that's somewhat
19
+ # higher level than the IP protocol. You can get a list of all the unique
20
+ # services in your index through the xtractr's methods:
21
+ #
22
+ # xtractr.services(/http/).each { |service| ... }
23
+ # xtractr.services.each { |service| ... }
24
+ #
25
+ # Some services like HTTP have counterparts in both flows and packets, while
26
+ # others like ARP (all non-IP, layer2 services) are only available in packets.
27
+ class Service
28
+ attr_reader :xtractr # :nodoc:
29
+
30
+ # Return the name of the service
31
+ attr_reader :name
32
+
33
+ def initialize xtractr, name # :nodoc:
34
+ @xtractr = xtractr
35
+ @name = name
36
+ end
37
+
38
+ # Get a unique list of clients for this service
39
+ # xtractr.service('http').clients
40
+ def clients q=nil
41
+ _q = "flow.service:\"#{name}\""
42
+ _q << " #{q}" if q
43
+ Flows.new(xtractr, :q => _q).count('flow.src')
44
+ end
45
+
46
+ # Get a unique list of servers for this service
47
+ # xtractr.service('http').servers
48
+ def servers q=nil
49
+ _q = "flow.service:\"#{name}\""
50
+ _q << " #{q}" if q
51
+ Flows.new(xtractr, :q => _q).count('flow.dst')
52
+ end
53
+
54
+ # Return an iterator that can yield all packets that have this service and
55
+ # matches the query
56
+ # xtractr.service("DNS").packets("mu").each { |pkt| ... }
57
+ def packets q=nil
58
+ _q = "pkt.service:\"#{name}\""
59
+ _q << " #{q}" if q
60
+ return Packets.new(xtractr, :q => _q)
61
+ end
62
+
63
+ # Return an iterator that can yield all flows that have this service and
64
+ # matches the query
65
+ # xtractr.service("DNS").flows("AAAA").each { |flow| ... }
66
+ def flows q=nil
67
+ _q = "flow.service:\"#{name}\""
68
+ _q << " #{q}" if q
69
+ return Flows.new(xtractr, :q => _q)
70
+ end
71
+
72
+ def inspect # :nodoc:
73
+ "#<service:#{name}>"
74
+ end
75
+ end
76
+ end # Xtractr
77
+ end # Mu
@@ -0,0 +1,103 @@
1
+ # "THE BEER-WARE LICENSE" (Revision 42):
2
+ # Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
3
+ # notice you can do whatever you want with this stuff. If we meet some day,
4
+ # and you think this stuff is worth it, you can buy us a beer in return.
5
+ #
6
+ # All about pcapr
7
+ # * http://www.pcapr.net
8
+ # * http://groups.google.com/group/pcapr-forum
9
+ # * http://twitter.com/pcapr
10
+ #
11
+ # Mu Dynamics
12
+ # * http://www.mudynamics.com
13
+ # * http://labs.mudynamics.com
14
+
15
+ require 'stringio'
16
+ require 'zlib'
17
+
18
+ module Mu
19
+ class Xtractr
20
+ class Stream
21
+ class HTTP < Processor # :nodoc:
22
+ # Check to see if this is a HTTP stream.
23
+ def self.matches? stream
24
+ not stream.messages.select { |message| message.bytes =~ /HTTP\/1\./ }.empty?
25
+ end
26
+
27
+ # Pull out the response body from the messages in the stream and convert
28
+ # them into contents on the stream.
29
+ def self.extract stream
30
+ stream.messages.each { |message| process stream, message }
31
+ end
32
+
33
+ def self.process stream, message
34
+ content = Content.new message
35
+ chunked = false
36
+ length = nil
37
+ ios = StringIO.new message.bytes
38
+ while true
39
+ line = ios.readline.chomp rescue nil
40
+ break if not line or line.empty?
41
+
42
+ case line
43
+ when /Content-Length:\s*(\d+)/i
44
+ length = $1.to_i
45
+ when /Content-Type:\s*(.*)/i
46
+ content.type = $1
47
+ when /Content-Encoding:\s*(.*)/i
48
+ content.encoding = $1
49
+ when /Transfer-Encoding:\s*chunked/i
50
+ chunked = true
51
+ end
52
+ end
53
+
54
+ # Read the content
55
+ bytes = ios.read(length)
56
+ return if not bytes or bytes.empty?
57
+
58
+ # Handle chunked encoding, if necessary
59
+ bytes = dechunk(bytes) if chunked
60
+
61
+ # And then decompress, if necessary
62
+ if ['gzip','deflate'].member? content.encoding
63
+ bytes = decompress(bytes, content.encoding)
64
+ end
65
+
66
+ if bytes
67
+ content.body = bytes
68
+ stream.contents << content
69
+ end
70
+ end
71
+
72
+ def self.dechunk text
73
+ ios = StringIO.new text
74
+ body = ''
75
+ while true
76
+ line = ios.readline rescue nil
77
+ break if not line or line.empty?
78
+
79
+ chunksz = line.to_i(16)
80
+ break if chunksz.zero?
81
+
82
+ body << ios.read(chunksz)
83
+ end
84
+ return body
85
+ end
86
+
87
+ def self.decompress text, method
88
+ if method == 'gzip'
89
+ ios = StringIO.new text
90
+ reader = Zlib::GzipReader.new ios
91
+ begin
92
+ return reader.read
93
+ ensure
94
+ reader.close
95
+ end
96
+ elsif method == 'deflate'
97
+ return Zlib::Inflate.inflate(text)
98
+ end
99
+ end
100
+ end
101
+ end # Stream
102
+ end # Xtractr
103
+ end # Mu
@@ -0,0 +1,132 @@
1
+ # "THE BEER-WARE LICENSE" (Revision 42):
2
+ # Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
3
+ # notice you can do whatever you want with this stuff. If we meet some day,
4
+ # and you think this stuff is worth it, you can buy us a beer in return.
5
+ #
6
+ # All about pcapr
7
+ # * http://www.pcapr.net
8
+ # * http://groups.google.com/group/pcapr-forum
9
+ # * http://twitter.com/pcapr
10
+ #
11
+ # Mu Dynamics
12
+ # * http://www.mudynamics.com
13
+ # * http://labs.mudynamics.com
14
+
15
+ module Mu
16
+ class Xtractr
17
+ # = Stream
18
+ # Represents a logical TCP stream made of messages (potentially spanning
19
+ # multiple packets across multiple pcaps). A given message in the stream is
20
+ # potentially stitched together from multiple packets. Streams form the basis
21
+ # of doing content-analysis with xtractr. xtractr does all the work of
22
+ # reassembling the packets and pulling out the appropriate payload from
23
+ # each of the packets.
24
+ #
25
+ # xtractr.flows('flow.service:http').first.stream.find do |message|
26
+ # m =~ /xml/
27
+ # end
28
+ #
29
+ # Each stream also has a set of content processors that are invoked at
30
+ # creation time which pull out attachments, images, etc from the reassembled
31
+ # stream.
32
+ #
33
+ # xtractr.flows('flow.service:http').first.stream.contents.each do |content|
34
+ # content.save if content.type == 'image/jpeg'
35
+ # end
36
+ class Stream
37
+ include Enumerable
38
+
39
+ # A list of stream processors that can pull out content from messages
40
+ class Processor # :nodoc:
41
+ def self.inherited klass
42
+ @processors ||= [] << klass
43
+ end
44
+
45
+ def self.processors
46
+ @processors ||= []
47
+ end
48
+ end
49
+
50
+ attr_reader :xtractr # :nodoc:
51
+
52
+ # Return the flow that this stream represents.
53
+ attr_reader :flow
54
+
55
+ # Return a list of Messages in this stream.
56
+ attr_reader :messages
57
+
58
+ # Return a list of extracted content from the messages.
59
+ attr_reader :contents
60
+
61
+ # = Message
62
+ # Represents a single logical TCP message that has been potentially
63
+ # reassembled from across multiple packets spanning multiple pcaps. Each
64
+ # message contains the stream to which it belongs in addition to whether
65
+ # this message was sent from the client or the server.
66
+ class Message
67
+ # Returns the stream to which this message belongs to.
68
+ attr_reader :stream
69
+
70
+ # Returns the index within the stream
71
+ attr_reader :index
72
+
73
+ # Returns the direction of the message (request/response).
74
+ attr_reader :dir
75
+
76
+ # Returns the actual bytes of the message.
77
+ attr_reader :bytes
78
+
79
+ def initialize stream, index, dir, bytes # :nodoc:
80
+ @stream = stream
81
+ @index = index
82
+ @dir = dir
83
+ @bytes = bytes
84
+ end
85
+
86
+ def inspect # :nodoc:
87
+ preview = bytes[0..32]
88
+ preview << "..." if bytes.size > 32
89
+ return "#<message:#{index} flow-#{stream.flow.id} #{preview.inspect}>"
90
+ end
91
+ end
92
+
93
+ def initialize xtractr, flow, json # :nodoc:
94
+ @xtractr = xtractr
95
+ @flow = flow
96
+ @messages = []
97
+ @contents = []
98
+
99
+ json['packets'].each do |pkt|
100
+ bytes = (pkt['b'] || []).map { |b| b.chr }.join('')
101
+ if messages.empty? or messages[-1].dir != pkt['d']
102
+ messages << Message.new(self, messages.size, pkt['d'], '')
103
+ end
104
+ messages[-1].bytes << bytes
105
+ end
106
+
107
+ # Run the stream/messages through each registered processor to pull
108
+ # out attachments, files, etc
109
+ Processor.processors.each do |processor|
110
+ if processor.matches? self
111
+ processor.extract self
112
+ break
113
+ end
114
+ end
115
+ end
116
+
117
+ # Iterate over each message in this stream
118
+ def each_message &blk
119
+ messages.each(&blk)
120
+ return self
121
+ end
122
+
123
+ def inspect # :nodoc:
124
+ return "#<stream:#{flow.id} ##{messages.size} messages>"
125
+ end
126
+
127
+ alias_method :each, :each_message
128
+ end
129
+ end # Xtractr
130
+ end # Mu
131
+
132
+ require 'mu/xtractr/stream/http'
@@ -0,0 +1,73 @@
1
+ # "THE BEER-WARE LICENSE" (Revision 42):
2
+ # Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
3
+ # notice you can do whatever you want with this stuff. If we meet some day,
4
+ # and you think this stuff is worth it, you can buy us a beer in return.
5
+ #
6
+ # All about pcapr
7
+ # * http://www.pcapr.net
8
+ # * http://groups.google.com/group/pcapr-forum
9
+ # * http://twitter.com/pcapr
10
+ #
11
+ # Mu Dynamics
12
+ # * http://www.mudynamics.com
13
+ # * http://labs.mudynamics.com
14
+
15
+ module Mu
16
+ class Xtractr
17
+ # = Term
18
+ # A term represents a tokenized value of a field that also contains the
19
+ # frequency of occurence across all of the packets in a given index. Terms
20
+ # are useful to get quick snapshots of the index as well as in trend analysis.
21
+ # For example the following shows the top HTTP request methods as well as the
22
+ # frequency of those methods across all of the packets.
23
+ #
24
+ # xtractr.field('http.request.method').terms.each do |term|
25
+ # p [ term.value, term.frequency ]
26
+ # end
27
+ class Term
28
+ include Enumerable
29
+
30
+ attr_reader :xtractr # :nodoc:
31
+
32
+ # Return the field containing this term.
33
+ attr_reader :field
34
+
35
+ # Return the value of this term.
36
+ attr_reader :value
37
+
38
+ # Return the (packet) frequency of this term.
39
+ attr_reader :frequency
40
+
41
+ def initialize field, json # :nodoc:
42
+ @field = field
43
+ @xtractr = field.xtractr
44
+ @value = json['key']
45
+ @frequency = json['value']
46
+ end
47
+
48
+ # Fetch each packet from the index that has this term in this field. If
49
+ # the optional q is specified, the search query is AND'd with the term's
50
+ # own search query
51
+ # field.term('mozilla').each { |pkt| ... }
52
+ def each_packet(q=nil, &blk) # :yields: packet
53
+ _q = "#{field.name}:#{value}"
54
+ _q << " #{q}" if q
55
+ return Packets.new(xtractr, :q => _q).each(&blk)
56
+ end
57
+
58
+ # Return an instance of Packets that serves as an iterator for all packets
59
+ # containing this term.
60
+ def packets q=nil
61
+ _q = "#{field.name}:#{value}"
62
+ _q << " #{q}" if q
63
+ return Packets.new(xtractr, :q => _q)
64
+ end
65
+
66
+ def inspect # :nodoc:
67
+ "#<term:#{field.name} #{value} #{frequency}>"
68
+ end
69
+
70
+ alias_method :each, :each_packet
71
+ end
72
+ end # Xtractr
73
+ end # Mu
@@ -0,0 +1,53 @@
1
+ # "THE BEER-WARE LICENSE" (Revision 42):
2
+ # Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
3
+ # notice you can do whatever you want with this stuff. If we meet some day,
4
+ # and you think this stuff is worth it, you can buy us a beer in return.
5
+ #
6
+ # All about pcapr
7
+ # * http://www.pcapr.net
8
+ # * http://groups.google.com/group/pcapr-forum
9
+ # * http://twitter.com/pcapr
10
+ #
11
+ # Mu Dynamics
12
+ # * http://www.mudynamics.com
13
+ # * http://labs.mudynamics.com
14
+
15
+ require 'mu/xtractr'
16
+ require 'test/unit'
17
+
18
+ module Mu
19
+ class Xtractr
20
+ class Stream
21
+ class HTTP
22
+ class Test < Test::Unit::TestCase
23
+ attr_reader :xtractr
24
+ attr_reader :flow
25
+
26
+ def setup
27
+ @xtractr = Xtractr.new
28
+ end
29
+
30
+ def test_extract
31
+ stream = xtractr.packets('http.content.type:gif').first.flow.stream
32
+ assert_equal(1, stream.contents.size)
33
+ content = stream.contents.first
34
+ assert_nothing_raised { content.inspect }
35
+ assert_equal('image/gif', content.type)
36
+ assert_nil(content.encoding)
37
+ assert_equal('content.4.1', content.name)
38
+ assert_equal(content.message.__id__, stream.messages[1].__id__)
39
+
40
+ stream = xtractr.packets('http.content.type:xml').first.flow.stream
41
+ assert_equal(1, stream.contents.size)
42
+ content = stream.contents.first
43
+ assert_nothing_raised { content.inspect }
44
+ assert_equal('text/xml', content.type)
45
+ assert_equal('gzip', content.encoding)
46
+ assert_equal('content.2.1', content.name)
47
+ assert_equal(content.message.__id__, stream.messages[1].__id__)
48
+ end
49
+ end
50
+ end # HTTP
51
+ end # Stream
52
+ end # Xtractr
53
+ end # Mu
@@ -0,0 +1,140 @@
1
+ # "THE BEER-WARE LICENSE" (Revision 42):
2
+ # Mu[http://www.mudynamics.com] wrote this file. As long as you retain this
3
+ # notice you can do whatever you want with this stuff. If we meet some day,
4
+ # and you think this stuff is worth it, you can buy us a beer in return.
5
+ #
6
+ # All about pcapr
7
+ # * http://www.pcapr.net
8
+ # * http://groups.google.com/group/pcapr-forum
9
+ # * http://twitter.com/pcapr
10
+ #
11
+ # Mu Dynamics
12
+ # * http://www.mudynamics.com
13
+ # * http://labs.mudynamics.com
14
+
15
+ require 'mu/xtractr'
16
+ require 'test/unit'
17
+
18
+ module Mu
19
+ class Xtractr
20
+ class Field
21
+ class Test < Test::Unit::TestCase
22
+ attr_reader :xtractr
23
+
24
+ def setup
25
+ @xtractr = Xtractr.new
26
+ end
27
+
28
+ def test_Field
29
+ assert(Field.ancestors.include?(Enumerable), "Field doesn't mixin Enumerable")
30
+ end
31
+
32
+ def test_each
33
+ field = xtractr.field('pkt.src')
34
+ terms = field.terms
35
+ assert_equal(11, terms.size)
36
+ terms.each { |t| assert_kind_of(Term, t) }
37
+
38
+ val = field.each_term { |t| assert_kind_of(Term, t) }
39
+ assert_equal(field, val)
40
+ end
41
+
42
+ def test_terms
43
+ field = xtractr.field('dns.qry.name')
44
+ terms = field.terms
45
+ assert_equal(12, terms.size)
46
+ assert_equal(true, terms.first.frequency > terms.last.frequency)
47
+
48
+ field.terms(/itunes/).each do |term|
49
+ assert_match(/itunes/, term.value)
50
+ end
51
+ end
52
+
53
+ def test_term
54
+ field = xtractr.field('dns.qry.name')
55
+
56
+ [ :[], :term ].each do |method|
57
+ term = field.send method, 'itunes'
58
+ assert_kind_of(Term, term)
59
+ assert_equal('itunes', term.value)
60
+ assert_equal(3, term.frequency)
61
+ end
62
+
63
+ assert_raise(ArgumentError) { field['foo'] }
64
+ end
65
+
66
+ def test_count
67
+ field = xtractr.field('dns.qry.name')
68
+ counts = field.count
69
+ assert_equal(7, counts.size)
70
+ counts.each { |v| assert_kind_of(Views::Count, v) }
71
+
72
+ counts = field.count('flow.dst:4.2.2.1')
73
+ assert_equal(4, counts.size)
74
+ counts.each { |v| assert_kind_of(Views::Count, v) }
75
+ end
76
+
77
+ def test_values
78
+ field = xtractr.field('dns.qry.name')
79
+ values = field.values
80
+ assert_equal(7, values.size)
81
+ values.each { |v| assert_kind_of(Field::Value, v) }
82
+
83
+ values = field.values('flow.dst:4.2.2.1')
84
+ assert_equal(4, values.size)
85
+ values.each { |v| assert_kind_of(Field::Value, v) }
86
+ end
87
+
88
+ def test_inspect
89
+ field = xtractr.field('dns.qry.name')
90
+ assert_nothing_raised { field.inspect }
91
+ end
92
+
93
+ class Value
94
+ class Test < ::Test::Unit::TestCase
95
+ attr_reader :xtractr
96
+ attr_reader :value
97
+
98
+ def setup
99
+ @xtractr = Xtractr.new
100
+ @value = xtractr.field('dns.qry.name').values.first
101
+ end
102
+
103
+ def test_q
104
+ assert_equal('dns.qry.name:"ax.search.itunes.apple.com"', value.q)
105
+ end
106
+
107
+ def test_packets
108
+ packets = value.packets
109
+ assert_kind_of(Packets, packets)
110
+ assert_equal('dns.qry.name:"ax.search.itunes.apple.com"', packets.q)
111
+ end
112
+
113
+ def test_each_packet
114
+ v = value.each_packet { |pkt| assert_kind_of(Packet, pkt) }
115
+ assert_equal(value, v)
116
+ end
117
+
118
+ def test_count
119
+ value.count('pkt.src').each do |c|
120
+ assert_kind_of(Views::Count, c)
121
+ assert_equal('pkt.src', c.field.name)
122
+ end
123
+ end
124
+
125
+ def test_sum
126
+ value.sum('pkt.src', 'pkt.length').each do |s|
127
+ assert_kind_of(Views::Sum, s)
128
+ assert_equal('pkt.src', s.field.name)
129
+ end
130
+ end
131
+
132
+ def test_inspect
133
+ assert_nothing_raised { value.inspect }
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end # Field
139
+ end # Xtractr
140
+ end # Mu