ax25 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +5 -0
- data/CONTRIBUTORS.md +19 -0
- data/Gemfile +3 -0
- data/LICENSE +201 -0
- data/README.md +49 -0
- data/Rakefile +66 -0
- data/ax25.gemspec +42 -0
- data/bin/setup +8 -0
- data/lib/ax25/app_info.rb +3 -0
- data/lib/ax25/encoder/aprs_kiss.rb +59 -0
- data/lib/ax25/encoder/encoder.rb +9 -0
- data/lib/ax25/encoder/igate_tcp.rb +184 -0
- data/lib/ax25/encoder.rb +3 -0
- data/lib/ax25/frame/entity.rb +10 -0
- data/lib/ax25/frame/frame.rb +22 -0
- data/lib/ax25/frame/hop.rb +11 -0
- data/lib/ax25/frame/immutable_entity.rb +87 -0
- data/lib/ax25/frame/immutable_frame.rb +50 -0
- data/lib/ax25/frame/immutable_hop.rb +93 -0
- data/lib/ax25/frame/immutable_message.rb +84 -0
- data/lib/ax25/frame/immutable_path.rb +124 -0
- data/lib/ax25/frame/message.rb +21 -0
- data/lib/ax25/frame/path.rb +8 -0
- data/lib/ax25/frame/path_agnostic_immutable_frame.rb +31 -0
- data/lib/ax25/frame.rb +11 -0
- data/lib/ax25.rb +3 -0
- metadata +198 -0
data/lib/ax25/encoder.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'abstractify'
|
|
2
|
+
require 'ax25/frame/message'
|
|
3
|
+
|
|
4
|
+
module Ax25
|
|
5
|
+
##
|
|
6
|
+
# An APXP compatible APRS frame, excluding the path data entierly. The
|
|
7
|
+
# UnpathedFrame and all its fields are immutable.
|
|
8
|
+
#
|
|
9
|
+
# @example Comparing frames
|
|
10
|
+
# orig_frame = Ax25.new('WI2ARD-1', 'OMG', ['WIDE2-2' 'WIDE1-1'], 'payload goes here')
|
|
11
|
+
# diff_frame = Ax25.new('WI2ARD-1', 'OMG', ['WIDE2-2' 'WIDE1-1'], 'different payload')
|
|
12
|
+
# assert not orig_frame.eql? diff_frame
|
|
13
|
+
public
|
|
14
|
+
module Frame
|
|
15
|
+
include Ax25::Message
|
|
16
|
+
include Abstractify::Abstract
|
|
17
|
+
|
|
18
|
+
abstract :path_agnostic_identity
|
|
19
|
+
|
|
20
|
+
attr_accessor :path
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'ax25/frame/entity'
|
|
2
|
+
|
|
3
|
+
module Ax25
|
|
4
|
+
public
|
|
5
|
+
class ImmutableEntity
|
|
6
|
+
include Entity
|
|
7
|
+
|
|
8
|
+
attr_reader :callsign, :ssid
|
|
9
|
+
|
|
10
|
+
public
|
|
11
|
+
def initialize(callsign, ssid, strict_callsign: true, strict_ssid: true)
|
|
12
|
+
raise ArgumentError.new("callsign can not be nil") if callsign.nil?
|
|
13
|
+
|
|
14
|
+
raise ArgumentError.new("callsign must be a String") if not callsign.kind_of? String
|
|
15
|
+
raise ArgumentError.new("ssid must be an Integer.") if (not ssid.nil?) && (not ssid.kind_of? Integer)
|
|
16
|
+
|
|
17
|
+
raise ArgumentError.new("ssid must be a value between 0 (inclusive) and 15 (inclusive): #{callsign}-#{ssid}") if (not ssid.nil?) && (ssid < 0 || ssid > 15) && (strict_ssid)
|
|
18
|
+
raise ArgumentError.new("Callsign can not be an empty string") if callsign.empty? && strict_callsign
|
|
19
|
+
raise ArgumentError.new("Callsign must only contain numebers and letters") if callsign.strip.match?(/[^a-zA-Z0-9]/) && (strict_callsign)
|
|
20
|
+
|
|
21
|
+
@callsign = callsign.strip.upcase.freeze
|
|
22
|
+
if (ssid.nil?) || (ssid.eql? 0)
|
|
23
|
+
@ssid = nil
|
|
24
|
+
else
|
|
25
|
+
@ssid = ssid
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
public
|
|
30
|
+
def self.from_raw(raw_hop, strict_callsign: true, strict_ssid: true)
|
|
31
|
+
raise ArgumentError.new("raw_hop can not be nil") if raw_hop.nil?
|
|
32
|
+
|
|
33
|
+
callsign = nil
|
|
34
|
+
ssid = nil
|
|
35
|
+
|
|
36
|
+
hop = raw_hop.dup
|
|
37
|
+
|
|
38
|
+
raise ArgumentError.new("Hops can only contain letters, numbers and dashes") if hop.strip.match?(/[^a-zA-Z0-9\-]/) && strict_callsign
|
|
39
|
+
|
|
40
|
+
if not hop.include? "-"
|
|
41
|
+
ssid = nil
|
|
42
|
+
callsign = hop.strip
|
|
43
|
+
else
|
|
44
|
+
split_hop = hop.strip.split("-")
|
|
45
|
+
raise ArgumentError.new("More than one hypen seen in a hop, invalid format") if split_hop.length > 2
|
|
46
|
+
raise ArgumentError.new("Hop format was not valid, hyphen placed at end or beginning of string") if split_hop.length <= 1
|
|
47
|
+
callsign = split_hop[0]
|
|
48
|
+
ssid = split_hop[1].to_i
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
return Ax25::ImmutableEntity.new(callsign, ssid, strict_callsign: strict_callsign, strict_ssid: strict_ssid)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
public
|
|
55
|
+
def decrement_ssid
|
|
56
|
+
raise RangeError.new("SSID can not be decremented when it is currently 0 or nil") if (self.ssid.nil?) or (self.ssid <= 0)
|
|
57
|
+
|
|
58
|
+
return Ax25::ImmutableEntity.new(self.callsign, self.ssid - 1, strict_callsign: false, strict_ssid: false)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
public
|
|
62
|
+
def eql?(other)
|
|
63
|
+
raise ArgumentError.new("The argument must be of type Hop (or a child class).") if not other.kind_of? Ax25::Entity
|
|
64
|
+
|
|
65
|
+
return self == other
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
public
|
|
69
|
+
def ==(other)
|
|
70
|
+
return false if other.nil?
|
|
71
|
+
return false if not other.respond_to? :callsign
|
|
72
|
+
return false if not other.respond_to? :ssid
|
|
73
|
+
|
|
74
|
+
return false if not self.callsign.eql? other.callsign
|
|
75
|
+
return false if not self.ssid.eql? other.ssid
|
|
76
|
+
|
|
77
|
+
return true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
public
|
|
81
|
+
def to_s
|
|
82
|
+
ret_val = self.callsign.dup
|
|
83
|
+
ret_val << ("-" + self.ssid.to_s) if not self.ssid.nil?
|
|
84
|
+
return ret_val
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'ax25/frame/immutable_message'
|
|
2
|
+
require 'ax25/frame/frame'
|
|
3
|
+
|
|
4
|
+
module Ax25
|
|
5
|
+
public
|
|
6
|
+
class ImmutableFrame < Ax25::ImmutableMessage
|
|
7
|
+
include Frame
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
def initialize(source, destination, path, payload)
|
|
11
|
+
raise ArgumentError.new("path argument can not be nil") if path.nil?
|
|
12
|
+
raise ArgumentError.new("path argument must be a Path, but found: " + path.class.to_s) if not path.kind_of? Ax25::Path
|
|
13
|
+
|
|
14
|
+
super(source, destination, payload)
|
|
15
|
+
|
|
16
|
+
@path = path
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
public
|
|
20
|
+
def eql?(other)
|
|
21
|
+
raise ArgumentError.new("The argument can not be a UnpathedFrame or a PathAgnosticFrame") if ((other.instance_of? ImmutableMessage) || (other.instance_of? PathAgnosticImmutableFrame))
|
|
22
|
+
raise ArgumentError.new("The argument must be of type Frame (or a child class).") if not other.kind_of? Frame
|
|
23
|
+
|
|
24
|
+
return self == other
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
public
|
|
28
|
+
def ==(other)
|
|
29
|
+
return false if not super(other)
|
|
30
|
+
|
|
31
|
+
return false if not other.respond_to? :path
|
|
32
|
+
|
|
33
|
+
if self.path.eql? other.path
|
|
34
|
+
return true
|
|
35
|
+
else
|
|
36
|
+
return false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
public
|
|
41
|
+
def hash
|
|
42
|
+
return [super, self.path].hash
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
public
|
|
46
|
+
def path_agnostic_identity
|
|
47
|
+
return PathAgnosticImmutableFrame.new(self.source, self.destination, self.path, self.payload)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require 'ax25/frame/hop'
|
|
2
|
+
require 'ax25/frame/immutable_entity'
|
|
3
|
+
|
|
4
|
+
module Ax25
|
|
5
|
+
public
|
|
6
|
+
class ImmutableHop < Ax25::ImmutableEntity
|
|
7
|
+
include Hop
|
|
8
|
+
|
|
9
|
+
public
|
|
10
|
+
def initialize(callsign, ssid, seen, strict_callsign: true, strict_ssid: true)
|
|
11
|
+
|
|
12
|
+
raise ArgumentError.new("seen can not be nil") if seen.nil?
|
|
13
|
+
raise ArgumentError.new("seen must be a Boolean") if not (!!seen == seen)
|
|
14
|
+
|
|
15
|
+
super(callsign, ssid, strict_callsign: strict_callsign, strict_ssid: strict_ssid)
|
|
16
|
+
|
|
17
|
+
@seen = seen
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
public
|
|
21
|
+
def self.from_raw(raw_hop, strict_callsign: true, strict_ssid: true)
|
|
22
|
+
raise ArgumentError.new("raw_hop can not be nil") if raw_hop.nil?
|
|
23
|
+
|
|
24
|
+
callsign = nil
|
|
25
|
+
ssid = nil
|
|
26
|
+
seen = nil
|
|
27
|
+
|
|
28
|
+
hop = raw_hop.dup
|
|
29
|
+
if hop[-1].eql? "*"
|
|
30
|
+
seen = true
|
|
31
|
+
hop = hop[0..-2]
|
|
32
|
+
else
|
|
33
|
+
seen = false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
raise ArgumentError.new("Hops can only contain letters, numbers and dashes. Hop: #{hop.to_s}") if hop.strip.match?(/[^a-zA-Z0-9\-]/) && strict_callsign
|
|
37
|
+
|
|
38
|
+
if not hop.include? "-"
|
|
39
|
+
ssid = nil
|
|
40
|
+
callsign = hop.strip
|
|
41
|
+
else
|
|
42
|
+
split_hop = hop.strip.split("-")
|
|
43
|
+
raise ArgumentError.new("More than one hypen seen in a hop, invalid format") if split_hop.length > 2
|
|
44
|
+
raise ArgumentError.new("Hop format was not valid, hyphen placed at end or beginning of string") if split_hop.length <= 1
|
|
45
|
+
callsign = split_hop[0]
|
|
46
|
+
ssid = split_hop[1].to_i
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
return Ax25::ImmutableHop.new(callsign, ssid, seen, strict_callsign: strict_callsign, strict_ssid: strict_ssid)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
public
|
|
53
|
+
def seen?
|
|
54
|
+
return @seen
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
public
|
|
58
|
+
def toggle_seen
|
|
59
|
+
return Ax25::ImmutableHop.new(self.callsign, self.ssid, !self.seen?, strict_callsign: false, strict_ssid: false)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
public
|
|
63
|
+
def decrement_ssid
|
|
64
|
+
raise RangeError.new("SSID can not be decremented when it is currently 0 or nil") if (self.ssid.nil?) or (self.ssid <= 0)
|
|
65
|
+
|
|
66
|
+
return Ax25::ImmutableHop.new(self.callsign, self.ssid - 1, self.seen?, strict_callsign: false, strict_ssid: false)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
public
|
|
70
|
+
def eql?(other)
|
|
71
|
+
raise ArgumentError.new("The argument must be of type Hop (or a child class).") if not other.kind_of? Ax25::Hop
|
|
72
|
+
|
|
73
|
+
return self == other
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
public
|
|
77
|
+
def ==(other)
|
|
78
|
+
return false if !super(other)
|
|
79
|
+
|
|
80
|
+
return false if not other.respond_to? :seen?
|
|
81
|
+
return false if not self.seen?.eql? other.seen?
|
|
82
|
+
|
|
83
|
+
return true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
public
|
|
87
|
+
def to_s
|
|
88
|
+
ret_val = super.dup
|
|
89
|
+
ret_val << "*" if self.seen?
|
|
90
|
+
return ret_val
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
require 'ax25/frame/message'
|
|
2
|
+
|
|
3
|
+
module Ax25
|
|
4
|
+
##
|
|
5
|
+
# An APXP compatible APRS frame, excluding the path data entierly. The
|
|
6
|
+
# UnpathedFrame and all its fields are immutable.
|
|
7
|
+
#
|
|
8
|
+
# @example Comparing frames
|
|
9
|
+
# orig_frame = Ax25.new('WI2ARD-1', 'OMG', ['WIDE2-2' 'WIDE1-1'], 'payload goes here')
|
|
10
|
+
# diff_frame = Ax25.new('WI2ARD-1', 'OMG', ['WIDE2-2' 'WIDE1-1'], 'different payload')
|
|
11
|
+
# assert not orig_frame.eql? diff_frame
|
|
12
|
+
public
|
|
13
|
+
class ImmutableMessage
|
|
14
|
+
include Message
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Creates a new frame, all fields are duplicated but and immutable. All
|
|
18
|
+
# fields are validated.
|
|
19
|
+
#
|
|
20
|
+
# @param source [Ax25::Entity] The source callsign for the frame. Must be a
|
|
21
|
+
# non-null string of non-zero length with only letters, numbers and
|
|
22
|
+
# dashes. Will be converted uppercase.
|
|
23
|
+
# @param destination [Ax25::Entity] The destination callsign for the frame. Must
|
|
24
|
+
# be a non-null string of non-zero length with only letters, numbers,
|
|
25
|
+
# and dashes. Will be converted to uppercase.
|
|
26
|
+
# @param payload [String] The payload of the frame. Must be a non-null
|
|
27
|
+
# string of non-zero length.
|
|
28
|
+
protected
|
|
29
|
+
def initialize(source, destination, payload)
|
|
30
|
+
raise ArgumentError.new("source argument can not be nil") if source.nil?
|
|
31
|
+
raise ArgumentError.new("destination argument can not be nil") if destination.nil?
|
|
32
|
+
raise ArgumentError.new("payload argument can not be nil") if payload.nil?
|
|
33
|
+
|
|
34
|
+
raise ArgumentError.new("source argument must be a Entity, but found: " + source.class.to_s) if not source.kind_of? Ax25::Entity
|
|
35
|
+
raise ArgumentError.new("destination argument must be a Entity, but found: " + source.class.to_s) if not destination.kind_of? Ax25::Entity
|
|
36
|
+
raise ArgumentError.new("payload argument must be a string, but found: " + source.class.to_s) if not payload.kind_of? String
|
|
37
|
+
|
|
38
|
+
# The following both duplicate/clone the argument and convert it to uppercase
|
|
39
|
+
@source = source
|
|
40
|
+
@destination = destination
|
|
41
|
+
@payload = payload.dup.freeze
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
public
|
|
45
|
+
def path_agnostic_eql?(other)
|
|
46
|
+
raise ArgumentError.new("The argument must be either an ImmutableMessage or a PathAgnosticFrame") if not ((other.instance_of? Ax25::ImmutableMessage) || (other.instance_of? Ax25::PathAgnosticImmutableFrame))
|
|
47
|
+
|
|
48
|
+
return self.path_agnostic_equality? other
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
public
|
|
52
|
+
def path_agnostic_equality?(other)
|
|
53
|
+
return false if (not other.respond_to? :source) ||
|
|
54
|
+
(not other.respond_to? :destination) ||
|
|
55
|
+
(not other.respond_to? :payload)
|
|
56
|
+
|
|
57
|
+
if (self.source.eql? other.source) && (self.destination.eql? other.destination) && (self.payload.eql? other.payload)
|
|
58
|
+
return true
|
|
59
|
+
else
|
|
60
|
+
return false
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
public
|
|
65
|
+
def path_agnostic_hash
|
|
66
|
+
return [self.source, self.destination, self.payload].hash
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
public
|
|
70
|
+
def ==(other)
|
|
71
|
+
return self.path_agnostic_equality? other
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
public
|
|
75
|
+
def eql?(other)
|
|
76
|
+
self.path_agnostic_eql? other
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
public
|
|
80
|
+
def hash
|
|
81
|
+
return self.path_agnostic_hash
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require 'ax25/frame/hop'
|
|
2
|
+
require 'ax25/frame/path'
|
|
3
|
+
|
|
4
|
+
module Ax25
|
|
5
|
+
public
|
|
6
|
+
class ImmutablePath
|
|
7
|
+
|
|
8
|
+
include Path
|
|
9
|
+
|
|
10
|
+
protected
|
|
11
|
+
def initialize(*hops)
|
|
12
|
+
@path_array = []
|
|
13
|
+
last_seen = true
|
|
14
|
+
hops.each do |hop|
|
|
15
|
+
raise ArgumentError.new("All arguments must not be nil: #{hops.to_s}") if (hop.nil?)
|
|
16
|
+
raise ArgumentError.new("All arguments must be of type Hop: #{hops.class.to_s}") if not hop.kind_of? Ax25::Hop
|
|
17
|
+
last_seen = false if not hop.seen?
|
|
18
|
+
@path_array << hop
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
@path_array.freeze
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
public
|
|
26
|
+
def self.from_raw(raw_path, strict_callsign: true, strict_ssid: true)
|
|
27
|
+
raise ArgumentError.new("raw_path can not be nil") if raw_path.nil?
|
|
28
|
+
|
|
29
|
+
if raw_path.kind_of? String
|
|
30
|
+
if raw_path.include? ","
|
|
31
|
+
raise ArgumentError.new("raw_path is too short") if raw_path.length < 3
|
|
32
|
+
raw_path = raw_path.split(",")
|
|
33
|
+
else
|
|
34
|
+
raise ArgumentError.new("raw_path is too short") if raw_path.length < 1
|
|
35
|
+
raw_path = [raw_path]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
raise ArgumentError.new("raw_path must be array-like") if not raw_path.respond_to? :[]
|
|
40
|
+
|
|
41
|
+
new_path_array = []
|
|
42
|
+
raw_path.each do |hop|
|
|
43
|
+
if hop.kind_of? Ax25::Hop
|
|
44
|
+
new_hop = hop
|
|
45
|
+
else
|
|
46
|
+
new_hop = Ax25::ImmutableHop.from_raw(hop, strict_callsign: strict_callsign, strict_ssid: strict_ssid)
|
|
47
|
+
end
|
|
48
|
+
new_path_array << new_hop
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
return Ax25::ImmutablePath.new(*new_path_array)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
public
|
|
55
|
+
def each
|
|
56
|
+
@path_array.each do |hop|
|
|
57
|
+
yield(hop)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
public
|
|
62
|
+
def [](idx)
|
|
63
|
+
return @path_array[idx].dup.freeze
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
public
|
|
67
|
+
def length
|
|
68
|
+
return @path_array.length
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
public
|
|
72
|
+
def empty?
|
|
73
|
+
return @path_array.empty?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
public
|
|
77
|
+
def eql?(other)
|
|
78
|
+
raise ArgumentError.new("The argument must be of type Path (or a child class).") if not other.kind_of? Path
|
|
79
|
+
|
|
80
|
+
return self == other
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
public
|
|
84
|
+
def ==(other)
|
|
85
|
+
return false if not other.respond_to? :[]
|
|
86
|
+
return false if not other.respond_to? :length
|
|
87
|
+
return false if not self.length.eql? other.length
|
|
88
|
+
|
|
89
|
+
(0..self.length - 1).each do |idx|
|
|
90
|
+
return false if not self[idx].eql? other[idx]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
return true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
public
|
|
97
|
+
def to_s
|
|
98
|
+
return @path_array.join(',')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
public
|
|
102
|
+
def to_string_array
|
|
103
|
+
return @path_array.map {|e| e.to_s}
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
public
|
|
107
|
+
def to_array
|
|
108
|
+
return @path_array.dup
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
public
|
|
112
|
+
def unseen_hops
|
|
113
|
+
@path_array.select { |hop| !hop.seen }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
public
|
|
117
|
+
def seen_hops
|
|
118
|
+
@path_array.select { |hop| hop.seen }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
public
|
|
122
|
+
EMPTY_PATH = ImmutablePath.new().freeze
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'abstractify'
|
|
2
|
+
|
|
3
|
+
module Ax25
|
|
4
|
+
##
|
|
5
|
+
# An APXP compatible APRS frame, excluding the path data entierly. The
|
|
6
|
+
# UnpathedFrame and all its fields are immutable.
|
|
7
|
+
#
|
|
8
|
+
# @example Comparing frames
|
|
9
|
+
# orig_frame = Ax25.new('WI2ARD-1', 'OMG', ['WIDE2-2' 'WIDE1-1'], 'payload goes here')
|
|
10
|
+
# diff_frame = Ax25.new('WI2ARD-1', 'OMG', ['WIDE2-2' 'WIDE1-1'], 'different payload')
|
|
11
|
+
# assert not orig_frame.eql? diff_frame
|
|
12
|
+
public
|
|
13
|
+
module Message
|
|
14
|
+
|
|
15
|
+
include Abstractify::Abstract
|
|
16
|
+
|
|
17
|
+
abstract :path_agnostic_eql?, :path_agnostic_equality?, :path_agnostic_hash, :==, :eql?, :hash
|
|
18
|
+
|
|
19
|
+
attr_accessor :source, :destination, :payload
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'ax25/frame/immutable_frame'
|
|
2
|
+
|
|
3
|
+
module Ax25
|
|
4
|
+
private
|
|
5
|
+
class PathAgnosticImmutableFrame < ImmutableFrame
|
|
6
|
+
protected
|
|
7
|
+
def initialize(source, destination, path, payload)
|
|
8
|
+
super(source, destination, path, payload)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
public
|
|
12
|
+
def eql?(other)
|
|
13
|
+
return self.path_agnostic_eql? other
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
public
|
|
17
|
+
def ==(other)
|
|
18
|
+
return self.path_agnostic_equality? other
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
public
|
|
22
|
+
def hash
|
|
23
|
+
return self.path_agnostic_hash
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
public
|
|
27
|
+
def path_agnostic_identity
|
|
28
|
+
return self
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/ax25/frame.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'ax25/frame/immutable_frame'
|
|
2
|
+
require 'ax25/frame/entity'
|
|
3
|
+
require 'ax25/frame/immutable_entity'
|
|
4
|
+
require 'ax25/frame/hop'
|
|
5
|
+
require 'ax25/frame/immutable_hop'
|
|
6
|
+
require 'ax25/frame/path'
|
|
7
|
+
require 'ax25/frame/immutable_path'
|
|
8
|
+
require 'ax25/frame/immutable_message'
|
|
9
|
+
require 'ax25/frame/path_agnostic_immutable_frame'
|
|
10
|
+
require 'ax25/frame/message'
|
|
11
|
+
require 'ax25/frame/frame'
|
data/lib/ax25.rb
ADDED