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.
@@ -0,0 +1,3 @@
1
+ require 'ax25/encoder/aprs_kiss'
2
+ require 'ax25/encoder/igate_tcp'
3
+ require 'ax25/encoder/encoder'
@@ -0,0 +1,10 @@
1
+ module Ax25
2
+ public
3
+ module Entity
4
+ include Abstractify::Abstract
5
+
6
+ abstract :decrement_ssid, :==, :eql?, :to_s
7
+
8
+ attr_reader :callsign, :ssid
9
+ end
10
+ end
@@ -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,11 @@
1
+ require 'ax25/frame/entity'
2
+
3
+ module Ax25
4
+ public
5
+ module Hop
6
+ include Ax25::Entity
7
+ include Abstractify::Abstract
8
+
9
+ abstract :toggle_seen, :decrement_ssid, :==, :eql?, :to_s, :seen?
10
+ end
11
+ 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,8 @@
1
+ module Ax25
2
+ public
3
+ module Path
4
+ include Abstractify::Abstract
5
+
6
+ abstract :each, :[], :length, :eql?, :==, :to_s, :to_string_array, :to_array, :empty?
7
+ end
8
+ 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
@@ -0,0 +1,3 @@
1
+ require 'ax25/encoder'
2
+ require 'ax25/frame'
3
+ require 'ax25/app_info'