Separating Functional Behaviour and Performance Constraints: AspectOriented Specification 1
1
2
Lynne Blair , Gordon Blair , Anders Andersen 1
Computing Department, Lancaster University, Bailrigg, Lancaster, LA1 4YR (Report No. MPG-98-07) 2 NORUT IT, Tromsø Science Park, N-9005 Tromsø, Norway e-mail:
[email protected], telephone: +44 (1524) 65201 x94899
Abstract This paper addresses the relationship between functional (qualitative) behaviour and the more quantitative nature of performance constraints. We propose an approach based on aspect-oriented specification, which exploits the diversity and power of existing formal specification languages. Importantly, we illustrate our approach by specifying an example of an adaptive algorithm. The chosen example is characteristic of QoS management functions in the field of distributed multimedia systems.
1. Introduction Process algebra such as LOTOS [Bolognesi88], CCS [Milner89], CSP [Hoare85] and pi-calculus [Milner92] have been around for many years now, and have successfully been used to analyse many systems (e.g. [Thomas94], [Kirkwood95], [Lewerentz95]). However, it has been shown that, whilst they are very good at specifying functional (qualitative) behaviour, the specification of more quantitative aspects (such as time and probabilities) is not possible. For example, in distributed multimedia systems, quality of service parameters such as throughput, latency, jitter and percentage error loss require realtime and probabilistic extensions to the above process algebra [Blair98a]. Many such extensions have been proposed, for example, Enhanced Timed LOTOS (ET-LOTOS) [Leonard97] and Timed Probabilistic CCS (TPCCS) [Hansson91]. For the modelling of stochastic systems (where events occur at a time determined by a random variable), stochastic process algebra have also been proposed, for example, Performance Evaluation of Process Algebra (PEPA) [Gilmore94] and Stochastic Process Algebra for Discrete Event Simulation ( SPADES) [Argenio98]. Extensions such as these unavoidably increase the complexity of the semantics for the process algebra. Furthermore, for stochastic process algebra, to maintain a finite and tractable semantic model it is usually necessary to constrain the expressiveness of the language (for example, by considering only exponential, rather than general, distributions). [Argenio98] presents a nice technique to get round this problem in SPADES. The technique is based on a separation of concerns at the syntactic and semantic level between timers set from a stochastic distribution and the enabling of events once these timers (which count down) have expired. Although this approach results in a slightly more complex syntax, it is claimed it provides a straightforward expansion law and more expressive power than alternative approaches. However, all of these extensions, whether for timed, probabilistic and/ or stochastic systems, have one thing in common. That is, the qualitative (functional) behaviour of the system and the quantitative behaviour are entwined, or tangled, together in the specification. Whilst this may be fine for fairly simple specifications, the entwining of different aspects makes larger and more complex specifications more difficult to develop, understand and maintain. The observation presented in this paper is that many quantitative issues can intuitively be considered distinct from the overall functional behaviour. Compare this with the way a constraint-oriented
specification style can be used to separate important constraints from the rest of the (functional) specification. For example, suppose a typical day consists of the following (semi-colons denote sequential composition): Day := get_up; eat_breakfast; go_to_work; read_email; ... ; eat_lunch; give_lecture; ... ; go_home; eat_dinner; watch_TV; go_to_bed; ...
Suppose now that a simple quantitative constraint states that your time at work should be 8 hours. This could be expressed simply as a constraint in parallel with the above behaviour. For example, assuming that the unit of time is hours: ConstrainedDay := go_to_work; go_home {8}; ... |[ go_to_work, go_home ]| Day
Here, the first line gives the constraint in a timed LOTOS-like notation, i.e. that go_home occurs 8 hours after go_to_work. This constraint is then placed in parallel with the behaviour Day, synchronising on the required two events (referred to inside the |[ ... ]| operator). Similar time constraints might be considered for the length of time awake/ asleep, the time between meals, etc. In a constraint-oriented specification style, as illustrated above, it is assumed that the constraints are written in the same language as the overall behaviour. When constraints are taken from a wide range of timed, probabilistic and stochastic systems, this means that the language for “ normal” (functional) behaviour carries a lot of unnecessary baggage, assuming, that is, that one language can in fact specify all the required constraints and the overall behaviour. Much research has focused on trying to find one all-encompassing formal language. However, this is often in conflict with the very nature of formal specification: clean and elegant semantics, a nice (yet often small) set of powerful operators and ease of use (by non-theoreticians). In this paper, we explore an approach which exploits the diversity of existing languages without seeking to unify them. Our approach allows different languages to be used for different aspects of a specification. Each aspect can be written in a language particularly suited to the properties that need specifying and analysing in that aspect. This is inspired by the work on aspect-oriented programming [Kiczales97]. A brief overview of this work will be given in section 2 below. The rest of the paper is structured as follows. Section 3 will address the idea of aspect-oriented specification (covering the choice of aspects and the important issue of aspect-weaving, or composition, of several aspects). Section 4 presents an example of aspect-oriented specification for a video stream. Section 5 considers the validation of properties using this technique whilst section 6 considers the synthesis of monitors and controllers. Sections 7 and 8 will then present future work and related work respectively and, finally, our conclusions are drawn in section 9. The paper is intentionally presented in a fairly informal style. In particular, we do not present the actual algorithms for composition (§3.3.) and model checking (§4.1.) since these have been published elsewhere. The key point of the paper is the presentation of an aspect-oriented specification technique, which exploits the diversity of existing specification languages, rather than being restricted to just one.
2. Aspect-oriented programming Before considering formal issues, we will first give a brief summary of Kiczales’ work since this is of paramount importance to our approach. In [Kiczales97], the same dilemma as that mentioned above is addressed from a programming perspective, i.e. the tangling (intertwining) of different aspects of a computer system. He argues that this tangling leads to complex systems that are difficult to develop and maintain. Whilst object-oriented programming (OOP) techniques have gone some way in helping programmers develop large systems, it seems that there are many problems where neither OOP
2
techniques, nor more traditional procedural techniques, are sufficient [Kiczales97]. The reason for this is argued to be aspects which cross-cut a system’s basic functionality. Such aspects “ tend not to be units of the system’s functional decomposition, but rather properties that affect the performance or semantics of the components in systemic ways” . To overcome such problems, Kiczales proposes a new programming paradigm, called aspect-oriented programming. This technique involves a clean separation between different aspects of a system, for example, communication, synchronisation (of concurrent objects), error and failure handling, security, memory access patterns, etc. Note that not all of these aspects need be considered for every system: different aspects may be required for different systems. Importantly, there is no requirement that different aspects of a program are written in the same language. Instead, each aspect is expressed in a “ separate and natural form” . The resulting descriptions are then automatically woven together using a tool called an aspect-weaver.
3. Aspect-oriented specification 3.1. Different aspects of a specification In this paper, we propose aspect-oriented specification, that is, maintaining a separation between different aspects of a specification. The question is, what are the aspects? As in the programming world, there are many possible interpretations of this for different applications. One example is in the Aster project at IRISA (Rennes, France) where a distinction is drawn between functional properties, interaction properties and classes of non-functional properties (see, for example, [Issarny96] and [Issarny98]). It is claimed that the first two types of property can be specified using existing specification techniques, in particular through their use of an Architecture Description Language, WRIGHT [Allen97]. Examples of the latter class of non-functional properties are timeliness, dependability (fault-tolerance), security, efficiency and concurrency control for which they use a first-order temporal logic (they acknowledge real-time extensions are necessary, particularly for timeliness properties). Whilst we acknowledge that other aspects such as those mentioned above are possible, and often desirable, for this paper we keep things simple and only draw a distinction between the qualitative (functional) behaviour and the quantitative aspects (performance constraints).
3.2. Qualitative versus quantitative aspects As in [Kiczales97], we do not require different aspects to be written in the same language. Consequently, we will use the process algebra LOTOS for an abstract specification of functional behaviour. For the quantitative aspects (performance constraints), we have evaluated real-time and probabilistic temporal logic in our previous research (see [Blair98a] and [Lakas96a]). We will continue with our use of temporal logic in this paper. To clarify terminology, by performance constraints, we mean characteristics of the actual system that cannot be defined purely in functional terms. Examples of such constraints include timeouts, the assumed performance of components (such as a data source transmitting at a rate of 25 packets per second, a data sink taking at least 5ms to process data, or a communication channel imposing a minimum latency of 80ms on each transmission, or losing 5% of all transmissions), or the stochastic behaviour of a component (such as the inter-arrival times of data packets arriving at a queue). Note that these performance constraints serve a different purpose to requirements, which are (usually) applied across several components or the entire specification. An equivalent interpretation of performance constraints is that they are “ bottom-up” constraints (derived from actual system components) whereas requirements are “ top-down” constraints (see figure 1). We will return to the issue of requirements later in the paper.
3
Application Performance constraints
Requirements System / Network
Figure 1. Requirements versus performance constraints
3.3. Aspect-weaving In aspect-oriented programming, the aspect-weaver initially generates a data flow graph from the component program (equivalent to our functional specification). Each aspect description is then considered in turn and nodes and transitions of the graph are edited where necessary. Finally, the modified graph is used to generate a program of the “ tangled” code (in C or Java). The question is, what is an equivalent process for aspect-oriented specification? In the world of specification, the crucial feature which underpins everything else is the semantics: they must be clear, concise and, above all, unambiguous. For our aspect-oriented specification technique, we must deal with the semantics of the composition of two or more aspect specifications. In our approach, the result of this composition is a global model (discussed in more detail below). The way in which semantics are assigned to the composition will depend not only on the languages used to specify each of the aspects (and their semantic models), but also on the intended use of the composition of specifications (e.g. what do you want to be able to prove at the end?). For example, work described in [Zave93] has considered the translation of various different specification techniques (e.g. Z, automata and formal grammars) into a first-order predicate logic and has focused on establishing the consistency of partial specifications. Our previous work (described in [Blair98a]) has also addressed the issue of consistency (between LOTOS and a temporal logic specification), but has additionally considered the proof of requirements over the composed specification, using model-checking techniques. Our use of LOTOS and temporal logic for different aspects of a specification led us to a different translation to that mentioned above. By using LOTOS, the availability of good toolsets such as [Lite98] makes it a relatively straightforward task to generate a labelled transition system from a LOTOS specification (assuming some restrictions on operators and data types definitions are obeyed). It is also fairly straightforward to map a temporal logic formula onto a labelled transition system. Algorithms for this can be found in [Blair98a] for a real-time temporal logic (QTL) and in [Lakas96a] for a stochastic real-time temporal logic (SQTL). Note that in our more recent work, we call the labelled transition system derived from the temporal logic formulae an event scheduler. Intuitively, the aim of this event scheduler is to schedule the functional behaviour (events) according to the specified performance constraints. To achieve this, every enabled action in the first labelled transition system (derived from LOTOS) is submitted to the event scheduler. The event scheduler then decides, according to any timing constraints (and/or stochastic constraints for the case of SQTL), if this action is allowed to happen, and, if so, when it will happen. A formal description of this algorithm is presented in [Lakas96a]. The labelled transition system and the event scheduler can then be composed together to form the required global model (also a labelled transition system), the behaviour of which satisfies both the LOTOS specification and the performance constraints. These composition rules can also be found in [Lakas96a]. It is this model that provides us with semantics for the composed specification. The process of generating our global model is summarised in figure 2.
4
Functional Specification
Performance constraints
LOTOS
Temporal logic
Labelled transition system
Event scheduler
Global model
Figure 2: Generating our global model
4. Validation and performance modelling Having generated our global model, there are various ways we could proceed (see figure 3). One way would be to return to the issue of user requirements, and to carry out a formal proof that these requirements satisfy our specification, for example based on model-checking techniques. A second possibility would be to do some performance analysis on our global model. These two possibilities are discussed below. A third possibility, based on the synthesis of monitors and controllers, is the subject of ongoing work and will also be discussed below. Requirements Temporal logic Event scheduler
Model Checking
Global model
Performance Analysis
Synthesis of monitor or controller
Real (or prototype) system
FORMAL
spectrum of techniques
PRACTICAL
Figure 3: Validation and synthesis possibilities from the global model
4.1. Formal proof of requirements Using our global model, we can formally check requirements for the system by applying modelchecking techniques. Such techniques involve checking that all infinite computations of the global model, M, satisfy a real-time temporal logic formula ϕ. Two solutions to this problem have been developed and presented previously. Firstly, a tableau-based technique is presented in [Blair98a] which checks that the inverse problem is not satisfiable, i.e. that no model in M satisfies ¬ϕ. A second approach, described in [Lakas96a], is based on an automata-theoretic technique. This technique involves checking the emptiness of a timed Muller automata generated from the cross product of two automata
5
representing M and ¬ϕ respectively, i.e. checking that the set of computations violating ϕ is empty. A model-checking technique for probabilistic real-time systems, also based on Muller automata, can be found in [Alur91]. Note that there are several well-developed tools that take timed automata (of a slightly different format to our global model) and check, automatically, whether they satisfy a temporal logic formula (for example, HYTECH [Henzinger97], KRONOS [Daws96] and UPPAAL [Bengtsson96]). However, there are problems with model checking for certain classes of temporal logic formulae. For example, UPPAAL currently only supports the analysis of reachability formulae and places certain other constraints on formulae (e.g. with respect to data sets and data passing), whilst [Alur93] explains conditions under which the reachability problem will not terminate for hybrid automata (as used in HYTECH).
4.2. Performance analysis Alternatives to model checking are provided by performance analysis techniques, of which there are many different techniques. One class of techniques concerns reachability analysis, that is techniques that traverse the state space of the system, and analyse it to identify any undesirable behaviour. A reachability algorithm generally tries to generate and analyse all states of a system reachable from a given start state. However, for large systems this may not be computationally possible. Hence algorithms with varying coverage of the state space have been proposed. For example, the SPIN tool described in [Holzmann91] can perform a full state space search (for small systems), a controlled partial search (to try and optimise the quality of analysis where a full search is infeasible), and random simulation (for larger systems where even a partial search is infeasible). It was a technique based on this latter class of algorithms that we developed in our previous research. This work was concerned with the development of a tool which takes, as input, an event scheduler generated from the stochastic real-time logic SQTL (or equally, our global model). This tool permits the simulation of the event scheduler (where appropriate, using random numbers from the distributions associated with SQTL formulae to determine the delays). As it stands, the tool can animate the execution of a model and allow either step-by-step analysis or continuous execution. It is also possible to perform some simple statistical calculations over the model (e.g. calculating the mean and variance of particular delays and calculating the probability of being in a given state). The algorithms on which this tool is based can be found in [Lakas96a] and [Lakas96b]. Alternative methods of performance analysis include queueing models [Bertsekas92] and stochastic Petri nets [Marsan95]. A further method of performance analysis is through stochastic extensions to process algebra, such as PEPA (based on Markovian processes) [Gilmore94] and TIPP (based on generalised stochastic processes) [Hermanns94].
4.3. Synthesis of monitors and controllers A third use for event schedulers occurs once you step out of the “ formal” world into a more “ practical” world. This is being addressed as part of the ongoing work in the V-QoS project at Lancaster University. Our aim in this work is to be able to take our “ formal” world (represented by the global model and our requirements) and use it to synthesise monitors and controllers for our “ real” world. These monitors or controllers have the ability to interact with an actual system (or prototype) through event mechanisms (as provided by component models such as JavaBeans). The run-time behaviour of the system can then be directly monitored, for example by observing behaviour to check a latency bound is not exceeded (perhaps with the option of reporting an error if something goes wrong). In contrast, controllers have the means to actively alter the behaviour of a system, for example if throughput drops then take an action to increase the send rate or switch to a more reliable communication channel.
6
Our current work is addressing the interface between these monitors and controllers and a prototype system developed using the reflective programming language Python. These components are written using an automata representation which is interpreted and used to monitor and/or adapt the run-time behaviour of the system as required. The reflective features of Python permit monitors and controllers to be inserted into the execution environment via a meta-interface (see [Blair98b]).
5. An example: a video stream and controller We conclude the main sections of this paper with an example to illustrate the use of an aspectoriented specification technique. We will model the functional and non-functional aspects of a video stream separately. The structure of the stream as shown in figure 4 reflects the structure of an implementation currently being undertaken (initially for audio).
Interfaces for monitoring & control SOURCE
SINK
BUFFER
Key: = object = interface
BINDING C
Stub
UDP connection
Stub
D
C = compressor (or encoder) D = decompressor (or decoder)
Figure 4: A stream illustrated as a connection of objects
5.1. A video stream A stream consists of a flow of data of a single continuous media type (such as audio or video). In this example, we have a source object sending data to a sink object by means of what we will assume is a video stream. As illustrated in the diagram, this stream encapsulates compression/ decompression objects, a binding object (using a simple UDP1 connection) and a buffer object for the sink. As an extension, if required, communication could be made secure by incorporating encoding and decoding objects into the stream. A LOTOS specification of this stream is given in Appendix A; this provides us with a description of the functional (qualitative) behaviour.
5.2. Non-functional (quantitative) behaviour Within the field of distributed multimedia systems, non-functional properties are often referred to as quality of service properties [Blair97][Issarny98]. An important issue related to such properties is quality of service management, that is, the necessary supervision and control to ensure that the desired quality of service properties are attained and, in the case of continuous media, sustained [Blair97]. Examples of static QoS management functions are admission control, QoS negotiation and resource reservation whilst
1
A UDP connection means that messages may be lost and/ or re-ordered during transmission. Importantly, no “ book-keeping” is undertaken by the protocol, for example regarding any lost/ re-ordered messages; all such activities are left to the system programmer. This avoids the overhead of retransmissions typical of RPC-based protocols such as TCP. Alternatively, an RTP connection could be used to provide real-time functionality.
7
examples of dynamic QoS management functions are QoS monitoring, re-negotiation, policing and maintenance. In this example, we will first specify some QoS properties and then consider an example of (dynamic) QoS management. Suppose we desire the stream to have the following real-time behaviour (the numbers are kept consistent with those in [Blair94]: 1. The data source generates frames at a rate of 30 frames per second (i.e. send every 33ms). 2. After generation, 5ms elapses before the frame is transmitted. 3. Successfully transmitted frames arrive at a buffer between 15ms and 20ms after transmission. 4. The data sink takes 5ms to process a frame. These properties can be specified in a real-time temporal logic such as QTL [Blair98a] as follows2:
φ1:
33
( generate ?fr:video →
=t(
t=0
¬(generate !fr) U=33-t generate ?next:video ) )
Whenever a frame is generated, no further frame will be generated until 33ms have passed, at which time the next frame will be generated. φ2: ( generate ?fr:video → =5 src_send !fr ) Whenever a frame is generated, this frame is transmitted in some future state 5ms later. 20
φ3:
( buf_put ?fr:video →
( t=15
=t
src_send !fr ) )
Whenever a buf_put occurs, between 15 and 20ms ago (inclusive) the corresponding src_send occurred. Note: this is not specified the other way round (e.g. in some state after a send, a put will occur) since the channel is unreliable and may lose messages during transmission. φ4: ( snk_get ?fr:video → =5 display !fr ) Whenever a snk_get occurs, a frame is displayed in some future state which occurs 5ms later. Using our extended logic SQTL [Lakas96a], we can also specify probabilistic and stochastic behaviour. For example: 5. The UDP connection loses a message with probability 0.05 (i.e. the probability of successful transmission is 0.95). 0.05
( cmp_send ?fr:video → ( loss ∨ bnd_put !fr ) ) Every frame sent to the binding will be lost (in the next state) with probability 0.05 or eventually transmitted (with probability 0.95). 6. The inter-arrival time of frames at the buffer is assumed to conform to an exponential distribution with rate 28½ frames per second.
φ5:
φ6:
( buf_put ?fr:video with_rate Exp(28.5) ) Note: with_rate (defined in [Lakas96a]) can be seen as shorthand for a formula similar to φ1 above, but for a stochastic choice. As stated in section 3 above, these formulae can now be translated into event schedulers. The event schedulers can then be composed with the labelled transition system generated from the LOTOS specification (given in Appendix A) to produce our global model.
2
means henceforth, means eventually, means next, U means once, subscripts on these operators assign the time at which the associated event
To summarise the relevant syntax of QTL,
means until,
33
should occur,
0.05
represents a choice of times (between 0 and 33ms) for the associated event, and ∨ represents
t=0
a probabilistic choice. Further details regarding the syntax and semantics of QTL can be found in [Blair98a].
8
However, suppose that we were also interested in specifying QoS management functions. As an example, suppose that we wished to monitor the state of the buffer and ensure that it was not “ frequently” too full or too empty. If either case occurs “ frequently” , the sending rate should be adjusted accordingly to compensate. One simple way to achieve this would be to count the number of buf_full and buf_empty events (generated in the LOTOS specification) and calculate the ratio of #buf_full to #puts and the ratio of #buf_empty to #gets. If these ratios exceed 0.5 (chosen arbitrarily), then this signifies that the buffer is either “ too frequently full” or “ too frequently empty” and the sending rate should be reduced or increased respectively 3. Note that there should then be a delay of a few frames (arbitrarily chosen to be 5 frames) to allow the change to take effect before assessing the buffer again. This information clearly affects the static sending rate as defined in formula φ1 above. However, a temporal logic is not the right choice of language to represent this adaptation strategy. Consequently, let us consider replacing formula φ1 with an automata description of the strategy (see figure 5). x, y := 0 zx, zy := 0
put → zx:=zx+1
(a)
(b)
r := 30, i := 1000/r, t := 0, send
t=i → send, t:=0
buf_full → x:=x+1 get → zy:=zy+1
NORMAL
FULL zx picture (* passed as parameter to sub-compressor *) header : -> picture (* used by compressor and decompressor *) packet : video_id, video -> picture eqns ... (* omitted for brevity *) endtype (* PICTURE *) type PICTYPES is Boolean, PICTURE sorts type_picture opns i_encode : picture -> picture (* standard encoding used for intra coded pictures – no reference to other pictures *) p_encode : picture, picture -> picture (* forward predictive encoding – parameters represent current picture and closest past i_pic or p_pic *) b_encode : picture, picture, picture -> picture (* bidirectionally predictive encoding – parameters represent current picture, closest past i_pic or p_pic, and closest future i_pic or p_pic *) past, future : picture -> bool (* test if picture is a past or future frame *) eqns ... (* omitted for brevity *) endtype (* PICTYPES *) process SOURCE[ generate, send ]( id:video_id ) : noexit := (* note: video_id should be a finite set of ids, larger than the buffer size (to avoid problems when the id cycles round to the beginning again), but small enough to permit translation to a FSM *) generate ?data:video; send !id !data; SOURCE[ generate, send ]( succ(id) ) endproc (* SOURCE *) process COMPRESSOR[ get, send ] : noexit := SUB_CMP [ get, send ]( init_state, null, null ) (* a sub-process of the compressor *) where process SUBCMP[ get, send ]( state:pair_states; closest_past, closest_future:picture ) : noexit := ( [ state eq init_state ] -> (* the initial state *) send !header; SUBCMP[ get, send ]( succ(state), closest_past, closest_future ) [] [ state eq sub_state ] -> (* a subsequent state *) get ?id:video_id ?data:video; (* to maintain abstraction we will model a non-deterministic choice between i-frames, p-frames and b-frames – this could be modelled more fully using MPEG compression rules *) ( hide i_choice, p_choice, b_choice in ( i_choice; (* intra coded packet *) send !i_encode( packet(id, data) ); exit(0, packet(id, data)) ) [] ( p_choice; (* forward predictive coded packet *) send !p_encode( packet(id, data), closest_past ); exit(0, packet(id, data)) ) [] ( b_choice; (* bidirectionally predictive coded picture *) send !b_encode( packet(id, data), closest_past, closest_future ); exit(1, packet(id, data)) ) ) >> accept num:binary_num, frame:picture in ( [ num eq 0 ] -> (* both i-frames and p-frames may be used as reference points, therefore change closest_past and closest_future parameters as necessary *) ( [ past(frame) ] -> SUBCMP[ get, send ]( state, frame, closest_future ) [] [ future(frame) ] -> SUBCMP[ get, send ]( state, closest_past, frame ) ) [] [ num eq 1 ] -> (* b-frames are never used as reference points for further prediction, therefore leave closest_past and closest_future parameters unchanged *) SUBCMP[ get, send ]( state, closest_past, closest_future ) ) ) endproc (* SUBCMP *) endproc (* COMPRESSOR *)
14
process BINDING[ get, put, loss ] : noexit := (* It is possible that frames are re-ordered or lost in the binding. We only consider frame loss in the specification below. Note that a simple way of specifying re-ordering has been presented in previous work using the ||| operator, but this leads to infinite state machines *) (* Also, this process could be broken down further into the sender and receiver stubs and the UDP connection, but this level of detail is omitted here *) get ?compressed_data:picture; ( put !compressed_data; (* picture is transmitted successfully *) BINDING[ get, put, loss ] [] loss; BINDING[ get, put, loss ] ) (* picture is lost during transmission *) endproc (* BINDING *) process DECOMPRESSOR[ get, put ] : noexit := get ?compressed_data:picture; (* Perform decompression of this data (the inverse of the compression process detailed above). This detail is omitted for brevity. *) put !identitifier_part(decode(compressed_data)) !data_part(decode(compressed_data)); DECOMPRESSOR[ get, put ] endproc (* DECOMPRESSOR *) process BUFFER[ get, put, buf_full, buf_empty ]( size:nat, contents:buffer_type ) : noexit := ( [ size lt max_buffer_size ] -> get ?id:video_id ?data:video; ( [ succ(size) eq max_buffer_size ] -> buf_full; exit [] [ succ(size) lt max_buffer_size ] -> exit ) >> BUFFER[ get, put ]( succ(size), append( id, data, contents ) ) [] [ size gt 0 ] -> put !identifier_part(head(contents)) !data_part(head(contents)); ( [ pred(size) eq 0 ] -> buf_empty; exit [] [ pred(size) gt 0 ] -> exit ) >> BUFFER[ get, put ]( pred(size), tail(contents) ) ) endproc (* BUFFER *) process SINK[ get, display ] : noexit := get ?id:video_id ?data:video; (* the sink should check here for re-ordered or lost frames, but this is omitted *) display !data; SINK[ get, display ] endproc (* SINK *) endspec (* VIDEO_STREAM *)
15