Subject: Comments on the Script MIB draft

From: Eamonn McManus <[email-masked]>
Date: Mon, 21 Sep 98 18:59:22 -0400

As I promised, or threatened, a week and a half ago, here are my
comments on the Script MIB draft.  I have now implemented Script MIB
support in an SNMP agent and a GUI for downloading and managing
scripts, and these are the comments that occurred to me as I was
working.  I do realise that it is too late for the current draft to be
affected but perhaps some of the points I raise could be of interest
for the next one.

,
Eamonn
--
The Open Group Research Institute, 2 ave de Vignate, 38610 Gieres, France.
Phone +33 476.63.48.82.  Fax +33 476.51.05.32.  http://www.gr.osf.org/~emcmanus

Comments on the IETF Script MIB draft.
Eamonn McManus <[email-masked]>

* Background

We are working on a project that will be adding downloadable-Java
functionality to an embedded system.  We were very pleased to discover
the Script MIB draft because it corresponded very closely to what we
were working towards and so saved us a lot of design effort.  I've
read through the disman archives and it looks as if I may be among the
first people implementing the draft standard without having been
involved in the discussions leading up to it.  This perhaps gives me a
clearer insight into what aspects of the text might be expressed
differently to make it clearer to the first-time reader how the MIB is
intended to work and what the rationale is for various aspects.

The system we are working with may be rather different from target
systems that have been used for the implementations done to date.  In
particular, it has no virtual memory, relatively limited physical RAM,
and very limited non-volatile memory (basically, what's left over in
Flash after the operating system and Java Virtual Machine have taken
out their bites).  Some of the comments I make below have to do with
simplifying the Script MIB, and the reason I am interested in that is
that I have this small-system perspective.

As I was implementing the draft as it stands, I made notes of the
areas that were unclear to me, whether things that are not explained
as clearly as they might be, or things that are left unspecified and
should I think be spelt out, if only to say explicitly that the
behaviour is up to the implementor.

As I mentioned, I would like the Script MIB to be as simple as possible
so that its implementation takes up as little space as possible.  There
is at least one major area where it appears to me that an important
simplification could be made: the smCodeTable.  I think it would be
useful either to make changes something like what I suggest, or to amend
the draft so that it explains why this simplification is undesirable.
My feeling is that other potential implementors are also going to
ask whether the Script MIB could be made smaller; if it can be then
small-system implementors will look on the standard more favourably,
and if it can't then at least they will see that their needs have been
taken into consideration.

Throughout this document, I use "agent" to mean the SNMP agent in the
system being managed, and "manager" to mean the entity issuing SNMP
commands (whether an automated system or a tool being run by a human
user).

* Technical and editorial comments and questions

- The draft introduces the term "launch button" rather suddenly in the
  middle of its ASN.1 text.  It should be defined earlier, along with
  a rationale for its existence: the specification would be
  substantially simpler without launch buttons.  While writing a
  manager that presents a GUI for managing the Script MIB I was faced
  with the choice of making launch buttons visible to the user, or
  risking having them remain undeleted in the agent long after they
  become irrelevant.  I found no good solution.

- Meaning of smLaunchOwner and smLaunchName.  The descriptions of
  these objects in the ASN.1 code are rather terse.  More
  information is to be had in section 7.5 (Creating a launch button)
  but I think it would be useful to spell out in the ASN.1:
  . that the smLaunchOwner is the owner not only of the launch button
    but also of any executing script instances started from that button;
  . that the smLaunchOwner is not necessarily the same as the owner of
    the script to be launched;
  . that the smLaunchName is an arbitrary name that must be different
    from any other launch buttons with the same smLaunchOwner but can be
    the same as other launch buttons with different smLaunchOwners;
  . that the smLaunchName does not have to be related in any way to
    the name of the script being launched.

- In the description of smLaunchStart the following text appears:
    The values contents of smLaunchOwner and smLaunchName of this row
    must identify an existing entry in the smScriptTable.
  I think one or other of the words "values" and "contents" is
  spurious, but more importantly the objects in question should be
  smLaunchScriptOwner and smLaunchScriptName.

