agent-virtualize credit card surcharge percentage, RT#72961
[freeside.git] / FS / FS / addr_range.pm
1 package FS::addr_range;
2
3 use strict;
4 use base qw( FS::Record );
5 use vars qw( %status_desc
6              %status_allow_auto
7              %status_allow_use
8            );
9 use FS::Record qw( qsearch qsearchs );
10 use NetAddr::IP;
11
12 # metadata about status strings:
13 # how to describe them
14 %status_desc = (
15   ''            => '',
16   'unavailable' => 'unavailable',
17 );
18
19 # whether addresses in this range are available for use
20 %status_allow_use = (
21   ''            => 1,
22   'unavailable' => 0,
23 );
24
25 =head1 NAME
26
27 FS::addr_range - Object methods for addr_range records
28
29 =head1 SYNOPSIS
30
31   use FS::addr_range;
32
33   $record = new FS::addr_range \%hash;
34   $record = new FS::addr_range { 'column' => 'value' };
35
36   $error = $record->insert;
37
38   $error = $new_record->replace($old_record);
39
40   $error = $record->delete;
41
42   $error = $record->check;
43
44 =head1 DESCRIPTION
45
46 An FS::addr_range object represents a contiguous range of IP 
47 addresses assigned to a certain purpose.  Unlike L<FS::addr_block>,
48 this isn't a routing block; the range doesn't have to be aligned on 
49 a subnet boundary, and doesn't have a gateway or broadcast address.
50 It's just a range.
51
52 =over 4
53
54 =item rangenum - primary key
55
56 =item start - starting address of the range, as a dotted quad
57
58 =item length - number of addresses in the range, including start
59
60 =item status - what to do with the addresses in this range; currently can 
61 only be "unavailable", which makes the addresses unavailable for assignment 
62 to any kind of service.
63
64 =back
65
66 =head1 METHODS
67
68 =over 4
69
70 =item new HASHREF
71
72 Creates a new range.  To add the example to the database, see L<"insert">.
73
74 Note that this stores the hash reference, not a distinct copy of the hash it
75 points to.  You can ask the object for a copy with the I<hash> method.
76
77 =cut
78
79 sub table { 'addr_range'; }
80
81 =item insert
82
83 Adds this record to the database.  If there is an error, returns the error,
84 otherwise returns false.
85
86 =cut
87
88 # the insert method can be inherited from FS::Record
89
90 =item delete
91
92 Delete this record from the database.
93
94 =cut
95
96 # the delete method can be inherited from FS::Record
97
98 =item replace OLD_RECORD
99
100 Replaces the OLD_RECORD with this one in the database.  If there is an error,
101 returns the error, otherwise returns false.
102
103 =cut
104
105 # the replace method can be inherited from FS::Record
106
107 =item check
108
109 Checks all fields to make sure this is a valid example.  If there is
110 an error, returns the error, otherwise returns false.  Called by the insert
111 and replace methods.
112
113 =cut
114
115 # the check method should currently be supplied - FS::Record contains some
116 # data checking routines
117
118 sub check {
119   my $self = shift;
120
121   my $error = 
122     $self->ut_numbern('rangenum')
123     || $self->ut_ip('start')
124     || $self->ut_number('length')
125     || $self->ut_textn('status')
126   ;
127   return $error if $error;
128
129   $self->SUPER::check;
130 }
131
132 =item end [ IPADDR ]
133
134 Get/set the end IP address in the range.  This isn't actually part of the
135 record but it's convenient.
136
137 =cut
138
139 sub end {
140   my $self = shift;
141   # if there's no start address, just return nothing
142   my $start = NetAddr::IP->new($self->start, 0) or return '';
143
144   my $new = shift;
145   if ( $new ) {
146     my $end = NetAddr::IP->new($new, 0)
147       or die "bad end address $new";
148     if ( $end < $start ) {
149       $self->set('start', $end);
150       ($end, $start) = ($start, $end);
151     }
152     # fails if $end - $start > 2^31
153     # so don't do that
154     # (fixed in NetAddr::IP 4.050 but we can't rely on that, apparently)
155     $self->set('length', $end - $start + 1);
156     return $end->addr;
157   }
158   my $end = $start + $self->get('length') - 1;
159   $end->addr;
160 }
161
162 =item contains IPADDR
163
164 Checks whether IPADDR (a dotted-quad IPv4 address) is within the range.
165
166 =cut
167
168 sub contains {
169   my $self = shift;
170   my $addr = shift;
171   $addr = NetAddr::IP->new($addr, 0);
172   return 0 unless $addr;
173
174   my $start = NetAddr::IP->new($self->start, 0);
175
176   return ($addr >= $start and $addr < ( $start + $self->length) )
177           ? 1 : 0;
178
179
180 =item as_string
181
182 Returns a readable string showing the address range.
183
184 =cut
185
186 sub as_string {
187   my $self = shift;
188   my $start = NetAddr::IP->new($self->start, 0);
189   my $end   = $start + $self->length - 1;
190
191   if ( $self->length == 1 ) {
192     # then just the address
193     return $self->start;
194   } else { # we have to get tricksy
195     my @end_octets = split('\.', $end->addr);
196     $start = ($start->numeric)[0] + 0;
197     $end   = ($end->numeric)[0] + 0;
198     # which octets are different between start and end?
199     my $delta = $end ^ $start;
200     foreach (0xffffff, 0xffff, 0xff) {
201       if ( $delta <= $_ ) {
202       # then they are identical in the first 8/16/24 bits
203         shift @end_octets;
204       }
205     }
206     return $self->start . '-' . join('.', @end_octets);
207   }
208 }
209
210 =item desc
211
212 Returns a semi-friendly description of the block status.
213
214 =item allow_use
215
216 Returns true if addresses in this range can be used by services, etc.
217
218 =cut
219
220 sub desc {
221   my $self = shift;
222   $status_desc{ $self->status };
223 }
224
225 sub allow_use {
226   my $self = shift;
227   $status_allow_use{ $self->status };
228 }
229
230 =back
231
232 =head1 CLASS METHODS
233
234 =sub any_contains IPADDR
235
236 Returns all address ranges that contain IPADDR.
237
238 =cut
239
240 sub any_contains {
241   my $self = shift;
242   my $addr = shift;
243   return grep { $_->contains($addr) } qsearch('addr_range', {});
244 }
245
246 =head1 DEVELOPER NOTE
247
248 L<NetAddr::IP> objects have netmasks.  They also have overloaded operators
249 for addition and subtraction, but those have range limitations when comparing
250 addresses.  (An IPv4 address is effectively a uint32; the difference
251 between two IPv4 addresses is the same range, but signed.)  In later versions
252 of the library the C<bigint> method can be used as a workaround, but 
253 otherwise it's not safe to subtract two addresses that might differ in the
254 first bit of the first octet.
255
256 =head1 BUGS
257
258 =head1 SEE ALSO
259
260 L<FS::svc_IP_Mixin>, L<FS::Record>, schema.html from the base documentation.
261
262 =cut
263
264 1;
265