File Coverage

blib/lib/Protocol/ControlChannel.pm
Criterion Covered Total %
statement 35 35 100.0
branch 9 12 75.0
condition 2 3 66.6
subroutine 7 7 100.0
pod 3 3 100.0
total 56 60 93.3


line stmt bran cond sub pod time code
1             package Protocol::ControlChannel;
2             # ABSTRACT: trivial key/value binary protocol
3 2     2   54371 use strict;
  2         5  
  2         75  
4 2     2   10 use warnings;
  2         5  
  2         109  
5              
6             our $VERSION = '0.003';
7              
8             =head1 NAME
9              
10             Protocol::ControlChannel - simple binary protocol for exchanging key/value pairs
11              
12             =head1 VERSION
13              
14             version 0.003
15              
16             =head1 SYNOPSIS
17              
18             my $cc = Protocol::ControlChannel->new;
19             my $data = $cc->create_frame(key => 'value');
20             my $frame = $cc->extract_frame(\$data);
21             print 'Key: ' . $frame->{key} . ', value ' . $frame->{value} . "\n";
22              
23             =head1 DESCRIPTION
24              
25             This is the abstract implementation for a wire protocol which can be used to exchange
26             data between two endpoints, as a series of key/value pairs.
27              
28             Typical use-case is for passing events between remote processes/hosts.
29              
30             The data packet looks like this:
31              
32             =over 4
33              
34             =item * packet_length - 32-bit network-order unsigned int - excludes the length field itself
35              
36             =item * type - 16-bit network-order unsigned int - defines the type of this message, typically
37             describes the format of the value field
38              
39             =item * name_length - 16-bit network-order unsigned int - length of the name field
40              
41             =item * UTF-8 encoded name information - no null terminator
42              
43             =item * remaining bytes are value information, content depends on 'type'
44              
45             =back
46              
47             Types are currently 0 for 'plain text', 1 for 'Storable::nfreeze'. It's quite possible that
48             L support will be added soon.
49              
50             Usage is simple: instantiate, call methods, if anything returns undef then things have
51             gone wrong so you're advised to terminate that session. If you're exchanging packets
52             via UDP then this may not be so simple.
53              
54             Note that content is either Perl data structures (i.e. a reference), or byte
55             data. If you have a string, you'll need to pick a suitable encoding and
56             decoding - probably UTF-8.
57              
58             =cut
59              
60 2     2   2055 use Encode;
  2         51938  
  2         203  
61 2     2   2476 use Storable;
  2         8515  
  2         764  
62              
63             =head1 METHODS
64              
65             =cut
66              
67             =head2 new
68              
69             Instantiate an object. Not technically necessary, since all the other methods could
70             just as well be class methods for the moment, but in future this is likely to change.
71              
72             =cut
73              
74 2     2 1 184 sub new { my $class = shift; bless {}, $class }
  2         10  
75              
76             =head2 extract_frame
77              
78             Given a scalar ref to a byte buffer, will attempt to extract the next frame.
79              
80             If a full, valid frame was found, it will be decoded, removed from the buffer,
81             and returned as a hashref.
82              
83             If not, you get undef.
84              
85             If something went wrong, you'll probably get undef at the moment. In future this
86             may raise an exception.
87              
88             =cut
89              
90             sub extract_frame {
91 10     10 1 20 my $self = shift;
92 10         17 my $data = shift;
93 10         20 my $len = length $$data;
94 10 50       38 return undef unless $len > 4;
95              
96 10         62 my ($size) = unpack 'N1', substr $$data, 0, 4;
97 10         18 $size += 4;
98 10 50       56 return undef unless $len >= $size;
99              
100 10         299 my $frame = substr $$data, 0, $size, '';
101 10         55 my (undef, $type, $key) = unpack 'N1n1n/a*', $frame;
102 10 50 66     58 die "unknown type $type" unless $type == 0 || $type == 1;
103 10         362 substr $frame, 0, 8 + length($key), '';
104 10 100       53 $frame = Storable::thaw($frame) if $type == 1;
105             return +{
106 10         127 type => 'text',
107             key => Encode::decode('UTF-8' => $key),
108             value => $frame
109             };
110             }
111              
112             =head2 create_frame
113              
114             Creates a frame. Takes a key => value pair, and returns them in packet form.
115              
116             Key must be something that can be utf8-encoded - so 'a perl string', or an
117             object that stringifies sanely.
118              
119             =cut
120              
121             sub create_frame {
122 10     10 1 72249 my $self = shift;
123 10         25 my $k = shift;
124 10         2667 my $v = shift;
125 10 100       42 my $type = ref($v) ? 1 : 0;
126 10 100       43 $v = Storable::nfreeze($v) if ref $v;
127 10         212 my $packed = pack 'n1n/a*', $type, Encode::encode('UTF-8' => $k);
128 10         571 $packed .= $v;
129 10         74 return pack 'N/a*', $packed;
130             }
131              
132             1;
133              
134             __END__