- In the description of smLaunchMaxCompleted it is not completely
  clear what happens if the object is set to a value less than the
  current number of expired smRunEntries.  The text currently says:
    The value of this object is checked whenever a script terminates.
    Entries in the smRunTable are deleted until the number of
    completed scripts is smaller than the value of this object.
  That seems to be saying that entries are only deleted on script
  termination.  A clearer phrasing would be:
    Whenever the value of this object is changed, and whenever a
    script terminates, entries in the smRunTable are deleted if
    necessary until...
  (assuming that is the desired behaviour).

- A similar problem applies to smLaunchMaxRunning.  What happens if
  this object is set to a value less than the current number of scripts
  running from this smLaunchEntry?  Are surplus scripts terminated?
  If so, oldest first, newest first, arbitrary order?  If not, is the
  set attempt rejected, or is the inconsistent state allowed?  If the
  behaviour is deliberately unspecified, that should be stated.

- In item 5 of the check list in the smLaunchStart description, there
  is a natural-language "dangling else" problem:
    If the value provided by the set operation is not 0, a check must
    be made that the value is currently not in use.  Otherwise a
    suitable unused value must be generated.
  It's not totally clear that the "otherwise" means "if the value
  provided by the set operation *is* 0" and not "if the value is
  currently in use".  (I agree that this is finicky!)

- If smLaunchStart is set to 0, does it retain that value 0?  It would
  be easy for an agent to set it to the smRunIndex value it chose.
  This almost seems to be implied by the sentence saying:
    The value will be zero if no script started execution yet.
  If and only if?

- It will hardly ever be useful to set smLaunchStart to 0 anyway,
  because if you don't get an ack how do you know if the script was
  launched or not?

- What is the purpose of smLaunchAdminStatus and smLaunchOperStatus?
  Couldn't they be subsumed into setting the smLaunchRowStatus to
  `active' and checking that it is indeed `active', respectively?
  Does the state where smLaunchRowStatus is `active' and
  smLaunchAdminStatus is `disabled' mean something different from the
  state where smLaunchRowStatus is `notInService'?

- It is not clear why the smRunIndex has to be globally unique, as
  specified in its description.  It certainly has to be different for
  all rows in the smRunTable that have the same smLaunchOwner and
  smLaunchName index values, but if an agent actually wants to enforce
  the global uniqueness constraint it needs either an auxiliary data
  structure or a search over the entire smRunTable; and if it doesn't
  enforce the constraint what's the point in having it?

* What is CodeTable editing for?

As I understand, it is intended to be possible to edit a script "in
place", that is, to issue SNMP set requests that modify parts of the
text of a script that has been downloaded at an earlier time.  An agent
doesn't have to provide the smCodeTable, but if it does then it must
also provide this editing capability.

I'll state right out that I've chosen not to include code pushing in
my implementation, so my comments here are not based on experience.

It's obvious that code pushing is useful to avoid having to set up a
separate HTTP or FTP server so the agent can download a script.

It's less obvious that being able to read back a script previously loaded
into the agent is useful, but one can certainly imagine circumstances
where one would want to know exactly what's running in the managed system.

It's not obvious at all that being able to make modifications to part
of the script within the agent is useful.  Under what circumstances
would a manager do that?  A manager that is a program would never do it:
it would read the script out, modify it, then send it back; or it
would just push a new version of the script.

So it looks as if the only use for in-place modification is to allow
humans to tweak scripts by issuing low-level SNMP commands.  Most
humans will not want to do that - they will want to edit the script on
a workstation and send back the edited version.

Even humans that do want to edit scripts directly within the agent
will be severely limited in what they do.  They'd better have
remembered to provide non-contiguous smCodeIndex values for the lines
of the script, and they'd better not want to insert more lines than
the gaps between the smCodeIndex values allow.  It reminds me
somewhat of microcomputer BASIC from twenty years ago.  Without the
RENUMBER command, of course.

My point here is that providing this flexible smCodeTable is not free.
An agent has to keep the whole table for as long as the script is
present, or at least it has to keep the smCodeIndex values and the
mapping from them into offsets in the script.  Small-system developers
that want to provide code pushing will resent this overhead.
`Scripts' aren't necessarily 20-line one-off TCL programs: they can be
substantial binaries, or big Java packages, that will already be
pushing the limits of the machine without adding extra permanent overhead
that is only used during download.  (I acknowledge that big downloads
are better done by pulling, or Yet Another Protocol, rather than
lock-step SNMP packets.)

Here's how I think code pushing should work:

