File Coverage

blib/lib/Data/MEID.pm
Criterion Covered Total %
statement 126 127 99.2
branch 47 48 97.9
condition 11 12 91.6
subroutine 19 19 100.0
pod 11 11 100.0
total 214 217 98.6


line stmt bran cond sub pod time code
1             package Data::MEID;
2              
3 2     2   39146 use 5.006;
  2         6  
4 2     2   12 use strict;
  2         3  
  2         48  
5 2     2   8 use warnings;
  2         6  
  2         68  
6              
7 2     2   10 use Exporter;
  2         3  
  2         119  
8 2     2   8 use Carp;
  2         4  
  2         156  
9 2     2   1218 use Digest::SHA qw( sha1_hex );
  2         5900  
  2         144  
10 2     2   2060 use Math::BigInt;
  2         43941  
  2         14  
11              
12 2     2   28902 use vars qw(@ISA @EXPORT_OK);
  2         7  
  2         3078  
13              
14             # base class Exporter
15             our @ISA = qw(Exporter);
16              
17             our @EXPORT_OK = qw(
18             meid_to_hex
19             meid_to_dec
20             meid_is_valid
21             meid_is_hex
22             meid_is_dec
23             meid_check_digit
24             meid_to_pesn
25             manu_code_dec
26             manu_code_hex
27             serial_num_dec
28             serial_num_hex
29             );
30              
31              
32              
33             =head1 NAME
34              
35             Data::MEID - Convert, check, and inspect mobile MEID values.
36              
37             =head1 VERSION
38              
39             Version 0.06
40              
41             =cut
42              
43             our $VERSION = '0.06';
44              
45              
46             =head1 SYNOPSIS
47              
48             Quick summary of what the module does.
49              
50             use Data::MEID qw(
51             meid_to_hex
52             meid_to_dec
53             meid_is_valid
54             meid_is_hex
55             meid_is_dec
56             meid_check_digit
57             meid_to_pesn
58             manu_code_dec
59             manu_code_hex
60             serial_num_dec
61             serial_num_hex
62             );
63              
64             # convert Decimal MEID to Hex
65             my $hex_meid = meid_to_hex( '270113177609606898' );
66              
67             # convert Hex MEID to Decimal
68             my $decimal_meid = meid_to_dec( 'A10000009296F2' );
69              
70             # check to see if a MEID is a valid in Hex or Decimal form
71             return 1 if meid_is_valid( 'A10000009296F2' );
72              
73             # check to see if a MEID is in proper Hex form
74             print "Hex MEID Detected" if meid_is_hex( 'A10000009296F2' );
75              
76             # check to see if a MEID is in proper Decimal form
77             print "Decimal MEID Detected" if meid_is_dec( '270113177609606898' );
78              
79             # calculate the MEID check digit using 3GPP2 X.S0008-0 v3.0
80             my $hex_cd = meid_check_digit( 'A10000009296F2' );
81             my $dec_cd = meid_check_digit( '270113177609606898' );
82              
83             # calculate Pseudo ESN
84             my $pseudo_esn = meid_to_pesn( 'A10000009296F2' );
85              
86             # get Decimal manufacturers code
87             my $manufacturer_code = manu_code_dec( 'A10000009296F2' );
88              
89             # get Hex manufacturers code
90             my $manufacturers_code = manu_code_hex( 'A10000009296F2' );
91              
92             # get Decimal serial number
93             my $serial_number = serial_num_dec( 'A10000009296F2' );
94              
95             # get Hex serial number
96             my $serial_number = serial_num_hex( 'A10000009296F2' );
97              
98             =head1 EXPORT
99              
100             meid_to_hex
101             meid_to_dec
102             meid_is_valid
103             meid_is_hex
104             meid_is_dec
105             meid_check_digit
106             meid_to_pesn
107             manu_code_dec
108             manu_code_hex
109             serial_num_dec
110             serial_num_hex
111              
112             =head1 SUBROUTINES/METHODS
113              
114             =head2 meid_to_hex
115              
116             Convert a Decimal MEID to Hex. If an invalid Decimal MEID is used, it will throw
117             a warning and return 0.
118              
119             =cut
120              
121             sub meid_to_hex {
122              
123 11     11 1 4855 my $meid = shift;
124              
125 11 100       22 unless ( meid_is_dec($meid) ) {
126 6 100       11 $meid = 'undef' if not defined $meid;
127 6         74 carp "invalid MEID ($meid) used for decimal to hex conversion 'meid_to_hex";
128 6         2829 return 0;
129             }
130              
131              
132             # take first 10 digits, convert to hex. Take next 8 digits and convert to hex
133 5         14 $meid =~ m/^(.{10})(.{8})$/;
134              
135 5         25 my $manufacturer = sprintf("%08x", $1);
136 5         14 my $serial = sprintf("%06x", $2);
137              
138 5         22 return $manufacturer . $serial;
139              
140             }
141              
142             =head2 meid_to_dec
143              
144             Convert a Hex MEID to Decimal. If an invalid Hex MEID is used, it will throw
145             a warning and return 0.
146              
147             =cut
148              
149             sub meid_to_dec {
150              
151 10     10 1 5093 my $meid = shift;
152              
153 10 100       21 unless ( meid_is_hex($meid) ) {
154 6 100       9 $meid = 'undef' if not defined $meid;
155 6         64 carp "invalid MEID ($meid) used for decimal to hex conversion 'meid_to_dec";
156 6         2585 return 0;
157             }
158              
159             # take first 8 digits, convert to hex, then next 6
160 4         8 $meid =~ m/^(.{8})(.{6})$/;
161              
162 4         33 my $n = Math::BigInt->new("0x$1");
163 4         736 my $manufacturer = sprintf("%010s", $n->bstr );
164 4         158 my $serial = sprintf("%08d" , hex $2);
165              
166 4         24 return $manufacturer . $serial;
167              
168             }
169              
170             =head2 meid_is_valid
171              
172             Check to see if a MEID is valid, either Decimal or Hex. If it
173             looks like a Decimal MEID, it returns "dec". If it looks like a Hex ESN it
174             returns "hex". If it doesn't match either it returns 0
175              
176             =cut
177              
178             sub meid_is_valid {
179              
180 28     28 1 4761 my $meid = shift;
181              
182 28 100       53 return 'hex' if meid_is_hex($meid);
183 24 100       51 return 'dec' if meid_is_dec($meid);
184 20         74 return 0;
185              
186             }
187              
188             =head2 meid_is_hex
189              
190             Return 1 if MEID looks like valid HEX MEID: RRXXXXXXZZZZZZ. Otherwise return 0.
191             RR = A0 ~ FF, XXXXXX = 000000 ~ FFFFFF, ZZZZZZ = 000000 ~ FFFFFF
192              
193             =cut
194              
195             sub meid_is_hex {
196              
197 97     97 1 5947 my $meid = shift;
198              
199 97 100 100     538 if ( defined $meid and $meid =~ /^[0-9a-f]{14}$/i ) {
200              
201             # get RR code, first two digits and make sure they're A0 thru FF
202 22         37 $meid =~ m/^([0-9a-f]{2})/i;
203 22         39 my $rr_code = $1;
204              
205             # we're obviously not above FF, but make sure we're not bleow A0
206 22 100       84 return 0 if hex($rr_code) < hex('A0');
207              
208 13         69 return 1;
209              
210             } else {
211              
212 75         198 return 0;
213              
214             }
215              
216             }
217              
218             =head2 meid_is_dec
219              
220             Return 1 if MEID looks like valid Decimal MEID. Otherwise return 0.
221              
222             =cut
223              
224             sub meid_is_dec {
225              
226 95     95 1 5606 my $meid = shift;
227              
228 95 100 100     463 if ( defined $meid and $meid =~ /^\d{18}$/ ) {
229              
230             # get Manufacturer code, first 10 digits
231             # get Serial Number, last 8 digits
232 39         90 $meid =~ m/^(\d{10})(\d{8})$/;
233 39         82 my $reserved_plus_manu_code = $1;
234 39         57 my $serial = $2;
235              
236             # make sure manu code is in this range 2_684_354_560 ~ 2_952_790_015
237 39 100 100     229 return 0 if not (
238             $reserved_plus_manu_code >= 2_684_354_560
239             and $reserved_plus_manu_code <= 2_952_790_015
240             );
241              
242             # make sure serial number is in this range 00_000_000 ~ 16_777_215
243 23 100 66     125 return 0 if not ( $serial >= 0 and $serial <= 16_777_215 );
244              
245             # if we get here, we're all good
246 15         63 return 1;
247              
248             } else {
249              
250 56         152 return 0;
251              
252             }
253              
254             }
255              
256             =head2 meid_check_digit
257              
258             Calculates the MEID check digit value as described in 3GPP2 X.S0008-0 v3.0.
259             For testing purposes, per the spec, AF 01 23 45 0A BC DE has a check digit of 'C'.
260             MEID 293608736500703710 has a check digit of '0'.
261              
262             If a Hex MEID is passed, a Hex check digit is returned.
263             If a Decimal MEID is passed, a Decimal check digit is returned.
264              
265             =cut
266              
267             sub meid_check_digit {
268              
269 4     4 1 482 my $meid = shift;
270              
271 4         11 my $wanted = meid_is_valid($meid);
272              
273 4 100       10 if ( $wanted eq 'hex' ) {
274              
275             # example MEID: AF 01 23 45 0A BC DE
276             # step 1, double the value of alternating digits beginning with the first
277             # right-hand digit (low order). F 1 3 5 A C E to 1E 2 6 A 14 18 1C
278 2         9 my ( @right_digits ) = $meid =~ /^\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)$/;
279 2         9 my ( @left_digits) = $meid =~ /^(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w$/;
280              
281 2         4 foreach my $digit ( @right_digits ) {
282 14         27 $digit = sprintf( "%x", hex($digit) * 2 );
283             }
284              
285             # step 2, add individual digits comprising the products obtained in step 1 to each
286             # of the unaffected digits in the origional number.
287 2         11 @right_digits = split //, ( join "", @right_digits );
288 2         8 my @all_digits = ( @left_digits, @right_digits);
289              
290 2         3 my $digit_sum = 0;
291 2         3 foreach my $digit ( @all_digits ) {
292 32         29 $digit_sum = $digit_sum + hex($digit);
293             }
294              
295             # convert sum total to hex;
296 2         4 $digit_sum = sprintf("%x", $digit_sum);
297              
298             # calculate check digit, if last digit is zero, check digit is zero
299 2 50       6 if ( $digit_sum =~ /0$/ ) {
300              
301 0         0 return 0;
302              
303             } else {
304              
305 2         3 my $next_higher = $digit_sum;
306             # increment number to next highest ending in zero
307 2         6 until ( $next_higher =~ /0$/ ) {
308 27         54 $next_higher = sprintf( "%x", hex($next_higher) + 1 );
309             }
310              
311             # subtract the $digit_sum from $next_higher and return it as the check digit
312 2         17 return sprintf( "%x", hex($next_higher) - hex($digit_sum) );
313              
314             }
315              
316             } else {
317              
318             # algorythm works just the same for decimal, just uses decimal math
319 2         14 my ( @right_digits ) = $meid =~ /^\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)$/;
320 2         12 my ( @left_digits) = $meid =~ /^(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w(\w)\w$/;
321              
322 2         6 foreach my $digit ( @right_digits ) {
323 18         20 $digit = $digit * 2;
324             }
325              
326             # step 2, add individual digits comprising the products obtained in step 1 to each
327             # of the unaffected digits in the origional number.
328 2         13 @right_digits = split //, ( join "", @right_digits );
329 2         9 my @all_digits = ( @left_digits, @right_digits);
330              
331 2         3 my $digit_sum = 0;
332 2         3 foreach my $digit ( @all_digits ) {
333 47         42 $digit_sum = $digit_sum + $digit;
334             }
335              
336             # calculate check digit, if last digit is zero, check digit is zero
337 2 100       10 if ( $digit_sum =~ /0$/ ) {
338              
339 1         7 return 0;
340              
341             } else {
342              
343 1         2 my $next_higher = $digit_sum;
344             # increment number to next highest ending in zero
345 1         5 until ( $next_higher =~ /0$/ ) {
346 4         8 $next_higher = $next_higher + 1 ;
347             }
348              
349             # subtract the $digit_sum from $next_higher and return it as the check digit
350 1         8 return ( $next_higher - $digit_sum );
351              
352             }
353              
354             }
355              
356             }
357              
358             =head2 meid_to_pesn
359              
360             Calculate the pESN ( Pseudo ESN ) from Decimal or Hex MEID.
361              
362             Output is in Hex form. Use Data::ESN if Decimal ESN's are needed.
363              
364             =cut
365              
366             sub meid_to_pesn {
367              
368 12     12 1 6262 my $meid = shift;
369              
370             # dump out if we see a bad MEID
371 12 100       34 return 0 if not meid_is_valid($meid);
372              
373             # convert to hex if not hex
374 2 100       6 $meid = meid_to_hex($meid) if meid_is_dec($meid);
375              
376 2         23 $meid = pack('H*', $meid );
377 2         31 my $meid_hash = sha1_hex($meid);
378              
379             # take last 6 digits of hash
380 2         7 $meid_hash = substr $meid_hash, -6;
381              
382             # prepend 80 to 6 digit hash
383 2         19 return "80$meid_hash";
384              
385             }
386              
387             =head2 manu_code_dec
388              
389             Return the manufacturer code in Decimal form from the MEID. If we have
390             a MEID that looks bad, then return 0.
391              
392             =cut
393              
394             sub manu_code_dec {
395 12     12 1 4952 my $meid = shift;
396              
397             # if code is hex, covnert to decimal and get the first 10 digits
398 12 100       27 if ( meid_is_hex($meid) ) {
    100          
399              
400 1         5 $meid = meid_to_dec($meid);
401 1         7 return substr $meid, 0, 10;
402              
403             # if the code is decimal, return the first 10 digits
404             } elsif ( meid_is_dec($meid) ) {
405              
406 1         11 return substr $meid, 0, 10;
407              
408             # if none if these match, we probably don't have a good MEID and return 0
409             } else {
410              
411 10         33 return 0;
412              
413             }
414              
415             }
416              
417             =head2 manu_code_hex
418              
419             Return the manufacturer code in Hex form from the MEID. If we have
420             a MEID that looks bad, then return 0.
421              
422             =cut
423              
424             sub manu_code_hex {
425 12     12 1 5537 my $meid = shift;
426              
427             # if code is dec, covnert to hex and get 8 digits after the frist two
428 12 100       27 if ( meid_is_dec($meid) ) {
    100          
429              
430 1         4 $meid = meid_to_hex($meid);
431 1         7 return substr $meid, 2, 6;
432              
433             # if the code is decimal, return 8 digits after the frist two
434             } elsif ( meid_is_hex($meid) ) {
435              
436 1         7 return substr $meid, 2, 6;
437              
438             # if none if these match, we probably don't have a good MEID and return 0
439             } else {
440              
441 10         35 return 0;
442              
443             }
444              
445             }
446              
447             =head2 serial_num_dec
448              
449             Return the serial number in Decimal form from the MEID. If we have
450             a MEID that looks bad, then return 0.
451              
452             =cut
453              
454             sub serial_num_dec {
455              
456 12     12 1 5750 my $meid = shift;
457              
458             # if code is hex, covnert to decimal and get the last 8 digits
459 12 100       25 if ( meid_is_hex($meid) ) {
    100          
460              
461 1         2 $meid = meid_to_dec($meid);
462 1         9 return substr $meid, -8;
463              
464             # if the code is decimal, return last 8 digits
465             } elsif ( meid_is_dec($meid) ) {
466              
467 1         10 return substr $meid, -8;
468              
469             # if none if these match, we probably don't have a good MEID and return 0
470             } else {
471              
472 10         32 return 0;
473              
474             }
475              
476             }
477              
478             =head2 serial_num_hex
479              
480             Return the serial number in Hex form from the MEID. If we have
481             a MEID that looks bad, then return 0.
482              
483             =cut
484              
485             sub serial_num_hex {
486              
487 12     12 1 6124 my $meid = shift;
488              
489             # if code is decimal, covnert to hex and get the last 6 digits
490 12 100       29 if ( meid_is_dec($meid) ) {
    100          
491              
492 1         3 $meid = meid_to_hex($meid);
493 1         7 return substr $meid, -6;
494              
495             # if the code is decimal, return last 6 digits
496             } elsif ( meid_is_hex($meid) ) {
497              
498 1         6 return substr $meid, -6;
499              
500             # if none if these match, we probably don't have a good MEID and return 0
501             } else {
502              
503 10         33 return 0;
504              
505             }
506              
507             }
508              
509             =head1 AUTHOR
510              
511             Adam Wohld, C<< >>
512              
513             =head1 BUGS
514              
515             Please report any bugs or feature requests to C, or through
516             the web interface at L. I will be notified, and then you'll
517             automatically be notified of progress on your bug as I make changes.
518              
519              
520              
521              
522             =head1 SUPPORT
523              
524             You can find documentation for this module with the perldoc command.
525              
526             perldoc Data::MEID
527              
528              
529             You can also look for information at:
530              
531             =over 4
532              
533             =item * RT: CPAN's request tracker (report bugs here)
534              
535             L
536              
537             =item * AnnoCPAN: Annotated CPAN documentation
538              
539             L
540              
541             =item * CPAN Ratings
542              
543             L
544              
545             =item * Search CPAN
546              
547             L
548              
549             =back
550              
551              
552             =head1 ACKNOWLEDGEMENTS
553              
554              
555             =head1 LICENSE AND COPYRIGHT
556              
557             Copyright 2012 Adam Wohld.
558              
559             This program is free software; you can redistribute it and/or modify it
560             under the terms of either: the GNU General Public License as published
561             by the Free Software Foundation; or the Artistic License.
562              
563             See http://dev.perl.org/licenses/ for more information.
564              
565              
566             =cut
567              
568             1; # End of Data::MEID