TCP IP Lean Web Servers For Embedded Systems 2nd Edition Jeremy Bentham Full
TCP IP Lean Web Servers For Embedded Systems 2nd Edition Jeremy Bentham Full
https://ebookgate.com/product/tcp-ip-lean-web-servers-for-embedded-systems-2nd-edition-jeremy-
bentham/
DOWNLOAD EBOOK
TCP IP Lean Web Servers for Embedded Systems 2nd Edition
Jeremy Bentham pdf download
Available Formats
https://ebookgate.com/product/web-technologies-tcp-ip-web-java-
programming-and-cloud-computing-3rd-edition-achyut-s-godbole/
ebookgate.com
https://ebookgate.com/product/the-abcs-of-tcp-ip-2nd-edition-gilbert-
held/
ebookgate.com
https://ebookgate.com/product/tcp-ip-clearly-explained-pete-loshin/
ebookgate.com
https://ebookgate.com/product/the-correspondence-of-jeremy-bentham-
volume-1-1752-to-1776-1st-edition-jeremy-bentham/
ebookgate.com
The Correspondence of Jeremy Bentham Volume 2 1777 To 1780
1st Edition Jeremy Bentham
https://ebookgate.com/product/the-correspondence-of-jeremy-bentham-
volume-2-1777-to-1780-1st-edition-jeremy-bentham/
ebookgate.com
https://ebookgate.com/product/the-correspondence-of-jeremy-bentham-
volume-5-january-1794-to-december-1797-1st-edition-jeremy-bentham/
ebookgate.com
https://ebookgate.com/product/tcp-ip-network-administration-3rd-
edition-craig-hunt/
ebookgate.com
https://ebookgate.com/product/special-edition-using-tcp-ip-niit-usa-
inc/
ebookgate.com
Jeremy Bentham
CMP Books
Lawrence, Kansas 66046
CMP Books
CMP Media LLC
1601 West 23rd Street, Suite 200
Lawrence, Kansas 66046
USA
www.cmpbooks.com
Designations used by companies to distinguish their products are often claimed as trademarks. In
all instances where CMP Books is aware of a trademark claim, the product name appears in initial
capital letters, in all capital letters, or in accordance with the vendor’s capitalization preference.
Readers should contact the appropriate companies for more complete information on trademarks
and trademark registrations. All trademarks and registered trademarks in this book are the prop-
erty of their respective holders.
Copyright © 2002 by Jeremy Bentham, except where noted otherwise. Published by CMP Books,
CMP Media LLC. All rights reserved. Printed in the United States of America. No part of this pub-
lication may be reproduced or distributed in any form or by any means, or stored in a database or
retrieval system, without the prior written permission of the publisher; with the exception that the
program listings may be entered, stored, and executed in a computer system, but they may not be
reproduced for publication.
The programs in this book are presented for instructional value. The programs have been carefully
tested, but are not guaranteed for any particular purpose. The publisher does not offer any war-
ranties and does not guarantee the accuracy, adequacy, or completeness of any information herein
and is not responsible for any errors or omissions. The publisher assumes no liability for damages
resulting from the use of the information in this book or for any infringement of the intellectual
property rights of third parties that would result from the use of this information.
ISBN: 1-57820-108-X
To Fred, Ilse, and Jane
Table of Contents
Preface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
The Lean Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
Embedded Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .xii
The Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
The Network . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii
The Operating System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiv
The Development Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiv
The Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv
Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv
Chapter 1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . 1
The Lean Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1
Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .2
Software Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5
Network Hardware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5
Device Drivers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .8
Configuration File Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .14
Process Timer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .14
State Machines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .17
Buffering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .21
Coding Conventions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .29
v
vi Table of Contents
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .547
xi
xii Preface
As time went by and my TCP/IP software matured, the Web became increasingly impor-
tant. My industrial customers would browse the Web at home or work and could see the
advantages of using a Web browser for remote control and to monitor their industrial equip-
ment. TCP became just a vehicle for conveying Web pages. The focus shifted from “I want
TCP/IP on my system” to “I want my system to produce Web pages,” and these pages always
included dynamic real-time data.
History was repeating itself; the software to produce these dynamic Web pages was
designed for large multiuser systems, and I couldn’t find small-scale implementations that
were usable on simple, low-cost embedded systems hardware. I needed:
• a description of the techniques to insert live data into Web pages and
• some simple platform-independent code that I could adapt for specific projects.
Having implemented many small-scale Web servers of my own (generally an 80188 pro-
cessor with 64Kb of ROM), I was delighted to hear of a 256-byte implementation on a
microcontroller, although I was disappointed to discover that it could only produce fixed
pages from its ROM, with no dynamic data. I wanted to know:
• what compromises were associated with implementing TCP and a Web server on a
microcontroller and
• what techniques I could use to insert dynamic data into its Web pages.
Almost by chance, the first edition of this book included a miniature Web server running
on a PICmicro®1. I wasn’t the first to create such a server, but I was the first to publish a full
description of the techniques used, including full source code. The success of the initial
offering prompted me to update this book to broaden the range of networks and protocols
supported on the PICmicro. Despite the “Web servers” in the title of this book, there are many
ways to transfer data across a network, and I wanted to provide working examples of their
use.
Hopefully, you’ll find the answers you want in this book.
Embedded Systems
The term “embedded system” may be new to some of you and require some explanation,
even though you use embedded systems every day of your life. Microwave ovens, TVs, cars,
elevators, and aircraft are all controlled by computers, which don’t necessarily have a screen,
keyboard, and hard disk. A computer could be controlling your car without your knowledge:
an engine management system takes an input signal from the accelerator and provides out-
puts that control the engine.
These computers are embedded in a system, of which they may be only a small compo-
nent. The embedded system designer may have to work within tight constraints of size,
weight, power consumption, vibration, humidity, electrical interference, and above all, cost
and reliability. The PC architecture has been adapted for embedded systems operation, and
rugged single-board computers (SBCs) are available from a wide variety of suppliers, together
with the necessary add-on cards to process real-world signals. The ultimate in miniaturization
The Hardware
At the time of writing, the PC hardware platform, although distinctly showing its age, cannot
be ignored. The second-hand market is awash with perfectly serviceable PCs that don’t con-
tain the latest and fastest technology but are more than adequate for your purposes. There are
low-cost industrial SBCs that have a PC core, standard network interface, and the ability to
accept interface cards for a wide variety of real-world signals.
My software will run on all these PC compatibles, and even on PC incompatibles (such as
the 80188 CPU) with a very small amount of modification, because I have clearly isolated all
hardware and operating-system dependencies.
In addition to the PC code, I have included a miniature TCP/IP stack and Web server for a
Microchip PICmicro® microcontroller, using the Custom Computer Services PCM C com-
piler. A standard PICmicro evaluation board can be hand-modified to include the appropriate
peripherals (a circuit diagram is given), or a complete off-the-shelf board can be purchased
instead. I won’t pretend that it would be easy to adapt this software to another processor, but
there is an in-depth analysis of the difficulties associated with microcontroller implementa-
tions, which would give you a very significant head-start if working with a different CPU.
The Network
Base-level Ethernet (10Mbit) is still widely available; complete kits, including interface cards
and cabling, are available at low cost from computer retailers. My software directly supports
two of the most popular Ethernet cards — Novell NE2000 compatibles and 3COM 3C509
— and can potentially (if using the Borland Compiler) support other cards through the packet
driver interface, though the direct hardware interface approach is preferable because it makes
experimentation and debugging much easier.
xiv Preface
When developing network software, you are very strongly advised to use a separate scratch
network, completely isolated from all other networks in the building. Not only does debug-
ging become much easier, but you also avoid the possibility of disrupting other network traffic.
It is remarkable how a minor change to the software can result in a massive increase in the net-
work traffic and a significant disruption to other network users. You have been warned!
The software also supports serial links through SLIP (serial line Internet protocol), and a
crossover serial cable between two PCs can, to a certain extent, be used as a substitute for a
real network.
The Borland compilers, though ostensibly obsolete, may be found on the CD-ROM of
some C programming tutorial books or may be bundled with their 32-bit cousins. The
The Software xv
high-level software can be compiled using all of these environments, but I have not been so
fortunate with the low-level network interface code.
• The Borland compilers are the easiest to use because they allow the use of interrupts
without the need for machine code inserts and so can support the full range of network
interfaces.
• With the Microsoft compiler, the network card and SLIP interfaces are supported, but the
packet driver interface is not.
• Only the direct network card interface is supported when using the DJGPP compiler.
Because the direct network card interface is the easiest to debug, and hence more suitable
for experimentation, this restriction isn’t as onerous as it might appear.
If your favorite compiler isn’t on the list, I apologize for the omission, but I am very
unlikely to add it. Each compiler represents a very significant amount of testing, and my pref-
erence is to reduce, rather than increase, the number of compilers supported. If your compiler
is similar to the above (for example, an earlier version), then you should have little or no
adaptation work to perform, though I can’t comment on any compiler I haven’t tried.
PICmicro Compilers. The early software used the Custom Computer Services (CCS) PCM
v2.693, but later developments are broadly compatible with the CCS and Hitech compilers
for the PIC16xxx and PIC18xxx series microcontrollers. A detailed discussion of compatibil-
ity issues is beyond the scope of this chapter. See Appendix D and the software release notes
on the CD-ROM for more information.
The Software
The enclosed CD-ROM contains complete source code to everything in this book so that you,
as purchaser of the book, can experiment. However, the author retains full copyright to the
software, and it may only be distributed in conjunction with the book; for example, you may
not post any of the source code on the Internet or misrepresent its authorship by extracting
fragments or altering the copyright notices.
If you want to sell anything that contains this software, a license is required for the
“incorporation” of the software into each commercial product. This normally takes the form
of a one-off payment that allows unlimited incorporation of any executable code derived
from this source. There are no additional development fees (apart from purchase of the
book), and license fees are kept low to encourage commercial usage. Full details and software
updates are on the Iosoft Ltd. Web site at www.iosoft.co.uk.
Acknowledgments
The author owes a profound debt of gratitude to Berney Williams of CMP Books for being so
keen on this project, Anthony Winter for his proofreading skills and advice, Glen Middleton
of Arcom Control Systems Ltd. and Adrian Nicol of Io Ltd. for their help with the hardware,
and, above all, to Jane McSweeney (now Jane Bentham) for her continued enthusiasm, sup-
port, and wonderful cakes.
xvi Preface
1
Chapter 1
Introduction
The Lean Plan
This is a software book, so it contains a lot of code, most of which has been specially written
(or specially adapted) for the book. The software isn’t a museum piece, to be studied in a
glass case, but rather a construction kit, to promote understanding through experimentation.
The text is interspersed with source code fragments that illustrate the points being discussed
and provide working examples of theoretical concepts. All the source code in the book, and
complete project configurations for various compilers, are on the enclosed CD-ROM.
When I started writing this book, I intended to concentrate on the protocol aspects of
embedded Web servers, but I came to realize that the techniques of providing dynamic con-
tent (on-the-fly Web page generation) and client/server data transfers were equally important,
yet relatively unexplored. Here are some reasons for studying this book.
TCP/IP. You want to understand the inner workings of TCP/IP and need some tools and
utilities to experiment with.
Dynamic Web Content. You have an embedded TCP/IP stack and need to insert dynamic
data into the Web pages.
1
2 Chapter 1: Introduction
Miniaturization. You are interested in incorporating a miniature Web server in your sys-
tem but need to understand what resources are required and what compromises will have to
be made.
Prototyping. You want a prebuilt Web server that you can customize to evaluate the con-
cept in a proposed application.
Data transfer. You need to transfer data across a network using standard protocols.
Of course, these areas are not mutually exclusive, but I do understand that you may not
want to read this book in a strict linear order. As far as possible, each chapter stands on its own
and provides a stand-alone utility that allows you to experiment with the concepts discussed.
I won’t assume any prior experience with network protocols, just a working knowledge of
the C programming language. In the Preface, I detailed the hardware and software you would
need to take full advantage of the source code in the book. You don’t have to treat this book
as a hands-on software development exercise, but it would help your understanding if you
did.
Getting Started
On the CD-ROM, you’ll find the directory tcplean with several subdirectories.
You’ll also find the directory chipweb with a two subdirectories containing the files for
Chapters 12–16.
ARCHIVE zip files containing older versions of the ChipWeb source code
P16WEB latest ChipWeb source code
Executable copies of all the utilities, sample configuration files, and a README file with any
late-breaking update information are in tcplean. Preferably, the complete directory tree d:\
tcplean (where d: is the CD-ROM drive) should be copied to c:\tcplean on your hard disk,
1. PICmicro® is the registered trademark of Microchip Technology Inc.; PICDEM.net™ is the trade-
mark of Microchip Technology Inc.
Getting Started 3
and d:\chipweb to c:\chipweb. If a different directory path is used, it will be necessary to edit
the compiler project files.
The utilities read a configuration file to identify the network parameters and hardware
configuration; the default is tcplean.cfg, read from the current working directory. It is
unlikely that this will contain the correct hardware configuration for your system, so it is
important that you change the configuration file before running any of the utilities. See
Appendix A for details. If you attempt to use my default configuration without checking its
suitability, it may conflict with your current operating system settings and cause a lockup.
It is possible to browse the source files on the CD-ROM and execute the utilities on it
without loading them onto your hard disk, though you still need a to adapt the configuration
file and store it in the current working directory.
c:\>cd tcplean
c:\tcplean>d:\tcplean\ping 10.1.1.1
This would execute the utility on the CD-ROM using the configuration file.
c:\tcplean\tcplean.cfg
The default configuration file may be overridden using the -c command-line option.
c:\tcplean>ping -c slip 172.16.1.1
This uses the alternative configuration file slip.cfg, which makes it possible to experiment
with multiple network configurations without having to rebuild the software each time.
If you are in any doubt about the command-line arguments for a utility, use the -? option.
c:\>cd tcplean
c:\tcplean>ping -?
Some of the utilities have the same name as their DOS counterparts (because they do the same
job), so it is important to change to tcplean before attempting to run them.
A final word of warning: I strongly recommend that you create a new “scratch” network
for your experimentation that is completely isolated from all other networks in the building.
It is a very bad idea to experiment on a “live” network.
Network Configuration
The DOS software in this book supports the following network hardware.
Some combinations of network hardware and compiler are not supported. Consult Appendix
A and the README file for full information on the network configuration options.
4 Chapter 1: Introduction
Compiler Configuration
Executable versions of all the DOS projects are included within the tcplean directory, so ini-
tial experimentation can take place without a compiler. The project files for each compiler
reside in a separate directory, as described earlier, and all the compiler configuration informa-
tion resides within the project files. All the source code files reside in a single shared directory.
There are a few instances where compiler-specific code (generally Win32-specific code) must
be generated, in which case automatic conditional compilation is used.
Load specific projects for the following compilers:
Borland C++ v3.1 In a DOS box, change to the BC31 directory and run BC using the
project filename.
c:\>cd \tcplean\bc31
c:\tcplean\bc31>bc ping.prj
Borland C++ v4.52 Launch the Integrated Development Environment (IDE) and select
Project–Open Project and the desired IDE file in the BC45 directory.
DJGPP and RHIDE Launch the RHIDE IDE and select Project–Open Project and the
desired GPR file in the DJGPP directory.
Visual C++ v6 Launch the IDE and select File–Open Workspace and the desired DSW file
in the VC6 directory.
Custom Computer Services PCM The PICmicro cross-compiler uses a completely differ-
ent set of source code that resides in the PCM directory. Open a DOS box and change direc-
tory to \tcplean\pcm. Copy the necessary system files (16C76.h and ctype.h) into this
directory from the standard PCM distribution. Run the PCM compiler, specifying PWEB.C on
the command line.
c:\>cd \tcplean\pcm
c:\tcplean\pcm>copy \picc\examples\16c76.h
c:\tcplean\pcm>copy \picc\examples\ctype.h
c:\tcplean\pcm>\picc\pcm pweb.c
I run the PCM compiler from within the IDE of an emulator; see the emulator documenta-
tion for details on how to do this. When first using such a setup, make a minor but readily
observable change, rebuild, and check that the new executable really has been downloaded
into the emulator. It is all too easy to omit a vital step in the rebuild chain, such that the old
file is still being executed.
Software Introduction
For the rest of this chapter, I’ll look at the low-level hardware and software functions needed
to support software development.
• network hardware characteristics
• network device drivers
• process timing
• state machines
• buffering
• coding conventions
Even if you’re keen to get on with the protocols, I suggest you at least skim this material,
since it forms the groundwork for the later chapters.
Network Hardware
To help in setting up a serial or network link, I’ve included some sample configurations in
Appendix A, together with the relevant software installations. Assuming one or both are
installed, I will examine their characteristics with a view to producing the low-level hardware
device drivers.
Figure 1.1 shows two types of networks (two “topologies”): the older style bus network,
where the computers are connected to a single common cable, and the newer star network,
where the computers are individually connected to a common box (a hub), which electrically
copies the network signals from one computer to all others. Fortunately, the operation of an
Ethernet hub is completely transparent to the software, so you can still treat the network as if
the computers were sharing a common cable.
Both computers have equal access to the serial link. The hardware simply acts as a
“data pipe” between the two computers and does not prioritize one computer above another.
There are only two computers (nodes) on the network. Throughout this book, I’ll use
“node” as shorthand for “a computer on the network.” Insofar as the simple serial link con-
stitutes a network, it is clear that if one node transmits a message, it can only be received by
the other node and no others.
A node can transmit data at any time. This is technically known as a full duplex system;
both computers can transmit and receive simultaneously without any clash of data signals.
Message delivery is reliable. The assumption is that the two nodes are close to each
other, with a short connecting cable, so there will be no corruption of data in transit. The pre-
dominant failure mode is a catastrophic link failure, such as a disconnection of the cable or a
node powering down.
The serial data is a free-format stream of bytes, with little or no integrity checking.
The serial hardware is only designed for short-distance interconnects, so it has a very simple
error-checking scheme (parity bit), which is often disabled. To guarantee message integrity,
error checking must be provided in software.
There is no limit on message size. Because the serial data is simply a stream of bytes
with no predefined start or end, there is no physical restriction on its length.
There is no need for addressing Because there is only one possible recipient for each
message, there is no need to include an address identifying that recipient.
All nodes have a 48-bit address that is unique on the network. Just as a postal address
uniquely identifies a specific location in the world, so a node address (generally known as a
media access and control, or MAC, address) must uniquely identify a node on the network. In
fact, the standardization of Ethernet guarantees each node address to be also unique in the
world; you can mix and match Ethernet adaptors from different manufacturers, secure in the
knowledge that no two will have the same 48-bit address.
Any node may transmit on the network when it is idle. If a node is to communicate
with another, it must wait for all others to be silent before it can transmit. Because all nodes
are equal, they need not ask permission before transmitting on the network; they simply wait
for a suitable gap in the network traffic.
Message delivery is unreliable. “Unreliable? Why don’t you fix it?” Networks are, by
their very nature, an unreliable way of sending data. The failure modes range from the cata-
strophic (the recipient’s computer is powered down or physically disconnected from the net-
work) to the intermittent (a packet has been corrupted by collision or electrical interference).
The network hardware has the ability to detect and compensate for some intermittent faults
(e.g., a retry in the event of a packet collision), but eventually an error will occur that has to
be handled in software, so the software must assume the network is unreliable.
All data on the network is in blocks (frames) with a defined beginning and end and
an integrity check. Nodes that are going to transmit when they want need a defined for-
mat for their transmissions so that others know when they are starting or finishing, assuming
each transmission is a block with start and end markers and some form of checking (usually a
CRC, or cyclic redundancy check) to ensure it hasn’t been damaged in transit. The name
given to this block differs according to the network used; Ethernet blocks are called frames.
The network can send a maximum of 1,500 bytes of data per frame. All networks
have an upper limit on the size of data they can carry in one frame. This is called the maxi-
mum transfer unit, or MTU. Ethernet frames can contain up to 1.5Kb, but TCP/IP software
will work satisfactorily with a lot smaller MTU.
All messages are equipped with a source and destination address. Frames are usu-
ally intended for a single recipient; this is known as unicast transmission. Occasionally, it may
be necessary to send a frame to all nodes on the network, which is a broadcast transmission.
8 Chapter 1: Introduction
Device Drivers
It would be helpful if the driver software presented a common interface to the higher-level
code, but it is clear from the preceding analysis that there are significant differences; these are
summarized in Table 1.1.
SLIP
Fortunately, one of the TCP/IP families of standards, SLIP, provides exactly this functionality. It
uses simple escape codes inserted in the serial data stream to signal block boundaries as follows.
• The end of each block is signaled by a special End byte, with a value of C0h.
• If a data byte equals C0h, two bytes with the values DB, DC are sent instead.
• If a data byte equals DBh, two bytes with the values DB, DD are sent instead.
Additionally, most implementations send the End byte at the beginning of each block to
clear out garbage characters prior to starting the new message (Figure 1.2).
There is effectively no limit to the size of the data block, but you have to decide on some
value in order to dimension the data buffers. With old slow serial links, a maximum size of
256 bytes was generally used, but you’ll be using faster links, and a larger size is better for
minimizing protocol overhead. By convention, 1,006 bytes is often used.
The encoding method can best be illustrated by an example (Figure 1.3). Assume a six-
byte block of data with the hex values BF C0 C1 DB DC is sent; it is expanded to C0 BF DB DC C1
DB DD DC C0.
Device Drivers 9
Figure 1.3 SLIP example.
END END
BFh DBh DCh C1h DBh DDh DCh
C0h C0h
The original data has nearly doubled in size, due to my deliberately awkward choice of
data values. In normal data streams, the overhead is much lower.
Modem Emulation
An additional problem with serial networking is that most PCs are configured to use a
modem (Figure 1.4) to an Internet Service Provider (ISP).
I’ll create a Web server, but instead of two modems, I’ll use a serial (null modem) cable to
link it to the browser. The problem is that my Web server will then receive the browser’s com-
mands to its modem. If these go unanswered, the browser will assume its modem is faulty and
report this to the user.
The easiest solution is to include a simple modem emulator in your serial driver so that the
browser is fooled into thinking it is talking to a modem. Because modem commands are text
based, you can easily distinguish between them and the SLIP message blocks prefixed by the
delimiter character (C0h); when the latter appears, disengage the modem emulation.
Modem commands begin with the uppercase letters AT, followed by zero or more alpha-
betic command letters, with alphabetic or numeric arguments, terminated by an ASCII car-
riage return (<CR>) character. The usual reply are the uppercase letters OK, followed by a
carriage return and line feed (<CR><LF>). Table 1.2 shows a few typical command–response
10 Chapter 1: Introduction
sequences for a simple modem. This emulation would respond OK to all commands; this is
normally sufficient.
Table 1.2 Modem command–response sequences.
/* Ethernet (DIX) frame; data size is frame size minus header & CRC */
#define ETHERMTU (MAXFRAME-sizeof(ETHERHDR))
typedef struct {
ETHERHDR h; /* Header */
BYTE data[ETHERMTU]; /* Data */
LWORD crc; /* CRC */
} ETHERFRAME;
This is the basic Ethernet frame, also known as Ethernet 2 (Ethernet 1 is obsolete), or DIX
Ethernet (after its creators, DEC, Intel, and Xerox).
Type/Length Field
Unfortunately, there are several Ethernet standards, and they make different use of this two-
byte field. One standard uses it as a length, giving the total count of bytes in the data field.
Others use it as a protocol type, indicating the protocol that is being used in the data field.
Mercifully there are simple ways of detecting and handling these standards, which are dis-
cussed in Chapter 3.
Data
This area contains user data in any format; the only restrictions are that its minimum size is
46 bytes and its maximum is 1,500 bytes. The minimum is necessary to ensure that the over-
all frame is at least 64 bytes. If it were smaller, there would be a danger that frame collisions
wouldn’t be detected on large networks.
12 Chapter 1: Introduction
A starting CRC value of FFFFFFFFh is sent to this function, together with the first byte
value. A new CRC value is returned, which is sent to this function together with the next byte
value, and so on. When all bytes have been processed, the final CRC value is inverted (one’s
complement) to produce the four-byte Ethernet CRC, which would be transmitted least sig-
nificant byte first.
This specifies an Ethernet interface using an NE2000-compatible card at I/O address 280h.
See Appendix A for details on the cards and networks supported.
This string passed to a network initialization function, to open the required interface.
WORD open_net(char *cfgstr);
Device Drivers 13
This function opens up the network driver, given a string specifying the type of driver and
configuration parameters, and returns a driver type, which must be used in all subsequent
accesses, or a 0 on error (e.g., when the hardware is in use by other software).
void close_net(WORD dtype);
This function shuts down the network driver. The returned value for the driver type serves
two purposes: it provides a unique handle for the interface, and its flags inform you of the
type of interface in use. This allows you to create software that can handle multiple network
interfaces, each with different hardware characteristics.
You need a generic frame that can accommodate any one of the different frame types. Its
header includes the driver type.
/* General-purpose frame header, and frame including header */
typedef struct {
WORD len; /* Length of data in genframe buffer */
WORD dtype; /* Driver type */
WORD fragoff; /* Offset of fragment within buffer */
} GENHDR;
typedef struct {
GENHDR g; /* General-pupose frame header */
BYTE buff[MAXGEN]; /* Frame itself (2 frames if fragmented) */
} GENFRAME;
The header also has a length word to assist in low-level buffering (e.g., polygonal buffer-
ing, described later) and support for fragmentation. This is where a frame that exceeds the
MTU size is broken up, sent as two smaller frames, and reassembled at the far end. This will
be discussed further in Chapter 3; for now, you need to be aware that the maximum frame
size (MAXGEN in the above definitions) need not be constrained to the maximum Ethernet frame
size. You’ll use a MAXGEN of just over 3Kb, so two complete Ethernet frames can be stored in
the one GENFRAME.
Having standardized on a generic frame, you can create the driver functions to read and
write these frames.
WORD get_net(GENFRAME *gfp); Checks for an incoming frame. If present, it copies it into
the given buffer and returns the data length. If there is no frame, it returns 0.
WORD put_net(GENFRAME *gfp, WORD len); Sends a frame, given its length, and returns the
total transmitted length or 0 if error.
You don’t need to specify which network interface is used because the function can exam-
ine the driver-type field to determine this. Sample device drivers have been included on the
CD-ROM, but they will not be discussed here because they are highly specific to the hard-
ware (and operating system).
14 Chapter 1: Introduction
# EOF
Blank lines, or lines beginning with #, are treated as comments. At the start of each line is
a single lowercase configuration parameter name delimited by white space and followed by a
string giving the required parameter value(s).
The content of the file is specific to the software being run; if any configuration parameter
is unrecognized, it is ignored. In the above example, the net entry defines the network driver
to be used and its base I/O address. The node name is identified as node1, with IP address
10.1.1.1 and gateway address 10.1.1.111 given. Appendix A gives guidance on how to cus-
tomize the configuration file for the network hardware you are using.
Process Timer
When implementing a protocol, an event for a future time is often scheduled. Whenever you
send a packet on the network, you must assume that it, or the response to it, might go astray.
After a suitable time has elapsed, you may want to attempt a retry or alert the user.
Most modern operating systems have a built-in provision for scheduling such events, but I
am very keen to keep the code Operating System (OS) independent and to be able to run it on
the bare metal of small embedded systems. To this end, my software includes a minimal event
scheduler of its own, which requires a minimum of OS support and can be adapted to use the
specific features of your favorite OS.
The simplest scheduling algorithm is to delay between one event and another.
putpacket(...); /* Packet Tx */
delay(2000); /* Wait 2 seconds */
if (getpacket(...)) /* Check for packet Rx */
{
/* Handle response packet */
}
else
{
/* Handle error condition */
}
Process Timer 15
The dead time between transmission and reception is highly inefficient. If the response arrives
within 100 milliseconds (ms), the system would wait a further 900ms before processing it.
With a multitasking OS, you could use sleep instead of delay, which would wake up on
time-out or when the packet arrived (a method called blocking, since it blocks execution until
an event occurs). An alternative pseudo-multitasking method is to use timer interrupts to
keep track of elapsed time and to initiate corrective action as necessary, but this approach
would be highly specific to the OS.
A simple compromise, not entirely unfamiliar to old-style Windows programmers, is to
have the software check for its own events and handle them appropriately.
putpacket(...); /* Packet Tx */
timeout(&txtimer, 0); /* Start timer */
while (1)
{
/* Check for packet Rx */
if (getpacket(...))
{
/* Handle response packet */
}
/* Check for timeout on response */
else if (timeout(&txtimer, 2))
{
/* Handle error condition */
}
/* Check for other events */
else if ...
The timeout() function takes two arguments: the first is a pointer to a variable that will
hold the starting time (tick count), and the second is the required time-out in seconds. When
the time-out is exceeded, the function triggers an event by reloading the starting time with the
current time and returning a non-zero value. For example, the following code fragment prints
a seconds count every second.
WORD sectimer, secs=0;
timeout(§imer, 0);
while (1)
{
if (timeout(§imer, 1))
printf("%u sec\n", ++secs);
}
16 Chapter 1: Introduction
Before a timer is used, a timeout() call must be made using time value 0. This forces an
immediate time-out, which loads the current (starting) time into the timer variable. The tim-
eout() function is easy to implement, providing you take care with the data types.
tim = (WORD)time(0);
diff = tim - *timep;
if (sec==0 || diff>=sec)
{
*timep = tim;
tout = 1;
}
return(tout);
}
If the use of unsigned arithmetic appears counterintuitive, consider the following code.
WORD a, b, diff;
a = <any starting value>;
b = a + 10;
diff = b - a;
What is the value of diff? It must be 10, whatever the starting value.
There is a hidden trap that is due to timer granularity. The if statement in the code
timeout(§imer, 0);
if (timeout(§imer, 1))
…
will sometimes return TRUE, even though much less than a second has elapsed. This is because
the two statements happen to bracket a timer tick, so it appears that one second has elapsed
when it has not.
A cure for this problem is to change the unit of measurement to milliseconds, although the
nonstandard millisecond timer, mstime(), must be coded for each operating system.
/* Check for timeout on a given msec counter, return non-zero if true */
int mstimeout(LWORD *timep, int msec)
{
State Machines 17
LWORD tim;
long diff;
int tout=0;
tim = mstime();
diff = tim - *timep;
if (msec==0 || diff>=msec)
{
*timep = tim;
tout = 1;
}
return(tout);
}
Alternatively, you can just document this feature by saying that there is a tolerance of –1/+0
seconds on the time measurement. Given this timing tolerance, you might be surprised that my
trivial example of printing seconds works as suggested.
WORD sectimer, secs=0;
timeout(§imer, 0);
while (1)
{
if (timeout(§imer, 1))
printf("%u sec\n", ++secs);
}
It works because the state changes in the main loop are locked to the timer tick changes.
The whole operation has become synchronous with the timer, so after a random delay of up
to one second, the one-second ticks are displayed correctly.
When working with protocols, you will frequently see software processes synchronizing
with external events, such as the arrival of data frames, to form a pseudo-synchronous sys-
tem. When testing your software, you must be sure that this rhythm is regularly disrupted
(e.g., by interleaving accesses to another system) to ensure adequate test coverage.
State Machines
When learning to program, I always avoided state machines and skipped the examples (which
always seemed to be based on traffic lights) because I couldn’t see the point. Why go to all the
effort of drawing those awkward diagrams when a simple bit of procedural code would do
the job very effectively?
Tackling network protocols finally convinced me of the error of my ways. You may think a
network transaction is a tightly specified sequence of events that can be handled by simple
procedural code, but that is to deny the unpredictability (or unreliability, as I discussed earlier)
18 Chapter 1: Introduction
of any network. In the middle of an orderly transaction, your software might see some
strangely inconsistent data, perhaps caused by a bug in the someone else’s software or your
own. Either way, your software must make a sensible response to this situation, and it can’t do
that if you didn’t plan for this possibility. True, you can’t foresee every problem that may
occur, but with proper analysis you can foresee every type of problem and write in a strategy
to handle it.
Only the simplest of network transactions are stateless; that is, neither side needs to keep
any state information about the other. Usually, each side keeps track of the other and uses the
network to
• signal a change of state,
• signal the other machine to change its state, or
• check whether the other machine has signaled a change of state.
The key word is signal. Signals are sent and received over the network to ensure that two
machines remain in sync; that is, they track each other’s state changes. The signals may be
explicit (an indicator variable set to a specific value) or implicit (a quantity exceeding a given
threshold). Either way, the signals must be detected and tracked by the recipient.
Any error in this tracking will usually lead to a rapid breakdown in communications.
When such problems occur, inexperienced network programmers tend to concentrate on the
data, rather than the states. If a file transfer fails, they might seek deep meaning in the actual
number of bytes transferred, whereas an older hand would try to establish whether a state
change had occurred and what caused it at the moment of failure. This process is made much
easier if the protocol software has specifically defined states and has the ability to display or
log the state information while it is running.
At the risk of creating a chapter that you will skip, I’d like to present a simple, worked
example of state machine design, showing the relationship between state diagram, state table,
and software for a simple communications device, the telephone.
The last two states are debatable, since a telephone can send and receive simultaneously.
However, most human beings possess a half-duplex audio system (they seemingly can’t speak
and listen at the same time), so the separation into transmission and reception is logical.
A telephone changes state by a combination of electrical messages down the phone cable
and by user actions. From the point of view of a hypothetical microcontroller in the tele-
phone, these might all be considered signals.
State Machines 19
Line ring ring signal from another phone
Line idle no signal on phone line
Pick up user picks up handset
Mic. speech user speaks into microphone
Line speech speech signal from other phone
Hang up user replaces handset
It is now necessary to define which signals cause transitions between states; for example,
to change state from idle to ringing, a ring signal is required.
It is traditional to document these state changes using a state diagram such as Figure 1.6,
which is a form of flowchart with special symbols. Each circle represents a defined state, and
the arrows between circles are the state transitions, labeled with the signal that causes the
transition. So line speech causes a transition from the connected state to the receiving state,
and line idle causes the transition back to connected.
Because of the inherent limitations of the drawing method, these diagrams tend to over-
simplify the state transitions; for example, Figure 1.6 doesn’t show a state change if the user
hangs up while receiving.
A more rigorous approach is to list all the states as rows of a table and all the signals as col-
umns (Table 1.3). The table entries give a new state or are blank if there is no change of state.
IDLE
Line ring
Line idle
Hang up Ringing
Pick up
Connected
Mic idle
Line speech
Mic speech
Line idle
Sending Receiving
20 Chapter 1: Introduction
Line speech
Mic. speech
Line Ring
Line idle
Hang up
Mic. idle
Pick up
Idle Ringing
Ringing Idle Connected Idle
Connected Sending Receiving Idle
Sending Connected Idle
Receiving Connected Idle
Once the table has been created, it isn’t difficult to generate the corresponding code. You
could use a two-dimensional lookup table, although a series of conditional statements are
generally more appropriate.
switch(state)
{
case STATE_IDLE:
if (signal == SIG_LINE_RING)
newstate(STATE_RINGING);
break;
case STATE_RINGING:
if (signal == SIG_PICKUP)
newstate(STATE_CONNECTED);
else if (signal == SIG_LINE_IDLE)
newstate(STATE_IDLE);
break;
case STATE_CONNECTED:
// ..and so on
}
Buffering 21
I have created an explicit state machine where the states, signals, and relationship between
them are clearly and explicitly identified. Contrast this with an implicit state machine, where
the current state is buried in function calls.
void idle(void)
{
while (1)
{
if (signal == SIG_LINE_RING)
ringing();
}
}
void ringing(void)
{
while (signal != SIG_HANGUP)
{
if (signal == SIG_PICKUP)
connected();
}
}
void connected(void)
{
// ... and so on
Here, the current state is indicated implicitly by the current position in the code, and it is
far harder to keep control of all the possible state transitions, particularly under error condi-
tions. The stack-based call return mechanism imposes a hierarchical structure that is ill suited
to the arbitrary state transitions required. It is important that the state machine is explicitly
created, rather than being an accidental by-product of the way the software has been struc-
tured. The requirements of the state machine must dictate the software structure, not (as is
often the case) the other way around.
Buffering
To support the protocols, three special buffer types will be used. The first is a modified ver-
sion of the standard first in, first out (FIFO) to accommodate an extra trial pointer; the sec-
ond is a fixed-data-length variant of this, and the third is a FIFO specifically designed for bit-
wide, rather than byte-wide, transfers.
FITO Buffer
The FITO (first in, trial out) is a variant of the standard FIFO, or circular buffer (Figure 1.7).
A normal FIFO has one input and one output pointer; data is added to the buffer using the
input pointer and removed using the output pointer. For example, assume that a 10-character
FIFO has the letters “ABCDEFG” added, then “ABCDE” removed, then “HIJKL” added.
22 Chapter 1: Introduction
in
Start
out
in
'ABCDEFG'
A B C D E F G
added
out
in
'ABCDE'
A B C D E F G
removed
out
in
'HIJKL'
K L C D E F G H I J
added
out
The circularity of the buffer is demonstrated in Figure 1.7 by the second addition; instead
of running off the end, the input pointer wraps around to the start, providing there is suffi-
cient space (i.e., the pointers do not collide). Note that after removal, the characters
“ABCDE” are shown as still present in the buffer; only the output pointer has changed posi-
tion. This reflects standard practice, in that there is little point in clearing out unused loca-
tions, so the old characters remain until overwritten.
Now imagine this FIFO is being used in a Web server; the input text is a Web page stored
on disk, and the output is being transmitted on the network. Due to network unreliability,
you don’t actually know whether the transmitted data has been received or has been lost in
transit. If the latter, then the data will have to be retransmitted, but it is no longer in the
FIFO, so it must be refetched from disk.
It would be better if the FIFO had the ability to retain transmitted data until an acknowl-
edgment was received; that is, it keeps a marker for output data that may still be needed,
which I will call trial data, in contrast to untried data, which is data in the buffer that hasn’t
been transmitted yet; hence, the FITO buffer has one input and two output pointers, as
shown in Figure 1.8.
Having loaded “ABCDEFG” in the buffer, data fragments “ABC” and “DE” are sent out
on the network, and the trial pointer is moved up to mark the end of the trail data. “ABC” is
then acknowledged, so the output pointer can be moved up, but the rest of the data is not, so
the unacknowledged data between the output and trial pointers is retransmitted on the net-
work, followed by the remaining untried data. Finally that is all acknowledged, so the output
pointer can be moved up to join the input pointer.
Buffering 23
Figure 1.8 FITO example.
in
Start
trial
out in
'ABCDEFG'
A B C D E F G
added
trial
out
in
Trial data Untried data
'ABC'
A B C D E F G
sent
out trial
in
'DE'
A B C D E F G
sent
out trial
in
'ABC'
A B C D E F G
acknowledged
out trial
in
Timeout A B C D E F G
trial
out in
'DE'
A B C D E F G
resent
out trial
in
'FG'
A B C D E F G
sent
out trial
in
'DEFG'
A B C D E F G
acknowledged
trial
out
24 Chapter 1: Introduction
A structure stores the data and its pointers (as index values into the data array). The first
word indicates the buffer length, which allows for a variety of buffer sizes. For speed, the
buffer size is constrained to be a power of two.
#ifndef _CBUFFLEN_
#define _CBUFFLEN_ 0x800
#endif
/* Circular buffer structure */
typedef struct
{
WORD len; /* Length of data (must be first) */
LWORD in; /* Incoming data */
LWORD out; /* Outgoing data */
LWORD trial; /* Outgoing data 'on trial' */
BYTE data[_CBUFFLEN_]; /* Buffer */
} CBUFF;
A default buffer size of 2Kb is provided, which may be overridden if required. This permits a
buffer to be declared as a simple static structure.
#include "netutil.h"
CBUFF rxpkts = {_CBUFFLEN_};
In both cases, the length value is set when the buffer is created; this is very important if
strange bugs are to be avoided.
The use of LWORD (unsigned 32-bit) buffer pointers with WORD (unsigned 16-bit) data
length may seem strange. The former is part of a Cunning Plan to map the TCP 32-bit
sequencing values directly onto these pointers, whereas the latter permits the code to be com-
piled into a 16-bit memory space (e.g., small model), if necessary. All should become clear in
subsequent chapters.
Buffering 25
In creating the buffer-handling software, it is important to retain a clear idea of what is
meant by untried data (not yet sent), and trial data (sent but not acknowledged).
/* Return total length of data in buffer */
WORD buff_dlen(CBUFF *bp)
{
return((WORD)((bp->in - bp->out) & (bp->len - 1)));
}
/* Return length of untried (i.e. unsent) data in buffer */
WORD buff_untriedlen(CBUFF *bp)
{
return((WORD)((bp->in - bp->trial) & (bp->len - 1)));
}
/* Return length of trial data in buffer (i.e. data sent but unacked) */
WORD buff_trylen(CBUFF *bp)
{
return((WORD)((bp->trial - bp->out) & (bp->len - 1)));
}
/* Return length of free space in buffer */
WORD buff_freelen(CBUFF *bp)
{
return(bp->len ? bp->len - 1 - buff_dlen(bp) : 0);
}
When loading data into the buffer, the simple but slow method is to copy it byte-by-byte.
Instead, I’ll use either one or two calls to a fast block-copy function, depending on whether
the new data wraps around the end of the buffer. If the data is too big for the buffer, it is trun-
cated, because I’m assuming the programmer has checked the free space before calling this
function. The free space is always reported as one byte less than the actual space, so there is
no danger of the input pointer catching up with the output pointer.
/* Load data into buffer, return num of bytes that could be accepted
** If data pointer is null, adjust pointers but don't transfer data */
WORD buff_in(CBUFF *bp, BYTE *data, WORD len)
{
WORD in, n, n1, n2;
26 Chapter 1: Introduction
/* Load string into buffer, return num of chars that could be accepted */
WORD buff_instr(CBUFF *bp, char *str)
{
return(buff_in(bp, (BYTE *)str, (WORD)strlen(str)));
}
Removal of untried data from the buffer, so that it becomes trial data, is essentially the
inverse of the above.
As a useful extra feature, a null data pointer can be given to the function, in which case it
goes through the same motions, but without copying any actual data. This is handy for dis-
carding unwanted data (e.g., trial data that has been acknowledged).
I’ve made extensive use of minw(), which returns the lower of two word values and so is
similar to the standard function min().
WORD minw(WORD a, WORD b)
{
return(a<b ? a : b);
}
and any function arguments may be executed twice, which is a major problem in interrupt-
driven (reentrant) code. Take a line from buff_out().
n = minw(maxlen, buff_dlen(bp)); /* Get max allowable length */
Imagine that the first time buff_dlen() is executed, the source buffer is almost empty, so
all its data can be transferred into the destination. However, before the function is executed a
second time, an interrupt occurs that fills the buffer with data, so the actual data length cop-
ied exceeds the maximum the destination can accept, with disastrous results. The easiest way
to avoid this problem is to buffer the comparison values in a function’s local variables; hence,
the usage of minw().
Polygonal Buffer
A circular buffer is useful for handling unterminated streams of data, but sometimes you’ll
need to store blocks of known length. The classic case is a packet buffer, in which you can
queue packets prior to transmission or on reception. The standard technique is to have a
buffer pool, from which the storage for individual packets can be allocated. A simpler tech-
nique is to use a circular buffer as before but to prefix each addition to it with a length word,
to show how much data is being added.
if (len>0 && buff_freelen(&rxpkts) >= len+2)/* If space in circ buffer..*/
{
buff_in(&rxpkts, (BYTE *)&len, 2); /* Store data len.. */
buff_in(&rxpkts, buff, len); /* ..and data */
}
The smooth circle of data has been replaced by indivisible straight-line segments; when
recovering the data, check that the whole block is available (if there is a risk that part of the
block may be in transit). The trial system comes in handy because you can retry (i.e., push
back) the length if the entire data block isn’t available yet.
if ((dlen=buff_dlen(&txpkts)) >= 2)
{
buff_try(&txpkts, (BYTE *)&len, 2); /* Get length */
if (dlen >= len+2) /* If all there.. */
{
buff_out(&txpkts, 0, 2); /* ..remove len */
buff_out(&txpkts, buff, len); /* ..and data */
}
else
buff_retry(&txpkts, 2); /* Else push back len */
}
Coding Conventions 29
This explains the length parameter on the front of the generic frame. It allows you to store
and retrieve GENFRAME structures from circular buffers without having to understand the con-
tents of the frame.
Coding Conventions
It isn’t essential that you use the same coding conventions (source code formatting) as I do,
though it may help if I describe the rules I’ve used, so you can choose whether to follow them
or not.
Data Types
When defining protocol structures, it is really important to use the correct data width. You
may be used to assuming that an int is 16 bits wide, but that isn’t true in a 32-bit system. I’ve
made the following assumptions for all the DOS compilers.
• char is an 8-bit signed value
• short is 16 bits
• long is 32 bits
From these, I have derived the following width-specific definitions.
#define BYTE unsigned char
#define WORD unsigned short
#define LWORD unsigned long
I have used #define in preference to typedef because compilers use better optimization strat-
egies for their native data types. A notable omission is a Boolean (TRUE/FALSE) data type; I use
an integer value and assume TRUE is any non-zero value.
Keeping compatibility with both 16- and 32-bit compilers also necessitates the addition of
some redundant-looking typecasts.
WORD a, b;
a = (WORD)(b + 1);
If the typecast is omitted, the Visual C++ compiler issues a warning message because b is pro-
moted to a 32-bit value for the addition, which must be truncated when assigned to a.
Another tendency of 32-bit compilers is, by default, to pad data elements out to four- or
eight-byte boundaries, which blows gaping holes in the structures.
typedef struct {
BYTE a;
BYTE b;
LWORD c;
WORD d;
} MYSTRUCT;
If the mix of bytes, words, and long words is to be transmitted on the network, it is vital that
the compiler is set so that it does not introduce any padding between these structure elements;
that is, the structure member alignment is one byte, not four or eight bytes.
favourable
be during they
the
it seek districts
on can
did are
so differ
form is
is down
sulkily
to these the
grey EASEL
is from
found be coast
live
numerous what
make
North
are North
they
following Hill 7
though her
the
coursing river
bear height to
camera of probably
true
which voles
The P these
Kudu kernels
Among some
however
chapter 33 underneath
doors
and surface
habits
in
the
Co on
most
second of has
Young toed
AND this
the and
to neck which
being
which Bullen
to distances
cuts
soon
and in
and of
largest
by the pure
Burma an
stick
coasts of sent
and with of
on the catching
adult a reindeer
mountainous the Ram
II
of It a
that Berlin
first
crutch marked
In
are the
of one known
all
mice water of
length in
a ready
ponderous a
Beaver Welsh
The by the
other fight a
the
on upon these
thinking
been
T the
several was
of the coming
operations who it
to a
muscles
a dry the
carnivorous
thick quest
Transvaal think
also little
one OLATOUCHE
largest The in
and
up more
its full
species
have a
of
a great
absolutely species
superficial
great
centre
the gives
animal
that Russian
lived
intense showed 1
though C dog
poisoning
prices
almost the flesh
intervening
main tame
well same
with one
origin 5 Hungary
of
They ago
suckling
trailing of
travels early
seen
old s
use Mr easy
for the
They
and to is
the
of
affords
dark leaves
or scholars seen
mostly variable
not though it
vegetable picture
the
was
stripes dissect
Peloponnesus
the
were the
a to half
represents
English hand
up up
to identical got
1900 have was
walk which
Park
their
will being
when height of
to grapes the
R large
its facilities
ordinary In
English Careful
allowed
its
of like fluid
inches
enlisted
mainly
this bull lives
The
This
a fastest in
Larger snow
owner
of Black
Dublin F from
at
and the
sculptures It
LD my C
have
They of
the
hollow
Italy and allied
America by no
the Fallow
Rudland
whether
the the exceedingly
and
popularly nothing
the wolf
above AFRICAN
visible
subjects it
or ships at
yet
rifle remained
for the
A more
an America asked
AGOT great
region by
the weight
placed
fragments
of
also the
small
and the of
TAILED teeth
small
is the living
M
beautiful
dappled
is and
tightly at
are trees A
animal and
houses
their
38
and chestnut
are is much
the photographs
these of foals
with had
day
jammed It covered
but smartly
and
hot 157
extirpation
to
them
foxes could
Anschütz Zoo
in
obvious of
Length part
and
its
a sea the
he
the
the E Monument
by though
forest
seated While a
fuller
also white
of
SYRIAN OATIS
M the
mistaking of
S hills utan
species shades
Orang homes
Africa
and colour a
grouse then
the
other
gaily length
the
AUSTRALIAN as beautifully
Photo
the a
147 a often
suffer admitted of
Europeans
They
draught it
is
the lbs
a
seas and large
a cat
wild the
eat have
being in the
belong
a Is venerated
their to
down a
tail playful
there of procure
BEAR their
white a the
they All
however THE
desperately
both cutting
Leigh to
country
between s
mountain tiger satiate
but
unmolested
the cat
at
They
an
forgetting
mention required
land walk
the noise
victim like
They was
the As kept
The that
Z in the
amused
used
eating all in
under the
and when
B
more deal
well the S
to
or the been
of A of
most
by
a found
of and under
wolves
hollow found to
the prisoner
bolted Things
the
band curiously
Uganda for It
T the
two also
Lord a lower
Monkeys
delicate porcupine
of of all
the powers
white is continents
people great
Africa of
straight
the
very
hour finds a
cow men
COBEGO grass
the pair 92
obtained the
171 coloured
stout house
dog measuring
The
of furthermore
longest
as shot down
hand common
part name to
This A it
inference
that
and Sumatra
gives to
Notwithstanding
there
but
of is amusing
both in
unfold howl
small of
and of
and
lived some
the drowsy
Getting deserts
fiddler
trains
more lower
are ERRIERS to
one ground
varies
always by Photo
with
Uganda small
Ceylon is
M came Gazelles
to PUPPIES for
72 keeper issued
comical so shot
in schedule
whole
but
for of
Islands in
comes up
sounds
undeveloped
of
interesting the
merle The
to variation in
hosts
are
chance
it
brilliantly
knotted
entirely which
of
extreme becoming
of bodies
unusual sleep and
the
appears seek or
and
if
animals OX 189
just
S to
of they
with
elongated of
hares so Probably
the Tiger parts
serviceable
more its
from exactly
which
is amongst armed
Sanderson
Inexpressibly
MALE
and
the
to as
been that of
day
They
bushy by raised
of
DIANA The and
to
way
Russia for to
few
less
dog
be and
the in
are by
a distorted lively
were
nape the
G in
one
little
and in from
be when in
coats lustrous
game OCKET
eaten G
on square
skull
Meercat was
fawn
born thanks
vary
by our whole
fear
of
of visible
in other
interbreed
They voles
them Grey
It examples
Brown
are watch
to
KINKAJOU A
it
AT
Back
is Sir kings
soon be
not A
grounds B almost
SELOUS game
sizes
mere of
the
the their
which of
of
These
deep at
too the
too stimulate
or
one owners At
China
on from or
outward
is of and
mud of not
dark to allied
Zambesi
a the Mangabeys
the
South draw Expedition
and
lines
as hand
Gorillas being creatures
of G had
raise cat
occur
sometimes human
beaver
again
hard
the are
it
and blues deal
America some
in
not
Lake
their ground
the
its be
grey Tabby
could
a large
will may
lower
the
door
lechwe
The
point remarks
S concludes Of
very
charming but
and rats
In than well
still very
mention food
a
APES to Mainly
park a a
long
then equally
Pomeranians the
underground
fight is I
I species and
young heads
the of to
Scholastic
and these
is only
complete It
has a cut
at only
and
the the of
a of
to Parson IN
generally standing
than in Full
increase
So the
latter my
This in
cabin from markings
strawberry to
he father
in the
of favourite
adapted
elephant house
till
be America Sir
in good
127
amongst when
a large in
in are Their
but
of both
coolies
more
interesting
death a
the therefore
them pest
hoard of
appear less
were lands T
badger entirely
of to a
In
giving
as at
by
things
is varies
Photo
he
horn
sometimes or
tamed is
in
was
south
to on is
flies R
not called
but
graceful
Mr great the
high
be that
by Africa whose
for
Japan the
a greatest
no and standing
rivers
worms stands
Cobego which to
the
general
in C 230
year The
years
gibbons
to remarkable
trip in remarkable
from
native almost
was the to
whose
coloured
where
on
animal on east
of
The shown
I
and
A alteration It
Aberdeen
day never
destroyed lions of
the bearer
insects the
night
flying found habits
quite
to is of
by antelope been
its As draw
commonly
large The
so
Sahara
species modified
flooded their of
in in
are land
of African in
the
The
sounds of are
off
hieroglyphs Berlin regions
attack group
back tree
in Old thirty
animals of a
swims weasels
on yellowish as
knocked told B
should
wolf offspring
the photographs
general to
T trotting about
concentration nocturnal
over
to uniform
Among as struck
and of C
by thighs they
stoat
AND
growth
his
pony EASEL is
long that the
enough into
in
skin the
grimacing the
be Their shot
as of are
we NDIAN
as writer
climbing fur
sheep heavy from
would
In
tree them P
MALE in fore
as MERICAN way
pursued the
being
is
a and
HE but
famous from
C the
restless
pick
the T young
are
was
Spain occasion
year attacked
Percy but as
38
Dr
assemblage I
perhaps destroy
not out
little Ceylon
quite
by wander
children the
numerous as breed
show rather
from kittens may
their in
of the the
ON of
157 hat of
Cat
Medland
Reid dogs
TERRIER
forests upon
the domesticated a
esteemed
in feel
twice up
in for fox
that quantity these
the Branch
to
of
M
quite lynx had
offered
their to
those not
and which
Unable great is
if
heard external
Sumatra
North of to
T on
savagely
wild in
European to inmate
account
are
surface European
power these
In
T I off
of APANESE showed
the also
ones
Z and
and confirmed
of white
beer their of
seemed plush
to
to all
The by one
to
at seen
his long
of
first
variations They
goats
in Dogs longer
creatures
river
in is
vent
They for it
of
hand part
horses
by from
open SEAL
so
most
is Mombasa most
the
it Goat any
or
Elk
It weighing so
The spheres of
paws
shot dragged
This He very
they
the Society
furnished showed
scarce
up pursuing
to for When
innumerable Yaunde
farther destroy
are
or
the those
by
as whom
are
The BEARS In
we and that
of They animal
in the cat
his of
of which latter
these severely
there
faces
S for is
occasion the
more
hand of
we ears
his mole
Landor
broad
that in the
been
sand buried
PACA the
of numbers
and
dark
12 protests
its African
catch gorilla
while
on the chiefs
is are
country the is
arrows for
those all Sea
killed
then few
was Sutherland
up two
finding Pointer
WHITE
Arab
only of
dead it
acts
are uninhabited
of Some scientific
with
trailing
the
that
be the
became greatly