- The smScriptAdminStatus object should be deleted.  Its `enabled'
  state will be equivalent to setting smScriptRowStatus to `active'.

- Entries can only be added to the smCodeTable before the corresponding
  smScriptRowStatus is made `active'.

- The first entry to be added to the smCodeTable must have smCodeIndex
  1, and every subsequent entry must have smCodeIndex one more than the
  current largest value.  Each entry must have its smCodeRowStatus
  set to `active' before any subsequent entry can be added.  Entries once
  added to the smCodeTable cannot be deleted.

  These constraints mean that during download the agent doesn't have
  to store smCodeRowStatus or smCodeIndex values anywhere.  It does
  have to remember where each smCodeText object is within the script,
  but only while the script is being pushed.

- Once the script has been pushed, the manager sets the smScriptRowStatus
  to `active'.  As at present, the fact that the code comes from the
  smCodeTable is indicated by an empty smScriptSource and an
  smScriptStorageType that is `volatile'.

- When the smScriptRowStatus is set to `active', an agent may choose
  to delete the smCodeTable, or it may reorganise it so that the same
  code is presented using a different division into smCodeText objects.
  In particular it may decide to chop up the presented text into
  fixed-length smCodeText objects so that it doesn't have to remember
  the offsets corresponding to smCodeIndex values.  This means that
  the smCodeTable takes up no storage: the agent can construct replies
  to SNMP get requests on the fly.

  If the smScriptStorageType is not volatile, the script text will be
  written to non-volatile storage when the smScriptRowStatus is set to
  `active'.

- The only way to modify the text of a script after it has been
  downloaded is to delete the smScriptEntry and create a new one.

---

From: Juergen Schoenwaelder <[email-masked]>
Date: Fri, 25 Sep 1998 14:41:16 +0200

>>>>> Eamonn McManus writes:

Eamonn> As I promised, or threatened, a week and a half ago, here are
Eamonn> my comments on the Script MIB draft.  I have now implemented
Eamonn> Script MIB support in an SNMP agent and a GUI for downloading
Eamonn> and managing scripts, and these are the comments that occurred
Eamonn> to me as I was working.

Thank you very much for sharing your experiences. Your input is much
appreciated. 

First, let me tell you that I am glad to hear that you did not run
into any problems that turned out to be show stoppers for implementing
the Script MIB. I understand that you have concerns about the
smCodeTable and some of them have been discussed previously within the
DISMAN WG. As you know, we did not implement the smCodeTable in our
Jasmin prototype. I am myself not a proponent of using SNMP to
distribute large volumes of data (whether this is a script or just a
large routing table of a large VACM configuration) so I will leave it
to others to point out the need for the smCodeTable as it is defined
now. (BTW, I appreciate that there is sense in the OPS area now that
bulk data transfer is a problem that needs to be addressed.)

Eamonn> We are working on a project that will be adding
Eamonn> downloadable-Java functionality to an embedded system.  We
Eamonn> were very pleased to discover the Script MIB draft because it
Eamonn> corresponded very closely to what we were working towards and
Eamonn> so saved us a lot of design effort.

Are you downloading Java byte code? If yes, which format do you use?
We use in our prototype the Java archive format, more specifically the
format for "applications bundled as JAR files".

Eamonn> As I mentioned, I would like the Script MIB to be as simple as
Eamonn> possible so that its implementation takes up as little space
Eamonn> as possible.  There is at least one major area where it
Eamonn> appears to me that an important simplification could be made:
Eamonn> the smCodeTable.  I think it would be useful either to make
Eamonn> changes something like what I suggest, or to amend the draft
Eamonn> so that it explains why this simplification is undesirable.

We know that it adds complexity and in many cases, it will be easier
to implement the pull method rather than the smCodeTable.  Therefore,
we decided to make the smCodeTable optional. (It is not required to
implement the smCodeTable in order to conform to the Script MIB).

Eamonn> - The draft introduces the term "launch button" rather
Eamonn> suddenly in the middle of its ASN.1 text.  It should be
Eamonn> defined earlier, along with a rationale for its existence: the
Eamonn> specification would be substantially simpler without launch
Eamonn> buttons.  While writing a manager that presents a GUI for
Eamonn> managing the Script MIB I was faced with the choice of making
Eamonn> launch buttons visible to the user, or risking having them
Eamonn> remain undeleted in the agent long after they become
Eamonn> irrelevant.  I found no good solution.

