aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormarkster <markster@f38db490-d61c-443f-a65b-d21fe96a405b>2004-02-24 21:27:16 +0000
committermarkster <markster@f38db490-d61c-443f-a65b-d21fe96a405b>2004-02-24 21:27:16 +0000
commit89db87c9d3523bcfd15441319322b6995a2bd02a (patch)
tree5ad5cad590bef104aa467e13d3758947336fec8c
parent90ee5c10094b0bcec991c1944dd2d142f9682e4f (diff)
Add IAX2 firmware upgrade support
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@2234 f38db490-d61c-443f-a65b-d21fe96a405b
-rwxr-xr-xMakefile2
-rwxr-xr-xchannels/chan_iax2.c282
-rwxr-xr-xchannels/iax2-parser.c38
-rwxr-xr-xchannels/iax2-parser.h6
-rwxr-xr-xchannels/iax2.h17
5 files changed, 340 insertions, 5 deletions
diff --git a/Makefile b/Makefile
index 3cc44afff..d0fe708c4 100755
--- a/Makefile
+++ b/Makefile
@@ -328,6 +328,8 @@ bininstall: all
mkdir -p $(DESTDIR)$(ASTVARLIBDIR)/sounds
mkdir -p $(DESTDIR)$(ASTLOGDIR)/cdr-csv
mkdir -p $(DESTDIR)$(ASTVARLIBDIR)/keys
+ mkdir -p $(DESTDIR)$(ASTVARLIBDIR)/firmware
+ mkdir -p $(DESTDIR)$(ASTVARLIBDIR)/firmware/iax
install -m 644 keys/iaxtel.pub $(DESTDIR)$(ASTVARLIBDIR)/keys
( cd $(DESTDIR)$(ASTVARLIBDIR)/sounds ; ln -s $(ASTSPOOLDIR)/vm . )
( cd $(DESTDIR)$(ASTVARLIBDIR)/sounds ; ln -s $(ASTSPOOLDIR)/voicemail . )
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index 35d43ef95..300ca765c 100755
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -33,7 +33,9 @@
#include <asterisk/app.h>
#include <asterisk/astdb.h>
#include <asterisk/musiconhold.h>
+#include <sys/mman.h>
#include <arpa/inet.h>
+#include <dirent.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
@@ -58,6 +60,7 @@
#endif
#include "iax2.h"
#include "iax2-parser.h"
+#include "../astconf.h"
#ifndef IPTOS_MINCOST
#define IPTOS_MINCOST 0x02
@@ -240,6 +243,15 @@ struct iax2_peer {
int notransfer;
};
+struct iax_firmware {
+ struct iax_firmware *next;
+ int fd;
+ int mmaplen;
+ int dead;
+ struct ast_iax2_firmware_header *fwh;
+ unsigned char *buf;
+};
+
#define REG_STATE_UNREGISTERED 0
#define REG_STATE_REGSENT 1
#define REG_STATE_AUTHSENT 2
@@ -434,6 +446,11 @@ static struct ast_peer_list {
ast_mutex_t lock;
} peerl;
+static struct ast_firmware_list {
+ struct iax_firmware *wares;
+ ast_mutex_t lock;
+} waresl;
+
/* Extension exists */
#define CACHE_FLAG_EXISTS (1 << 0)
/* Extension is non-existant */
@@ -851,6 +868,216 @@ static int iax2_queue_frame(int callno, struct ast_frame *f)
return 0;
}
+static void destroy_firmware(struct iax_firmware *cur)
+{
+ /* Close firmware */
+ if (cur->fwh) {
+ munmap(cur->fwh, ntohl(cur->fwh->datalen) + sizeof(*(cur->fwh)));
+ }
+ close(cur->fd);
+ free(cur);
+}
+
+static int try_firmware(char *s)
+{
+ struct stat stbuf;
+ struct iax_firmware *cur;
+ int fd;
+ int res;
+ struct ast_iax2_firmware_header *fwh, fwh2;
+ struct MD5Context md5;
+ unsigned char sum[16];
+ res = stat(s, &stbuf);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Failed to stat '%s': %s\n", s, strerror(errno));
+ return -1;
+ }
+ /* Make sure it's not a directory */
+ if (S_ISDIR(stbuf.st_mode))
+ return -1;
+ fd = open(s, O_RDONLY);
+ if (fd < 0) {
+ ast_log(LOG_WARNING, "Cannot open '%s': %s\n", s, strerror(errno));
+ return -1;
+ }
+ if ((res = read(fd, &fwh2, sizeof(fwh2))) != sizeof(fwh2)) {
+ ast_log(LOG_WARNING, "Unable to read firmware header in '%s'\n", s);
+ close(fd);
+ return -1;
+ }
+ if (ntohl(fwh2.magic) != IAX_FIRMWARE_MAGIC) {
+ ast_log(LOG_WARNING, "'%s' is not a valid firmware file\n", s);
+ close(fd);
+ return -1;
+ }
+ if (ntohl(fwh2.datalen) != (stbuf.st_size - sizeof(fwh2))) {
+ ast_log(LOG_WARNING, "Invalid data length in firmware '%s'\n", s);
+ close(fd);
+ return -1;
+ }
+ if (fwh2.devname[sizeof(fwh2.devname) - 1] || !strlen(fwh2.devname)) {
+ ast_log(LOG_WARNING, "No or invalid device type specified for '%s'\n", s);
+ close(fd);
+ return -1;
+ }
+ fwh = mmap(NULL, stbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (!fwh) {
+ ast_log(LOG_WARNING, "mmap failed: %s\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ MD5Init(&md5);
+ MD5Update(&md5, fwh->data, ntohl(fwh->datalen));
+ MD5Final(sum, &md5);
+ if (memcmp(sum, fwh->chksum, sizeof(sum))) {
+ ast_log(LOG_WARNING, "Firmware file '%s' fails checksum\n", s);
+ munmap(fwh, stbuf.st_size);
+ close(fd);
+ return -1;
+ }
+ cur = waresl.wares;
+ while(cur) {
+ if (!strcmp(cur->fwh->devname, fwh->devname)) {
+ /* Found a candidate */
+ if (cur->dead || (ntohs(cur->fwh->version) < ntohs(fwh->version)))
+ /* The version we have on loaded is older, load this one instead */
+ break;
+ /* This version is no newer than what we have. Don't worry about it.
+ We'll consider it a proper load anyhow though */
+ munmap(fwh, stbuf.st_size);
+ close(fd);
+ return 0;
+ }
+ cur = cur->next;
+ }
+ if (!cur) {
+ /* Allocate a new one and link it */
+ cur = malloc(sizeof(struct iax_firmware));
+ if (cur) {
+ memset(cur, 0, sizeof(struct iax_firmware));
+ cur->fd = -1;
+ cur->next = waresl.wares;
+ waresl.wares = cur;
+ }
+ }
+ if (cur) {
+ if (cur->fwh) {
+ munmap(cur->fwh, cur->mmaplen);
+ }
+ if (cur->fd > -1)
+ close(cur->fd);
+ cur->fwh = fwh;
+ cur->fd = fd;
+ cur->mmaplen = stbuf.st_size;
+ cur->dead = 0;
+ }
+ return 0;
+}
+
+static int iax_check_version(char *dev)
+{
+ int res = 0;
+ struct iax_firmware *cur;
+ if (dev && strlen(dev)) {
+ ast_mutex_lock(&waresl.lock);
+ cur = waresl.wares;
+ while(cur) {
+ if (!strcmp(dev, cur->fwh->devname)) {
+ res = ntohs(cur->fwh->version);
+ break;
+ }
+ cur = cur->next;
+ }
+ ast_mutex_unlock(&waresl.lock);
+ }
+ return res;
+}
+
+static int iax_firmware_append(struct iax_ie_data *ied, const unsigned char *dev, unsigned int desc)
+{
+ int res = -1;
+ unsigned int bs = desc & 0xff;
+ unsigned int start = (desc >> 8) & 0xffffff;
+ unsigned int bytes;
+ struct iax_firmware *cur;
+ if (dev && strlen(dev) && bs) {
+ start *= bs;
+ ast_mutex_lock(&waresl.lock);
+ cur = waresl.wares;
+ while(cur) {
+ if (!strcmp(dev, cur->fwh->devname)) {
+ iax_ie_append_int(ied, IAX_IE_FWBLOCKDESC, desc);
+ if (start < ntohl(cur->fwh->datalen)) {
+ bytes = ntohl(cur->fwh->datalen) - start;
+ if (bytes > bs)
+ bytes = bs;
+ iax_ie_append_raw(ied, IAX_IE_FWBLOCKDATA, cur->fwh->data + start, bytes);
+ } else {
+ bytes = 0;
+ iax_ie_append(ied, IAX_IE_FWBLOCKDATA);
+ }
+ if (bytes == bs)
+ res = 0;
+ else
+ res = 1;
+ break;
+ }
+ cur = cur->next;
+ }
+ ast_mutex_unlock(&waresl.lock);
+ }
+ return res;
+}
+
+
+static void reload_firmware(void)
+{
+ struct iax_firmware *cur, *curl, *curp;
+ DIR *fwd;
+ struct dirent *de;
+ char dir[256];
+ char fn[256];
+ /* Mark all as dead */
+ ast_mutex_lock(&waresl.lock);
+ cur = waresl.wares;
+ while(cur) {
+ cur->dead = 1;
+ cur = cur->next;
+ }
+ /* Now that we've freed them, load the new ones */
+ snprintf(dir, sizeof(dir), "%s/firmware/iax", (char *)ast_config_AST_VAR_DIR);
+ fwd = opendir(dir);
+ while((de = readdir(fwd))) {
+ if (de->d_name[0] != '.') {
+ snprintf(fn, sizeof(fn), "%s/%s", dir, de->d_name);
+ if (!try_firmware(fn)) {
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Loaded firmware '%s'\n", de->d_name);
+ }
+ }
+ }
+ closedir(fwd);
+
+ /* Clean up leftovers */
+ cur = waresl.wares;
+ curp = NULL;
+ while(cur) {
+ curl = cur;
+ cur = cur->next;
+ if (curl->dead) {
+ if (curp) {
+ curp->next = cur;
+ } else {
+ waresl.wares = cur;
+ }
+ destroy_firmware(curl);
+ } else {
+ curp = cur;
+ }
+ }
+ ast_mutex_unlock(&waresl.lock);
+}
+
static int iax2_send(struct chan_iax2_pvt *pvt, struct ast_frame *f, unsigned int ts, int seqno, int now, int transfer, int final);
static int __do_deliver(void *data)
@@ -2614,6 +2841,27 @@ static int iax2_show_peers(int fd, int argc, char *argv[])
#undef FORMAT2
}
+static int iax2_show_firmware(int fd, int argc, char *argv[])
+{
+#define FORMAT2 "%-15.15s %-15.15s %-15.15s\n"
+#define FORMAT "%-15.15s %-15.4x %-15d\n"
+ struct iax_firmware *cur;
+ if ((argc != 3) && (argc != 4))
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&waresl.lock);
+
+ ast_cli(fd, FORMAT2, "Device", "Version", "Size");
+ for (cur = waresl.wares;cur;cur = cur->next) {
+ if ((argc == 3) || (!strcasecmp(argv[3], cur->fwh->devname)))
+ ast_cli(fd, FORMAT, cur->fwh->devname, ntohs(cur->fwh->version),
+ ntohl(cur->fwh->datalen));
+ }
+ ast_mutex_unlock(&waresl.lock);
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
/* JDG: callback to display iax peers in manager */
static int manager_iax2_show_peers( struct mansession *s, struct message *m )
{
@@ -2742,6 +2990,10 @@ static char show_peers_usage[] =
"Usage: iax2 show peers\n"
" Lists all known IAX peers.\n";
+static char show_firmware_usage[] =
+"Usage: iax2 show firmware\n"
+" Lists all known IAX firmware images.\n";
+
static char show_reg_usage[] =
"Usage: iax2 show registry\n"
" Lists all registration requests and status.\n";
@@ -2760,6 +3012,8 @@ static char debug_trunk_usage[] =
static struct ast_cli_entry cli_show_users =
{ { "iax2", "show", "users", NULL }, iax2_show_users, "Show defined IAX users", show_users_usage };
+static struct ast_cli_entry cli_show_firmware =
+ { { "iax2", "show", "firmware", NULL }, iax2_show_firmware, "Show available IAX firmwares", show_firmware_usage };
static struct ast_cli_entry cli_show_channels =
{ { "iax2", "show", "channels", NULL }, iax2_show_channels, "Show active IAX channels", show_channels_usage };
static struct ast_cli_entry cli_show_peers =
@@ -3606,13 +3860,14 @@ static void reg_source_db(struct iax2_peer *p)
}
}
-static int update_registry(char *name, struct sockaddr_in *sin, int callno)
+static int update_registry(char *name, struct sockaddr_in *sin, int callno, char *devtype)
{
/* Called from IAX thread only, with proper iaxsl lock */
struct iax_ie_data ied;
struct iax2_peer *p;
int msgcount;
char data[80];
+ int version;
memset(&ied, 0, sizeof(ied));
for (p = peerl.peers;p;p = p->next) {
if (!strcasecmp(name, p->name)) {
@@ -3666,6 +3921,9 @@ static int update_registry(char *name, struct sockaddr_in *sin, int callno)
if (p->hascallerid)
iax_ie_append_str(&ied, IAX_IE_CALLING_NAME, p->callerid);
}
+ version = iax_check_version(devtype);
+ if (version)
+ iax_ie_append_short(&ied, IAX_IE_FIRMWAREVER, version);
if (p->temponly)
free(p);
return send_command_final(iaxs[callno], AST_FRAME_IAX, IAX_COMMAND_REGACK, 0, ied.buf, ied.pos, -1);
@@ -4174,7 +4432,7 @@ static int socket_read(int *id, int fd, short events, void *cbdata)
f.subclass = uncompress_subclass(fh->csub);
}
if ((f.frametype == AST_FRAME_IAX) && ((f.subclass == IAX_COMMAND_NEW) || (f.subclass == IAX_COMMAND_REGREQ)
- || (f.subclass == IAX_COMMAND_POKE)))
+ || (f.subclass == IAX_COMMAND_POKE) || (f.subclass == IAX_COMMAND_FWDOWNL)))
new = NEW_ALLOW;
} else {
/* Don't knwo anything about it yet */
@@ -4195,7 +4453,8 @@ static int socket_read(int *id, int fd, short events, void *cbdata)
/* We can only raw hangup control frames */
if (((f.subclass != IAX_COMMAND_INVAL) &&
(f.subclass != IAX_COMMAND_TXCNT) &&
- (f.subclass != IAX_COMMAND_TXACC))||
+ (f.subclass != IAX_COMMAND_TXACC) &&
+ (f.subclass != IAX_COMMAND_FWDOWNL))||
(f.frametype != AST_FRAME_IAX))
raw_hangup(&sin, ntohs(fh->dcallno) & ~IAX_FLAG_RETRANS, ntohs(mh->callno) & ~IAX_FLAG_FULL
);
@@ -4791,7 +5050,7 @@ retryowner:
if ((!strlen(iaxs[fr.callno]->secret) && !strlen(iaxs[fr.callno]->inkeys)) || (iaxs[fr.callno]->state & IAX_STATE_AUTHENTICATED)) {
if (f.subclass == IAX_COMMAND_REGREL)
memset(&sin, 0, sizeof(sin));
- if (update_registry(iaxs[fr.callno]->peer, &sin, fr.callno))
+ if (update_registry(iaxs[fr.callno]->peer, &sin, fr.callno, ies.devicetype))
ast_log(LOG_WARNING, "Registry error\n");
break;
}
@@ -4884,6 +5143,17 @@ retryowner:
case IAX_COMMAND_UNSUPPORT:
ast_log(LOG_NOTICE, "Peer did not understand our iax command '%d'\n", ies.iax_unknown);
break;
+ case IAX_COMMAND_FWDOWNL:
+ /* Firmware download */
+ memset(&ied0, 0, sizeof(ied0));
+ res = iax_firmware_append(&ied0, ies.devicetype, ies.fwdesc);
+ if (res < 0)
+ send_command_final(iaxs[fr.callno], AST_FRAME_IAX, IAX_COMMAND_REJECT, 0, ied0.buf, ied0.pos, -1);
+ else if (res > 0)
+ send_command_final(iaxs[fr.callno], AST_FRAME_IAX, IAX_COMMAND_FWDATA, 0, ied0.buf, ied0.pos, -1);
+ else
+ send_command(iaxs[fr.callno], AST_FRAME_IAX, IAX_COMMAND_FWDATA, 0, ied0.buf, ied0.pos, -1);
+ break;
default:
ast_log(LOG_DEBUG, "Unknown IAX command %d on %d/%d\n", f.subclass, fr.callno, iaxs[fr.callno]->peercallno);
memset(&ied0, 0, sizeof(ied0));
@@ -5782,6 +6052,7 @@ static int reload_config(void)
for (peer = peerl.peers; peer; peer = peer->next)
iax2_poke_peer(peer, 0);
ast_mutex_unlock(&peerl.lock);
+ reload_firmware();
return 0;
}
@@ -6154,6 +6425,7 @@ static int __unload_module(void)
ast_cli_unregister(&cli_show_users);
ast_cli_unregister(&cli_show_channels);
ast_cli_unregister(&cli_show_peers);
+ ast_cli_unregister(&cli_show_firmware);
ast_cli_unregister(&cli_show_registry);
ast_cli_unregister(&cli_debug);
ast_cli_unregister(&cli_trunk_debug);
@@ -6220,6 +6492,7 @@ int load_module(void)
ast_cli_register(&cli_show_users);
ast_cli_register(&cli_show_channels);
ast_cli_register(&cli_show_peers);
+ ast_cli_register(&cli_show_firmware);
ast_cli_register(&cli_show_registry);
ast_cli_register(&cli_debug);
ast_cli_register(&cli_trunk_debug);
@@ -6273,6 +6546,7 @@ int load_module(void)
for (peer = peerl.peers; peer; peer = peer->next)
iax2_poke_peer(peer, 0);
ast_mutex_unlock(&peerl.lock);
+ reload_firmware();
return res;
}
diff --git a/channels/iax2-parser.c b/channels/iax2-parser.c
index 8ffbd2c69..32d40d129 100755
--- a/channels/iax2-parser.c
+++ b/channels/iax2-parser.c
@@ -120,6 +120,11 @@ static struct iax2_ie {
{ IAX_IE_PROVISIONING, "PROVISIONING" },
{ IAX_IE_AESPROVISIONING, "AES PROVISIONING" },
{ IAX_IE_DATETIME, "DATE TIME", dump_int },
+ { IAX_IE_DEVICETYPE, "DEVICE TYPE", dump_string },
+ { IAX_IE_SERVICEIDENT, "SERVICE IDENT", dump_string },
+ { IAX_IE_FIRMWAREVER, "FIRMWARE VER", dump_short },
+ { IAX_IE_FWBLOCKDESC, "FW BLOCK DESC", dump_int },
+ { IAX_IE_FWBLOCKDATA, "FW BLOCK DATA" },
};
const char *iax_ie2str(int ie)
@@ -158,7 +163,11 @@ static void dump_ies(unsigned char *iedata, int len)
snprintf(tmp, sizeof(tmp), " %-15.15s : %s\n", ies[x].name, interp);
outputf(tmp);
} else {
- snprintf(tmp, sizeof(tmp), " %-15.15s : Present\n", ies[x].name);
+ if (ielen)
+ snprintf(interp, sizeof(interp), "%d bytes", ielen);
+ else
+ strcpy(interp, "Present");
+ snprintf(tmp, sizeof(tmp), " %-15.15s : %s\n", ies[x].name, interp);
outputf(tmp);
}
found++;
@@ -223,6 +232,8 @@ void iax_showframe(struct iax_frame *f, struct ast_iax2_full_hdr *fhi, int rx, s
"UNSUPPORTED",
"TRANSFER",
"PROVISION",
+ "FWDOWNLD",
+ "FWDATA"
};
char *cmds[] = {
"(0?)",
@@ -363,6 +374,7 @@ int iax_parse_ies(struct iax_ies *ies, unsigned char *data, int datalen)
char tmp[256];
memset(ies, 0, sizeof(struct iax_ies));
ies->msgcount = -1;
+ ies->firmwarever = -1;
while(datalen >= 2) {
ie = data[0];
len = data[1];
@@ -507,6 +519,30 @@ int iax_parse_ies(struct iax_ies *ies, unsigned char *data, int datalen)
} else
ies->datetime = ntohl(*((unsigned int *)(data + 2)));
break;
+ case IAX_IE_FIRMWAREVER:
+ if (len != sizeof(unsigned short)) {
+ snprintf(tmp, sizeof(tmp), "Expecting firmwarever to be %d bytes long but was %d\n", sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->firmwarever = ntohs(*((unsigned short *)(data + 2)));
+ break;
+ case IAX_IE_DEVICETYPE:
+ ies->devicetype = data + 2;
+ break;
+ case IAX_IE_SERVICEIDENT:
+ ies->serviceident = data + 2;
+ break;
+ case IAX_IE_FWBLOCKDESC:
+ if (len != sizeof(unsigned int)) {
+ snprintf(tmp, sizeof(tmp), "Expected block desc to be %d bytes long but was %d\n", sizeof(unsigned int), len);
+ errorf(tmp);
+ } else
+ ies->fwdesc = ntohl(*((unsigned int *)(data + 2)));
+ break;
+ case IAX_IE_FWBLOCKDATA:
+ ies->fwdata = data + 2;
+ ies->fwdatalen = len;
+ break;
default:
snprintf(tmp, sizeof(tmp), "Ignoring unknown information element '%s' (%d) of length %d\n", iax_ie2str(ie), ie, len);
errorf(tmp);
diff --git a/channels/iax2-parser.h b/channels/iax2-parser.h
index 0e74df3e3..0b4ac5639 100755
--- a/channels/iax2-parser.h
+++ b/channels/iax2-parser.h
@@ -44,6 +44,12 @@ struct iax_ies {
int musiconhold;
unsigned int transferid;
unsigned int datetime;
+ char *devicetype;
+ char *serviceident;
+ int firmwarever;
+ unsigned int fwdesc;
+ unsigned char *fwdata;
+ unsigned char fwdatalen;
};
#define DIRECTION_INGRESS 1
diff --git a/channels/iax2.h b/channels/iax2.h
index c9cf280ca..8997a7107 100755
--- a/channels/iax2.h
+++ b/channels/iax2.h
@@ -65,6 +65,8 @@
#define IAX_COMMAND_UNSUPPORT 33 /* Unsupported message received */
#define IAX_COMMAND_TRANSFER 34 /* Request remote transfer */
#define IAX_COMMAND_PROVISION 35 /* Provision device */
+#define IAX_COMMAND_FWDOWNL 36 /* Download firmware */
+#define IAX_COMMAND_FWDATA 37 /* Firmware Data */
#define IAX_DEFAULT_REG_EXPIRE 60 /* By default require re-registration once per minute */
@@ -104,6 +106,11 @@
#define IAX_IE_PROVISIONING 29 /* Provisioning info */
#define IAX_IE_AESPROVISIONING 30 /* AES Provisioning info */
#define IAX_IE_DATETIME 31 /* Date/Time */
+#define IAX_IE_DEVICETYPE 32 /* Device Type -- string */
+#define IAX_IE_SERVICEIDENT 33 /* Service Identifier -- string */
+#define IAX_IE_FIRMWAREVER 34 /* Firmware revision -- u16 */
+#define IAX_IE_FWBLOCKDESC 35 /* Firmware block description -- u32 */
+#define IAX_IE_FWBLOCKDATA 36 /* Firmware block of data -- raw */
#define IAX_AUTH_PLAINTEXT (1 << 0)
#define IAX_AUTH_MD5 (1 << 1)
@@ -163,4 +170,14 @@ struct ast_iax2_meta_trunk_entry {
unsigned short len; /* Length of data for this callno */
} __attribute__ ((__packed__));
+#define IAX_FIRMWARE_MAGIC 0x69617879
+
+struct ast_iax2_firmware_header {
+ unsigned int magic; /* Magic number */
+ unsigned short version; /* Software version */
+ unsigned char devname[16]; /* Device */
+ unsigned int datalen; /* Data length of file beyond header */
+ unsigned char chksum[16]; /* Checksum of all data */
+ unsigned char data[0];
+} __attribute__ ((__packed__));
#endif