code: func SaveDownlinkFrames(p *redis.Pool, devEUI lorawan.EUI64, frames []gw.DownlinkFrame) error {
  	if len(frames) == 0 {
  		return nil
  	}
  
  	var token uint32
  	var frameBytes [][]byte
  	for _, frame := range frames {
  		token = frame.Token
  		b, err := proto.Marshal(&frame)
  		if err != nil {
  			return errors.Wrap(err, "marshal proto error")
  		}
  		frameBytes = append(frameBytes, b)
  	}
  
  	c := p.Get()
  	defer c.Close()
  
  	exp := int64(downlinkFramesTTL) / int64(time.Millisecond)
  	c.Send("MULTI")
  
  	// store frames
  	key := fmt.Sprintf(downlinkFramesKeyTempl, token)
  	for i := range frameBytes {
  		c.Send("RPUSH", key, frameBytes[i])
  	}
  	c.Send("PEXPIRE", key, exp)
  
  	// store pointer to deveui
  	key = fmt.Sprintf(downlinkFramesDevEUIKeyTempl, token)
  	c.Send("PSETEX", key, exp, devEUI[:])
  
  	// execute
  	if _, err := c.Do("EXEC"); err != nil {
  		return errors.Wrap(err, "exec error")
  	}
  
  	log.WithFields(log.Fields{
  		"token":   token,
  		"dev_eui": devEUI,
  	}).Info("downlink-frames saved")
  
  	return nil
  }
docs: // SaveDownlinkFrames saves the given downlink-frames. The downlink-frames
  // must share the same token!