You are rasing two issues here. The first is an editorial problem and
I agree that the term "launch button" should be defined in a more
prominent place. The second (I guess) is why we have the smLaunchTable
at all. There are several reasons for having this table like support
for normal and setuid calling conventions, the interface to the
scheduling MIB or the ability to kick-off scripts without having to
ship all parameters in a single SNMP request (size restrictions).  I
think you need to make launch buttons visible if you want to offer a
user to launch a script where the launch button and the parameters are
pre-configured by another user.

Eamonn> - Meaning of smLaunchOwner and smLaunchName.  The descriptions
Eamonn> of these objects in the ASN.1 code are rather terse.  More
Eamonn> information is to be had in section 7.5 (Creating a launch
Eamonn> button) but I think it would be useful to spell out in the
Eamonn> ASN.1: . that the smLaunchOwner is the owner not only of the
Eamonn> launch button but also of any executing script instances
Eamonn> started from that button; . that the smLaunchOwner is not
Eamonn> necessarily the same as the owner of the script to be
Eamonn> launched; . that the smLaunchName is an arbitrary name that
Eamonn> must be different from any other launch buttons with the same
Eamonn> smLaunchOwner but can be the same as other launch buttons with
Eamonn> different smLaunchOwners; . that the smLaunchName does not
Eamonn> have to be related in any way to the name of the script being
Eamonn> launched.

I agree that the text in the description is pretty short and that
adding text as you suggested is a good idea.

Eamonn> - In the description of smLaunchStart the following text
Eamonn> appears: The values contents of smLaunchOwner and smLaunchName
Eamonn> of this row must identify an existing entry in the
Eamonn> smScriptTable.  I think one or other of the words "values" and
Eamonn> "contents" is spurious, but more importantly the objects in
Eamonn> question should be smLaunchScriptOwner and smLaunchScriptName.

Thanks for catching this. This text needs to be fixed.

Eamonn> - In the description of smLaunchMaxCompleted it is not
Eamonn> completely clear what happens if the object is set to a value
Eamonn> less than the current number of expired smRunEntries.  The
Eamonn> text currently says: The value of this object is checked
Eamonn> whenever a script terminates.  Entries in the smRunTable are
Eamonn> deleted until the number of completed scripts is smaller than
Eamonn> the value of this object.  That seems to be saying that
Eamonn> entries are only deleted on script termination.  A clearer
Eamonn> phrasing would be: Whenever the value of this object is
Eamonn> changed, and whenever a script terminates, entries in the
Eamonn> smRunTable are deleted if necessary until...  (assuming that
Eamonn> is the desired behaviour).

Your interpretation certainly makes sense. And this is how we
implemented smLaunchMaxCompleted in our prototype.

Eamonn> - A similar problem applies to smLaunchMaxRunning.  What
Eamonn> happens if this object is set to a value less than the current
Eamonn> number of scripts running from this smLaunchEntry?  Are
Eamonn> surplus scripts terminated?  If so, oldest first, newest
Eamonn> first, arbitrary order?  If not, is the set attempt rejected,
Eamonn> or is the inconsistent state allowed?  If the behaviour is
Eamonn> deliberately unspecified, that should be stated.

Our implementation accepts the new value but does not terminate any
running scripts since there is no way to decide which one to
abort. However, accepting the value means that no new scripts will be
started until the number of running scripts drops below
smLaunchMaxRunning. Returning an inconsistentValue error would require
that a manager manually aborts scripts in order to reduce
smLaunchMaxRunning. The manager will likely have the same problem to
decide which script should be aborted. So simply accepting the new
value seems to be the best solution.

Eamonn> - In item 5 of the check list in the smLaunchStart
Eamonn> description, there is a natural-language "dangling else"
Eamonn> problem: If the value provided by the set operation is not 0,
Eamonn> a check must be made that the value is currently not in use.
Eamonn> Otherwise a suitable unused value must be generated.  It's not
Eamonn> totally clear that the "otherwise" means "if the value
Eamonn> provided by the set operation *is* 0" and not "if the value is
Eamonn> currently in use".  (I agree that this is finicky!)

This is an editorial problem.

Eamonn> - If smLaunchStart is set to 0, does it retain that value 0?
Eamonn> It would be easy for an agent to set it to the smRunIndex
Eamonn> value it chose.  This almost seems to be implied by the
Eamonn> sentence saying: The value will be zero if no script started
Eamonn> execution yet.  If and only if?

The description says that smLaunchStart always returns value of
smRunIndex for the last script that started execution by manipulating
this object in a retrieval operations. However, we can't return the
new smRunIndex value when processing an SNMP set-request due to the
way SNMP set operations are defined.

Eamonn> - It will hardly ever be useful to set smLaunchStart to 0
Eamonn> anyway, because if you don't get an ack how do you know if the
Eamonn> script was launched or not?

It is useful in situations where you don't care about the running
script. The prominent example is the Scheduling MIB. And this 
mentioned in the description of smLaunchStart:

>       Setting this object to the special value 0 will start
>       the script with a self-generated smRunIndex value. The
>       consequence is that the script invoker has no reliable
>       way to determine the smRunIndex value for this script
>       invocation and that the invoker has therefore no way
>       to obtain the results from this script invocation. The
>       special value 0 is however useful for scheduled script
>       invocations.

Eamonn> - What is the purpose of smLaunchAdminStatus and
Eamonn> smLaunchOperStatus?  Couldn't they be subsumed into setting
Eamonn> the smLaunchRowStatus to `active' and checking that it is
Eamonn> indeed `active', respectively?  Does the state where
Eamonn> smLaunchRowStatus is `active' and smLaunchAdminStatus is
Eamonn> `disabled' mean something different from the state where
Eamonn> smLaunchRowStatus is `notInService'?

Yes. Early versions of the MIB actually combined the admin status
object with the RowStatus object. However, this bundling creates some
problems. The most important is that a row will automatically be
expired from the notInService state. RFC 1903 defines a default expire
time of 5 minutes. It is however useful to have scripts or launch
buttons installed that are disabled for longer periods of time.

Eamonn> - It is not clear why the smRunIndex has to be globally
Eamonn> unique, as specified in its description.  It certainly has to
Eamonn> be different for all rows in the smRunTable that have the same
Eamonn> smLaunchOwner and smLaunchName index values, but if an agent
Eamonn> actually wants to enforce the global uniqueness constraint it
Eamonn> needs either an auxiliary data structure or a search over the
Eamonn> entire smRunTable; and if it doesn't enforce the constraint
Eamonn> what's the point in having it?

I guess this requirement is inherited from earlier versions of the
MIB. It turns out that it is sometimes useful to be able to refer to a
running script with a simple smRunIndex number instead of the triple
{smLaunchOwner, smLaunchName, smRunIndex}. But you are right that
there is no architectural reason for this requirement.

Eamonn> * What is CodeTable editing for?

Eamonn> As I understand, it is intended to be possible to edit a
Eamonn> script "in place", that is, to issue SNMP set requests that
Eamonn> modify parts of the text of a script that has been downloaded
Eamonn> at an earlier time.  An agent doesn't have to provide the
Eamonn> smCodeTable, but if it does then it must also provide this
Eamonn> editing capability.

[snip]

Eamonn> So it looks as if the only use for in-place modification is to
Eamonn> allow humans to tweak scripts by issuing low-level SNMP
Eamonn> commands.  Most humans will not want to do that - they will
Eamonn> want to edit the script on a workstation and send back the
Eamonn> edited version.

Some people within the WG said that they indeed need the capability to
edit scripts via SNMP...

Eamonn> Even humans that do want to edit scripts directly within the
Eamonn> agent will be severely limited in what they do.  They'd better
Eamonn> have remembered to provide non-contiguous smCodeIndex values
Eamonn> for the lines of the script, and they'd better not want to
Eamonn> insert more lines than the gaps between the smCodeIndex values
Eamonn> allow.  It reminds me somewhat of microcomputer BASIC from
Eamonn> twenty years ago.  Without the RENUMBER command, of course.

There was actually a discussion about renumbering and the conclusion
was to not allow it. The compromise was something like "you can edit
scripts in place but once you run out of numbers, you have to download
it again." Whether this really makes sense or not needs to be
discussed with the proponents of the smCodeTable. ;-)

Eamonn> My point here is that providing this flexible smCodeTable is
Eamonn> not free.  An agent has to keep the whole table for as long as
Eamonn> the script is present, or at least it has to keep the
Eamonn> smCodeIndex values and the mapping from them into offsets in
Eamonn> the script.  Small-system developers that want to provide code
Eamonn> pushing will resent this overhead.  `Scripts' aren't
Eamonn> necessarily 20-line one-off TCL programs: they can be
Eamonn> substantial binaries, or big Java packages, that will already
Eamonn> be pushing the limits of the machine without adding extra
Eamonn> permanent overhead that is only used during download.  (I
Eamonn> acknowledge that big downloads are better done by pulling, or
Eamonn> Yet Another Protocol, rather than lock-step SNMP packets.)

I fully understand your point. And this is why we made the smCodeTable
optional. Yes, the smCodeTable is more expensive to implement than it
seems if you look at the number of objects.

Eamonn> Here's how I think code pushing should work:

Eamonn> - The smScriptAdminStatus object should be deleted.  Its
Eamonn> `enabled' state will be equivalent to setting
Eamonn> smScriptRowStatus to `active'.

See above. This does not work due to the fact that notInService as
a timer associated with it.

Eamonn> - Entries can only be added to the smCodeTable before the
Eamonn> corresponding smScriptRowStatus is made `active'.

We had this before. We moved away from it when we realized that we
need an smScriptAdminStatus (see above).

Eamonn> - The first entry to be added to the smCodeTable must have
Eamonn> smCodeIndex 1, and every subsequent entry must have
Eamonn> smCodeIndex one more than the current largest value.  Each
Eamonn> entry must have its smCodeRowStatus set to `active' before any
Eamonn> subsequent entry can be added.  Entries once added to the
Eamonn> smCodeTable cannot be deleted.

Eamonn>   These constraints mean that during download the agent
Eamonn> doesn't have to store smCodeRowStatus or smCodeIndex values
Eamonn> anywhere.  It does have to remember where each smCodeText
Eamonn> object is within the script, but only while the script is
Eamonn> being pushed.

Some WG members did not like this approach. I am not sure whether 
they realized the cost trade-off. This probably needs additional
discussions.

Eamonn> - Once the script has been pushed, the manager sets the
Eamonn> smScriptRowStatus to `active'.  As at present, the fact that
Eamonn> the code comes from the smCodeTable is indicated by an empty
Eamonn> smScriptSource and an smScriptStorageType that is `volatile'.

See above. We need smScriptAdminStatus anyway so we can use it and
leave the RowStatus free of additional semantics.

Eamonn> - When the smScriptRowStatus is set to `active', an agent may
Eamonn> choose to delete the smCodeTable, or it may reorganise it so
Eamonn> that the same code is presented using a different division
Eamonn> into smCodeText objects.  In particular it may decide to chop
Eamonn> up the presented text into fixed-length smCodeText objects so
Eamonn> that it doesn't have to remember the offsets corresponding to
Eamonn> smCodeIndex values.  This means that the smCodeTable takes up
Eamonn> no storage: the agent can construct replies to SNMP get
Eamonn> requests on the fly.

People who want to edit the script via SNMP do not want that the agent
reorganizes the table because they would have to read it back before
being able to make modifications...

Eamonn>   If the smScriptStorageType is not volatile, the script text
Eamonn> will be written to non-volatile storage when the
Eamonn> smScriptRowStatus is set to `active'.

Eamonn> - The only way to modify the text of a script after it has
Eamonn> been downloaded is to delete the smScriptEntry and create a
Eamonn> new one.

It all boils down to the question whether script editing via SNMP
really makes sense or not. The smCodeTable allows to do that and it is
made optional so that implementors do not have to pay for this feature
if they do not need it. So far we have two implementations that do not
implement the smCodeTable. I think we will have to see whether other
implementations support the smCodeTable and what the experiences are.
If it turns out that nobody is willing to pay the price, we certainly
have to look at it again and discuss whether we want to keep the
smCodeTable as it is defined right now.

Thanks again for your input. It would be nice to hear from you again
once the Script MIB is again open for technical changes.

							Juergen

Juergen Schoenwaelder  [email-masked] http://www.cs.tu-bs.de/~schoenw
Technical University Braunschweig, Dept. Operating Systems & Computer Networks
Bueltenweg 74/75, 38106 Braunschweig, Germany.        (Tel. +49 531 / 391 3289)